diff options
author | Xin Li <delphij@google.com> | 2019-07-01 20:59:51 +0000 |
---|---|---|
committer | Xin Li <delphij@google.com> | 2019-07-01 20:59:51 +0000 |
commit | e30861d43c7b5fdcfe4cfeeae6d763ce1d0480f3 (patch) | |
tree | 2c40513c549bcd6ed9d5c8805a24209e7d44f60c | |
parent | a91a5608eabfce0dadbc7706f7fd8393356e7419 (diff) | |
parent | 83fa2c850b78aab77baebd5143e4c555c7c2a28b (diff) | |
download | av-e30861d43c7b5fdcfe4cfeeae6d763ce1d0480f3.tar.gz |
DO NOT MERGE - Merge qt-dev-plus-aosp-without-vendor (5699924) into stage-aosp-master
Bug: 134405016
Change-Id: I605f62d294a4dd99e69c4ca9acddfa67b39c1c9c
142 files changed, 7179 insertions, 7997 deletions
@@ -3,7 +3,3 @@ soong_namespace { "hardware/google/interfaces", ], } - -subdirs = [ - "media", -] diff --git a/codec2/Android.bp b/codec2/Android.bp index ee46c24..9addc79 100644 --- a/codec2/Android.bp +++ b/codec2/Android.bp @@ -40,8 +40,3 @@ cc_library_shared { ldflags: ["-Wl,-Bsymbolic"], } - -subdirs = [ - "tests", - "vndk", -] diff --git a/codec2/Android.mk b/codec2/Android.mk deleted file mode 100644 index 82d739f..0000000 --- a/codec2/Android.mk +++ /dev/null @@ -1,48 +0,0 @@ -# ============================================================================= -# DOCUMENTATION GENERATION -# ============================================================================= -C2_ROOT := $(call my-dir) - -C2_DOCS_ROOT := $(OUT_DIR)/target/common/docs/codec2 - -C2_OUT_TEMP := $(PRODUCT_OUT)/gen/ETC/Codec2-docs_intermediates - -C2_DOXY := $(or $(shell command -v doxygen),\ - $(shell command -v /Applications/Doxygen.app/Contents/Resources/doxygen)) - -.PHONY: check-doxygen -check-doxygen: -ifndef C2_DOXY - $(error 'doxygen is not available') -endif - -$(C2_OUT_TEMP)/doxy-api.config: $(C2_ROOT)/docs/doxygen.config - # only document include directory, no internal sections - sed 's/\(^INPUT *=.*\)/\1include\//; \ - s/\(^INTERNAL_DOCS *= *\).*/\1NO/; \ - s/\(^ENABLED_SECTIONS *=.*\)INTERNAL\(.*\).*/\1\2/; \ - s:\(^OUTPUT_DIRECTORY *= \)out:\1'$(OUT_DIR)':;' \ - $(C2_ROOT)/docs/doxygen.config > $@ - -$(C2_OUT_TEMP)/doxy-internal.config: $(C2_ROOT)/docs/doxygen.config - sed 's:\(^OUTPUT_DIRECTORY *= \)out\(.*\)api:\1'$(OUT_DIR)'\2internal:;' \ - $(C2_ROOT)/docs/doxygen.config > $@ - -.PHONY: docs-api -docs-api: $(C2_OUT_TEMP)/doxy-api.config check-doxygen - echo API docs are building in $(C2_DOCS_ROOT)/api - rm -rf $(C2_DOCS_ROOT)/api - mkdir -p $(C2_DOCS_ROOT)/api - $(C2_DOXY) $(C2_OUT_TEMP)/doxy-api.config - -.PHONY: docs-internal -docs-internal: $(C2_OUT_TEMP)/doxy-internal.config check-doxygen - echo Internal docs are building in $(C2_DOCS_ROOT)/internal - rm -rf $(C2_DOCS_ROOT)/internal - mkdir -p $(C2_DOCS_ROOT)/internal - $(C2_DOXY) $(C2_OUT_TEMP)/doxy-internal.config - -.PHONY: docs-all -docs-all: docs-api docs-internal - -include $(call all-makefiles-under,$(call my-dir)) diff --git a/codec2/docs/doxyfilter.sh b/codec2/docs/doxyfilter.sh deleted file mode 100755 index d813153..0000000 --- a/codec2/docs/doxyfilter.sh +++ /dev/null @@ -1,100 +0,0 @@ -#!/usr/bin/env python3 -import re, sys - -global in_comment, current, indent, hold -in_comment, current, indent, hold = False, None, '', [] - -class ChangeCStyleCommentsToDoxy: - def dump_hold(): - global hold - for h in hold: - print(h, end='') - hold[:] = [] - - def doxy_hold(): - global current, hold - if current == '//': - for h in hold: - print(re.sub(r'^( *//(?!/))', r'\1/', h), end='') - else: - first = True - for h in hold: - if first: - h = re.sub(r'^( */[*](?![*]))', r'\1*', h) - first = False - print(h, end='') - hold[:] = [] - - def process_comment(t, ind, line): - global current, indent, hold - if t != current or ind not in (indent, indent + ' '): - dump_hold() - current, indent = t, ind - hold.append(line) - - def process_line(ind, line): - global current, indent - if ind in (indent, ''): - doxy_hold() - else: - dump_hold() - current, indent = None, None - print(line, end='') - - def process(self, input, path): - for line in input: - ind = re.match(r'^( *)', line).group(1) - if in_comment: - # TODO: this is not quite right, but good enough - m = re.match(r'^ *[*]/', line) - if m: - process_comment('/*', ind, line) - in_comment = False - else: - process_comment('/*', ind, line) - continue - m = re.match(r'^ *//', line) - if m: - # one-line comment - process_comment('//', ind, line) - continue - m = re.match(r'^ */[*]', line) - if m: - # multi-line comment - process_comment('/*', ind, line) - # TODO: this is not quite right, but good enough - in_comment = not re.match(r'^ *[*]/', line) - continue - process_line(ind, line) - -class AutoGroup: - def process(self, input, path): - if '/codec2/include/' in path: - group = 'API Codec2 API' - elif False: - return - elif '/codec2/vndk/' in path: - group = 'VNDK Platform provided glue' - elif '/codec2/tests/' in path: - group = 'Tests Unit tests' - else: - group = 'Random Misc. sandbox' - - print('#undef __APPLE__') - - for line in input: - if re.match(r'^namespace android {', line): - print(line, end='') - print() - print(r'/// \addtogroup {}'.format(group)) - print(r'/// @{') - continue - elif re.match(r'^} +// +namespace', line): - print(r'/// @}') - print() - print(line, end='') - -P = AutoGroup() -for path in sys.argv[1:]: - with open(path, 'rt') as input: - P.process(input, path) diff --git a/codec2/docs/doxygen.config b/codec2/docs/doxygen.config deleted file mode 100644 index 11a921f..0000000 --- a/codec2/docs/doxygen.config +++ /dev/null @@ -1,2446 +0,0 @@ -# Doxyfile 1.8.11 - -# This file describes the settings to be used by the documentation system -# doxygen (www.doxygen.org) for a project. -# -# All text after a double hash (##) is considered a comment and is placed in -# front of the TAG it is preceding. -# -# All text after a single hash (#) is considered a comment and will be ignored. -# The format is: -# TAG = value [value, ...] -# For lists, items can also be appended using: -# TAG += value [value, ...] -# Values that contain spaces should be placed between quotes (\" \"). - -#--------------------------------------------------------------------------- -# Project related configuration options -#--------------------------------------------------------------------------- - -# This tag specifies the encoding used for all characters in the config file -# that follow. The default is UTF-8 which is also the encoding used for all text -# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv -# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv -# for the list of possible encodings. -# The default value is: UTF-8. - -DOXYFILE_ENCODING = UTF-8 - -# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by -# double-quotes, unless you are using Doxywizard) that should identify the -# project for which the documentation is generated. This name is used in the -# title of most generated pages and in a few other places. -# The default value is: My Project. - -PROJECT_NAME = Codec2 - -# The PROJECT_NUMBER tag can be used to enter a project or revision number. This -# could be handy for archiving the generated documentation or if some version -# control system is used. - -PROJECT_NUMBER = - -# Using the PROJECT_BRIEF tag one can provide an optional one line description -# for a project that appears at the top of each page and should give viewer a -# quick idea about the purpose of the project. Keep the description short. - -PROJECT_BRIEF = - -# With the PROJECT_LOGO tag one can specify a logo or an icon that is included -# in the documentation. The maximum height of the logo should not exceed 55 -# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy -# the logo to the output directory. - -PROJECT_LOGO = - -# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path -# into which the generated documentation will be written. If a relative path is -# entered, it will be relative to the location where doxygen was started. If -# left blank the current directory will be used. - -OUTPUT_DIRECTORY = out/target/common/docs/codec2/api - -# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- -# directories (in 2 levels) under the output directory of each output format and -# will distribute the generated files over these directories. Enabling this -# option can be useful when feeding doxygen a huge amount of source files, where -# putting all generated files in the same directory would otherwise causes -# performance problems for the file system. -# The default value is: NO. - -CREATE_SUBDIRS = NO - -# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII -# characters to appear in the names of generated files. If set to NO, non-ASCII -# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode -# U+3044. -# The default value is: NO. - -ALLOW_UNICODE_NAMES = NO - -# The OUTPUT_LANGUAGE tag is used to specify the language in which all -# documentation generated by doxygen is written. Doxygen will use this -# information to generate all constant output in the proper language. -# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, -# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), -# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, -# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), -# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, -# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, -# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, -# Ukrainian and Vietnamese. -# The default value is: English. - -OUTPUT_LANGUAGE = English - -# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member -# descriptions after the members that are listed in the file and class -# documentation (similar to Javadoc). Set to NO to disable this. -# The default value is: YES. - -BRIEF_MEMBER_DESC = YES - -# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief -# description of a member or function before the detailed description -# -# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the -# brief descriptions will be completely suppressed. -# The default value is: YES. - -REPEAT_BRIEF = YES - -# This tag implements a quasi-intelligent brief description abbreviator that is -# used to form the text in various listings. Each string in this list, if found -# as the leading text of the brief description, will be stripped from the text -# and the result, after processing the whole list, is used as the annotated -# text. Otherwise, the brief description is used as-is. If left blank, the -# following values are used ($name is automatically replaced with the name of -# the entity):The $name class, The $name widget, The $name file, is, provides, -# specifies, contains, represents, a, an and the. - -ABBREVIATE_BRIEF = "The $name class" \ - "The $name widget" \ - "The $name file" \ - is \ - provides \ - specifies \ - contains \ - represents \ - a \ - an \ - the - -# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then -# doxygen will generate a detailed section even if there is only a brief -# description. -# The default value is: NO. - -ALWAYS_DETAILED_SEC = NO - -# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all -# inherited members of a class in the documentation of that class as if those -# members were ordinary class members. Constructors, destructors and assignment -# operators of the base classes will not be shown. -# The default value is: NO. - -INLINE_INHERITED_MEMB = YES - -# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path -# before files name in the file list and in the header files. If set to NO the -# shortest path that makes the file name unique will be used -# The default value is: YES. - -FULL_PATH_NAMES = YES - -# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. -# Stripping is only done if one of the specified strings matches the left-hand -# part of the path. The tag can be used to show relative paths in the file list. -# If left blank the directory from which doxygen is run is used as the path to -# strip. -# -# Note that you can specify absolute paths here, but also relative paths, which -# will be relative from the directory where doxygen is started. -# This tag requires that the tag FULL_PATH_NAMES is set to YES. - -STRIP_FROM_PATH = frameworks/av/media/libstagefright/codec2 - -# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the -# path mentioned in the documentation of a class, which tells the reader which -# header file to include in order to use a class. If left blank only the name of -# the header file containing the class definition is used. Otherwise one should -# specify the list of include paths that are normally passed to the compiler -# using the -I flag. - -STRIP_FROM_INC_PATH = - -# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but -# less readable) file names. This can be useful is your file systems doesn't -# support long names like on DOS, Mac, or CD-ROM. -# The default value is: NO. - -SHORT_NAMES = NO - -# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the -# first line (until the first dot) of a Javadoc-style comment as the brief -# description. If set to NO, the Javadoc-style will behave just like regular Qt- -# style comments (thus requiring an explicit @brief command for a brief -# description.) -# The default value is: NO. - -JAVADOC_AUTOBRIEF = YES - -# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first -# line (until the first dot) of a Qt-style comment as the brief description. If -# set to NO, the Qt-style will behave just like regular Qt-style comments (thus -# requiring an explicit \brief command for a brief description.) -# The default value is: NO. - -QT_AUTOBRIEF = YES - -# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a -# multi-line C++ special comment block (i.e. a block of //! or /// comments) as -# a brief description. This used to be the default behavior. The new default is -# to treat a multi-line C++ comment block as a detailed description. Set this -# tag to YES if you prefer the old behavior instead. -# -# Note that setting this tag to YES also means that rational rose comments are -# not recognized any more. -# The default value is: NO. - -MULTILINE_CPP_IS_BRIEF = NO - -# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the -# documentation from any documented member that it re-implements. -# The default value is: YES. - -INHERIT_DOCS = YES - -# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new -# page for each member. If set to NO, the documentation of a member will be part -# of the file/class/namespace that contains it. -# The default value is: NO. - -SEPARATE_MEMBER_PAGES = NO - -# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen -# uses this value to replace tabs by spaces in code fragments. -# Minimum value: 1, maximum value: 16, default value: 4. - -TAB_SIZE = 4 - -# This tag can be used to specify a number of aliases that act as commands in -# the documentation. An alias has the form: -# name=value -# For example adding -# "sideeffect=@par Side Effects:\n" -# will allow you to put the command \sideeffect (or @sideeffect) in the -# documentation, which will result in a user-defined paragraph with heading -# "Side Effects:". You can put \n's in the value part of an alias to insert -# newlines. - -ALIASES = - -# This tag can be used to specify a number of word-keyword mappings (TCL only). -# A mapping has the form "name=value". For example adding "class=itcl::class" -# will allow you to use the command class in the itcl::class meaning. - -TCL_SUBST = - -# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources -# only. Doxygen will then generate output that is more tailored for C. For -# instance, some of the names that are used will be different. The list of all -# members will be omitted, etc. -# The default value is: NO. - -OPTIMIZE_OUTPUT_FOR_C = NO - -# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or -# Python sources only. Doxygen will then generate output that is more tailored -# for that language. For instance, namespaces will be presented as packages, -# qualified scopes will look different, etc. -# The default value is: NO. - -OPTIMIZE_OUTPUT_JAVA = NO - -# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran -# sources. Doxygen will then generate output that is tailored for Fortran. -# The default value is: NO. - -OPTIMIZE_FOR_FORTRAN = NO - -# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL -# sources. Doxygen will then generate output that is tailored for VHDL. -# The default value is: NO. - -OPTIMIZE_OUTPUT_VHDL = NO - -# Doxygen selects the parser to use depending on the extension of the files it -# parses. With this tag you can assign which parser to use for a given -# extension. Doxygen has a built-in mapping, but you can override or extend it -# using this tag. The format is ext=language, where ext is a file extension, and -# language is one of the parsers supported by doxygen: IDL, Java, Javascript, -# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: -# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: -# Fortran. In the later case the parser tries to guess whether the code is fixed -# or free formatted code, this is the default for Fortran type files), VHDL. For -# instance to make doxygen treat .inc files as Fortran files (default is PHP), -# and .f files as C (default is Fortran), use: inc=Fortran f=C. -# -# Note: For files without extension you can use no_extension as a placeholder. -# -# Note that for custom extensions you also need to set FILE_PATTERNS otherwise -# the files are not read by doxygen. - -EXTENSION_MAPPING = - -# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments -# according to the Markdown format, which allows for more readable -# documentation. See http://daringfireball.net/projects/markdown/ for details. -# The output of markdown processing is further processed by doxygen, so you can -# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in -# case of backward compatibilities issues. -# The default value is: YES. - -MARKDOWN_SUPPORT = YES - -# When enabled doxygen tries to link words that correspond to documented -# classes, or namespaces to their corresponding documentation. Such a link can -# be prevented in individual cases by putting a % sign in front of the word or -# globally by setting AUTOLINK_SUPPORT to NO. -# The default value is: YES. - -AUTOLINK_SUPPORT = YES - -# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want -# to include (a tag file for) the STL sources as input, then you should set this -# tag to YES in order to let doxygen match functions declarations and -# definitions whose arguments contain STL classes (e.g. func(std::string); -# versus func(std::string) {}). This also make the inheritance and collaboration -# diagrams that involve STL classes more complete and accurate. -# The default value is: NO. - -BUILTIN_STL_SUPPORT = YES - -# If you use Microsoft's C++/CLI language, you should set this option to YES to -# enable parsing support. -# The default value is: NO. - -CPP_CLI_SUPPORT = NO - -# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: -# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen -# will parse them like normal C++ but will assume all classes use public instead -# of private inheritance when no explicit protection keyword is present. -# The default value is: NO. - -SIP_SUPPORT = NO - -# For Microsoft's IDL there are propget and propput attributes to indicate -# getter and setter methods for a property. Setting this option to YES will make -# doxygen to replace the get and set methods by a property in the documentation. -# This will only work if the methods are indeed getting or setting a simple -# type. If this is not the case, or you want to show the methods anyway, you -# should set this option to NO. -# The default value is: YES. - -IDL_PROPERTY_SUPPORT = YES - -# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC -# tag is set to YES then doxygen will reuse the documentation of the first -# member in the group (if any) for the other members of the group. By default -# all members of a group must be documented explicitly. -# The default value is: NO. - -DISTRIBUTE_GROUP_DOC = NO - -# If one adds a struct or class to a group and this option is enabled, then also -# any nested class or struct is added to the same group. By default this option -# is disabled and one has to add nested compounds explicitly via \ingroup. -# The default value is: NO. - -GROUP_NESTED_COMPOUNDS = NO - -# Set the SUBGROUPING tag to YES to allow class member groups of the same type -# (for instance a group of public functions) to be put as a subgroup of that -# type (e.g. under the Public Functions section). Set it to NO to prevent -# subgrouping. Alternatively, this can be done per class using the -# \nosubgrouping command. -# The default value is: YES. - -SUBGROUPING = YES - -# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions -# are shown inside the group in which they are included (e.g. using \ingroup) -# instead of on a separate page (for HTML and Man pages) or section (for LaTeX -# and RTF). -# -# Note that this feature does not work in combination with -# SEPARATE_MEMBER_PAGES. -# The default value is: NO. - -INLINE_GROUPED_CLASSES = NO - -# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions -# with only public data fields or simple typedef fields will be shown inline in -# the documentation of the scope in which they are defined (i.e. file, -# namespace, or group documentation), provided this scope is documented. If set -# to NO, structs, classes, and unions are shown on a separate page (for HTML and -# Man pages) or section (for LaTeX and RTF). -# The default value is: NO. - -INLINE_SIMPLE_STRUCTS = NO - -# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or -# enum is documented as struct, union, or enum with the name of the typedef. So -# typedef struct TypeS {} TypeT, will appear in the documentation as a struct -# with name TypeT. When disabled the typedef will appear as a member of a file, -# namespace, or class. And the struct will be named TypeS. This can typically be -# useful for C code in case the coding convention dictates that all compound -# types are typedef'ed and only the typedef is referenced, never the tag name. -# The default value is: NO. - -TYPEDEF_HIDES_STRUCT = YES - -# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This -# cache is used to resolve symbols given their name and scope. Since this can be -# an expensive process and often the same symbol appears multiple times in the -# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small -# doxygen will become slower. If the cache is too large, memory is wasted. The -# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range -# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 -# symbols. At the end of a run doxygen will report the cache usage and suggest -# the optimal cache size from a speed point of view. -# Minimum value: 0, maximum value: 9, default value: 0. - -LOOKUP_CACHE_SIZE = 0 - -#--------------------------------------------------------------------------- -# Build related configuration options -#--------------------------------------------------------------------------- - -# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in -# documentation are documented, even if no documentation was available. Private -# class members and static file members will be hidden unless the -# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. -# Note: This will also disable the warnings about undocumented members that are -# normally produced when WARNINGS is set to YES. -# The default value is: NO. - -EXTRACT_ALL = YES - -# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will -# be included in the documentation. -# The default value is: NO. - -EXTRACT_PRIVATE = NO - -# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal -# scope will be included in the documentation. -# The default value is: NO. - -EXTRACT_PACKAGE = NO - -# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be -# included in the documentation. -# The default value is: NO. - -EXTRACT_STATIC = NO - -# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined -# locally in source files will be included in the documentation. If set to NO, -# only classes defined in header files are included. Does not have any effect -# for Java sources. -# The default value is: YES. - -EXTRACT_LOCAL_CLASSES = YES - -# This flag is only useful for Objective-C code. If set to YES, local methods, -# which are defined in the implementation section but not in the interface are -# included in the documentation. If set to NO, only methods in the interface are -# included. -# The default value is: NO. - -EXTRACT_LOCAL_METHODS = NO - -# If this flag is set to YES, the members of anonymous namespaces will be -# extracted and appear in the documentation as a namespace called -# 'anonymous_namespace{file}', where file will be replaced with the base name of -# the file that contains the anonymous namespace. By default anonymous namespace -# are hidden. -# The default value is: NO. - -EXTRACT_ANON_NSPACES = NO - -# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all -# undocumented members inside documented classes or files. If set to NO these -# members will be included in the various overviews, but no documentation -# section is generated. This option has no effect if EXTRACT_ALL is enabled. -# The default value is: NO. - -HIDE_UNDOC_MEMBERS = NO - -# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all -# undocumented classes that are normally visible in the class hierarchy. If set -# to NO, these classes will be included in the various overviews. This option -# has no effect if EXTRACT_ALL is enabled. -# The default value is: NO. - -HIDE_UNDOC_CLASSES = NO - -# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend -# (class|struct|union) declarations. If set to NO, these declarations will be -# included in the documentation. -# The default value is: NO. - -HIDE_FRIEND_COMPOUNDS = NO - -# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any -# documentation blocks found inside the body of a function. If set to NO, these -# blocks will be appended to the function's detailed documentation block. -# The default value is: NO. - -HIDE_IN_BODY_DOCS = NO - -# The INTERNAL_DOCS tag determines if documentation that is typed after a -# \internal command is included. If the tag is set to NO then the documentation -# will be excluded. Set it to YES to include the internal documentation. -# The default value is: NO. - -INTERNAL_DOCS = YES - -# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file -# names in lower-case letters. If set to YES, upper-case letters are also -# allowed. This is useful if you have classes or files whose names only differ -# in case and if your file system supports case sensitive file names. Windows -# and Mac users are advised to set this option to NO. -# The default value is: system dependent. - -CASE_SENSE_NAMES = NO - -# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with -# their full class and namespace scopes in the documentation. If set to YES, the -# scope will be hidden. -# The default value is: NO. - -HIDE_SCOPE_NAMES = YES - -# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will -# append additional text to a page's title, such as Class Reference. If set to -# YES the compound reference will be hidden. -# The default value is: NO. - -HIDE_COMPOUND_REFERENCE= NO - -# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of -# the files that are included by a file in the documentation of that file. -# The default value is: YES. - -SHOW_INCLUDE_FILES = YES - -# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each -# grouped member an include statement to the documentation, telling the reader -# which file to include in order to use the member. -# The default value is: NO. - -SHOW_GROUPED_MEMB_INC = NO - -# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include -# files with double quotes in the documentation rather than with sharp brackets. -# The default value is: NO. - -FORCE_LOCAL_INCLUDES = NO - -# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the -# documentation for inline members. -# The default value is: YES. - -INLINE_INFO = YES - -# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the -# (detailed) documentation of file and class members alphabetically by member -# name. If set to NO, the members will appear in declaration order. -# The default value is: YES. - -SORT_MEMBER_DOCS = YES - -# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief -# descriptions of file, namespace and class members alphabetically by member -# name. If set to NO, the members will appear in declaration order. Note that -# this will also influence the order of the classes in the class list. -# The default value is: NO. - -SORT_BRIEF_DOCS = NO - -# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the -# (brief and detailed) documentation of class members so that constructors and -# destructors are listed first. If set to NO the constructors will appear in the -# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. -# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief -# member documentation. -# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting -# detailed member documentation. -# The default value is: NO. - -SORT_MEMBERS_CTORS_1ST = NO - -# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy -# of group names into alphabetical order. If set to NO the group names will -# appear in their defined order. -# The default value is: NO. - -SORT_GROUP_NAMES = NO - -# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by -# fully-qualified names, including namespaces. If set to NO, the class list will -# be sorted only by class name, not including the namespace part. -# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. -# Note: This option applies only to the class list, not to the alphabetical -# list. -# The default value is: NO. - -SORT_BY_SCOPE_NAME = NO - -# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper -# type resolution of all parameters of a function it will reject a match between -# the prototype and the implementation of a member function even if there is -# only one candidate or it is obvious which candidate to choose by doing a -# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still -# accept a match between prototype and implementation in such cases. -# The default value is: NO. - -STRICT_PROTO_MATCHING = NO - -# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo -# list. This list is created by putting \todo commands in the documentation. -# The default value is: YES. - -GENERATE_TODOLIST = YES - -# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test -# list. This list is created by putting \test commands in the documentation. -# The default value is: YES. - -GENERATE_TESTLIST = YES - -# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug -# list. This list is created by putting \bug commands in the documentation. -# The default value is: YES. - -GENERATE_BUGLIST = YES - -# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) -# the deprecated list. This list is created by putting \deprecated commands in -# the documentation. -# The default value is: YES. - -GENERATE_DEPRECATEDLIST= YES - -# The ENABLED_SECTIONS tag can be used to enable conditional documentation -# sections, marked by \if <section_label> ... \endif and \cond <section_label> -# ... \endcond blocks. - -ENABLED_SECTIONS = INTERNAL - -# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the -# initial value of a variable or macro / define can have for it to appear in the -# documentation. If the initializer consists of more lines than specified here -# it will be hidden. Use a value of 0 to hide initializers completely. The -# appearance of the value of individual variables and macros / defines can be -# controlled using \showinitializer or \hideinitializer command in the -# documentation regardless of this setting. -# Minimum value: 0, maximum value: 10000, default value: 30. - -MAX_INITIALIZER_LINES = 30 - -# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at -# the bottom of the documentation of classes and structs. If set to YES, the -# list will mention the files that were used to generate the documentation. -# The default value is: YES. - -SHOW_USED_FILES = YES - -# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This -# will remove the Files entry from the Quick Index and from the Folder Tree View -# (if specified). -# The default value is: YES. - -SHOW_FILES = YES - -# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces -# page. This will remove the Namespaces entry from the Quick Index and from the -# Folder Tree View (if specified). -# The default value is: YES. - -SHOW_NAMESPACES = YES - -# The FILE_VERSION_FILTER tag can be used to specify a program or script that -# doxygen should invoke to get the current version for each file (typically from -# the version control system). Doxygen will invoke the program by executing (via -# popen()) the command command input-file, where command is the value of the -# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided -# by doxygen. Whatever the program writes to standard output is used as the file -# version. For an example see the documentation. - -FILE_VERSION_FILTER = - -# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed -# by doxygen. The layout file controls the global structure of the generated -# output files in an output format independent way. To create the layout file -# that represents doxygen's defaults, run doxygen with the -l option. You can -# optionally specify a file name after the option, if omitted DoxygenLayout.xml -# will be used as the name of the layout file. -# -# Note that if you run doxygen from a directory containing a file called -# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE -# tag is left empty. - -LAYOUT_FILE = - -# The CITE_BIB_FILES tag can be used to specify one or more bib files containing -# the reference definitions. This must be a list of .bib files. The .bib -# extension is automatically appended if omitted. This requires the bibtex tool -# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. -# For LaTeX the style of the bibliography can be controlled using -# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the -# search path. See also \cite for info how to create references. - -CITE_BIB_FILES = - -#--------------------------------------------------------------------------- -# Configuration options related to warning and progress messages -#--------------------------------------------------------------------------- - -# The QUIET tag can be used to turn on/off the messages that are generated to -# standard output by doxygen. If QUIET is set to YES this implies that the -# messages are off. -# The default value is: NO. - -QUIET = NO - -# The WARNINGS tag can be used to turn on/off the warning messages that are -# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES -# this implies that the warnings are on. -# -# Tip: Turn warnings on while writing the documentation. -# The default value is: YES. - -WARNINGS = YES - -# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate -# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag -# will automatically be disabled. -# The default value is: YES. - -WARN_IF_UNDOCUMENTED = YES - -# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for -# potential errors in the documentation, such as not documenting some parameters -# in a documented function, or documenting parameters that don't exist or using -# markup commands wrongly. -# The default value is: YES. - -WARN_IF_DOC_ERROR = YES - -# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that -# are documented, but have no documentation for their parameters or return -# value. If set to NO, doxygen will only warn about wrong or incomplete -# parameter documentation, but not about the absence of documentation. -# The default value is: NO. - -WARN_NO_PARAMDOC = NO - -# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when -# a warning is encountered. -# The default value is: NO. - -WARN_AS_ERROR = NO - -# The WARN_FORMAT tag determines the format of the warning messages that doxygen -# can produce. The string should contain the $file, $line, and $text tags, which -# will be replaced by the file and line number from which the warning originated -# and the warning text. Optionally the format may contain $version, which will -# be replaced by the version of the file (if it could be obtained via -# FILE_VERSION_FILTER) -# The default value is: $file:$line: $text. - -WARN_FORMAT = "$file:$line: $text" - -# The WARN_LOGFILE tag can be used to specify a file to which warning and error -# messages should be written. If left blank the output is written to standard -# error (stderr). - -WARN_LOGFILE = - -#--------------------------------------------------------------------------- -# Configuration options related to the input files -#--------------------------------------------------------------------------- - -# The INPUT tag is used to specify the files and/or directories that contain -# documented source files. You may enter file names like myfile.cpp or -# directories like /usr/src/myproject. Separate the files or directories with -# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING -# Note: If this tag is empty the current directory is searched. - -INPUT = frameworks/av/media/libstagefright/codec2/ - -# This tag can be used to specify the character encoding of the source files -# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses -# libiconv (or the iconv built into libc) for the transcoding. See the libiconv -# documentation (see: http://www.gnu.org/software/libiconv) for the list of -# possible encodings. -# The default value is: UTF-8. - -INPUT_ENCODING = UTF-8 - -# If the value of the INPUT tag contains directories, you can use the -# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and -# *.h) to filter out the source-files in the directories. -# -# Note that for custom extensions or not directly supported extensions you also -# need to set EXTENSION_MAPPING for the extension otherwise the files are not -# read by doxygen. -# -# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, -# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, -# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, -# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f, *.for, *.tcl, -# *.vhd, *.vhdl, *.ucf, *.qsf, *.as and *.js. - -FILE_PATTERNS = C2*.c \ - C2*.cpp \ - C2*.h - -# The RECURSIVE tag can be used to specify whether or not subdirectories should -# be searched for input files as well. -# The default value is: NO. - -RECURSIVE = YES - -# The EXCLUDE tag can be used to specify files and/or directories that should be -# excluded from the INPUT source files. This way you can easily exclude a -# subdirectory from a directory tree whose root is specified with the INPUT tag. -# -# Note that relative paths are relative to the directory from which doxygen is -# run. - -EXCLUDE = - -# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or -# directories that are symbolic links (a Unix file system feature) are excluded -# from the input. -# The default value is: NO. - -EXCLUDE_SYMLINKS = NO - -# If the value of the INPUT tag contains directories, you can use the -# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude -# certain files from those directories. -# -# Note that the wildcards are matched against the file with absolute path, so to -# exclude all test directories for example use the pattern */test/* - -EXCLUDE_PATTERNS = ._* - -# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names -# (namespaces, classes, functions, etc.) that should be excluded from the -# output. The symbol name can be a fully qualified name, a word, or if the -# wildcard * is used, a substring. Examples: ANamespace, AClass, -# AClass::ANamespace, ANamespace::*Test -# -# Note that the wildcards are matched against the file with absolute path, so to -# exclude all test directories use the pattern */test/* - -EXCLUDE_SYMBOLS = - -# The EXAMPLE_PATH tag can be used to specify one or more files or directories -# that contain example code fragments that are included (see the \include -# command). - -EXAMPLE_PATH = - -# If the value of the EXAMPLE_PATH tag contains directories, you can use the -# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and -# *.h) to filter out the source-files in the directories. If left blank all -# files are included. - -EXAMPLE_PATTERNS = * - -# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be -# searched for input files to be used with the \include or \dontinclude commands -# irrespective of the value of the RECURSIVE tag. -# The default value is: NO. - -EXAMPLE_RECURSIVE = NO - -# The IMAGE_PATH tag can be used to specify one or more files or directories -# that contain images that are to be included in the documentation (see the -# \image command). - -IMAGE_PATH = - -# The INPUT_FILTER tag can be used to specify a program that doxygen should -# invoke to filter for each input file. Doxygen will invoke the filter program -# by executing (via popen()) the command: -# -# <filter> <input-file> -# -# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the -# name of an input file. Doxygen will then use the output that the filter -# program writes to standard output. If FILTER_PATTERNS is specified, this tag -# will be ignored. -# -# Note that the filter must not add or remove lines; it is applied before the -# code is scanned, but not when the output code is generated. If lines are added -# or removed, the anchors will not be placed correctly. -# -# Note that for custom extensions or not directly supported extensions you also -# need to set EXTENSION_MAPPING for the extension otherwise the files are not -# properly processed by doxygen. - -INPUT_FILTER = frameworks/av/media/libstagefright/codec2/docs/doxyfilter.sh - -# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern -# basis. Doxygen will compare the file name with each pattern and apply the -# filter if there is a match. The filters are a list of the form: pattern=filter -# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how -# filters are used. If the FILTER_PATTERNS tag is empty or if none of the -# patterns match the file name, INPUT_FILTER is applied. -# -# Note that for custom extensions or not directly supported extensions you also -# need to set EXTENSION_MAPPING for the extension otherwise the files are not -# properly processed by doxygen. - -FILTER_PATTERNS = - -# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using -# INPUT_FILTER) will also be used to filter the input files that are used for -# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). -# The default value is: NO. - -FILTER_SOURCE_FILES = YES - -# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file -# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and -# it is also possible to disable source filtering for a specific pattern using -# *.ext= (so without naming a filter). -# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. - -FILTER_SOURCE_PATTERNS = - -# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that -# is part of the input, its contents will be placed on the main page -# (index.html). This can be useful if you have a project on for instance GitHub -# and want to reuse the introduction page also for the doxygen output. - -USE_MDFILE_AS_MAINPAGE = - -#--------------------------------------------------------------------------- -# Configuration options related to source browsing -#--------------------------------------------------------------------------- - -# If the SOURCE_BROWSER tag is set to YES then a list of source files will be -# generated. Documented entities will be cross-referenced with these sources. -# -# Note: To get rid of all source code in the generated output, make sure that -# also VERBATIM_HEADERS is set to NO. -# The default value is: NO. - -SOURCE_BROWSER = YES - -# Setting the INLINE_SOURCES tag to YES will include the body of functions, -# classes and enums directly into the documentation. -# The default value is: NO. - -INLINE_SOURCES = NO - -# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any -# special comment blocks from generated source code fragments. Normal C, C++ and -# Fortran comments will always remain visible. -# The default value is: YES. - -STRIP_CODE_COMMENTS = YES - -# If the REFERENCED_BY_RELATION tag is set to YES then for each documented -# function all documented functions referencing it will be listed. -# The default value is: NO. - -REFERENCED_BY_RELATION = YES - -# If the REFERENCES_RELATION tag is set to YES then for each documented function -# all documented entities called/used by that function will be listed. -# The default value is: NO. - -REFERENCES_RELATION = YES - -# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set -# to YES then the hyperlinks from functions in REFERENCES_RELATION and -# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will -# link to the documentation. -# The default value is: YES. - -REFERENCES_LINK_SOURCE = YES - -# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the -# source code will show a tooltip with additional information such as prototype, -# brief description and links to the definition and documentation. Since this -# will make the HTML file larger and loading of large files a bit slower, you -# can opt to disable this feature. -# The default value is: YES. -# This tag requires that the tag SOURCE_BROWSER is set to YES. - -SOURCE_TOOLTIPS = YES - -# If the USE_HTAGS tag is set to YES then the references to source code will -# point to the HTML generated by the htags(1) tool instead of doxygen built-in -# source browser. The htags tool is part of GNU's global source tagging system -# (see http://www.gnu.org/software/global/global.html). You will need version -# 4.8.6 or higher. -# -# To use it do the following: -# - Install the latest version of global -# - Enable SOURCE_BROWSER and USE_HTAGS in the config file -# - Make sure the INPUT points to the root of the source tree -# - Run doxygen as normal -# -# Doxygen will invoke htags (and that will in turn invoke gtags), so these -# tools must be available from the command line (i.e. in the search path). -# -# The result: instead of the source browser generated by doxygen, the links to -# source code will now point to the output of htags. -# The default value is: NO. -# This tag requires that the tag SOURCE_BROWSER is set to YES. - -USE_HTAGS = NO - -# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a -# verbatim copy of the header file for each class for which an include is -# specified. Set to NO to disable this. -# See also: Section \class. -# The default value is: YES. - -VERBATIM_HEADERS = YES - -# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the -# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the -# cost of reduced performance. This can be particularly helpful with template -# rich C++ code for which doxygen's built-in parser lacks the necessary type -# information. -# Note: The availability of this option depends on whether or not doxygen was -# generated with the -Duse-libclang=ON option for CMake. -# The default value is: NO. - -CLANG_ASSISTED_PARSING = YES - -# If clang assisted parsing is enabled you can provide the compiler with command -# line options that you would normally use when invoking the compiler. Note that -# the include paths will already be set by doxygen for the files and directories -# specified with INPUT and INCLUDE_PATH. -# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. - -CLANG_OPTIONS = -std=c++11 - -#--------------------------------------------------------------------------- -# Configuration options related to the alphabetical class index -#--------------------------------------------------------------------------- - -# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all -# compounds will be generated. Enable this if the project contains a lot of -# classes, structs, unions or interfaces. -# The default value is: YES. - -ALPHABETICAL_INDEX = YES - -# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in -# which the alphabetical index list will be split. -# Minimum value: 1, maximum value: 20, default value: 5. -# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. - -COLS_IN_ALPHA_INDEX = 5 - -# In case all classes in a project start with a common prefix, all classes will -# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag -# can be used to specify a prefix (or a list of prefixes) that should be ignored -# while generating the index headers. -# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. - -IGNORE_PREFIX = - -#--------------------------------------------------------------------------- -# Configuration options related to the HTML output -#--------------------------------------------------------------------------- - -# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output -# The default value is: YES. - -GENERATE_HTML = YES - -# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a -# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of -# it. -# The default directory is: html. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_OUTPUT = html - -# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each -# generated HTML page (for example: .htm, .php, .asp). -# The default value is: .html. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_FILE_EXTENSION = .html - -# The HTML_HEADER tag can be used to specify a user-defined HTML header file for -# each generated HTML page. If the tag is left blank doxygen will generate a -# standard header. -# -# To get valid HTML the header file that includes any scripts and style sheets -# that doxygen needs, which is dependent on the configuration options used (e.g. -# the setting GENERATE_TREEVIEW). It is highly recommended to start with a -# default header using -# doxygen -w html new_header.html new_footer.html new_stylesheet.css -# YourConfigFile -# and then modify the file new_header.html. See also section "Doxygen usage" -# for information on how to generate the default header that doxygen normally -# uses. -# Note: The header is subject to change so you typically have to regenerate the -# default header when upgrading to a newer version of doxygen. For a description -# of the possible markers and block names see the documentation. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_HEADER = - -# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each -# generated HTML page. If the tag is left blank doxygen will generate a standard -# footer. See HTML_HEADER for more information on how to generate a default -# footer and what special commands can be used inside the footer. See also -# section "Doxygen usage" for information on how to generate the default footer -# that doxygen normally uses. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_FOOTER = - -# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style -# sheet that is used by each HTML page. It can be used to fine-tune the look of -# the HTML output. If left blank doxygen will generate a default style sheet. -# See also section "Doxygen usage" for information on how to generate the style -# sheet that doxygen normally uses. -# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as -# it is more robust and this tag (HTML_STYLESHEET) will in the future become -# obsolete. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_STYLESHEET = - -# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined -# cascading style sheets that are included after the standard style sheets -# created by doxygen. Using this option one can overrule certain style aspects. -# This is preferred over using HTML_STYLESHEET since it does not replace the -# standard style sheet and is therefore more robust against future updates. -# Doxygen will copy the style sheet files to the output directory. -# Note: The order of the extra style sheet files is of importance (e.g. the last -# style sheet in the list overrules the setting of the previous ones in the -# list). For an example see the documentation. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_EXTRA_STYLESHEET = - -# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or -# other source files which should be copied to the HTML output directory. Note -# that these files will be copied to the base HTML output directory. Use the -# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these -# files. In the HTML_STYLESHEET file, use the file name only. Also note that the -# files will be copied as-is; there are no commands or markers available. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_EXTRA_FILES = - -# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen -# will adjust the colors in the style sheet and background images according to -# this color. Hue is specified as an angle on a colorwheel, see -# http://en.wikipedia.org/wiki/Hue for more information. For instance the value -# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 -# purple, and 360 is red again. -# Minimum value: 0, maximum value: 359, default value: 220. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_COLORSTYLE_HUE = 220 - -# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors -# in the HTML output. For a value of 0 the output will use grayscales only. A -# value of 255 will produce the most vivid colors. -# Minimum value: 0, maximum value: 255, default value: 100. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_COLORSTYLE_SAT = 100 - -# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the -# luminance component of the colors in the HTML output. Values below 100 -# gradually make the output lighter, whereas values above 100 make the output -# darker. The value divided by 100 is the actual gamma applied, so 80 represents -# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not -# change the gamma. -# Minimum value: 40, maximum value: 240, default value: 80. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_COLORSTYLE_GAMMA = 80 - -# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML -# page will contain the date and time when the page was generated. Setting this -# to YES can help to show when doxygen was last run and thus if the -# documentation is up to date. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_TIMESTAMP = NO - -# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML -# documentation will contain sections that can be hidden and shown after the -# page has loaded. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_DYNAMIC_SECTIONS = YES - -# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries -# shown in the various tree structured indices initially; the user can expand -# and collapse entries dynamically later on. Doxygen will expand the tree to -# such a level that at most the specified number of entries are visible (unless -# a fully collapsed tree already exceeds this amount). So setting the number of -# entries 1 will produce a full collapsed tree by default. 0 is a special value -# representing an infinite number of entries and will result in a full expanded -# tree by default. -# Minimum value: 0, maximum value: 9999, default value: 100. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_INDEX_NUM_ENTRIES = 100 - -# If the GENERATE_DOCSET tag is set to YES, additional index files will be -# generated that can be used as input for Apple's Xcode 3 integrated development -# environment (see: http://developer.apple.com/tools/xcode/), introduced with -# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a -# Makefile in the HTML output directory. Running make will produce the docset in -# that directory and running make install will install the docset in -# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at -# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html -# for more information. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_DOCSET = NO - -# This tag determines the name of the docset feed. A documentation feed provides -# an umbrella under which multiple documentation sets from a single provider -# (such as a company or product suite) can be grouped. -# The default value is: Doxygen generated docs. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_FEEDNAME = "Doxygen generated docs" - -# This tag specifies a string that should uniquely identify the documentation -# set bundle. This should be a reverse domain-name style string, e.g. -# com.mycompany.MyDocSet. Doxygen will append .docset to the name. -# The default value is: org.doxygen.Project. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_BUNDLE_ID = org.doxygen.Project - -# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify -# the documentation publisher. This should be a reverse domain-name style -# string, e.g. com.mycompany.MyDocSet.documentation. -# The default value is: org.doxygen.Publisher. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_PUBLISHER_ID = org.doxygen.Publisher - -# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. -# The default value is: Publisher. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_PUBLISHER_NAME = Publisher - -# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three -# additional HTML index files: index.hhp, index.hhc, and index.hhk. The -# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop -# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on -# Windows. -# -# The HTML Help Workshop contains a compiler that can convert all HTML output -# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML -# files are now used as the Windows 98 help format, and will replace the old -# Windows help format (.hlp) on all Windows platforms in the future. Compressed -# HTML files also contain an index, a table of contents, and you can search for -# words in the documentation. The HTML workshop also contains a viewer for -# compressed HTML files. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_HTMLHELP = NO - -# The CHM_FILE tag can be used to specify the file name of the resulting .chm -# file. You can add a path in front of the file if the result should not be -# written to the html output directory. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -CHM_FILE = - -# The HHC_LOCATION tag can be used to specify the location (absolute path -# including file name) of the HTML help compiler (hhc.exe). If non-empty, -# doxygen will try to run the HTML help compiler on the generated index.hhp. -# The file has to be specified with full path. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -HHC_LOCATION = - -# The GENERATE_CHI flag controls if a separate .chi index file is generated -# (YES) or that it should be included in the master .chm file (NO). -# The default value is: NO. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -GENERATE_CHI = NO - -# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) -# and project file content. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -CHM_INDEX_ENCODING = - -# The BINARY_TOC flag controls whether a binary table of contents is generated -# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it -# enables the Previous and Next buttons. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -BINARY_TOC = NO - -# The TOC_EXPAND flag can be set to YES to add extra items for group members to -# the table of contents of the HTML help documentation and to the tree view. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -TOC_EXPAND = NO - -# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and -# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that -# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help -# (.qch) of the generated HTML documentation. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_QHP = NO - -# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify -# the file name of the resulting .qch file. The path specified is relative to -# the HTML output folder. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QCH_FILE = - -# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help -# Project output. For more information please see Qt Help Project / Namespace -# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). -# The default value is: org.doxygen.Project. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_NAMESPACE = org.doxygen.Project - -# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt -# Help Project output. For more information please see Qt Help Project / Virtual -# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- -# folders). -# The default value is: doc. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_VIRTUAL_FOLDER = doc - -# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom -# filter to add. For more information please see Qt Help Project / Custom -# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- -# filters). -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_CUST_FILTER_NAME = - -# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the -# custom filter to add. For more information please see Qt Help Project / Custom -# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- -# filters). -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_CUST_FILTER_ATTRS = - -# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this -# project's filter section matches. Qt Help Project / Filter Attributes (see: -# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_SECT_FILTER_ATTRS = - -# The QHG_LOCATION tag can be used to specify the location of Qt's -# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the -# generated .qhp file. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHG_LOCATION = - -# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be -# generated, together with the HTML files, they form an Eclipse help plugin. To -# install this plugin and make it available under the help contents menu in -# Eclipse, the contents of the directory containing the HTML and XML files needs -# to be copied into the plugins directory of eclipse. The name of the directory -# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. -# After copying Eclipse needs to be restarted before the help appears. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_ECLIPSEHELP = NO - -# A unique identifier for the Eclipse help plugin. When installing the plugin -# the directory name containing the HTML and XML files should also have this -# name. Each documentation set should have its own identifier. -# The default value is: org.doxygen.Project. -# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. - -ECLIPSE_DOC_ID = org.doxygen.Project - -# If you want full control over the layout of the generated HTML pages it might -# be necessary to disable the index and replace it with your own. The -# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top -# of each HTML page. A value of NO enables the index and the value YES disables -# it. Since the tabs in the index contain the same information as the navigation -# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -DISABLE_INDEX = NO - -# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index -# structure should be generated to display hierarchical information. If the tag -# value is set to YES, a side panel will be generated containing a tree-like -# index structure (just like the one that is generated for HTML Help). For this -# to work a browser that supports JavaScript, DHTML, CSS and frames is required -# (i.e. any modern browser). Windows users are probably better off using the -# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can -# further fine-tune the look of the index. As an example, the default style -# sheet generated by doxygen has an example that shows how to put an image at -# the root of the tree instead of the PROJECT_NAME. Since the tree basically has -# the same information as the tab index, you could consider setting -# DISABLE_INDEX to YES when enabling this option. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_TREEVIEW = YES - -# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that -# doxygen will group on one line in the generated HTML documentation. -# -# Note that a value of 0 will completely suppress the enum values from appearing -# in the overview section. -# Minimum value: 0, maximum value: 20, default value: 4. -# This tag requires that the tag GENERATE_HTML is set to YES. - -ENUM_VALUES_PER_LINE = 4 - -# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used -# to set the initial width (in pixels) of the frame in which the tree is shown. -# Minimum value: 0, maximum value: 1500, default value: 250. -# This tag requires that the tag GENERATE_HTML is set to YES. - -TREEVIEW_WIDTH = 250 - -# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to -# external symbols imported via tag files in a separate window. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -EXT_LINKS_IN_WINDOW = NO - -# Use this tag to change the font size of LaTeX formulas included as images in -# the HTML documentation. When you change the font size after a successful -# doxygen run you need to manually remove any form_*.png images from the HTML -# output directory to force them to be regenerated. -# Minimum value: 8, maximum value: 50, default value: 10. -# This tag requires that the tag GENERATE_HTML is set to YES. - -FORMULA_FONTSIZE = 10 - -# Use the FORMULA_TRANPARENT tag to determine whether or not the images -# generated for formulas are transparent PNGs. Transparent PNGs are not -# supported properly for IE 6.0, but are supported on all modern browsers. -# -# Note that when changing this option you need to delete any form_*.png files in -# the HTML output directory before the changes have effect. -# The default value is: YES. -# This tag requires that the tag GENERATE_HTML is set to YES. - -FORMULA_TRANSPARENT = YES - -# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see -# http://www.mathjax.org) which uses client side Javascript for the rendering -# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX -# installed or if you want to formulas look prettier in the HTML output. When -# enabled you may also need to install MathJax separately and configure the path -# to it using the MATHJAX_RELPATH option. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -USE_MATHJAX = NO - -# When MathJax is enabled you can set the default output format to be used for -# the MathJax output. See the MathJax site (see: -# http://docs.mathjax.org/en/latest/output.html) for more details. -# Possible values are: HTML-CSS (which is slower, but has the best -# compatibility), NativeMML (i.e. MathML) and SVG. -# The default value is: HTML-CSS. -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_FORMAT = HTML-CSS - -# When MathJax is enabled you need to specify the location relative to the HTML -# output directory using the MATHJAX_RELPATH option. The destination directory -# should contain the MathJax.js script. For instance, if the mathjax directory -# is located at the same level as the HTML output directory, then -# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax -# Content Delivery Network so you can quickly see the result without installing -# MathJax. However, it is strongly recommended to install a local copy of -# MathJax from http://www.mathjax.org before deployment. -# The default value is: http://cdn.mathjax.org/mathjax/latest. -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest - -# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax -# extension names that should be enabled during MathJax rendering. For example -# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_EXTENSIONS = - -# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces -# of code that will be used on startup of the MathJax code. See the MathJax site -# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an -# example see the documentation. -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_CODEFILE = - -# When the SEARCHENGINE tag is enabled doxygen will generate a search box for -# the HTML output. The underlying search engine uses javascript and DHTML and -# should work on any modern browser. Note that when using HTML help -# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) -# there is already a search function so this one should typically be disabled. -# For large projects the javascript based search engine can be slow, then -# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to -# search using the keyboard; to jump to the search box use <access key> + S -# (what the <access key> is depends on the OS and browser, but it is typically -# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down -# key> to jump into the search results window, the results can be navigated -# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel -# the search. The filter options can be selected when the cursor is inside the -# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys> -# to select a filter and <Enter> or <escape> to activate or cancel the filter -# option. -# The default value is: YES. -# This tag requires that the tag GENERATE_HTML is set to YES. - -SEARCHENGINE = YES - -# When the SERVER_BASED_SEARCH tag is enabled the search engine will be -# implemented using a web server instead of a web client using Javascript. There -# are two flavors of web server based searching depending on the EXTERNAL_SEARCH -# setting. When disabled, doxygen will generate a PHP script for searching and -# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing -# and searching needs to be provided by external tools. See the section -# "External Indexing and Searching" for details. -# The default value is: NO. -# This tag requires that the tag SEARCHENGINE is set to YES. - -SERVER_BASED_SEARCH = NO - -# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP -# script for searching. Instead the search results are written to an XML file -# which needs to be processed by an external indexer. Doxygen will invoke an -# external search engine pointed to by the SEARCHENGINE_URL option to obtain the -# search results. -# -# Doxygen ships with an example indexer (doxyindexer) and search engine -# (doxysearch.cgi) which are based on the open source search engine library -# Xapian (see: http://xapian.org/). -# -# See the section "External Indexing and Searching" for details. -# The default value is: NO. -# This tag requires that the tag SEARCHENGINE is set to YES. - -EXTERNAL_SEARCH = NO - -# The SEARCHENGINE_URL should point to a search engine hosted by a web server -# which will return the search results when EXTERNAL_SEARCH is enabled. -# -# Doxygen ships with an example indexer (doxyindexer) and search engine -# (doxysearch.cgi) which are based on the open source search engine library -# Xapian (see: http://xapian.org/). See the section "External Indexing and -# Searching" for details. -# This tag requires that the tag SEARCHENGINE is set to YES. - -SEARCHENGINE_URL = - -# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed -# search data is written to a file for indexing by an external tool. With the -# SEARCHDATA_FILE tag the name of this file can be specified. -# The default file is: searchdata.xml. -# This tag requires that the tag SEARCHENGINE is set to YES. - -SEARCHDATA_FILE = searchdata.xml - -# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the -# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is -# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple -# projects and redirect the results back to the right project. -# This tag requires that the tag SEARCHENGINE is set to YES. - -EXTERNAL_SEARCH_ID = - -# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen -# projects other than the one defined by this configuration file, but that are -# all added to the same external search index. Each project needs to have a -# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of -# to a relative location where the documentation can be found. The format is: -# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ... -# This tag requires that the tag SEARCHENGINE is set to YES. - -EXTRA_SEARCH_MAPPINGS = - -#--------------------------------------------------------------------------- -# Configuration options related to the LaTeX output -#--------------------------------------------------------------------------- - -# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output. -# The default value is: YES. - -GENERATE_LATEX = NO - -# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a -# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of -# it. -# The default directory is: latex. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_OUTPUT = latex - -# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be -# invoked. -# -# Note that when enabling USE_PDFLATEX this option is only used for generating -# bitmaps for formulas in the HTML output, but not in the Makefile that is -# written to the output directory. -# The default file is: latex. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_CMD_NAME = latex - -# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate -# index for LaTeX. -# The default file is: makeindex. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -MAKEINDEX_CMD_NAME = makeindex - -# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX -# documents. This may be useful for small projects and may help to save some -# trees in general. -# The default value is: NO. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -COMPACT_LATEX = NO - -# The PAPER_TYPE tag can be used to set the paper type that is used by the -# printer. -# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x -# 14 inches) and executive (7.25 x 10.5 inches). -# The default value is: a4. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -PAPER_TYPE = a4 - -# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names -# that should be included in the LaTeX output. The package can be specified just -# by its name or with the correct syntax as to be used with the LaTeX -# \usepackage command. To get the times font for instance you can specify : -# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times} -# To use the option intlimits with the amsmath package you can specify: -# EXTRA_PACKAGES=[intlimits]{amsmath} -# If left blank no extra packages will be included. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -EXTRA_PACKAGES = - -# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the -# generated LaTeX document. The header should contain everything until the first -# chapter. If it is left blank doxygen will generate a standard header. See -# section "Doxygen usage" for information on how to let doxygen write the -# default header to a separate file. -# -# Note: Only use a user-defined header if you know what you are doing! The -# following commands have a special meaning inside the header: $title, -# $datetime, $date, $doxygenversion, $projectname, $projectnumber, -# $projectbrief, $projectlogo. Doxygen will replace $title with the empty -# string, for the replacement values of the other commands the user is referred -# to HTML_HEADER. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_HEADER = - -# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the -# generated LaTeX document. The footer should contain everything after the last -# chapter. If it is left blank doxygen will generate a standard footer. See -# LATEX_HEADER for more information on how to generate a default footer and what -# special commands can be used inside the footer. -# -# Note: Only use a user-defined footer if you know what you are doing! -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_FOOTER = - -# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined -# LaTeX style sheets that are included after the standard style sheets created -# by doxygen. Using this option one can overrule certain style aspects. Doxygen -# will copy the style sheet files to the output directory. -# Note: The order of the extra style sheet files is of importance (e.g. the last -# style sheet in the list overrules the setting of the previous ones in the -# list). -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_EXTRA_STYLESHEET = - -# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or -# other source files which should be copied to the LATEX_OUTPUT output -# directory. Note that the files will be copied as-is; there are no commands or -# markers available. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_EXTRA_FILES = - -# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is -# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will -# contain links (just like the HTML output) instead of page references. This -# makes the output suitable for online browsing using a PDF viewer. -# The default value is: YES. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -PDF_HYPERLINKS = YES - -# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate -# the PDF file directly from the LaTeX files. Set this option to YES, to get a -# higher quality PDF documentation. -# The default value is: YES. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -USE_PDFLATEX = YES - -# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode -# command to the generated LaTeX files. This will instruct LaTeX to keep running -# if errors occur, instead of asking the user for help. This option is also used -# when generating formulas in HTML. -# The default value is: NO. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_BATCHMODE = NO - -# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the -# index chapters (such as File Index, Compound Index, etc.) in the output. -# The default value is: NO. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_HIDE_INDICES = NO - -# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source -# code with syntax highlighting in the LaTeX output. -# -# Note that which sources are shown also depends on other settings such as -# SOURCE_BROWSER. -# The default value is: NO. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_SOURCE_CODE = NO - -# The LATEX_BIB_STYLE tag can be used to specify the style to use for the -# bibliography, e.g. plainnat, or ieeetr. See -# http://en.wikipedia.org/wiki/BibTeX and \cite for more info. -# The default value is: plain. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_BIB_STYLE = plain - -# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated -# page will contain the date and time when the page was generated. Setting this -# to NO can help when comparing the output of multiple runs. -# The default value is: NO. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_TIMESTAMP = NO - -#--------------------------------------------------------------------------- -# Configuration options related to the RTF output -#--------------------------------------------------------------------------- - -# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The -# RTF output is optimized for Word 97 and may not look too pretty with other RTF -# readers/editors. -# The default value is: NO. - -GENERATE_RTF = NO - -# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a -# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of -# it. -# The default directory is: rtf. -# This tag requires that the tag GENERATE_RTF is set to YES. - -RTF_OUTPUT = rtf - -# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF -# documents. This may be useful for small projects and may help to save some -# trees in general. -# The default value is: NO. -# This tag requires that the tag GENERATE_RTF is set to YES. - -COMPACT_RTF = NO - -# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will -# contain hyperlink fields. The RTF file will contain links (just like the HTML -# output) instead of page references. This makes the output suitable for online -# browsing using Word or some other Word compatible readers that support those -# fields. -# -# Note: WordPad (write) and others do not support links. -# The default value is: NO. -# This tag requires that the tag GENERATE_RTF is set to YES. - -RTF_HYPERLINKS = NO - -# Load stylesheet definitions from file. Syntax is similar to doxygen's config -# file, i.e. a series of assignments. You only have to provide replacements, -# missing definitions are set to their default value. -# -# See also section "Doxygen usage" for information on how to generate the -# default style sheet that doxygen normally uses. -# This tag requires that the tag GENERATE_RTF is set to YES. - -RTF_STYLESHEET_FILE = - -# Set optional variables used in the generation of an RTF document. Syntax is -# similar to doxygen's config file. A template extensions file can be generated -# using doxygen -e rtf extensionFile. -# This tag requires that the tag GENERATE_RTF is set to YES. - -RTF_EXTENSIONS_FILE = - -# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code -# with syntax highlighting in the RTF output. -# -# Note that which sources are shown also depends on other settings such as -# SOURCE_BROWSER. -# The default value is: NO. -# This tag requires that the tag GENERATE_RTF is set to YES. - -RTF_SOURCE_CODE = NO - -#--------------------------------------------------------------------------- -# Configuration options related to the man page output -#--------------------------------------------------------------------------- - -# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for -# classes and files. -# The default value is: NO. - -GENERATE_MAN = NO - -# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a -# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of -# it. A directory man3 will be created inside the directory specified by -# MAN_OUTPUT. -# The default directory is: man. -# This tag requires that the tag GENERATE_MAN is set to YES. - -MAN_OUTPUT = man - -# The MAN_EXTENSION tag determines the extension that is added to the generated -# man pages. In case the manual section does not start with a number, the number -# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is -# optional. -# The default value is: .3. -# This tag requires that the tag GENERATE_MAN is set to YES. - -MAN_EXTENSION = .3 - -# The MAN_SUBDIR tag determines the name of the directory created within -# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by -# MAN_EXTENSION with the initial . removed. -# This tag requires that the tag GENERATE_MAN is set to YES. - -MAN_SUBDIR = - -# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it -# will generate one additional man file for each entity documented in the real -# man page(s). These additional files only source the real man page, but without -# them the man command would be unable to find the correct page. -# The default value is: NO. -# This tag requires that the tag GENERATE_MAN is set to YES. - -MAN_LINKS = NO - -#--------------------------------------------------------------------------- -# Configuration options related to the XML output -#--------------------------------------------------------------------------- - -# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that -# captures the structure of the code including all documentation. -# The default value is: NO. - -GENERATE_XML = NO - -# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a -# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of -# it. -# The default directory is: xml. -# This tag requires that the tag GENERATE_XML is set to YES. - -XML_OUTPUT = xml - -# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program -# listings (including syntax highlighting and cross-referencing information) to -# the XML output. Note that enabling this will significantly increase the size -# of the XML output. -# The default value is: YES. -# This tag requires that the tag GENERATE_XML is set to YES. - -XML_PROGRAMLISTING = YES - -#--------------------------------------------------------------------------- -# Configuration options related to the DOCBOOK output -#--------------------------------------------------------------------------- - -# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files -# that can be used to generate PDF. -# The default value is: NO. - -GENERATE_DOCBOOK = NO - -# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in -# front of it. -# The default directory is: docbook. -# This tag requires that the tag GENERATE_DOCBOOK is set to YES. - -DOCBOOK_OUTPUT = docbook - -# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the -# program listings (including syntax highlighting and cross-referencing -# information) to the DOCBOOK output. Note that enabling this will significantly -# increase the size of the DOCBOOK output. -# The default value is: NO. -# This tag requires that the tag GENERATE_DOCBOOK is set to YES. - -DOCBOOK_PROGRAMLISTING = NO - -#--------------------------------------------------------------------------- -# Configuration options for the AutoGen Definitions output -#--------------------------------------------------------------------------- - -# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an -# AutoGen Definitions (see http://autogen.sf.net) file that captures the -# structure of the code including all documentation. Note that this feature is -# still experimental and incomplete at the moment. -# The default value is: NO. - -GENERATE_AUTOGEN_DEF = NO - -#--------------------------------------------------------------------------- -# Configuration options related to the Perl module output -#--------------------------------------------------------------------------- - -# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module -# file that captures the structure of the code including all documentation. -# -# Note that this feature is still experimental and incomplete at the moment. -# The default value is: NO. - -GENERATE_PERLMOD = NO - -# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary -# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI -# output from the Perl module output. -# The default value is: NO. -# This tag requires that the tag GENERATE_PERLMOD is set to YES. - -PERLMOD_LATEX = NO - -# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely -# formatted so it can be parsed by a human reader. This is useful if you want to -# understand what is going on. On the other hand, if this tag is set to NO, the -# size of the Perl module output will be much smaller and Perl will parse it -# just the same. -# The default value is: YES. -# This tag requires that the tag GENERATE_PERLMOD is set to YES. - -PERLMOD_PRETTY = YES - -# The names of the make variables in the generated doxyrules.make file are -# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful -# so different doxyrules.make files included by the same Makefile don't -# overwrite each other's variables. -# This tag requires that the tag GENERATE_PERLMOD is set to YES. - -PERLMOD_MAKEVAR_PREFIX = - -#--------------------------------------------------------------------------- -# Configuration options related to the preprocessor -#--------------------------------------------------------------------------- - -# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all -# C-preprocessor directives found in the sources and include files. -# The default value is: YES. - -ENABLE_PREPROCESSING = YES - -# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names -# in the source code. If set to NO, only conditional compilation will be -# performed. Macro expansion can be done in a controlled way by setting -# EXPAND_ONLY_PREDEF to YES. -# The default value is: NO. -# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. - -MACRO_EXPANSION = YES - -# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then -# the macro expansion is limited to the macros specified with the PREDEFINED and -# EXPAND_AS_DEFINED tags. -# The default value is: NO. -# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. - -EXPAND_ONLY_PREDEF = YES - -# If the SEARCH_INCLUDES tag is set to YES, the include files in the -# INCLUDE_PATH will be searched if a #include is found. -# The default value is: YES. -# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. - -SEARCH_INCLUDES = YES - -# The INCLUDE_PATH tag can be used to specify one or more directories that -# contain include files that are not input files but should be processed by the -# preprocessor. -# This tag requires that the tag SEARCH_INCLUDES is set to YES. - -INCLUDE_PATH = /Volumes/A/aosp/prebuilts/clang/darwin-x86/host/3.6/lib/clang/3.6/include \ - /Volumes/A/aosp/external/libcxx/include \ - /Volumes/A/aosp/bionic/libc/include \ - /Volumes/A/aosp/bionic/libc/kernel/uapi \ - /Volumes/A/aosp/bionic/libc/kernel/uapi/asm-arm64 \ - /Volumes/A/aosp/external/gtest - -# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard -# patterns (like *.h and *.hpp) to filter out the header-files in the -# directories. If left blank, the patterns specified with FILE_PATTERNS will be -# used. -# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. - -INCLUDE_FILE_PATTERNS = - -# The PREDEFINED tag can be used to specify one or more macro names that are -# defined before the preprocessor is started (similar to the -D option of e.g. -# gcc). The argument of the tag is a list of macros of the form: name or -# name=definition (no spaces). If the definition and the "=" are omitted, "=1" -# is assumed. To prevent a macro definition from being undefined via #undef or -# recursively expanded use the := operator instead of the = operator. -# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. - -PREDEFINED = __APPLE__= \ - __ANDROID__=1 \ - ANDROID:=1 \ - __unused= - -# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this -# tag can be used to specify a list of macro names that should be expanded. The -# macro definition that is found in the sources will be used. Use the PREDEFINED -# tag if you want to use a different macro definition that overrules the -# definition found in the source code. -# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. - -EXPAND_AS_DEFINED = DEFINE_FLEXIBLE_METHODS \ - DEFINE_CAST_OPERATORS - -# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will -# remove all references to function-like macros that are alone on a line, have -# an all uppercase name, and do not end with a semicolon. Such function macros -# are typically used for boiler-plate code, and will confuse the parser if not -# removed. -# The default value is: YES. -# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. - -SKIP_FUNCTION_MACROS = YES - -#--------------------------------------------------------------------------- -# Configuration options related to external references -#--------------------------------------------------------------------------- - -# The TAGFILES tag can be used to specify one or more tag files. For each tag -# file the location of the external documentation should be added. The format of -# a tag file without this location is as follows: -# TAGFILES = file1 file2 ... -# Adding location for the tag files is done as follows: -# TAGFILES = file1=loc1 "file2 = loc2" ... -# where loc1 and loc2 can be relative or absolute paths or URLs. See the -# section "Linking to external documentation" for more information about the use -# of tag files. -# Note: Each tag file must have a unique name (where the name does NOT include -# the path). If a tag file is not located in the directory in which doxygen is -# run, you must also specify the path to the tagfile here. - -TAGFILES = - -# When a file name is specified after GENERATE_TAGFILE, doxygen will create a -# tag file that is based on the input files it reads. See section "Linking to -# external documentation" for more information about the usage of tag files. - -GENERATE_TAGFILE = - -# If the ALLEXTERNALS tag is set to YES, all external class will be listed in -# the class index. If set to NO, only the inherited external classes will be -# listed. -# The default value is: NO. - -ALLEXTERNALS = NO - -# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed -# in the modules index. If set to NO, only the current project's groups will be -# listed. -# The default value is: YES. - -EXTERNAL_GROUPS = YES - -# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in -# the related pages index. If set to NO, only the current project's pages will -# be listed. -# The default value is: YES. - -EXTERNAL_PAGES = YES - -# The PERL_PATH should be the absolute path and name of the perl script -# interpreter (i.e. the result of 'which perl'). -# The default file (with absolute path) is: /usr/bin/perl. - -PERL_PATH = /usr/bin/perl - -#--------------------------------------------------------------------------- -# Configuration options related to the dot tool -#--------------------------------------------------------------------------- - -# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram -# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to -# NO turns the diagrams off. Note that this option also works with HAVE_DOT -# disabled, but it is recommended to install and use dot, since it yields more -# powerful graphs. -# The default value is: YES. - -CLASS_DIAGRAMS = YES - -# You can define message sequence charts within doxygen comments using the \msc -# command. Doxygen will then run the mscgen tool (see: -# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the -# documentation. The MSCGEN_PATH tag allows you to specify the directory where -# the mscgen tool resides. If left empty the tool is assumed to be found in the -# default search path. - -MSCGEN_PATH = - -# You can include diagrams made with dia in doxygen documentation. Doxygen will -# then run dia to produce the diagram and insert it in the documentation. The -# DIA_PATH tag allows you to specify the directory where the dia binary resides. -# If left empty dia is assumed to be found in the default search path. - -DIA_PATH = - -# If set to YES the inheritance and collaboration graphs will hide inheritance -# and usage relations if the target is undocumented or is not a class. -# The default value is: YES. - -HIDE_UNDOC_RELATIONS = YES - -# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is -# available from the path. This tool is part of Graphviz (see: -# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent -# Bell Labs. The other options in this section have no effect if this option is -# set to NO -# The default value is: NO. - -HAVE_DOT = NO - -# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed -# to run in parallel. When set to 0 doxygen will base this on the number of -# processors available in the system. You can set it explicitly to a value -# larger than 0 to get control over the balance between CPU load and processing -# speed. -# Minimum value: 0, maximum value: 32, default value: 0. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_NUM_THREADS = 0 - -# When you want a differently looking font in the dot files that doxygen -# generates you can specify the font name using DOT_FONTNAME. You need to make -# sure dot is able to find the font, which can be done by putting it in a -# standard location or by setting the DOTFONTPATH environment variable or by -# setting DOT_FONTPATH to the directory containing the font. -# The default value is: Helvetica. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_FONTNAME = Helvetica - -# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of -# dot graphs. -# Minimum value: 4, maximum value: 24, default value: 10. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_FONTSIZE = 10 - -# By default doxygen will tell dot to use the default font as specified with -# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set -# the path where dot can find it using this tag. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_FONTPATH = - -# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for -# each documented class showing the direct and indirect inheritance relations. -# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO. -# The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. - -CLASS_GRAPH = YES - -# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a -# graph for each documented class showing the direct and indirect implementation -# dependencies (inheritance, containment, and class references variables) of the -# class with other documented classes. -# The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. - -COLLABORATION_GRAPH = YES - -# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for -# groups, showing the direct groups dependencies. -# The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. - -GROUP_GRAPHS = YES - -# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and -# collaboration diagrams in a style similar to the OMG's Unified Modeling -# Language. -# The default value is: NO. -# This tag requires that the tag HAVE_DOT is set to YES. - -UML_LOOK = NO - -# If the UML_LOOK tag is enabled, the fields and methods are shown inside the -# class node. If there are many fields or methods and many nodes the graph may -# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the -# number of items for each type to make the size more manageable. Set this to 0 -# for no limit. Note that the threshold may be exceeded by 50% before the limit -# is enforced. So when you set the threshold to 10, up to 15 fields may appear, -# but if the number exceeds 15, the total amount of fields shown is limited to -# 10. -# Minimum value: 0, maximum value: 100, default value: 10. -# This tag requires that the tag HAVE_DOT is set to YES. - -UML_LIMIT_NUM_FIELDS = 10 - -# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and -# collaboration graphs will show the relations between templates and their -# instances. -# The default value is: NO. -# This tag requires that the tag HAVE_DOT is set to YES. - -TEMPLATE_RELATIONS = NO - -# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to -# YES then doxygen will generate a graph for each documented file showing the -# direct and indirect include dependencies of the file with other documented -# files. -# The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. - -INCLUDE_GRAPH = YES - -# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are -# set to YES then doxygen will generate a graph for each documented file showing -# the direct and indirect include dependencies of the file with other documented -# files. -# The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. - -INCLUDED_BY_GRAPH = YES - -# If the CALL_GRAPH tag is set to YES then doxygen will generate a call -# dependency graph for every global function or class method. -# -# Note that enabling this option will significantly increase the time of a run. -# So in most cases it will be better to enable call graphs for selected -# functions only using the \callgraph command. Disabling a call graph can be -# accomplished by means of the command \hidecallgraph. -# The default value is: NO. -# This tag requires that the tag HAVE_DOT is set to YES. - -CALL_GRAPH = NO - -# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller -# dependency graph for every global function or class method. -# -# Note that enabling this option will significantly increase the time of a run. -# So in most cases it will be better to enable caller graphs for selected -# functions only using the \callergraph command. Disabling a caller graph can be -# accomplished by means of the command \hidecallergraph. -# The default value is: NO. -# This tag requires that the tag HAVE_DOT is set to YES. - -CALLER_GRAPH = NO - -# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical -# hierarchy of all classes instead of a textual one. -# The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. - -GRAPHICAL_HIERARCHY = YES - -# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the -# dependencies a directory has on other directories in a graphical way. The -# dependency relations are determined by the #include relations between the -# files in the directories. -# The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. - -DIRECTORY_GRAPH = YES - -# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images -# generated by dot. For an explanation of the image formats see the section -# output formats in the documentation of the dot tool (Graphviz (see: -# http://www.graphviz.org/)). -# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order -# to make the SVG files visible in IE 9+ (other browsers do not have this -# requirement). -# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo, -# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and -# png:gdiplus:gdiplus. -# The default value is: png. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_IMAGE_FORMAT = png - -# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to -# enable generation of interactive SVG images that allow zooming and panning. -# -# Note that this requires a modern browser other than Internet Explorer. Tested -# and working are Firefox, Chrome, Safari, and Opera. -# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make -# the SVG files visible. Older versions of IE do not have SVG support. -# The default value is: NO. -# This tag requires that the tag HAVE_DOT is set to YES. - -INTERACTIVE_SVG = NO - -# The DOT_PATH tag can be used to specify the path where the dot tool can be -# found. If left blank, it is assumed the dot tool can be found in the path. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_PATH = - -# The DOTFILE_DIRS tag can be used to specify one or more directories that -# contain dot files that are included in the documentation (see the \dotfile -# command). -# This tag requires that the tag HAVE_DOT is set to YES. - -DOTFILE_DIRS = - -# The MSCFILE_DIRS tag can be used to specify one or more directories that -# contain msc files that are included in the documentation (see the \mscfile -# command). - -MSCFILE_DIRS = - -# The DIAFILE_DIRS tag can be used to specify one or more directories that -# contain dia files that are included in the documentation (see the \diafile -# command). - -DIAFILE_DIRS = - -# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the -# path where java can find the plantuml.jar file. If left blank, it is assumed -# PlantUML is not used or called during a preprocessing step. Doxygen will -# generate a warning when it encounters a \startuml command in this case and -# will not generate output for the diagram. - -PLANTUML_JAR_PATH = - -# When using plantuml, the specified paths are searched for files specified by -# the !include statement in a plantuml block. - -PLANTUML_INCLUDE_PATH = - -# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes -# that will be shown in the graph. If the number of nodes in a graph becomes -# larger than this value, doxygen will truncate the graph, which is visualized -# by representing a node as a red box. Note that doxygen if the number of direct -# children of the root node in a graph is already larger than -# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that -# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. -# Minimum value: 0, maximum value: 10000, default value: 50. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_GRAPH_MAX_NODES = 50 - -# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs -# generated by dot. A depth value of 3 means that only nodes reachable from the -# root by following a path via at most 3 edges will be shown. Nodes that lay -# further from the root node will be omitted. Note that setting this option to 1 -# or 2 may greatly reduce the computation time needed for large code bases. Also -# note that the size of a graph can be further restricted by -# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. -# Minimum value: 0, maximum value: 1000, default value: 0. -# This tag requires that the tag HAVE_DOT is set to YES. - -MAX_DOT_GRAPH_DEPTH = 0 - -# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent -# background. This is disabled by default, because dot on Windows does not seem -# to support this out of the box. -# -# Warning: Depending on the platform used, enabling this option may lead to -# badly anti-aliased labels on the edges of a graph (i.e. they become hard to -# read). -# The default value is: NO. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_TRANSPARENT = NO - -# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output -# files in one run (i.e. multiple -o and -T options on the command line). This -# makes dot run faster, but since only newer versions of dot (>1.8.10) support -# this, this feature is disabled by default. -# The default value is: NO. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_MULTI_TARGETS = NO - -# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page -# explaining the meaning of the various boxes and arrows in the dot generated -# graphs. -# The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. - -GENERATE_LEGEND = YES - -# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot -# files that are used to generate the various graphs. -# The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_CLEANUP = YES diff --git a/codec2/faultinjection/Android.bp b/codec2/faultinjection/Android.bp new file mode 100644 index 0000000..ef7871e --- /dev/null +++ b/codec2/faultinjection/Android.bp @@ -0,0 +1,29 @@ +cc_library_shared { + name: "libc2_component_wrapper", + vendor_available: true, + + srcs: [ + "C2ComponentWrapper.cpp", + "SimpleMethodState.cpp", + ], + + shared_libs: [ + "libcutils", + "liblog", + "libstagefright_codec2", + "libstagefright_codec2_vndk", + "libstagefright_foundation", + "libutils", + ], + + sanitize: { + misc_undefined: [ + "unsigned-integer-overflow", + "signed-integer-overflow", + ], + cfi: true, + }, + + ldflags: ["-Wl,-Bsymbolic"], +} + diff --git a/codec2/faultinjection/C2ComponentWrapper.cpp b/codec2/faultinjection/C2ComponentWrapper.cpp new file mode 100644 index 0000000..c45f8bf --- /dev/null +++ b/codec2/faultinjection/C2ComponentWrapper.cpp @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_NDEBUG 0 +#define LOG_TAG "C2ComponentWrapper" + +#include <chrono> +#include <functional> +#include <thread> + +#include <C2ComponentWrapper.h> +#include <C2Config.h> +#include <C2PlatformSupport.h> + +namespace android { + +namespace { + +using namespace std::chrono_literals; + +c2_status_t WrapSimpleMethod( + std::function<c2_status_t(void)> op, const SimpleMethodState &state) { + c2_status_t result = C2_OK; + switch (state.getMode()) { + case SimpleMethodState::EXECUTE: + result = op(); + break; + case SimpleMethodState::NO_OP: + break; + case SimpleMethodState::HANG: + while (true) { + std::this_thread::sleep_for(1s); + } + break; + } + (void)state.overrideResult(&result); + return result; +} + +} // namespace + +C2ComponentWrapper::Injecter::Injecter(C2ComponentWrapper *thiz) : mThiz(thiz) {} + +SimpleMethodState::Injecter C2ComponentWrapper::Injecter::start() { + return SimpleMethodState::Injecter(&mThiz->mStartState); +} + +C2ComponentWrapper::Listener::Listener( + const std::shared_ptr<C2Component::Listener> &listener) : mListener(listener) {} + +void C2ComponentWrapper::Listener::onWorkDone_nb(std::weak_ptr<C2Component> component, + std::list<std::unique_ptr<C2Work>> workItems) { + mListener->onWorkDone_nb(component, std::move(workItems)); +} + +void C2ComponentWrapper::Listener::onTripped_nb(std::weak_ptr<C2Component> component, + std::vector<std::shared_ptr<C2SettingResult>> settingResult) { + mListener->onTripped_nb(component,settingResult); +} + +void C2ComponentWrapper::Listener::onError_nb( + std::weak_ptr<C2Component> component, uint32_t errorCode) { + mListener->onError_nb(component, errorCode); +} + +C2ComponentWrapper::C2ComponentWrapper( + const std::shared_ptr<C2Component> &comp) : mComp(comp) {} + +c2_status_t C2ComponentWrapper::setListener_vb( + const std::shared_ptr<C2Component::Listener> &listener, c2_blocking_t mayBlock) { + mListener = std::make_shared<Listener>(listener); + return mComp->setListener_vb(mListener, mayBlock); +} + +c2_status_t C2ComponentWrapper::queue_nb(std::list<std::unique_ptr<C2Work>>* const items) { + return mComp->queue_nb(items); +} + +c2_status_t C2ComponentWrapper::announce_nb(const std::vector<C2WorkOutline> &items) { + return mComp->announce_nb(items); +} + +c2_status_t C2ComponentWrapper::flush_sm( + C2Component::flush_mode_t mode, std::list<std::unique_ptr<C2Work>>* const flushedWork) { + return mComp->flush_sm(mode, flushedWork); +} + +c2_status_t C2ComponentWrapper::drain_nb(C2Component::drain_mode_t mode) { + return mComp->drain_nb(mode); +} + +c2_status_t C2ComponentWrapper::start() { + return WrapSimpleMethod([this] { return mComp->start(); }, mStartState); +} + +c2_status_t C2ComponentWrapper::stop() { + return mComp->stop(); +} + +c2_status_t C2ComponentWrapper::reset() { + return mComp->reset(); +} + +c2_status_t C2ComponentWrapper::release() { + return mComp->release(); +} + +std::shared_ptr<C2ComponentInterface> C2ComponentWrapper::intf(){ + return mComp->intf(); +} + +C2ComponentWrapper::Injecter C2ComponentWrapper::inject() { + return Injecter(this); +} + +} // namespace android diff --git a/codec2/faultinjection/C2ComponentWrapper.h b/codec2/faultinjection/C2ComponentWrapper.h new file mode 100644 index 0000000..737350d --- /dev/null +++ b/codec2/faultinjection/C2ComponentWrapper.h @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef C2_COMPONENT_WRAPPER_H_ +#define C2_COMPONENT_WRAPPER_H_ + +#include <C2Component.h> + +#include "SimpleMethodState.h" + +namespace android { + +/** + * Creates a Wrapper around the class C2Component and its methods. The wrapper is used to + * simulate errors in the android media components by fault injection technique. + * This is done to check how the framework handles the error situation. + */ +class C2ComponentWrapper + : public C2Component, public std::enable_shared_from_this<C2ComponentWrapper> { +public: + class Injecter { + public: + explicit Injecter(C2ComponentWrapper *thiz); + + SimpleMethodState::Injecter start(); + private: + C2ComponentWrapper *const mThiz; + }; + + /** + * A wrapper around the listener class inside C2Component class. + */ + class Listener : public C2Component::Listener { + public: + explicit Listener(const std::shared_ptr<C2Component::Listener> &listener); + virtual ~Listener() = default; + + void onWorkDone_nb(std::weak_ptr<C2Component> component, + std::list<std::unique_ptr<C2Work>> workItems) override; + void onTripped_nb(std::weak_ptr<C2Component> component, + std::vector<std::shared_ptr<C2SettingResult>> settingResult) override; + void onError_nb(std::weak_ptr<C2Component> component, uint32_t errorCode) override; + + private: + std::shared_ptr<C2Component::Listener> mListener; + }; + + explicit C2ComponentWrapper(const std::shared_ptr<C2Component> &comp); + virtual ~C2ComponentWrapper() = default; + + virtual c2_status_t setListener_vb( + const std::shared_ptr<C2Component::Listener> &listener, + c2_blocking_t mayBlock) override; + virtual c2_status_t queue_nb(std::list<std::unique_ptr<C2Work>>* const items) override; + virtual c2_status_t announce_nb(const std::vector<C2WorkOutline> &items) override; + virtual c2_status_t flush_sm( + flush_mode_t mode, std::list<std::unique_ptr<C2Work>>* const flushedWork) override; + virtual c2_status_t drain_nb(drain_mode_t mode) override; + virtual c2_status_t start() override; + virtual c2_status_t stop() override; + virtual c2_status_t reset() override; + virtual c2_status_t release() override; + virtual std::shared_ptr<C2ComponentInterface> intf() override; + + Injecter inject(); + +private: + std::shared_ptr<Listener> mListener; + std::shared_ptr<C2Component> mComp; + + SimpleMethodState mStartState; +}; + +} // namespace android + +#endif // C2_COMPONENT_WRAPPER_H_ diff --git a/codec2/faultinjection/SimpleMethodState.cpp b/codec2/faultinjection/SimpleMethodState.cpp new file mode 100644 index 0000000..179d64e --- /dev/null +++ b/codec2/faultinjection/SimpleMethodState.cpp @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_NDEBUG 0 +#define LOG_TAG "SimpleMethodState" +#include <log/log.h> + +#include "SimpleMethodState.h" + +namespace android { + +SimpleMethodState::Injecter::Injecter(SimpleMethodState *thiz) : mThiz(thiz) {} + +void SimpleMethodState::Injecter::hang() { + mThiz->mMode = HANG; +} + +void SimpleMethodState::Injecter::fail(c2_status_t err, bool execute) { + mThiz->mMode = execute ? EXECUTE : NO_OP; + mThiz->mOverride = true; + mThiz->mResultOverride = err; +} + +SimpleMethodState::SimpleMethodState() + : mMode(EXECUTE), mOverride(false), mResultOverride(C2_OK) {} + +SimpleMethodState::Mode SimpleMethodState::getMode() const { + return mMode; +} + +bool SimpleMethodState::overrideResult(c2_status_t *result) const { + if (!mOverride) { + return false; + } + *result = mResultOverride; + return true; +} + +} // namespace android diff --git a/codec2/faultinjection/SimpleMethodState.h b/codec2/faultinjection/SimpleMethodState.h new file mode 100644 index 0000000..dc0459d --- /dev/null +++ b/codec2/faultinjection/SimpleMethodState.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SIMPLE_METHOD_STATE_H_ +#define SIMPLE_METHOD_STATE_H_ + +#include <C2.h> + +namespace android { + +/** + * State for a simple method which returns c2_status_t and takes no parameters. + */ +class SimpleMethodState { +public: + enum Mode { + // Execute the normal operation + EXECUTE, + // Don't do anything + NO_OP, + // Hang; never return + HANG, + }; + + /** + * Injecter class that modifies the internal states of this class. + */ + class Injecter { + public: + explicit Injecter(SimpleMethodState *thiz); + + /** + * Hang the operation. + */ + void hang(); + + /** + * Fail the operation with given params. + * + * \param err error code to replace the actual return value + * \param execute whether the wrapper should execute the operation + */ + void fail(c2_status_t err, bool execute = false); + + private: + SimpleMethodState *const mThiz; + }; + + SimpleMethodState(); + + /** + * Get execution mode. + */ + Mode getMode() const; + + /** + * Override result from running the operation if configured so. + */ + bool overrideResult(c2_status_t *result) const; + +private: + Mode mMode; + bool mOverride; + c2_status_t mResultOverride; +}; + +} // namespace android + +#endif // SIMPLE_METHOD_STATE_H_ diff --git a/codec2/hidl/1.0/mts/audio/MtsHidlC2V1_0TargetAudioDecTest.cpp b/codec2/hidl/1.0/mts/audio/MtsHidlC2V1_0TargetAudioDecTest.cpp index f9ae8ba..1e87f38 100644 --- a/codec2/hidl/1.0/mts/audio/MtsHidlC2V1_0TargetAudioDecTest.cpp +++ b/codec2/hidl/1.0/mts/audio/MtsHidlC2V1_0TargetAudioDecTest.cpp @@ -19,6 +19,7 @@ #include <android-base/logging.h> #include <gtest/gtest.h> +#include <algorithm> #include <stdio.h> #include <fstream> @@ -133,6 +134,8 @@ class Codec2AudioDecHidlTest : public ::testing::VtsHalHidlTargetTestBase { } mEos = false; mFramesReceived = 0; + mTimestampUs = 0u; + mTimestampDevTest = false; if (mCompName == unknown_comp) mDisableTest = true; if (mDisableTest) std::cout << "[ WARN ] Test Disabled \n"; } @@ -146,38 +149,39 @@ class Codec2AudioDecHidlTest : public ::testing::VtsHalHidlTargetTestBase { Super::TearDown(); } + struct outputMetaData { + uint64_t timestampUs; + uint32_t rangeLength; + }; // callback function to process onWorkDone received by Listener void handleWorkDone(std::list<std::unique_ptr<C2Work>>& workItems) { for (std::unique_ptr<C2Work>& work : workItems) { - // handle configuration changes in work done - if (!work->worklets.empty() && - (work->worklets.front()->output.configUpdate.size() != 0)) { - ALOGV("Config Update"); - std::vector<std::unique_ptr<C2Param>> updates = - std::move(work->worklets.front()->output.configUpdate); - std::vector<C2Param*> configParam; - std::vector<std::unique_ptr<C2SettingResult>> failures; - for (size_t i = 0; i < updates.size(); ++i) { - C2Param* param = updates[i].get(); - if ((param->index() == C2StreamSampleRateInfo::output::PARAM_TYPE) || - (param->index() == C2StreamChannelCountInfo::output::PARAM_TYPE)) { - configParam.push_back(param); + if (!work->worklets.empty()) { + // For decoder components current timestamp always exceeds + // previous timestamp + bool codecConfig = ((work->worklets.front()->output.flags & + C2FrameData::FLAG_CODEC_CONFIG) != 0); + if (!codecConfig && + !work->worklets.front()->output.buffers.empty()) { + EXPECT_GE(work->worklets.front()->output.ordinal.timestamp.peeku(), + mTimestampUs); + mTimestampUs = + work->worklets.front()->output.ordinal.timestamp.peeku(); + uint32_t rangeLength = + work->worklets.front()->output.buffers[0]->data() + .linearBlocks().front().map().get().capacity(); + //List of timestamp values and output size to calculate timestamp + if (mTimestampDevTest) { + outputMetaData meta = {mTimestampUs, rangeLength}; + oBufferMetaData.push_back(meta); } } - mComponent->config(configParam, C2_DONT_BLOCK, &failures); - ASSERT_EQ(failures.size(), 0u); + bool mCsd = false; + workDone(mComponent, work, mFlushedIndices, mQueueLock, + mQueueCondition, mWorkQueue, mEos, mCsd, + mFramesReceived); + (void)mCsd; } - mFramesReceived++; - mEos = (work->worklets.front()->output.flags & - C2FrameData::FLAG_END_OF_STREAM) != 0; - ALOGV("WorkDone: frameID received %d", - (int)work->worklets.front()->output.ordinal.frameIndex.peeku()); - work->input.buffers.clear(); - work->worklets.clear(); - typedef std::unique_lock<std::mutex> ULock; - ULock l(mQueueLock); - mWorkQueue.push_back(std::move(work)); - mQueueCondition.notify_all(); } } @@ -200,8 +204,14 @@ class Codec2AudioDecHidlTest : public ::testing::VtsHalHidlTargetTestBase { bool mEos; bool mDisableTest; + bool mTimestampDevTest; standardComp mCompName; + uint64_t mTimestampUs; uint32_t mFramesReceived; + std::list<uint64_t> mFlushedIndices; + std::list<uint64_t> mTimestampUslist; + ::android::List<outputMetaData> oBufferMetaData; + C2BlockPool::local_id_t mBlockPoolId; std::shared_ptr<C2BlockPool> mLinearPool; std::shared_ptr<C2Allocator> mLinearAllocator; @@ -254,7 +264,7 @@ void validateComponent( // Validates component name if (compName == Codec2AudioDecHidlTest::unknown_comp) { - ALOGD("Component InValid"); + ALOGE("Component InValid"); disableTest = true; return; } @@ -295,37 +305,9 @@ void getInputChannelInfo( ASSERT_TRUE(false); } else { size_t offset = sizeof(C2Param); - ALOGD("--------------PARAMS-----------------"); for (size_t i = 0; i < inParams.size(); ++i) { C2Param* param = inParams[i].get(); bitStreamInfo[i] = *(int32_t*)((uint8_t*)param + offset); - ALOGD("Param value : %u", *(int32_t*)((uint8_t*)param + offset)); - ALOGD("Param isVendor %d", param->isVendor()); - ALOGD("Param forInput %d", param->forInput()); - ALOGD("Param forOutput %d", param->forOutput()); - switch (param->kind()) { - case C2Param::NONE: - ALOGD("Param kind NONE"); - break; - case C2Param::STRUCT: - ALOGD("Param kind STRUCT"); - break; - case C2Param::INFO: - ALOGD("Param kind INFO"); - break; - case C2Param::SETTING: - ALOGD("Param kind SETTING"); - break; - case C2Param::TUNING: - ALOGD("Param kind TUNING"); - break; - default: - ALOGD("Param kind Invalid"); - break; - } - ALOGD("Param size (if size is 0 parameter is invalid) %zu", - param->size()); - ALOGD("--------------------------------------"); } switch (compName) { case Codec2AudioDecHidlTest::amrnb: { @@ -404,20 +386,16 @@ void GetURLForComponent(Codec2AudioDecHidlTest::standardComp comp, char* mURL, } } -void decodeNFrames(const std::shared_ptr<android::Codec2Client::Component> &component, - std::mutex &queueLock, std::condition_variable &queueCondition, - std::list<std::unique_ptr<C2Work>> &workQueue, - std::shared_ptr<C2Allocator> &linearAllocator, - std::shared_ptr<C2BlockPool> &linearPool, - C2BlockPool::local_id_t blockPoolId, +void decodeNFrames(const std::shared_ptr<android::Codec2Client::Component>& component, + std::mutex &queueLock, std::condition_variable& queueCondition, + std::list<std::unique_ptr<C2Work>>& workQueue, + std::list<uint64_t>& flushedIndices, + std::shared_ptr<C2BlockPool>& linearPool, std::ifstream& eleStream, android::Vector<FrameInfo>* Info, int offset, int range, bool signalEOS = true) { typedef std::unique_lock<std::mutex> ULock; - int frameID = 0; - linearPool = - std::make_shared<C2PooledBlockPool>(linearAllocator, blockPoolId++); - component->start(); + int frameID = offset; int maxRetry = 0; while (1) { if (frameID == (int)Info->size() || frameID == (offset + range)) break; @@ -440,42 +418,49 @@ void decodeNFrames(const std::shared_ptr<android::Codec2Client::Component> &comp int64_t timestamp = (*Info)[frameID].timestamp; if ((*Info)[frameID].flags) flags = 1u << ((*Info)[frameID].flags - 1); if (signalEOS && ((frameID == (int)Info->size() - 1) || - (frameID == (offset + range - 1)))) - flags |= C2FrameData::FLAG_END_OF_STREAM; + (frameID == (offset + range - 1)))) + flags |= C2FrameData::FLAG_END_OF_STREAM; work->input.flags = (C2FrameData::flags_t)flags; work->input.ordinal.timestamp = timestamp; work->input.ordinal.frameIndex = frameID; + { + ULock l(queueLock); + flushedIndices.emplace_back(frameID); + } int size = (*Info)[frameID].bytesCount; char* data = (char*)malloc(size); + ASSERT_NE(data, nullptr); eleStream.read(data, size); ASSERT_EQ(eleStream.gcount(), size); - std::shared_ptr<C2LinearBlock> block; - ASSERT_EQ(C2_OK, - linearPool->fetchLinearBlock( - size, {C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE}, - &block)); - ASSERT_TRUE(block); - - // Write View - C2WriteView view = block->map().get(); - if (view.error() != C2_OK) { - fprintf(stderr, "C2LinearBlock::map() failed : %d", view.error()); - break; - } - ASSERT_EQ((size_t)size, view.capacity()); - ASSERT_EQ(0u, view.offset()); - ASSERT_EQ((size_t)size, view.size()); + work->input.buffers.clear(); + if (size) { + std::shared_ptr<C2LinearBlock> block; + ASSERT_EQ(C2_OK, + linearPool->fetchLinearBlock( + size, {C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE}, + &block)); + ASSERT_TRUE(block); + + // Write View + C2WriteView view = block->map().get(); + if (view.error() != C2_OK) { + fprintf(stderr, "C2LinearBlock::map() failed : %d", view.error()); + break; + } + ASSERT_EQ((size_t)size, view.capacity()); + ASSERT_EQ(0u, view.offset()); + ASSERT_EQ((size_t)size, view.size()); - memcpy(view.base(), data, size); + memcpy(view.base(), data, size); - work->input.buffers.clear(); - work->input.buffers.emplace_back(new LinearBuffer(block)); + work->input.buffers.emplace_back(new LinearBuffer(block)); + free(data); + } work->worklets.clear(); work->worklets.emplace_back(new C2Worklet); - free(data); std::list<std::unique_ptr<C2Work>> items; items.push_back(std::move(work)); @@ -488,28 +473,6 @@ void decodeNFrames(const std::shared_ptr<android::Codec2Client::Component> &comp } } -void waitOnInputConsumption(std::mutex& queueLock, - std::condition_variable& queueCondition, - std::list<std::unique_ptr<C2Work>>& workQueue) { - typedef std::unique_lock<std::mutex> ULock; - uint32_t queueSize; - uint32_t maxRetry = 0; - { - ULock l(queueLock); - queueSize = workQueue.size(); - } - while ((maxRetry < MAX_RETRY) && (queueSize < MAX_INPUT_BUFFERS)) { - ULock l(queueLock); - if (queueSize != workQueue.size()) { - queueSize = workQueue.size(); - maxRetry = 0; - } else { - queueCondition.wait_for(l, TIME_OUT); - maxRetry++; - } - } -} - TEST_F(Codec2AudioDecHidlTest, validateCompName) { if (mDisableTest) return; ALOGV("Checks if the given component is a valid audio component"); @@ -520,16 +483,204 @@ TEST_F(Codec2AudioDecHidlTest, validateCompName) { TEST_F(Codec2AudioDecHidlTest, configComp) { description("Tests component specific configuration"); if (mDisableTest) return; + ASSERT_EQ(mComponent->start(), C2_OK); int32_t bitStreamInfo[2] = {0}; ASSERT_NO_FATAL_FAILURE( getInputChannelInfo(mComponent, mCompName, bitStreamInfo)); setupConfigParam(mComponent, bitStreamInfo); + ASSERT_EQ(mComponent->stop(), C2_OK); } TEST_F(Codec2AudioDecHidlTest, DecodeTest) { description("Decodes input file"); if (mDisableTest) return; + ASSERT_EQ(mComponent->start(), C2_OK); + mTimestampDevTest = true; + char mURL[512], info[512]; + std::ifstream eleStream, eleInfo; + + strcpy(mURL, gEnv->getRes().c_str()); + strcpy(info, gEnv->getRes().c_str()); + GetURLForComponent(mCompName, mURL, info); + + eleInfo.open(info); + ASSERT_EQ(eleInfo.is_open(), true); + android::Vector<FrameInfo> Info; + int bytesCount = 0; + uint32_t flags = 0; + uint32_t timestamp = 0; + while (1) { + if (!(eleInfo >> bytesCount)) break; + eleInfo >> flags; + eleInfo >> timestamp; + bool codecConfig = + ((1 << (flags - 1)) & C2FrameData::FLAG_CODEC_CONFIG) != 0; + if (mTimestampDevTest && !codecConfig) + mTimestampUslist.push_back(timestamp); + Info.push_back({bytesCount, flags, timestamp}); + } + eleInfo.close(); + int32_t bitStreamInfo[2] = {0}; + if (mCompName == raw) { + bitStreamInfo[0] = 8000; + bitStreamInfo[1] = 1; + } else { + ASSERT_NO_FATAL_FAILURE( + getInputChannelInfo(mComponent, mCompName, bitStreamInfo)); + } + setupConfigParam(mComponent, bitStreamInfo); + ALOGV("mURL : %s", mURL); + eleStream.open(mURL, std::ifstream::binary); + ASSERT_EQ(eleStream.is_open(), true); + ASSERT_NO_FATAL_FAILURE(decodeNFrames( + mComponent, mQueueLock, mQueueCondition, mWorkQueue, mFlushedIndices, + mLinearPool, eleStream, &Info, 0, (int)Info.size())); + // blocking call to ensures application to Wait till all the inputs are + // consumed + ASSERT_NO_FATAL_FAILURE( + waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue)); + eleStream.close(); + if (mFramesReceived != Info.size()) { + ALOGE("Input buffer count and Output buffer count mismatch"); + ALOGE("framesReceived : %d inputFrames : %zu", mFramesReceived, + Info.size()); + ASSERT_TRUE(false); + } + ASSERT_EQ(mEos, true); + if (mTimestampDevTest) { + uint64_t expTs; + uint32_t samplesReceived = 0; + // Update SampleRate and ChannelCount + ASSERT_NO_FATAL_FAILURE( + getInputChannelInfo(mComponent, mCompName, bitStreamInfo)); + int nSampleRate = bitStreamInfo[0]; + int nChannels = bitStreamInfo[1]; + std::list<uint64_t>::iterator itIn = mTimestampUslist.begin(); + android::List<outputMetaData>::iterator itOut = oBufferMetaData.begin(); + EXPECT_EQ(*itIn, itOut->timestampUs); + expTs = *itIn; + while (itOut != oBufferMetaData.end()) { + EXPECT_EQ(expTs, itOut->timestampUs); + if (expTs != itOut->timestampUs) break; + // buffer samples = ((total bytes) / (ac * (bits per sample / 8)) + samplesReceived += ((itOut->rangeLength) / (nChannels * 2)); + expTs = samplesReceived * 1000000ll / nSampleRate; + itOut++; + } + itIn = mTimestampUslist.end(); + --itIn; + EXPECT_GT(expTs, *itIn); + oBufferMetaData.clear(); + mTimestampUslist.clear(); + } + ASSERT_EQ(mComponent->stop(), C2_OK); +} + +// thumbnail test +TEST_F(Codec2AudioDecHidlTest, ThumbnailTest) { + description("Test Request for thumbnail"); + if (mDisableTest) return; + + ASSERT_EQ(mComponent->start(), C2_OK); + char mURL[512], info[512]; + std::ifstream eleStream, eleInfo; + + strcpy(mURL, gEnv->getRes().c_str()); + strcpy(info, gEnv->getRes().c_str()); + GetURLForComponent(mCompName, mURL, info); + + eleInfo.open(info); + ASSERT_EQ(eleInfo.is_open(), true); + android::Vector<FrameInfo> Info; + int bytesCount = 0; + uint32_t flags = 0; + uint32_t timestamp = 0; + while (1) { + if (!(eleInfo >> bytesCount)) break; + eleInfo >> flags; + eleInfo >> timestamp; + Info.push_back({bytesCount, flags, timestamp}); + } + eleInfo.close(); + int32_t bitStreamInfo[2] = {0}; + if (mCompName == raw) { + bitStreamInfo[0] = 8000; + bitStreamInfo[1] = 1; + } else { + ASSERT_NO_FATAL_FAILURE( + getInputChannelInfo(mComponent, mCompName, bitStreamInfo)); + } + setupConfigParam(mComponent, bitStreamInfo); + ALOGV("mURL : %s", mURL); + + // request EOS for thumbnail + // signal EOS flag with last frame + size_t i = -1; + do { + i++; + flags = 0; + if (Info[i].flags) flags = 1u << (Info[i].flags - 1); + + } while (!(flags & SYNC_FRAME)); + eleStream.open(mURL, std::ifstream::binary); + ASSERT_EQ(eleStream.is_open(), true); + ASSERT_NO_FATAL_FAILURE(decodeNFrames( + mComponent, mQueueLock, mQueueCondition, mWorkQueue, mFlushedIndices, + mLinearPool, eleStream, &Info, 0, i + 1)); + ASSERT_NO_FATAL_FAILURE( + waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue)); + eleStream.close(); + EXPECT_GE(mFramesReceived, 1U); + ASSERT_EQ(mEos, true); + ASSERT_EQ(mComponent->stop(), C2_OK); +} + +TEST_F(Codec2AudioDecHidlTest, EOSTest) { + description("Test empty input buffer with EOS flag"); + if (mDisableTest) return; + typedef std::unique_lock<std::mutex> ULock; + ASSERT_EQ(mComponent->start(), C2_OK); + std::unique_ptr<C2Work> work; + // Prepare C2Work + { + ULock l(mQueueLock); + if (!mWorkQueue.empty()) { + work.swap(mWorkQueue.front()); + mWorkQueue.pop_front(); + } else { + ASSERT_TRUE(false) << "mWorkQueue Empty at the start of test"; + } + } + ASSERT_NE(work, nullptr); + + work->input.flags = (C2FrameData::flags_t)C2FrameData::FLAG_END_OF_STREAM; + work->input.ordinal.timestamp = 0; + work->input.ordinal.frameIndex = 0; + work->input.buffers.clear(); + work->worklets.clear(); + work->worklets.emplace_back(new C2Worklet); + + std::list<std::unique_ptr<C2Work>> items; + items.push_back(std::move(work)); + ASSERT_EQ(mComponent->queue(&items), C2_OK); + + { + ULock l(mQueueLock); + if (mWorkQueue.size() != MAX_INPUT_BUFFERS) { + mQueueCondition.wait_for(l, TIME_OUT); + } + } + ASSERT_EQ(mEos, true); + ASSERT_EQ(mWorkQueue.size(), (size_t)MAX_INPUT_BUFFERS); + ASSERT_EQ(mComponent->stop(), C2_OK); +} + +TEST_F(Codec2AudioDecHidlTest, FlushTest) { + description("Tests Flush calls"); + if (mDisableTest) return; + typedef std::unique_lock<std::mutex> ULock; + ASSERT_EQ(mComponent->start(), C2_OK); char mURL[512], info[512]; std::ifstream eleStream, eleInfo; @@ -543,6 +694,7 @@ TEST_F(Codec2AudioDecHidlTest, DecodeTest) { int bytesCount = 0; uint32_t flags = 0; uint32_t timestamp = 0; + mFlushedIndices.clear(); while (1) { if (!(eleInfo >> bytesCount)) break; eleInfo >> flags; @@ -562,14 +714,141 @@ TEST_F(Codec2AudioDecHidlTest, DecodeTest) { ALOGV("mURL : %s", mURL); eleStream.open(mURL, std::ifstream::binary); ASSERT_EQ(eleStream.is_open(), true); + // Decode 128 frames and flush. here 128 is chosen to ensure there is a key + // frame after this so that the below section can be covered for all + // components + uint32_t numFramesFlushed = 128; + ASSERT_NO_FATAL_FAILURE(decodeNFrames( + mComponent, mQueueLock, mQueueCondition, mWorkQueue, mFlushedIndices, + mLinearPool, eleStream, &Info, 0, numFramesFlushed, false)); + // flush + std::list<std::unique_ptr<C2Work>> flushedWork; + c2_status_t err = + mComponent->flush(C2Component::FLUSH_COMPONENT, &flushedWork); + ASSERT_EQ(err, C2_OK); + ASSERT_NO_FATAL_FAILURE( + waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue, + (size_t)MAX_INPUT_BUFFERS - flushedWork.size())); + uint64_t frameIndex; + { + //Update mFlushedIndices based on the index received from flush() + ULock l(mQueueLock); + for (std::unique_ptr<C2Work>& work : flushedWork) { + ASSERT_NE(work, nullptr); + frameIndex = work->input.ordinal.frameIndex.peeku(); + std::list<uint64_t>::iterator frameIndexIt = + std::find(mFlushedIndices.begin(), mFlushedIndices.end(), + frameIndex); + if (!mFlushedIndices.empty() && + (frameIndexIt != mFlushedIndices.end())) { + mFlushedIndices.erase(frameIndexIt); + work->input.buffers.clear(); + work->worklets.clear(); + mWorkQueue.push_back(std::move(work)); + } + } + } + // Seek to next key frame and start decoding till the end + mFlushedIndices.clear(); + int index = numFramesFlushed; + bool keyFrame = false; + flags = 0; + while (index < (int)Info.size()) { + if (Info[index].flags) flags = 1u << (Info[index].flags - 1); + if ((flags & SYNC_FRAME) == SYNC_FRAME) { + keyFrame = true; + break; + } + flags = 0; + eleStream.ignore(Info[index].bytesCount); + index++; + } + if (keyFrame) { + ASSERT_NO_FATAL_FAILURE( + decodeNFrames(mComponent, mQueueLock, mQueueCondition, mWorkQueue, + mFlushedIndices, mLinearPool, eleStream, &Info, index, + (int)Info.size() - index)); + } + eleStream.close(); + err = + mComponent->flush(C2Component::FLUSH_COMPONENT, &flushedWork); + ASSERT_EQ(err, C2_OK); + ASSERT_NO_FATAL_FAILURE( + waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue, + (size_t)MAX_INPUT_BUFFERS - flushedWork.size())); + { + //Update mFlushedIndices based on the index received from flush() + ULock l(mQueueLock); + for (std::unique_ptr<C2Work>& work : flushedWork) { + ASSERT_NE(work, nullptr); + frameIndex = work->input.ordinal.frameIndex.peeku(); + std::list<uint64_t>::iterator frameIndexIt = + std::find(mFlushedIndices.begin(), mFlushedIndices.end(), + frameIndex); + if (!mFlushedIndices.empty() && + (frameIndexIt != mFlushedIndices.end())) { + mFlushedIndices.erase(frameIndexIt); + work->input.buffers.clear(); + work->worklets.clear(); + mWorkQueue.push_back(std::move(work)); + } + } + } + ASSERT_EQ(mFlushedIndices.empty(), true); + ASSERT_EQ(mComponent->stop(), C2_OK); +} + +TEST_F(Codec2AudioDecHidlTest, DecodeTestEmptyBuffersInserted) { + description("Decode with multiple empty input frames"); + if (mDisableTest) return; + + ASSERT_EQ(mComponent->start(), C2_OK); + + char mURL[512], info[512]; + std::ifstream eleStream, eleInfo; + + strcpy(mURL, gEnv->getRes().c_str()); + strcpy(info, gEnv->getRes().c_str()); + GetURLForComponent(mCompName, mURL, info); + + eleInfo.open(info); + ASSERT_EQ(eleInfo.is_open(), true) << mURL << " - file not found"; + android::Vector<FrameInfo> Info; + int bytesCount = 0; + uint32_t frameId = 0; + uint32_t flags = 0; + uint32_t timestamp = 0; + bool codecConfig = false; + // This test introduces empty CSD after every 20th frame + // and empty input frames at an interval of 5 frames. + while (1) { + if (!(frameId % 5)) { + if (!(frameId % 20)) flags = 32; + else flags = 0; + bytesCount = 0; + } else { + if (!(eleInfo >> bytesCount)) break; + eleInfo >> flags; + eleInfo >> timestamp; + codecConfig = flags ? + ((1 << (flags - 1)) & C2FrameData::FLAG_CODEC_CONFIG) != 0 : 0; + } + Info.push_back({bytesCount, flags, timestamp}); + frameId++; + } + eleInfo.close(); + + ALOGV("mURL : %s", mURL); + eleStream.open(mURL, std::ifstream::binary); + ASSERT_EQ(eleStream.is_open(), true); ASSERT_NO_FATAL_FAILURE(decodeNFrames( - mComponent, mQueueLock, mQueueCondition, mWorkQueue, mLinearAllocator, - mLinearPool, mBlockPoolId, eleStream, &Info, 0, (int)Info.size())); + mComponent, mQueueLock, mQueueCondition, mWorkQueue, mFlushedIndices, + mLinearPool, eleStream, &Info, 0, (int)Info.size())); // blocking call to ensures application to Wait till all the inputs are // consumed if (!mEos) { - ALOGD("Waiting for input consumption"); + ALOGV("Waiting for input consumption"); ASSERT_NO_FATAL_FAILURE( waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue)); } @@ -577,18 +856,16 @@ TEST_F(Codec2AudioDecHidlTest, DecodeTest) { eleStream.close(); if (mFramesReceived != Info.size()) { ALOGE("Input buffer count and Output buffer count mismatch"); - ALOGE("framesReceived : %d inputFrames : %zu", mFramesReceived, + ALOGV("framesReceived : %d inputFrames : %zu", mFramesReceived, Info.size()); ASSERT_TRUE(false); } + + ASSERT_EQ(mComponent->stop(), C2_OK); } } // anonymous namespace -// TODO : Thumbnail Test -// TODO : Test EOS -// TODO : Flush Test -// TODO : Timestamps deviation int main(int argc, char** argv) { gEnv = new ComponentTestEnvironment(); ::testing::AddGlobalTestEnvironment(gEnv); diff --git a/codec2/hidl/1.0/mts/audio/MtsHidlC2V1_0TargetAudioEncTest.cpp b/codec2/hidl/1.0/mts/audio/MtsHidlC2V1_0TargetAudioEncTest.cpp index af71e38..5d66ee5 100644 --- a/codec2/hidl/1.0/mts/audio/MtsHidlC2V1_0TargetAudioEncTest.cpp +++ b/codec2/hidl/1.0/mts/audio/MtsHidlC2V1_0TargetAudioEncTest.cpp @@ -21,6 +21,7 @@ #include <gtest/gtest.h> #include <stdio.h> #include <fstream> +#include <algorithm> #include <codec2/hidl/client.h> #include <C2AllocatorIon.h> @@ -95,6 +96,7 @@ class Codec2AudioEncHidlTest : public ::testing::VtsHalHidlTargetTestBase { const StringToName kStringToName[] = { {"aac", aac}, {"flac", flac}, + {"opus", opus}, {"amrnb", amrnb}, {"amrwb", amrwb}, }; @@ -134,37 +136,17 @@ class Codec2AudioEncHidlTest : public ::testing::VtsHalHidlTargetTestBase { // callback function to process onWorkDone received by Listener void handleWorkDone(std::list<std::unique_ptr<C2Work>>& workItems) { for (std::unique_ptr<C2Work>& work : workItems) { - // handle configuration changes in work done - if (!work->worklets.empty() && - (work->worklets.front()->output.configUpdate.size() != 0)) { - ALOGV("Config Update"); - std::vector<std::unique_ptr<C2Param>> updates = - std::move(work->worklets.front()->output.configUpdate); - std::vector<C2Param*> configParam; - std::vector<std::unique_ptr<C2SettingResult>> failures; - for (size_t i = 0; i < updates.size(); ++i) { - C2Param* param = updates[i].get(); - if (param->index() == C2StreamCsdInfo::output::PARAM_TYPE) { - mCsd = true; - } - } + if (!work->worklets.empty()) { + workDone(mComponent, work, mFlushedIndices, mQueueLock, + mQueueCondition, mWorkQueue, mEos, mCsd, + mFramesReceived); } - mFramesReceived++; - mEos = (work->worklets.front()->output.flags & - C2FrameData::FLAG_END_OF_STREAM) != 0; - ALOGV("WorkDone: frameID received %d", - (int)work->worklets.front()->output.ordinal.frameIndex.peeku()); - work->input.buffers.clear(); - work->worklets.clear(); - typedef std::unique_lock<std::mutex> ULock; - ULock l(mQueueLock); - mWorkQueue.push_back(std::move(work)); - mQueueCondition.notify_all(); } } enum standardComp { aac, flac, + opus, amrnb, amrwb, unknown_comp, @@ -175,6 +157,8 @@ class Codec2AudioEncHidlTest : public ::testing::VtsHalHidlTargetTestBase { bool mDisableTest; standardComp mCompName; uint32_t mFramesReceived; + std::list<uint64_t> mFlushedIndices; + C2BlockPool::local_id_t mBlockPoolId; std::shared_ptr<C2BlockPool> mLinearPool; std::shared_ptr<C2Allocator> mLinearAllocator; @@ -264,6 +248,8 @@ void GetURLForComponent(Codec2AudioEncHidlTest::standardComp comp, char* mURL) { "bbb_raw_1ch_16khz_s16le.raw"}, {Codec2AudioEncHidlTest::standardComp::flac, "bbb_raw_2ch_48khz_s16le.raw"}, + {Codec2AudioEncHidlTest::standardComp::opus, + "bbb_raw_2ch_48khz_s16le.raw"}, }; for (size_t i = 0; i < sizeof(kCompToURL) / sizeof(kCompToURL[0]); ++i) { @@ -274,19 +260,16 @@ void GetURLForComponent(Codec2AudioEncHidlTest::standardComp comp, char* mURL) { } } -void encodeNFrames(const std::shared_ptr<android::Codec2Client::Component> &component, - std::mutex &queueLock, std::condition_variable &queueCondition, - std::list<std::unique_ptr<C2Work>> &workQueue, - std::shared_ptr<C2Allocator> &linearAllocator, - std::shared_ptr<C2BlockPool> &linearPool, - C2BlockPool::local_id_t blockPoolId, +void encodeNFrames(const std::shared_ptr<android::Codec2Client::Component>& component, + std::mutex &queueLock, std::condition_variable& queueCondition, + std::list<std::unique_ptr<C2Work>>& workQueue, + std::list<uint64_t>& flushedIndices, + std::shared_ptr<C2BlockPool>& linearPool, std::ifstream& eleStream, uint32_t nFrames, int32_t samplesPerFrame, int32_t nChannels, - int32_t nSampleRate,bool signalEOS = true) { + int32_t nSampleRate, bool flushed = false, + bool signalEOS = true) { typedef std::unique_lock<std::mutex> ULock; - linearPool = - std::make_shared<C2PooledBlockPool>(linearAllocator, blockPoolId++); - component->start(); uint32_t frameID = 0; uint32_t maxRetry = 0; @@ -314,11 +297,19 @@ void encodeNFrames(const std::shared_ptr<android::Codec2Client::Component> &comp } if (signalEOS && (nFrames == 1)) flags |= C2FrameData::FLAG_END_OF_STREAM; - + if (flushed) { + flags |= SYNC_FRAME; + flushed = false; + } work->input.flags = (C2FrameData::flags_t)flags; work->input.ordinal.timestamp = timestamp; work->input.ordinal.frameIndex = frameID; + { + ULock l(queueLock); + flushedIndices.emplace_back(frameID); + } char* data = (char*)malloc(bytesCount); + ASSERT_NE(data, nullptr); eleStream.read(data, bytesCount); ASSERT_EQ(eleStream.gcount(), bytesCount); std::shared_ptr<C2LinearBlock> block; @@ -357,28 +348,6 @@ void encodeNFrames(const std::shared_ptr<android::Codec2Client::Component> &comp } } -void waitOnInputConsumption(std::mutex& queueLock, - std::condition_variable& queueCondition, - std::list<std::unique_ptr<C2Work>>& workQueue) { - typedef std::unique_lock<std::mutex> ULock; - uint32_t queueSize; - uint32_t maxRetry = 0; - { - ULock l(queueLock); - queueSize = workQueue.size(); - } - while ((maxRetry < MAX_RETRY) && (queueSize < MAX_INPUT_BUFFERS)) { - ULock l(queueLock); - if (queueSize != workQueue.size()) { - queueSize = workQueue.size(); - maxRetry = 0; - } else { - queueCondition.wait_for(l, TIME_OUT); - maxRetry++; - } - } -} - TEST_F(Codec2AudioEncHidlTest, validateCompName) { if (mDisableTest) return; ALOGV("Checks if the given component is a valid audio component"); @@ -389,6 +358,7 @@ TEST_F(Codec2AudioEncHidlTest, validateCompName) { TEST_F(Codec2AudioEncHidlTest, EncodeTest) { ALOGV("EncodeTest"); if (mDisableTest) return; + ASSERT_EQ(mComponent->start(), C2_OK); char mURL[512]; strcpy(mURL, gEnv->getRes().c_str()); GetURLForComponent(mCompName, mURL); @@ -408,6 +378,11 @@ TEST_F(Codec2AudioEncHidlTest, EncodeTest) { nSampleRate = 48000; samplesPerFrame = 1152; break; + case opus: + nChannels = 2; + nSampleRate = 48000; + samplesPerFrame = 960; + break; case amrnb: nChannels = 1; nSampleRate = 8000; @@ -423,42 +398,192 @@ TEST_F(Codec2AudioEncHidlTest, EncodeTest) { } setupConfigParam(mComponent, nChannels, nSampleRate); std::ifstream eleStream; + uint32_t numFrames = 128; eleStream.open(mURL, std::ifstream::binary); ASSERT_EQ(eleStream.is_open(), true); ALOGV("mURL : %s", mURL); ASSERT_NO_FATAL_FAILURE( encodeNFrames(mComponent, mQueueLock, mQueueCondition, mWorkQueue, - mLinearAllocator, mLinearPool, mBlockPoolId, eleStream, - 128, samplesPerFrame, nChannels, nSampleRate)); - + mFlushedIndices, mLinearPool, eleStream, numFrames, + samplesPerFrame, nChannels, nSampleRate)); // blocking call to ensures application to Wait till all the inputs are // consumed - if (!mEos) { - ALOGD("Waiting for input consumption"); - ASSERT_NO_FATAL_FAILURE( - waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue)); - } - + ASSERT_NO_FATAL_FAILURE( + waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue)); eleStream.close(); - if (mFramesReceived != 128) { + if (mFramesReceived != numFrames) { ALOGE("Input buffer count and Output buffer count mismatch"); - ALOGE("framesReceived : %d inputFrames : 128", mFramesReceived); + ALOGE("framesReceived : %d inputFrames : %u", mFramesReceived, numFrames); ASSERT_TRUE(false); } - if ((mCompName == flac || mCompName == aac)) { + if ((mCompName == flac || mCompName == opus || mCompName == aac)) { if (!mCsd) { ALOGE("CSD buffer missing"); ASSERT_TRUE(false); } } + ASSERT_EQ(mEos, true); + ASSERT_EQ(mComponent->stop(), C2_OK); +} + +TEST_F(Codec2AudioEncHidlTest, EOSTest) { + description("Test empty input buffer with EOS flag"); + if (mDisableTest) return; + ASSERT_EQ(mComponent->start(), C2_OK); + + typedef std::unique_lock<std::mutex> ULock; + std::unique_ptr<C2Work> work; + { + ULock l(mQueueLock); + if (!mWorkQueue.empty()) { + work.swap(mWorkQueue.front()); + mWorkQueue.pop_front(); + } else { + ALOGE("mWorkQueue Empty is not expected at the start of the test"); + ASSERT_TRUE(false); + } + } + ASSERT_NE(work, nullptr); + work->input.flags = (C2FrameData::flags_t)C2FrameData::FLAG_END_OF_STREAM; + work->input.ordinal.timestamp = 0; + work->input.ordinal.frameIndex = 0; + work->input.buffers.clear(); + work->worklets.clear(); + work->worklets.emplace_back(new C2Worklet); + + std::list<std::unique_ptr<C2Work>> items; + items.push_back(std::move(work)); + ASSERT_EQ(mComponent->queue(&items), C2_OK); + uint32_t queueSize; + { + ULock l(mQueueLock); + queueSize = mWorkQueue.size(); + if (queueSize < MAX_INPUT_BUFFERS) { + mQueueCondition.wait_for(l, TIME_OUT); + } + } + ASSERT_EQ(mEos, true); + ASSERT_EQ(mComponent->stop(), C2_OK); +} + +TEST_F(Codec2AudioEncHidlTest, FlushTest) { + description("Test Request for flush"); + if (mDisableTest) return; + ASSERT_EQ(mComponent->start(), C2_OK); + + typedef std::unique_lock<std::mutex> ULock; + char mURL[512]; + strcpy(mURL, gEnv->getRes().c_str()); + GetURLForComponent(mCompName, mURL); + + // Setting default configuration + mFlushedIndices.clear(); + int32_t nChannels = 2; + int32_t nSampleRate = 44100; + int32_t samplesPerFrame = 1024; + switch (mCompName) { + case aac: + nChannels = 2; + nSampleRate = 48000; + samplesPerFrame = 1024; + break; + case flac: + nChannels = 2; + nSampleRate = 48000; + samplesPerFrame = 1152; + break; + case opus: + nChannels = 2; + nSampleRate = 48000; + samplesPerFrame = 960; + break; + case amrnb: + nChannels = 1; + nSampleRate = 8000; + samplesPerFrame = 160; + break; + case amrwb: + nChannels = 1; + nSampleRate = 16000; + samplesPerFrame = 160; + break; + default: + ASSERT_TRUE(false); + } + setupConfigParam(mComponent, nChannels, nSampleRate); + std::ifstream eleStream; + uint32_t numFramesFlushed = 30; + uint32_t numFrames = 128; + eleStream.open(mURL, std::ifstream::binary); + ASSERT_EQ(eleStream.is_open(), true); + ALOGV("mURL : %s", mURL); + ASSERT_NO_FATAL_FAILURE( + encodeNFrames(mComponent, mQueueLock, mQueueCondition, mWorkQueue, + mFlushedIndices, mLinearPool, eleStream, numFramesFlushed, + samplesPerFrame, nChannels, nSampleRate)); + std::list<std::unique_ptr<C2Work>> flushedWork; + c2_status_t err = + mComponent->flush(C2Component::FLUSH_COMPONENT, &flushedWork); + ASSERT_EQ(err, C2_OK); + ASSERT_NO_FATAL_FAILURE( + waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue, + (size_t)MAX_INPUT_BUFFERS - flushedWork.size())); + uint64_t frameIndex; + { + //Update mFlushedIndices based on the index received from flush() + ULock l(mQueueLock); + for (std::unique_ptr<C2Work>& work : flushedWork) { + ASSERT_NE(work, nullptr); + frameIndex = work->input.ordinal.frameIndex.peeku(); + std::list<uint64_t>::iterator frameIndexIt = + std::find(mFlushedIndices.begin(), mFlushedIndices.end(), + frameIndex); + if (!mFlushedIndices.empty() && + (frameIndexIt != mFlushedIndices.end())) { + mFlushedIndices.erase(frameIndexIt); + work->input.buffers.clear(); + work->worklets.clear(); + mWorkQueue.push_back(std::move(work)); + } + } + } + mFlushedIndices.clear(); + ASSERT_NO_FATAL_FAILURE( + encodeNFrames(mComponent, mQueueLock, mQueueCondition, mWorkQueue, + mFlushedIndices, mLinearPool, eleStream, + numFrames - numFramesFlushed, samplesPerFrame, + nChannels, nSampleRate, true)); + eleStream.close(); + err = + mComponent->flush(C2Component::FLUSH_COMPONENT, &flushedWork); + ASSERT_EQ(err, C2_OK); + ASSERT_NO_FATAL_FAILURE( + waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue, + (size_t)MAX_INPUT_BUFFERS - flushedWork.size())); + { + //Update mFlushedIndices based on the index received from flush() + ULock l(mQueueLock); + for (std::unique_ptr<C2Work>& work : flushedWork) { + ASSERT_NE(work, nullptr); + frameIndex = work->input.ordinal.frameIndex.peeku(); + std::list<uint64_t>::iterator frameIndexIt = + std::find(mFlushedIndices.begin(), mFlushedIndices.end(), + frameIndex); + if (!mFlushedIndices.empty() && + (frameIndexIt != mFlushedIndices.end())) { + mFlushedIndices.erase(frameIndexIt); + work->input.buffers.clear(); + work->worklets.clear(); + mWorkQueue.push_back(std::move(work)); + } + } + } + ASSERT_EQ(mFlushedIndices.empty(), true); + ASSERT_EQ(mComponent->stop(), C2_OK); } } // anonymous namespace -// TODO : Thumbnail Test -// TODO : Test EOS -// TODO : Flush Test -// TODO : Timestamps deviation int main(int argc, char** argv) { gEnv = new ComponentTestEnvironment(); ::testing::AddGlobalTestEnvironment(gEnv); diff --git a/codec2/hidl/1.0/mts/audio/media_c2_audio_hidl_test_common.h b/codec2/hidl/1.0/mts/audio/media_c2_audio_hidl_test_common.h index 63370b3..4d773ce 100644 --- a/codec2/hidl/1.0/mts/audio/media_c2_audio_hidl_test_common.h +++ b/codec2/hidl/1.0/mts/audio/media_c2_audio_hidl_test_common.h @@ -17,8 +17,5 @@ #ifndef MEDIA_C2_AUDIO_HIDL_TEST_COMMON_H #define MEDIA_C2_AUDIO_HIDL_TEST_COMMON_H -#define MAX_RETRY 20 -#define TIME_OUT 400ms -#define MAX_INPUT_BUFFERS 8 #endif // MEDIA_C2_AUDIO_HIDL_TEST_COMMON_H diff --git a/codec2/hidl/1.0/mts/common/media_c2_hidl_test_common.cpp b/codec2/hidl/1.0/mts/common/media_c2_hidl_test_common.cpp index 1b2772f..64a458c 100644 --- a/codec2/hidl/1.0/mts/common/media_c2_hidl_test_common.cpp +++ b/codec2/hidl/1.0/mts/common/media_c2_hidl_test_common.cpp @@ -14,39 +14,115 @@ * limitations under the License. */ +// #define LOG_NDEBUG 0 #define LOG_TAG "media_c2_hidl_test_common" #include <stdio.h> #include "media_c2_hidl_test_common.h" -using ::hardware::google::media::c2::V1_0::FieldSupportedValues; -void dumpFSV(const FieldSupportedValues& sv) { - ALOGD("Dumping FSV data"); - using namespace std; - if (sv.type == FieldSupportedValues::Type::EMPTY) { - ALOGD("FSV Value is Empty"); +// Test the codecs for NullBuffer, Empty Input Buffer with(out) flags set +void testInputBuffer( + const std::shared_ptr<android::Codec2Client::Component>& component, + std::mutex& queueLock, std::list<std::unique_ptr<C2Work>>& workQueue, + uint32_t flags, bool isNullBuffer) { + std::unique_ptr<C2Work> work; + { + typedef std::unique_lock<std::mutex> ULock; + ULock l(queueLock); + if (!workQueue.empty()) { + work.swap(workQueue.front()); + workQueue.pop_front(); + } else { + ASSERT_TRUE(false) << "workQueue Empty at the start of test"; + } + } + ASSERT_NE(work, nullptr); + + work->input.flags = (C2FrameData::flags_t)flags; + work->input.ordinal.timestamp = 0; + work->input.ordinal.frameIndex = 0; + work->input.buffers.clear(); + if (isNullBuffer) { + work->input.buffers.emplace_back(nullptr); } - if (sv.type == FieldSupportedValues::Type::RANGE) { - ALOGD("Dumping FSV range"); - cout << ".range(" << sv.range.min; - if (sv.range.step != 0) { - cout << ":" << sv.range.step; + work->worklets.clear(); + work->worklets.emplace_back(new C2Worklet); + + std::list<std::unique_ptr<C2Work>> items; + items.push_back(std::move(work)); + ASSERT_EQ(component->queue(&items), C2_OK); +} + +// Wait for all the inputs to be consumed by the plugin. +void waitOnInputConsumption(std::mutex& queueLock, + std::condition_variable& queueCondition, + std::list<std::unique_ptr<C2Work>>& workQueue, + size_t bufferCount) { + typedef std::unique_lock<std::mutex> ULock; + uint32_t queueSize; + uint32_t maxRetry = 0; + { + ULock l(queueLock); + queueSize = workQueue.size(); + } + while ((maxRetry < MAX_RETRY) && (queueSize < bufferCount)) { + ULock l(queueLock); + if (queueSize != workQueue.size()) { + queueSize = workQueue.size(); + maxRetry = 0; + } else { + queueCondition.wait_for(l, TIME_OUT); + maxRetry++; } - if (sv.range.num != 1 || sv.range.denom != 1) { - cout << ":" << sv.range.num << "/" << sv.range.denom; + } +} + +// process onWorkDone received by Listener +void workDone( + const std::shared_ptr<android::Codec2Client::Component>& component, + std::unique_ptr<C2Work>& work, std::list<uint64_t>& flushedIndices, + std::mutex& queueLock, std::condition_variable& queueCondition, + std::list<std::unique_ptr<C2Work>>& workQueue, bool& eos, bool& csd, + uint32_t& framesReceived) { + // handle configuration changes in work done + if (work->worklets.front()->output.configUpdate.size() != 0) { + ALOGV("Config Update"); + std::vector<std::unique_ptr<C2Param>> updates = + std::move(work->worklets.front()->output.configUpdate); + std::vector<C2Param*> configParam; + std::vector<std::unique_ptr<C2SettingResult>> failures; + for (size_t i = 0; i < updates.size(); ++i) { + C2Param* param = updates[i].get(); + if (param->index() == C2StreamCsdInfo::output::PARAM_TYPE) { + csd = true; + } else if ((param->index() == + C2StreamSampleRateInfo::output::PARAM_TYPE) || + (param->index() == + C2StreamChannelCountInfo::output::PARAM_TYPE) || + (param->index() == + C2VideoSizeStreamInfo::output::PARAM_TYPE)) { + configParam.push_back(param); + } } - cout << " " << sv.range.max << ")"; + component->config(configParam, C2_DONT_BLOCK, &failures); + ASSERT_EQ(failures.size(), 0u); } - if (sv.values.size()) { - ALOGD("Dumping FSV value"); - cout << (sv.type == FieldSupportedValues::Type::FLAGS ? ".flags(" - : ".list("); - const char* sep = ""; - for (const auto& p : sv.values) { - cout << sep << p; - sep = ","; + framesReceived++; + eos = (work->worklets.front()->output.flags & + C2FrameData::FLAG_END_OF_STREAM) != 0; + auto frameIndexIt = std::find(flushedIndices.begin(), flushedIndices.end(), + work->input.ordinal.frameIndex.peeku()); + ALOGV("WorkDone: frameID received %d", + (int)work->worklets.front()->output.ordinal.frameIndex.peeku()); + work->input.buffers.clear(); + work->worklets.clear(); + { + typedef std::unique_lock<std::mutex> ULock; + ULock l(queueLock); + workQueue.push_back(std::move(work)); + if (!flushedIndices.empty()) { + flushedIndices.erase(frameIndexIt); } - cout << ")"; + queueCondition.notify_all(); } - cout << endl; -} +}
\ No newline at end of file diff --git a/codec2/hidl/1.0/mts/common/media_c2_hidl_test_common.h b/codec2/hidl/1.0/mts/common/media_c2_hidl_test_common.h index 0e7db26..04e02c9 100644 --- a/codec2/hidl/1.0/mts/common/media_c2_hidl_test_common.h +++ b/codec2/hidl/1.0/mts/common/media_c2_hidl_test_common.h @@ -22,6 +22,7 @@ #include <hardware/google/media/c2/1.0/types.h> #include <C2Component.h> +#include <C2Config.h> #include <getopt.h> #include <hidl/HidlSupport.h> #include <media/stagefright/foundation/ALooper.h> @@ -38,6 +39,10 @@ using ::android::hardware::hidl_string; #include <VtsHalHidlTargetTestEnvBase.h> +#define MAX_RETRY 20 +#define TIME_OUT 400ms +#define MAX_INPUT_BUFFERS 8 + /* * Handle Callback functions onWorkDone(), onTripped(), * onError(), onDeath(), onFramesRendered() @@ -176,5 +181,21 @@ class ComponentTestEnvironment : public ::testing::VtsHalHidlTargetTestEnvBase { /* * common functions declarations */ -void dumpFSV(const FieldSupportedValues& sv); +void testInputBuffer( + const std::shared_ptr<android::Codec2Client::Component>& component, + std::mutex& queueLock, std::list<std::unique_ptr<C2Work>>& workQueue, + uint32_t flags, bool isNullBuffer); + +void waitOnInputConsumption(std::mutex& queueLock, + std::condition_variable& queueCondition, + std::list<std::unique_ptr<C2Work>>& workQueue, + size_t bufferCount = MAX_INPUT_BUFFERS); + +void workDone( + const std::shared_ptr<android::Codec2Client::Component>& component, + std::unique_ptr<C2Work>& work, std::list<uint64_t>& flushedIndices, + std::mutex& queueLock, std::condition_variable& queueCondition, + std::list<std::unique_ptr<C2Work>>& workQueue, bool& eos, bool& csd, + uint32_t& framesReceived); + #endif // MEDIA_C2_HIDL_TEST_COMMON_H diff --git a/codec2/hidl/1.0/mts/component/MtsHidlC2V1_0TargetComponentTest.cpp b/codec2/hidl/1.0/mts/component/MtsHidlC2V1_0TargetComponentTest.cpp index 03b1716..ec803d7 100644 --- a/codec2/hidl/1.0/mts/component/MtsHidlC2V1_0TargetComponentTest.cpp +++ b/codec2/hidl/1.0/mts/component/MtsHidlC2V1_0TargetComponentTest.cpp @@ -31,21 +31,28 @@ static ComponentTestEnvironment* gEnv = nullptr; namespace { // google.codec2 Component test setup -class Codec2ComponentHalTest : public ::testing::VtsHalHidlTargetTestBase { +class Codec2ComponentHidlTest : public ::testing::VtsHalHidlTargetTestBase { private: typedef ::testing::VtsHalHidlTargetTestBase Super; public: virtual void SetUp() override { Super::SetUp(); + mEos = false; mClient = android::Codec2Client::CreateFromService( gEnv->getInstance().c_str()); ASSERT_NE(mClient, nullptr); - mListener.reset(new CodecListener()); + mListener.reset(new CodecListener( + [this](std::list<std::unique_ptr<C2Work>>& workItems) { + handleWorkDone(workItems); + })); ASSERT_NE(mListener, nullptr); mClient->createComponent(gEnv->getComponent().c_str(), mListener, &mComponent); ASSERT_NE(mComponent, nullptr); + for (int i = 0; i < MAX_INPUT_BUFFERS; ++i) { + mWorkQueue.emplace_back(new C2Work); + } } virtual void TearDown() override { @@ -59,6 +66,23 @@ class Codec2ComponentHalTest : public ::testing::VtsHalHidlTargetTestBase { } Super::TearDown(); } + // callback function to process onWorkDone received by Listener + void handleWorkDone(std::list<std::unique_ptr<C2Work>>& workItems) { + for (std::unique_ptr<C2Work>& work : workItems) { + if (!work->worklets.empty()) { + bool mCsd = false; + uint32_t mFramesReceived = 0; + std::list<uint64_t> mFlushedIndices; + workDone(mComponent, work, mFlushedIndices, mQueueLock, mQueueCondition, + mWorkQueue, mEos, mCsd, mFramesReceived); + } + } + } + + bool mEos; + std::mutex mQueueLock; + std::condition_variable mQueueCondition; + std::list<std::unique_ptr<C2Work>> mWorkQueue; std::shared_ptr<android::Codec2Client> mClient; std::shared_ptr<android::Codec2Client::Listener> mListener; @@ -71,7 +95,7 @@ class Codec2ComponentHalTest : public ::testing::VtsHalHidlTargetTestBase { }; // Test Empty Flush -TEST_F(Codec2ComponentHalTest, EmptyFlush) { +TEST_F(Codec2ComponentHidlTest, EmptyFlush) { ALOGV("Empty Flush Test"); c2_status_t err = mComponent->start(); ASSERT_EQ(err, C2_OK); @@ -87,7 +111,7 @@ TEST_F(Codec2ComponentHalTest, EmptyFlush) { } // Test Queue Empty Work -TEST_F(Codec2ComponentHalTest, QueueEmptyWork) { +TEST_F(Codec2ComponentHidlTest, QueueEmptyWork) { ALOGV("Queue Empty Work Test"); c2_status_t err = mComponent->start(); ASSERT_EQ(err, C2_OK); @@ -102,45 +126,39 @@ TEST_F(Codec2ComponentHalTest, QueueEmptyWork) { } // Test Component Configuration -TEST_F(Codec2ComponentHalTest, Config) { +TEST_F(Codec2ComponentHidlTest, Config) { ALOGV("Configuration Test"); C2String name = mComponent->getName(); EXPECT_NE(name.empty(), true) << "Invalid Component Name"; + c2_status_t err = C2_OK; + std::vector<std::unique_ptr<C2Param>> queried; + std::vector<std::unique_ptr<C2SettingResult>> failures; + // Query supported params by the component std::vector<std::shared_ptr<C2ParamDescriptor>> params; - c2_status_t err = mComponent->querySupportedParams(¶ms); + err = mComponent->querySupportedParams(¶ms); ASSERT_EQ(err, C2_OK); ALOGV("Number of total params - %zu", params.size()); - // Query Component Domain Type - std::vector<std::unique_ptr<C2Param>> queried; - err = mComponent->query({}, {C2PortMediaTypeSetting::input::PARAM_TYPE}, - C2_DONT_BLOCK, &queried); - EXPECT_NE(queried.size(), 0u); - std::string inputDomain = - ((C2StreamMediaTypeSetting::input*)queried[0].get())->m.value; - EXPECT_NE(inputDomain.empty(), true) << "Invalid Component Domain"; - - // Configure Component Domain - std::vector<std::unique_ptr<C2SettingResult>> failures; - C2PortMediaTypeSetting::input* portMediaType = - C2PortMediaTypeSetting::input::From(queried[0].get()); - err = mComponent->config({portMediaType}, C2_DONT_BLOCK, &failures); - ASSERT_EQ(err, C2_OK); - ASSERT_EQ(failures.size(), 0u); - - // TODO: Query/Config on other common Params + // Query and config all the supported params + for (std::shared_ptr<C2ParamDescriptor> p : params) { + ALOGD("Querying index %d", (int)p->index()); + err = mComponent->query({}, {p->index()}, C2_DONT_BLOCK, &queried); + EXPECT_NE(queried.size(), 0u); + EXPECT_EQ(err, C2_OK); + err = mComponent->config({queried[0].get()}, C2_DONT_BLOCK, &failures); + ASSERT_EQ(err, C2_OK); + ASSERT_EQ(failures.size(), 0u); + } } // Test Multiple Start Stop Reset Test -TEST_F(Codec2ComponentHalTest, MultipleStartStopReset) { +TEST_F(Codec2ComponentHidlTest, MultipleStartStopReset) { ALOGV("Multiple Start Stop and Reset Test"); c2_status_t err = C2_OK; -#define MAX_RETRY 16 - for (size_t i = 0; i < MAX_RETRY; i++) { err = mComponent->start(); ASSERT_EQ(err, C2_OK); @@ -168,9 +186,67 @@ TEST_F(Codec2ComponentHalTest, MultipleStartStopReset) { ASSERT_NE(err, C2_OK); } +// Test Component Release API +TEST_F(Codec2ComponentHidlTest, MultipleRelease) { + ALOGV("Multiple Release Test"); + c2_status_t err = mComponent->start(); + ASSERT_EQ(err, C2_OK); + + // Query Component Domain Type + std::vector<std::unique_ptr<C2Param>> queried; + err = mComponent->query({}, {C2PortMediaTypeSetting::input::PARAM_TYPE}, + C2_DONT_BLOCK, &queried); + EXPECT_NE(queried.size(), 0u); + + // Configure Component Domain + std::vector<std::unique_ptr<C2SettingResult>> failures; + C2PortMediaTypeSetting::input* portMediaType = + C2PortMediaTypeSetting::input::From(queried[0].get()); + err = mComponent->config({portMediaType}, C2_DONT_BLOCK, &failures); + ASSERT_EQ(err, C2_OK); + ASSERT_EQ(failures.size(), 0u); + + for (size_t i = 0; i < MAX_RETRY; i++) { + err = mComponent->release(); + ASSERT_EQ(err, C2_OK); + } +} + +class Codec2ComponentInputTests : public Codec2ComponentHidlTest, + public ::testing::WithParamInterface<std::pair<uint32_t, bool> > { +}; + +TEST_P(Codec2ComponentInputTests, InputBufferTest) { + description("Tests for different inputs"); + + uint32_t flags = GetParam().first; + bool isNullBuffer = GetParam().second; + if (isNullBuffer) ALOGD("Testing for null input buffer with flag : %u", flags); + else ALOGD("Testing for empty input buffer with flag : %u", flags); + mEos = false; + ASSERT_EQ(mComponent->start(), C2_OK); + ASSERT_NO_FATAL_FAILURE(testInputBuffer( + mComponent, mQueueLock, mWorkQueue, flags, isNullBuffer)); + + ALOGD("Waiting for input consumption"); + ASSERT_NO_FATAL_FAILURE( + waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue)); + + if (flags == C2FrameData::FLAG_END_OF_STREAM) ASSERT_EQ(mEos, true); + ASSERT_EQ(mComponent->stop(), C2_OK); + ASSERT_EQ(mComponent->reset(), C2_OK); +} + +INSTANTIATE_TEST_CASE_P(NonStdInputs, Codec2ComponentInputTests, ::testing::Values( + std::make_pair(0, true), + std::make_pair(C2FrameData::FLAG_END_OF_STREAM, true), + std::make_pair(0, false), + std::make_pair(C2FrameData::FLAG_CODEC_CONFIG, false), + std::make_pair(C2FrameData::FLAG_END_OF_STREAM, false))); + } // anonymous namespace -// TODO: Add test for Invalid work, Invalid Config handling +// TODO: Add test for Invalid work, // TODO: Add test for Invalid states int main(int argc, char** argv) { gEnv = new ComponentTestEnvironment(); diff --git a/codec2/hidl/1.0/mts/res/bbb_av1_176_144.av1 b/codec2/hidl/1.0/mts/res/bbb_av1_176_144.av1 Binary files differnew file mode 100644 index 0000000..1d67af9 --- /dev/null +++ b/codec2/hidl/1.0/mts/res/bbb_av1_176_144.av1 diff --git a/codec2/hidl/1.0/mts/res/bbb_av1_176_144.info b/codec2/hidl/1.0/mts/res/bbb_av1_176_144.info new file mode 100644 index 0000000..cc51168 --- /dev/null +++ b/codec2/hidl/1.0/mts/res/bbb_av1_176_144.info @@ -0,0 +1,300 @@ +6027 1 0 +6879 0 33000 +5 0 66000 +532 0 100000 +5 0 133000 +2458 0 166000 +5 0 200000 +475 0 233000 +5 0 266000 +1262 0 300000 +5 0 333000 +554 0 366000 +27 0 400000 +6971 0 433000 +5 0 466000 +601 0 500000 +5 0 533000 +3276 0 566000 +5 0 600000 +658 0 633000 +5 0 666000 +1680 0 700000 +5 0 733000 +610 0 766000 +24 0 800000 +6728 0 833000 +5 0 866000 +764 0 900000 +5 0 933000 +2656 0 966000 +5 0 1000000 +462 0 1033000 +5 0 1066000 +1459 0 1100000 +5 0 1133000 +608 0 1166000 +24 0 1200000 +7038 0 1233000 +5 0 1266000 +721 0 1300000 +5 0 1333000 +3102 0 1366000 +5 0 1400000 +752 0 1433000 +5 0 1466000 +1815 0 1500000 +5 0 1533000 +755 0 1566000 +25 0 1600000 +7657 0 1633000 +5 0 1666000 +852 0 1700000 +5 0 1733000 +3537 0 1766000 +5 0 1800000 +673 0 1833000 +5 0 1866000 +1774 0 1900000 +5 0 1933000 +554 0 1966000 +24 0 2000000 +8028 0 2033000 +5 0 2066000 +715 0 2100000 +5 0 2133000 +3395 0 2166000 +5 0 2200000 +736 0 2233000 +5 0 2266000 +1759 0 2300000 +5 0 2333000 +605 0 2366000 +23 0 2400000 +7651 0 2433000 +5 0 2466000 +619 0 2500000 +5 0 2533000 +2788 0 2566000 +5 0 2600000 +556 0 2633000 +5 0 2666000 +1335 0 2700000 +5 0 2733000 +521 0 2766000 +24 0 2800000 +2274 0 2833000 +676 0 2866000 +25 0 2900000 +6224 0 2933000 +5798 0 2966000 +5 0 3000000 +448 0 3033000 +5 0 3066000 +1950 0 3100000 +5 0 3133000 +386 0 3166000 +5 0 3200000 +1218 0 3233000 +5 0 3266000 +1316 0 3300000 +5 0 3333000 +580 0 3366000 +26 0 3400000 +6673 0 3433000 +5 0 3466000 +473 0 3500000 +5 0 3533000 +2467 0 3566000 +5 0 3600000 +429 0 3633000 +5 0 3666000 +1420 0 3700000 +5 0 3733000 +583 0 3766000 +29 0 3800000 +8492 0 3833000 +5 0 3866000 +720 0 3900000 +5 0 3933000 +3635 0 3966000 +5 0 4000000 +621 0 4033000 +5 0 4066000 +1969 0 4100000 +5 0 4133000 +49 0 4166000 +25 0 4200000 +7416 0 4233000 +5 0 4266000 +947 0 4300000 +5 0 4333000 +3713 0 4366000 +5 0 4400000 +714 0 4433000 +5 0 4466000 +2003 0 4500000 +5 0 4533000 +750 0 4566000 +25 0 4600000 +8470 0 4633000 +5 0 4666000 +737 0 4700000 +5 0 4733000 +4094 0 4766000 +5 0 4800000 +1019 0 4833000 +5 0 4866000 +2160 0 4900000 +5 0 4933000 +828 0 4966000 +24 0 5000000 +9282 0 5033000 +5 0 5066000 +655 0 5100000 +5 0 5133000 +3491 0 5166000 +5 0 5200000 +651 0 5233000 +5 0 5266000 +1906 0 5300000 +5 0 5333000 +662 0 5366000 +24 0 5400000 +9724 0 5433000 +5 0 5466000 +617 0 5500000 +5 0 5533000 +3145 0 5566000 +5 0 5600000 +578 0 5633000 +5 0 5666000 +1592 0 5700000 +5 0 5733000 +569 0 5766000 +25 0 5800000 +10015 0 5833000 +5 0 5866000 +609 0 5900000 +5 0 5933000 +3618 0 5966000 +5 0 6000000 +734 0 6033000 +5 0 6066000 +1748 0 6100000 +5 0 6133000 +550 0 6166000 +24 0 6200000 +8806 0 6233000 +5 0 6266000 +498 0 6300000 +5 0 6333000 +2913 0 6366000 +5 0 6400000 +597 0 6433000 +5 0 6466000 +1235 0 6500000 +5 0 6533000 +362 0 6566000 +24 0 6600000 +6592 0 6633000 +5 0 6666000 +468 0 6700000 +5 0 6733000 +1920 0 6766000 +5 0 6800000 +419 0 6833000 +5 0 6866000 +843 0 6900000 +5 0 6933000 +237 0 6966000 +24 0 7000000 +2687 0 7033000 +5 0 7066000 +399 0 7100000 +5 0 7133000 +200 0 7166000 +143 0 7200000 +25 0 7233000 +12603 0 7266000 +1139 0 7300000 +5 0 7333000 +56 0 7366000 +5 0 7400000 +273 0 7433000 +5 0 7466000 +48 0 7500000 +5 0 7533000 +102 0 7566000 +5 0 7600000 +39 0 7633000 +24 0 7666000 +3635 0 7700000 +5 0 7733000 +46 0 7766000 +5 0 7800000 +647 0 7833000 +5 0 7866000 +61 0 7900000 +5 0 7933000 +824 0 7966000 +5 0 8000000 +691 0 8033000 +27 0 8066000 +4573 0 8100000 +5 0 8133000 +473 0 8166000 +5 0 8200000 +1637 0 8233000 +5 0 8266000 +451 0 8300000 +5 0 8333000 +969 0 8366000 +5 0 8400000 +234 0 8433000 +24 0 8466000 +3361 0 8500000 +5 0 8533000 +168 0 8566000 +5 0 8600000 +662 0 8633000 +5 0 8666000 +129 0 8700000 +5 0 8733000 +443 0 8766000 +5 0 8800000 +183 0 8833000 +23 0 8866000 +2769 0 8900000 +5 0 8933000 +182 0 8966000 +5 0 9000000 +890 0 9033000 +5 0 9066000 +171 0 9100000 +5 0 9133000 +599 0 9166000 +5 0 9200000 +236 0 9233000 +23 0 9266000 +2316 0 9300000 +5 0 9333000 +333 0 9366000 +5 0 9400000 +759 0 9433000 +5 0 9466000 +210 0 9500000 +5 0 9533000 +324 0 9566000 +5 0 9600000 +98 0 9633000 +23 0 9666000 +1107 0 9700000 +5 0 9733000 +42 0 9766000 +5 0 9800000 +145 0 9833000 +5 0 9866000 +109 0 9900000 +89 0 9933000 +24 0 9966000
\ No newline at end of file diff --git a/codec2/hidl/1.0/mts/res/bbb_av1_640_360.av1 b/codec2/hidl/1.0/mts/res/bbb_av1_640_360.av1 Binary files differnew file mode 100644 index 0000000..529bace --- /dev/null +++ b/codec2/hidl/1.0/mts/res/bbb_av1_640_360.av1 diff --git a/codec2/hidl/1.0/mts/res/bbb_av1_640_360.info b/codec2/hidl/1.0/mts/res/bbb_av1_640_360.info new file mode 100644 index 0000000..fca7833 --- /dev/null +++ b/codec2/hidl/1.0/mts/res/bbb_av1_640_360.info @@ -0,0 +1,167 @@ +12571 1 0 +9881 0 33000 +5 0 66000 +544 0 100000 +5 0 133000 +2642 0 166000 +5 0 200000 +531 0 233000 +5 0 266000 +1359 0 300000 +5 0 333000 +551 0 366000 +28 0 400000 +10791 0 433000 +5 0 466000 +655 0 500000 +5 0 533000 +3769 0 566000 +5 0 600000 +711 0 633000 +5 0 666000 +2004 0 700000 +5 0 733000 +657 0 766000 +26 0 800000 +8969 0 833000 +5 0 866000 +630 0 900000 +5 0 933000 +2787 0 966000 +5 0 1000000 +404 0 1033000 +5 0 1066000 +1518 0 1100000 +5 0 1133000 +493 0 1166000 +26 0 1200000 +9900 0 1233000 +5 0 1266000 +620 0 1300000 +5 0 1333000 +3072 0 1366000 +5 0 1400000 +668 0 1433000 +5 0 1466000 +1821 0 1500000 +5 0 1533000 +682 0 1566000 +26 0 1600000 +9560 0 1633000 +5 0 1666000 +667 0 1700000 +5 0 1733000 +3249 0 1766000 +5 0 1800000 +589 0 1833000 +5 0 1866000 +1816 0 1900000 +5 0 1933000 +548 0 1966000 +26 0 2000000 +9916 0 2033000 +5 0 2066000 +628 0 2100000 +5 0 2133000 +3034 0 2166000 +5 0 2200000 +590 0 2233000 +5 0 2266000 +1581 0 2300000 +5 0 2333000 +524 0 2366000 +26 0 2400000 +8182 0 2433000 +5 0 2466000 +552 0 2500000 +5 0 2533000 +2412 0 2566000 +5 0 2600000 +489 0 2633000 +5 0 2666000 +1227 0 2700000 +5 0 2733000 +432 0 2766000 +26 0 2800000 +2017 0 2833000 +516 0 2866000 +26 0 2900000 +16619 0 2933000 +6710 0 2966000 +5 0 3000000 +425 0 3033000 +5 0 3066000 +1964 0 3100000 +5 0 3133000 +386 0 3166000 +5 0 3200000 +1129 0 3233000 +5 0 3266000 +1156 0 3300000 +5 0 3333000 +486 0 3366000 +28 0 3400000 +10304 0 3433000 +5 0 3466000 +412 0 3500000 +5 0 3533000 +2608 0 3566000 +5 0 3600000 +397 0 3633000 +5 0 3666000 +1514 0 3700000 +5 0 3733000 +533 0 3766000 +26 0 3800000 +11698 0 3833000 +5 0 3866000 +542 0 3900000 +5 0 3933000 +3334 0 3966000 +5 0 4000000 +547 0 4033000 +5 0 4066000 +1809 0 4100000 +5 0 4133000 +55 0 4166000 +26 0 4200000 +9496 0 4233000 +5 0 4266000 +658 0 4300000 +5 0 4333000 +3232 0 4366000 +5 0 4400000 +600 0 4433000 +5 0 4466000 +1766 0 4500000 +5 0 4533000 +550 0 4566000 +25 0 4600000 +9951 0 4633000 +5 0 4666000 +624 0 4700000 +5 0 4733000 +3617 0 4766000 +5 0 4800000 +644 0 4833000 +5 0 4866000 +1841 0 4900000 +5 0 4933000 +649 0 4966000 +25 0 5000000 +9901 0 5033000 +5 0 5066000 +515 0 5100000 +5 0 5133000 +2814 0 5166000 +5 0 5200000 +511 0 5233000 +5 0 5266000 +1521 0 5300000 +5 0 5333000 +509 0 5366000 +26 0 5400000 +10579 0 5433000 +5 0 5466000 +575 0 5500000 +5 0 5533000
\ No newline at end of file diff --git a/codec2/hidl/1.0/mts/video/MtsHidlC2V1_0TargetVideoDecTest.cpp b/codec2/hidl/1.0/mts/video/MtsHidlC2V1_0TargetVideoDecTest.cpp index 1a3820d..9a42d72 100644 --- a/codec2/hidl/1.0/mts/video/MtsHidlC2V1_0TargetVideoDecTest.cpp +++ b/codec2/hidl/1.0/mts/video/MtsHidlC2V1_0TargetVideoDecTest.cpp @@ -101,7 +101,7 @@ class Codec2VideoDecHidlTest : public ::testing::VtsHalHidlTargetTestBase { const StringToName kStringToName[] = { {"h263", h263}, {"avc", avc}, {"mpeg2", mpeg2}, {"mpeg4", mpeg4}, - {"hevc", hevc}, {"vp8", vp8}, {"vp9", vp9}, + {"hevc", hevc}, {"vp8", vp8}, {"vp9", vp9}, {"av1", av1}, }; const size_t kNumStringToName = @@ -142,79 +142,48 @@ class Codec2VideoDecHidlTest : public ::testing::VtsHalHidlTargetTestBase { // callback function to process onWorkDone received by Listener void handleWorkDone(std::list<std::unique_ptr<C2Work>>& workItems) { for (std::unique_ptr<C2Work>& work : workItems) { - // handle configuration changes in work done - if (!work->worklets.empty() && - (work->worklets.front()->output.configUpdate.size() != 0)) { - ALOGV("Config Update"); - std::vector<std::unique_ptr<C2Param>> updates = - std::move(work->worklets.front()->output.configUpdate); - std::vector<C2Param*> configParam; - std::vector<std::unique_ptr<C2SettingResult>> failures; - for (size_t i = 0; i < updates.size(); ++i) { - C2Param* param = updates[i].get(); - if (param->index() == - C2VideoSizeStreamInfo::output::PARAM_TYPE) { - ALOGV("Received C2VideoSizeStreamInfo"); - configParam.push_back(param); - } - } - mComponent->config(configParam, C2_DONT_BLOCK, &failures); - ASSERT_EQ(failures.size(), 0u); - } - - mFramesReceived++; - mEos = (work->worklets.front()->output.flags & - C2FrameData::FLAG_END_OF_STREAM) != 0; - auto frameIndexIt = - std::find(mFlushedIndices.begin(), mFlushedIndices.end(), - work->input.ordinal.frameIndex.peeku()); - - // For decoder components current timestamp always exceeds - // previous timestamp - typedef std::unique_lock<std::mutex> ULock; - bool codecConfig = ((work->worklets.front()->output.flags & - C2FrameData::FLAG_CODEC_CONFIG) != 0); - if (!codecConfig && - !work->worklets.front()->output.buffers.empty()) { - EXPECT_GE( - (work->worklets.front()->output.ordinal.timestamp.peeku()), - mTimestampUs); - mTimestampUs = - work->worklets.front()->output.ordinal.timestamp.peeku(); - - ULock l(mQueueLock); - if (mTimestampDevTest) { - bool tsHit = false; - std::list<uint64_t>::iterator it = mTimestampUslist.begin(); - while (it != mTimestampUslist.end()) { - if (*it == mTimestampUs) { - mTimestampUslist.erase(it); - tsHit = true; - break; + if (!work->worklets.empty()) { + // For decoder components current timestamp always exceeds + // previous timestamp + typedef std::unique_lock<std::mutex> ULock; + bool codecConfig = ((work->worklets.front()->output.flags & + C2FrameData::FLAG_CODEC_CONFIG) != 0); + if (!codecConfig && + !work->worklets.front()->output.buffers.empty()) { + EXPECT_GE( + (work->worklets.front()->output.ordinal.timestamp.peeku()), + mTimestampUs); + mTimestampUs = + work->worklets.front()->output.ordinal.timestamp.peeku(); + + ULock l(mQueueLock); + if (mTimestampDevTest) { + bool tsHit = false; + std::list<uint64_t>::iterator it = mTimestampUslist.begin(); + while (it != mTimestampUslist.end()) { + if (*it == mTimestampUs) { + mTimestampUslist.erase(it); + tsHit = true; + break; + } + it++; } - it++; - } - if (tsHit == false) { - if (mTimestampUslist.empty() == false) { - EXPECT_EQ(tsHit, true) - << "TimeStamp not recognized"; - } else { - std::cout << "[ INFO ] Received non-zero " - "output / TimeStamp not recognized \n"; + if (tsHit == false) { + if (mTimestampUslist.empty() == false) { + EXPECT_EQ(tsHit, true) + << "TimeStamp not recognized"; + } else { + std::cout << "[ INFO ] Received non-zero " + "output / TimeStamp not recognized \n"; + } } } } - } - - work->input.buffers.clear(); - work->worklets.clear(); - { - ULock l(mQueueLock); - mWorkQueue.push_back(std::move(work)); - if (!mFlushedIndices.empty()) { - mFlushedIndices.erase(frameIndexIt); - } - mQueueCondition.notify_all(); + bool mCsd; + workDone(mComponent, work, mFlushedIndices, mQueueLock, + mQueueCondition, mWorkQueue, mEos, mCsd, + mFramesReceived); + (void)mCsd; } } } @@ -227,6 +196,7 @@ class Codec2VideoDecHidlTest : public ::testing::VtsHalHidlTargetTestBase { hevc, vp8, vp9, + av1, unknown_comp, }; @@ -341,6 +311,11 @@ void GetURLForComponent(Codec2VideoDecHidlTest::standardComp comp, char* mURL, "bbb_vp9_640x360_1600kbps_30fps.vp9"}, {"bbb_vp9_176x144_285kbps_60fps.info", "bbb_vp9_640x360_1600kbps_30fps.info"}}, + {Codec2VideoDecHidlTest::standardComp::av1, + {"bbb_av1_640_360.av1", + "bbb_av1_176_144.av1"}, + {"bbb_av1_640_360.info", + "bbb_av1_176_144.info"}}, }; for (size_t i = 0; i < sizeof(kCompToURL) / sizeof(kCompToURL[0]); ++i) { @@ -352,11 +327,11 @@ void GetURLForComponent(Codec2VideoDecHidlTest::standardComp comp, char* mURL, } } -void decodeNFrames(const std::shared_ptr<android::Codec2Client::Component> &component, - std::mutex &queueLock, std::condition_variable &queueCondition, - std::list<std::unique_ptr<C2Work>> &workQueue, - std::list<uint64_t> &flushedIndices, - std::shared_ptr<C2BlockPool> &linearPool, +void decodeNFrames(const std::shared_ptr<android::Codec2Client::Component>& component, + std::mutex &queueLock, std::condition_variable& queueCondition, + std::list<std::unique_ptr<C2Work>>& workQueue, + std::list<uint64_t>& flushedIndices, + std::shared_ptr<C2BlockPool>& linearPool, std::ifstream& eleStream, android::Vector<FrameInfo>* Info, int offset, int range, bool signalEOS = true) { @@ -397,35 +372,37 @@ void decodeNFrames(const std::shared_ptr<android::Codec2Client::Component> &comp int size = (*Info)[frameID].bytesCount; char* data = (char*)malloc(size); + ASSERT_NE(data, nullptr); eleStream.read(data, size); ASSERT_EQ(eleStream.gcount(), size); - std::shared_ptr<C2LinearBlock> block; - ASSERT_EQ(C2_OK, - linearPool->fetchLinearBlock( - size, {C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE}, - &block)); - ASSERT_TRUE(block); - - // Write View - C2WriteView view = block->map().get(); - if (view.error() != C2_OK) { - fprintf(stderr, "C2LinearBlock::map() failed : %d", view.error()); - break; - } - ASSERT_EQ((size_t)size, view.capacity()); - ASSERT_EQ(0u, view.offset()); - ASSERT_EQ((size_t)size, view.size()); + work->input.buffers.clear(); + if (size) { + std::shared_ptr<C2LinearBlock> block; + ASSERT_EQ(C2_OK, + linearPool->fetchLinearBlock( + size, {C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE}, + &block)); + ASSERT_TRUE(block); + + // Write View + C2WriteView view = block->map().get(); + if (view.error() != C2_OK) { + fprintf(stderr, "C2LinearBlock::map() failed : %d", view.error()); + break; + } + ASSERT_EQ((size_t)size, view.capacity()); + ASSERT_EQ(0u, view.offset()); + ASSERT_EQ((size_t)size, view.size()); - memcpy(view.base(), data, size); + memcpy(view.base(), data, size); - work->input.buffers.clear(); - work->input.buffers.emplace_back(new LinearBuffer(block)); + work->input.buffers.emplace_back(new LinearBuffer(block)); + free(data); + } work->worklets.clear(); work->worklets.emplace_back(new C2Worklet); - free(data); - std::list<std::unique_ptr<C2Work>> items; items.push_back(std::move(work)); @@ -437,29 +414,6 @@ void decodeNFrames(const std::shared_ptr<android::Codec2Client::Component> &comp } } -void waitOnInputConsumption(std::mutex& queueLock, - std::condition_variable& queueCondition, - std::list<std::unique_ptr<C2Work>>& workQueue, - size_t bufferCount = MAX_INPUT_BUFFERS) { - typedef std::unique_lock<std::mutex> ULock; - uint32_t queueSize; - uint32_t maxRetry = 0; - { - ULock l(queueLock); - queueSize = workQueue.size(); - } - while ((maxRetry < MAX_RETRY) && (queueSize < bufferCount)) { - ULock l(queueLock); - if (queueSize != workQueue.size()) { - queueSize = workQueue.size(); - maxRetry = 0; - } else { - queueCondition.wait_for(l, TIME_OUT); - maxRetry++; - } - } -} - TEST_F(Codec2VideoDecHidlTest, validateCompName) { if (mDisableTest) return; ALOGV("Checks if the given component is a valid video component"); @@ -489,8 +443,8 @@ TEST_F(Codec2VideoDecHidlTest, DecodeTest) { if (!(eleInfo >> bytesCount)) break; eleInfo >> flags; eleInfo >> timestamp; - bool codecConfig = - ((1 << (flags - 1)) & C2FrameData::FLAG_CODEC_CONFIG) != 0; + bool codecConfig = flags ? + ((1 << (flags - 1)) & C2FrameData::FLAG_CODEC_CONFIG) != 0 : 0; if (mTimestampDevTest && !codecConfig) mTimestampUslist.push_back(timestamp); Info.push_back({bytesCount, flags, timestamp}); @@ -560,8 +514,9 @@ TEST_F(Codec2VideoDecHidlTest, AdaptiveDecodeTest) { eleInfo >> timestamp; timestamp += timestampOffset; Info.push_back({bytesCount, flags, timestamp}); - bool codecConfig = - ((1 << (flags - 1)) & C2FrameData::FLAG_CODEC_CONFIG) != 0; + bool codecConfig = flags ? + ((1 << (flags - 1)) & C2FrameData::FLAG_CODEC_CONFIG) != 0 : 0; + { ULock l(mQueueLock); if (mTimestampDevTest && !codecConfig) @@ -677,8 +632,8 @@ TEST_F(Codec2VideoDecHidlTest, ThumbnailTest) { EXPECT_GE(mFramesReceived, 1U); ASSERT_EQ(mEos, true); ASSERT_EQ(mComponent->stop(), C2_OK); - ASSERT_EQ(mComponent->release(), C2_OK); } + ASSERT_EQ(mComponent->release(), C2_OK); } TEST_F(Codec2VideoDecHidlTest, EOSTest) { @@ -711,7 +666,6 @@ TEST_F(Codec2VideoDecHidlTest, EOSTest) { ASSERT_EQ(mComponent->queue(&items), C2_OK); { - typedef std::unique_lock<std::mutex> ULock; ULock l(mQueueLock); if (mWorkQueue.size() != MAX_INPUT_BUFFERS) { mQueueCondition.wait_for(l, TIME_OUT); @@ -722,46 +676,6 @@ TEST_F(Codec2VideoDecHidlTest, EOSTest) { ASSERT_EQ(mComponent->stop(), C2_OK); } -TEST_F(Codec2VideoDecHidlTest, EmptyBufferTest) { - description("Tests empty input buffer"); - if (mDisableTest) return; - typedef std::unique_lock<std::mutex> ULock; - ASSERT_EQ(mComponent->start(), C2_OK); - std::unique_ptr<C2Work> work; - // Prepare C2Work - { - ULock l(mQueueLock); - if (!mWorkQueue.empty()) { - work.swap(mWorkQueue.front()); - mWorkQueue.pop_front(); - } else { - ASSERT_TRUE(false) << "mWorkQueue Empty at the start of test"; - } - } - ASSERT_NE(work, nullptr); - - work->input.flags = (C2FrameData::flags_t)0; - work->input.ordinal.timestamp = 0; - work->input.ordinal.frameIndex = 0; - work->input.buffers.clear(); - work->worklets.clear(); - work->worklets.emplace_back(new C2Worklet); - - std::list<std::unique_ptr<C2Work>> items; - items.push_back(std::move(work)); - ASSERT_EQ(mComponent->queue(&items), C2_OK); - - { - typedef std::unique_lock<std::mutex> ULock; - ULock l(mQueueLock); - if (mWorkQueue.size() != MAX_INPUT_BUFFERS) { - mQueueCondition.wait_for(l, TIME_OUT); - } - } - ASSERT_EQ(mWorkQueue.size(), (size_t)MAX_INPUT_BUFFERS); - ASSERT_EQ(mComponent->stop(), C2_OK); -} - TEST_F(Codec2VideoDecHidlTest, FlushTest) { description("Tests Flush calls"); if (mDisableTest) return; @@ -873,6 +787,69 @@ TEST_F(Codec2VideoDecHidlTest, FlushTest) { ASSERT_EQ(mComponent->stop(), C2_OK); } +TEST_F(Codec2VideoDecHidlTest, DecodeTestEmptyBuffersInserted) { + description("Decode with multiple empty input frames"); + if (mDisableTest) return; + + char mURL[512], info[512]; + std::ifstream eleStream, eleInfo; + + strcpy(mURL, gEnv->getRes().c_str()); + strcpy(info, gEnv->getRes().c_str()); + GetURLForComponent(mCompName, mURL, info); + + eleInfo.open(info); + ASSERT_EQ(eleInfo.is_open(), true) << mURL << " - file not found"; + android::Vector<FrameInfo> Info; + int bytesCount = 0; + uint32_t frameId = 0; + uint32_t flags = 0; + uint32_t timestamp = 0; + bool codecConfig = false; + // This test introduces empty CSD after every 20th frame + // and empty input frames at an interval of 5 frames. + while (1) { + if (!(frameId % 5)) { + if (!(frameId % 20)) flags = 32; + else flags = 0; + bytesCount = 0; + } else { + if (!(eleInfo >> bytesCount)) break; + eleInfo >> flags; + eleInfo >> timestamp; + codecConfig = flags ? + ((1 << (flags - 1)) & C2FrameData::FLAG_CODEC_CONFIG) != 0 : 0; + } + Info.push_back({bytesCount, flags, timestamp}); + frameId++; + } + eleInfo.close(); + + ASSERT_EQ(mComponent->start(), C2_OK); + ALOGV("mURL : %s", mURL); + eleStream.open(mURL, std::ifstream::binary); + ASSERT_EQ(eleStream.is_open(), true); + ASSERT_NO_FATAL_FAILURE(decodeNFrames( + mComponent, mQueueLock, mQueueCondition, mWorkQueue, mFlushedIndices, + mLinearPool, eleStream, &Info, 0, (int)Info.size())); + + // blocking call to ensures application to Wait till all the inputs are + // consumed + if (!mEos) { + ALOGV("Waiting for input consumption"); + ASSERT_NO_FATAL_FAILURE( + waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue)); + } + + eleStream.close(); + if (mFramesReceived != Info.size()) { + ALOGE("Input buffer count and Output buffer count mismatch"); + ALOGV("framesReceived : %d inputFrames : %zu", mFramesReceived, + Info.size()); + ASSERT_TRUE(false); + } +} + } // anonymous namespace // TODO : Video specific configuration Test diff --git a/codec2/hidl/1.0/mts/video/MtsHidlC2V1_0TargetVideoEncTest.cpp b/codec2/hidl/1.0/mts/video/MtsHidlC2V1_0TargetVideoEncTest.cpp index 87b7902..8585c87 100644 --- a/codec2/hidl/1.0/mts/video/MtsHidlC2V1_0TargetVideoEncTest.cpp +++ b/codec2/hidl/1.0/mts/video/MtsHidlC2V1_0TargetVideoEncTest.cpp @@ -139,40 +139,11 @@ class Codec2VideoEncHidlTest : public ::testing::VtsHalHidlTargetTestBase { // callback function to process onWorkDone received by Listener void handleWorkDone(std::list<std::unique_ptr<C2Work>>& workItems) { for (std::unique_ptr<C2Work>& work : workItems) { - // handle configuration changes in work done - if (!work->worklets.empty() && - (work->worklets.front()->output.configUpdate.size() != 0)) { - ALOGV("Config Update"); - std::vector<std::unique_ptr<C2Param>> updates = - std::move(work->worklets.front()->output.configUpdate); - std::vector<C2Param*> configParam; - std::vector<std::unique_ptr<C2SettingResult>> failures; - for (size_t i = 0; i < updates.size(); ++i) { - C2Param* param = updates[i].get(); - if (param->index() == C2StreamCsdInfo::output::PARAM_TYPE) { - mCsd = true; - } - } - } - mFramesReceived++; - if (work->result != C2_OK) mFailedWorkReceived++; - mEos = (work->worklets.front()->output.flags & - C2FrameData::FLAG_END_OF_STREAM) != 0; - auto frameIndexIt = - std::find(mFlushedIndices.begin(), mFlushedIndices.end(), - work->input.ordinal.frameIndex.peeku()); - ALOGV("WorkDone: frameID received %d", - (int)work->worklets.front()->output.ordinal.frameIndex.peeku()); - work->input.buffers.clear(); - work->worklets.clear(); - { - typedef std::unique_lock<std::mutex> ULock; - ULock l(mQueueLock); - mWorkQueue.push_back(std::move(work)); - if (!mFlushedIndices.empty()) { - mFlushedIndices.erase(frameIndexIt); - } - mQueueCondition.notify_all(); + if (!work->worklets.empty()) { + if (work->result != C2_OK) mFailedWorkReceived++; + workDone(mComponent, work, mFlushedIndices, mQueueLock, + mQueueCondition, mWorkQueue, mEos, mCsd, + mFramesReceived); } } } @@ -272,11 +243,11 @@ void GetURLForComponent(char* URL) { strcat(URL, "bbb_352x288_420p_30fps_32frames.yuv"); } -void encodeNFrames(const std::shared_ptr<android::Codec2Client::Component> &component, - std::mutex &queueLock, std::condition_variable &queueCondition, - std::list<std::unique_ptr<C2Work>> &workQueue, - std::list<uint64_t> &flushedIndices, - std::shared_ptr<C2BlockPool> &graphicPool, +void encodeNFrames(const std::shared_ptr<android::Codec2Client::Component>& component, + std::mutex &queueLock, std::condition_variable& queueCondition, + std::list<std::unique_ptr<C2Work>>& workQueue, + std::list<uint64_t>& flushedIndices, + std::shared_ptr<C2BlockPool>& graphicPool, std::ifstream& eleStream, uint32_t frameID, uint32_t nFrames, uint32_t nWidth, int32_t nHeight, bool flushed = false,bool signalEOS = true) { @@ -319,6 +290,7 @@ void encodeNFrames(const std::shared_ptr<android::Codec2Client::Component> &comp flushedIndices.emplace_back(frameID); } char* data = (char*)malloc(bytesCount); + ASSERT_NE(data, nullptr); memset(data, 0, bytesCount); if (eleStream.is_open()) { eleStream.read(data, bytesCount); @@ -365,30 +337,6 @@ void encodeNFrames(const std::shared_ptr<android::Codec2Client::Component> &comp } } -void waitOnInputConsumption(std::mutex &queueLock, - std::condition_variable &queueCondition, - std::list<std::unique_ptr<C2Work>> &workQueue, - size_t bufferCount = MAX_INPUT_BUFFERS) { - typedef std::unique_lock<std::mutex> ULock; - uint32_t queueSize; - int maxRetry = 0; - { - ULock l(queueLock); - queueSize = workQueue.size(); - } - while ((maxRetry < MAX_RETRY) && (queueSize < bufferCount)) { - ULock l(queueLock); - if (queueSize != workQueue.size()) { - queueSize = workQueue.size(); - maxRetry = 0; - } else { - queueCondition.wait_for(l, TIME_OUT); - maxRetry++; - } - } -} - - TEST_F(Codec2VideoEncHidlTest, validateCompName) { if (mDisableTest) return; ALOGV("Checks if the given component is a valid video component"); @@ -488,46 +436,6 @@ TEST_F(Codec2VideoEncHidlTest, EOSTest) { ASSERT_EQ(mComponent->stop(), C2_OK); } -TEST_F(Codec2VideoEncHidlTest, EmptyBufferTest) { - description("Tests empty input buffer"); - if (mDisableTest) return; - ASSERT_EQ(mComponent->start(), C2_OK); - - typedef std::unique_lock<std::mutex> ULock; - std::unique_ptr<C2Work> work; - { - ULock l(mQueueLock); - if (!mWorkQueue.empty()) { - work.swap(mWorkQueue.front()); - mWorkQueue.pop_front(); - } else { - ALOGE("mWorkQueue Empty is not expected at the start of the test"); - ASSERT_TRUE(false); - } - } - ASSERT_NE(work, nullptr); - work->input.flags = (C2FrameData::flags_t)0; - work->input.ordinal.timestamp = 0; - work->input.ordinal.frameIndex = 0; - work->input.buffers.clear(); - work->worklets.clear(); - work->worklets.emplace_back(new C2Worklet); - - std::list<std::unique_ptr<C2Work>> items; - items.push_back(std::move(work)); - ASSERT_EQ(mComponent->queue(&items), C2_OK); - uint32_t queueSize; - { - ULock l(mQueueLock); - queueSize = mWorkQueue.size(); - if (queueSize < MAX_INPUT_BUFFERS) { - mQueueCondition.wait_for(l, TIME_OUT); - } - } - ASSERT_EQ(mWorkQueue.size(), (uint32_t)MAX_INPUT_BUFFERS); - ASSERT_EQ(mComponent->stop(), C2_OK); -} - TEST_F(Codec2VideoEncHidlTest, FlushTest) { description("Test Request for flush"); if (mDisableTest) return; diff --git a/codec2/hidl/1.0/mts/video/media_c2_video_hidl_test_common.h b/codec2/hidl/1.0/mts/video/media_c2_video_hidl_test_common.h index 1215b13..dd45557 100644 --- a/codec2/hidl/1.0/mts/video/media_c2_video_hidl_test_common.h +++ b/codec2/hidl/1.0/mts/video/media_c2_video_hidl_test_common.h @@ -17,9 +17,6 @@ #ifndef MEDIA_C2_VIDEO_HIDL_TEST_COMMON_H #define MEDIA_C2_VIDEO_HIDL_TEST_COMMON_H -#define MAX_RETRY 20 -#define TIME_OUT 400ms -#define MAX_INPUT_BUFFERS 8 #define ENCODER_TIMESTAMP_INCREMENT 40000 #define ENC_NUM_FRAMES 32 #define ENC_DEFAULT_FRAME_WIDTH 352 diff --git a/codec2/hidl/1.0/utils/Android.bp b/codec2/hidl/1.0/utils/Android.bp index 1641e51..ed404ad 100644 --- a/codec2/hidl/1.0/utils/Android.bp +++ b/codec2/hidl/1.0/utils/Android.bp @@ -16,6 +16,7 @@ cc_library { ], header_libs: [ + "libgui_headers", "libsystem_headers", "libstagefright_codec2_internal", // private ], @@ -26,6 +27,7 @@ cc_library { "android.hardware.media.bufferpool@1.0", "android.hardware.media.omx@1.0", "android.hardware.media@1.0", + "android.hidl.token@1.0-utils", "hardware.google.media.c2@1.0", "libbase", "libcutils", @@ -46,6 +48,7 @@ cc_library { ], export_shared_lib_headers: [ + "android.hidl.token@1.0-utils", "hardware.google.media.c2@1.0", "libhidlbase", "libstagefright_bufferpool@1.0", diff --git a/codec2/hidl/1.0/utils/ComponentStore.cpp b/codec2/hidl/1.0/utils/ComponentStore.cpp index 4941ede..9a46b77 100644 --- a/codec2/hidl/1.0/utils/ComponentStore.cpp +++ b/codec2/hidl/1.0/utils/ComponentStore.cpp @@ -24,7 +24,7 @@ #include <codec2/hidl/1.0/ConfigurableC2Intf.h> #include <codec2/hidl/1.0/types.h> -#include <media/stagefright/bqhelper/WGraphicBufferProducer.h> +#include <gui/bufferqueue/1.0/WGraphicBufferProducer.h> #include <media/stagefright/bqhelper/GraphicBufferSource.h> #include <C2PlatformSupport.h> diff --git a/codec2/hidl/1.0/utils/InputSurfaceConnection.cpp b/codec2/hidl/1.0/utils/InputSurfaceConnection.cpp index eea4e9d..4f4af6a 100644 --- a/codec2/hidl/1.0/utils/InputSurfaceConnection.cpp +++ b/codec2/hidl/1.0/utils/InputSurfaceConnection.cpp @@ -168,7 +168,7 @@ struct InputSurfaceConnection::Impl : public ComponentWrapper { std::shared_ptr<C2GraphicAllocation> alloc; C2Handle* handle = WrapNativeCodec2GrallocHandle( - native_handle_clone(buffer->handle), + buffer->handle, buffer->width, buffer->height, buffer->format, buffer->usage, buffer->stride); mAllocatorMutex.lock(); diff --git a/codec2/hidl/1.0/utils/types.cpp b/codec2/hidl/1.0/utils/types.cpp index 56db4d5..10a7cef 100644 --- a/codec2/hidl/1.0/utils/types.cpp +++ b/codec2/hidl/1.0/utils/types.cpp @@ -20,8 +20,7 @@ #include <codec2/hidl/1.0/types.h> -#include <media/stagefright/bqhelper/WGraphicBufferProducer.h> - +#include <gui/bufferqueue/1.0/WGraphicBufferProducer.h> #include <C2AllocatorIon.h> #include <C2AllocatorGralloc.h> #include <C2BlockInternal.h> @@ -1657,7 +1656,8 @@ void forEachBlock(const std::list<std::unique_ptr<C2Work>>& workList, } sp<HGraphicBufferProducer> getHgbp(const sp<IGraphicBufferProducer>& igbp) { - sp<HGraphicBufferProducer> hgbp = igbp->getHalInterface(); + sp<HGraphicBufferProducer> hgbp = + igbp->getHalInterface<HGraphicBufferProducer>(); return hgbp ? hgbp : new TWGraphicBufferProducer<HGraphicBufferProducer>(igbp); } diff --git a/codec2/hidl/client/client.cpp b/codec2/hidl/client/client.cpp index ff67681..8b4b48b 100644 --- a/codec2/hidl/client/client.cpp +++ b/codec2/hidl/client/client.cpp @@ -30,8 +30,8 @@ #include <bufferpool/ClientManager.h> #include <cutils/native_handle.h> #include <gui/bufferqueue/1.0/H2BGraphicBufferProducer.h> +#include <gui/bufferqueue/1.0/WGraphicBufferProducer.h> #include <hidl/HidlSupport.h> -#include <media/stagefright/bqhelper/WGraphicBufferProducer.h> #undef LOG #include <android/hardware/media/bufferpool/1.0/IClientManager.h> @@ -553,6 +553,7 @@ const std::vector<C2Component::Traits>& Codec2Client::listComponents() const { for (size_t i = 0; i < t.size(); ++i) { c2_status_t status = objcpy( &mTraitsList[i], &mAliasesBuffer[i], t[i]); + mTraitsList[i].owner = mInstanceName; if (status != C2_OK) { ALOGE("listComponents -- corrupted output."); return; @@ -1130,7 +1131,8 @@ c2_status_t Codec2Client::Component::setOutputSurface( C2BlockPool::local_id_t blockPoolId, const sp<IGraphicBufferProducer>& surface, uint32_t generation) { - sp<HGraphicBufferProducer> igbp = surface->getHalInterface(); + sp<HGraphicBufferProducer> igbp = + surface->getHalInterface<HGraphicBufferProducer>(); if (!igbp) { igbp = new TWGraphicBufferProducer<HGraphicBufferProducer>(surface); } diff --git a/codec2/hidl/services/Android.bp b/codec2/hidl/services/Android.bp index 6b73383..00d25ca 100644 --- a/codec2/hidl/services/Android.bp +++ b/codec2/hidl/services/Android.bp @@ -38,8 +38,9 @@ cc_binary { } cc_library_shared { - name: "libmedia_codecserviceregistrant", - soc_specific: true, + name: "libcodec2_serviceregistrant", + // need vendor version for update packaging, system version may have more dependencies + vendor_available: true, srcs: [ "C2SoftwareCodecServiceRegistrant.cpp", ], @@ -49,46 +50,45 @@ cc_library_shared { ], shared_libs: [ - "android.hardware.media.omx@1.0", "hardware.google.media.c2@1.0", "liblog", "libcodec2_hidl_utils@1.0", "libstagefright_codec2_vndk", - "libstagefright_omx", - "libstagefright_xmlparser", "libutils", ], // Codecs - required: [ - "libstagefright_soft_c2avcdec.vendor", - "libstagefright_soft_c2avcenc.vendor", - "libstagefright_soft_c2aacdec.vendor", - "libstagefright_soft_c2aacenc.vendor", - "libstagefright_soft_c2amrnbdec.vendor", - "libstagefright_soft_c2amrnbenc.vendor", - "libstagefright_soft_c2amrwbdec.vendor", - "libstagefright_soft_c2amrwbenc.vendor", - "libstagefright_soft_c2hevcdec.vendor", - "libstagefright_soft_c2g711alawdec.vendor", - "libstagefright_soft_c2g711mlawdec.vendor", - "libstagefright_soft_c2mpeg2dec.vendor", - "libstagefright_soft_c2h263dec.vendor", - "libstagefright_soft_c2h263enc.vendor", - "libstagefright_soft_c2mpeg4dec.vendor", - "libstagefright_soft_c2mpeg4enc.vendor", - "libstagefright_soft_c2mp3dec.vendor", - "libstagefright_soft_c2vorbisdec.vendor", - "libstagefright_soft_c2opusdec.vendor", - "libstagefright_soft_c2vp8dec.vendor", - "libstagefright_soft_c2vp9dec.vendor", - "libstagefright_soft_c2vp8enc.vendor", - "libstagefright_soft_c2vp9enc.vendor", - "libstagefright_soft_c2rawdec.vendor", - "libstagefright_soft_c2flacdec.vendor", - "libstagefright_soft_c2flacenc.vendor", - "libstagefright_soft_c2gsmdec.vendor", + runtime_libs: [ + "libstagefright_soft_c2avcdec", + "libstagefright_soft_c2avcenc", + "libstagefright_soft_c2aacdec", + "libstagefright_soft_c2aacenc", + "libstagefright_soft_c2amrnbdec", + "libstagefright_soft_c2amrnbenc", + "libstagefright_soft_c2amrwbdec", + "libstagefright_soft_c2amrwbenc", + "libstagefright_soft_c2hevcdec", + "libstagefright_soft_c2g711alawdec", + "libstagefright_soft_c2g711mlawdec", + "libstagefright_soft_c2mpeg2dec", + "libstagefright_soft_c2h263dec", + "libstagefright_soft_c2h263enc", + "libstagefright_soft_c2mpeg4dec", + "libstagefright_soft_c2mpeg4enc", + "libstagefright_soft_c2mp3dec", + "libstagefright_soft_c2vorbisdec", + "libstagefright_soft_c2opusdec", + "libstagefright_soft_c2vp8dec", + "libstagefright_soft_c2vp9dec", + "libstagefright_soft_c2vp8enc", + "libstagefright_soft_c2vp9enc", + "libstagefright_soft_c2rawdec", + "libstagefright_soft_c2flacdec", + "libstagefright_soft_c2flacenc", + "libstagefright_soft_c2gsmdec", + "libstagefright_soft_c2xaacdec", ], + compile_multilib: "32", } diff --git a/codec2/hidl/services/Android.mk b/codec2/hidl/services/Android.mk deleted file mode 100644 index d9b28e7..0000000 --- a/codec2/hidl/services/Android.mk +++ /dev/null @@ -1,24 +0,0 @@ -LOCAL_PATH := $(call my-dir) - -# vendor service seccomp policy -ifeq ($(TARGET_ARCH),$(filter $(TARGET_ARCH), x86 x86_64 arm arm64)) -include $(CLEAR_VARS) -LOCAL_MODULE := codec2.vendor.base.policy -LOCAL_VENDOR_MODULE := true -LOCAL_MODULE_CLASS := ETC -LOCAL_MODULE_PATH := $(TARGET_OUT_VENDOR)/etc/seccomp_policy -LOCAL_REQUIRED_MODULES := crash_dump.policy -ifdef TARGET_2ND_ARCH - ifneq ($(TARGET_TRANSLATE_2ND_ARCH),true) - LOCAL_SRC_FILES := seccomp_policy/codec2.vendor.base-$(TARGET_2ND_ARCH).policy - else - LOCAL_SRC_FILES := seccomp_policy/codec2.vendor.base-$(TARGET_ARCH).policy - endif -else - LOCAL_SRC_FILES := seccomp_policy/codec2.vendor.base-$(TARGET_ARCH).policy -endif -include $(BUILD_PREBUILT) -endif - -include $(call all-makefiles-under, $(LOCAL_PATH)) - diff --git a/codec2/hidl/services/C2SoftwareCodecServiceRegistrant.cpp b/codec2/hidl/services/C2SoftwareCodecServiceRegistrant.cpp index eddfbcb..04efa44 100644 --- a/codec2/hidl/services/C2SoftwareCodecServiceRegistrant.cpp +++ b/codec2/hidl/services/C2SoftwareCodecServiceRegistrant.cpp @@ -20,7 +20,6 @@ #include <C2PlatformSupport.h> #include <codec2/hidl/1.0/ComponentStore.h> #include <media/CodecServiceRegistrant.h> -#include <media/stagefright/omx/1.0/Omx.h> #include <log/log.h> extern "C" void RegisterCodecServices() { @@ -38,15 +37,5 @@ extern "C" void RegisterCodecServices() { ALOGI("Codec2's IComponentStore software service created."); } } - - // Default codec services - using namespace ::android::hardware::media::omx::V1_0; - android::sp<IOmx> omx = new implementation::Omx(); - if (omx == nullptr) { - ALOGE("Cannot create IOmx HAL service."); - } else if (omx->registerAsService() != android::OK) { - ALOGE("Cannot register IOmx HAL service."); - } - } diff --git a/codec2/hidl/services/seccomp_policy/codec2.software.base-arm.policy b/codec2/hidl/services/seccomp_policy/codec2.software.base-arm.policy deleted file mode 100644 index d5871d1..0000000 --- a/codec2/hidl/services/seccomp_policy/codec2.software.base-arm.policy +++ /dev/null @@ -1,73 +0,0 @@ -# Copyright (C) 2018 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Organized by frequency of systemcall - in descending order for -# best performance. -futex: 1 -ioctl: 1 -write: 1 -prctl: 1 -clock_gettime: 1 -getpriority: 1 -read: 1 -close: 1 -writev: 1 -dup: 1 -ppoll: 1 -mmap2: 1 -getrandom: 1 - -# mremap: Ensure |flags| are (MREMAP_MAYMOVE | MREMAP_FIXED) TODO: Once minijail -# parser support for '<' is in this needs to be modified to also prevent -# |old_address| and |new_address| from touching the exception vector page, which -# on ARM is statically loaded at 0xffff 0000. See -# http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0211h/Babfeega.html -# for more details. -mremap: arg3 == 3 -munmap: 1 -mprotect: 1 -madvise: 1 -openat: 1 -sigaltstack: 1 -clone: 1 -setpriority: 1 -getuid32: 1 -fstat64: 1 -fstatfs64: 1 -pread64: 1 -faccessat: 1 -readlinkat: 1 -exit: 1 -rt_sigprocmask: 1 -set_tid_address: 1 -restart_syscall: 1 -exit_group: 1 -rt_sigreturn: 1 -pipe2: 1 -gettimeofday: 1 -sched_yield: 1 -nanosleep: 1 -lseek: 1 -_llseek: 1 -sched_get_priority_max: 1 -sched_get_priority_min: 1 -statfs64: 1 -sched_setscheduler: 1 -fstatat64: 1 -ugetrlimit: 1 -getdents64: 1 -getrandom: 1 - -@include /system/etc/seccomp_policy/crash_dump.arm.policy - diff --git a/codec2/hidl/services/seccomp_policy/codec2.software.base-x86.policy b/codec2/hidl/services/seccomp_policy/codec2.software.base-x86.policy deleted file mode 100644 index 20c7625..0000000 --- a/codec2/hidl/services/seccomp_policy/codec2.software.base-x86.policy +++ /dev/null @@ -1,57 +0,0 @@ -# Copyright (C) 2018 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -read: 1 -mprotect: 1 -prctl: 1 -openat: 1 -getuid32: 1 -writev: 1 -ioctl: 1 -close: 1 -mmap2: 1 -fstat64: 1 -madvise: 1 -fstatat64: 1 -futex: 1 -munmap: 1 -faccessat: 1 -_llseek: 1 -lseek: 1 -clone: 1 -sigaltstack: 1 -setpriority: 1 -restart_syscall: 1 -exit: 1 -exit_group: 1 -rt_sigreturn: 1 -ugetrlimit: 1 -readlinkat: 1 -_llseek: 1 -fstatfs64: 1 -pread64: 1 -mremap: 1 -dup: 1 -set_tid_address: 1 -write: 1 -nanosleep: 1 - -# Required by AddressSanitizer -gettid: 1 -sched_yield: 1 -getpid: 1 -gettid: 1 - -@include /system/etc/seccomp_policy/crash_dump.x86.policy - diff --git a/codec2/hidl/services/seccomp_policy/codec2.vendor.base-arm.policy b/codec2/hidl/services/seccomp_policy/codec2.vendor.base-arm.policy deleted file mode 100644 index d5871d1..0000000 --- a/codec2/hidl/services/seccomp_policy/codec2.vendor.base-arm.policy +++ /dev/null @@ -1,73 +0,0 @@ -# Copyright (C) 2018 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Organized by frequency of systemcall - in descending order for -# best performance. -futex: 1 -ioctl: 1 -write: 1 -prctl: 1 -clock_gettime: 1 -getpriority: 1 -read: 1 -close: 1 -writev: 1 -dup: 1 -ppoll: 1 -mmap2: 1 -getrandom: 1 - -# mremap: Ensure |flags| are (MREMAP_MAYMOVE | MREMAP_FIXED) TODO: Once minijail -# parser support for '<' is in this needs to be modified to also prevent -# |old_address| and |new_address| from touching the exception vector page, which -# on ARM is statically loaded at 0xffff 0000. See -# http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0211h/Babfeega.html -# for more details. -mremap: arg3 == 3 -munmap: 1 -mprotect: 1 -madvise: 1 -openat: 1 -sigaltstack: 1 -clone: 1 -setpriority: 1 -getuid32: 1 -fstat64: 1 -fstatfs64: 1 -pread64: 1 -faccessat: 1 -readlinkat: 1 -exit: 1 -rt_sigprocmask: 1 -set_tid_address: 1 -restart_syscall: 1 -exit_group: 1 -rt_sigreturn: 1 -pipe2: 1 -gettimeofday: 1 -sched_yield: 1 -nanosleep: 1 -lseek: 1 -_llseek: 1 -sched_get_priority_max: 1 -sched_get_priority_min: 1 -statfs64: 1 -sched_setscheduler: 1 -fstatat64: 1 -ugetrlimit: 1 -getdents64: 1 -getrandom: 1 - -@include /system/etc/seccomp_policy/crash_dump.arm.policy - diff --git a/codec2/hidl/services/seccomp_policy/codec2.vendor.base-x86.policy b/codec2/hidl/services/seccomp_policy/codec2.vendor.base-x86.policy deleted file mode 100644 index 20c7625..0000000 --- a/codec2/hidl/services/seccomp_policy/codec2.vendor.base-x86.policy +++ /dev/null @@ -1,57 +0,0 @@ -# Copyright (C) 2018 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -read: 1 -mprotect: 1 -prctl: 1 -openat: 1 -getuid32: 1 -writev: 1 -ioctl: 1 -close: 1 -mmap2: 1 -fstat64: 1 -madvise: 1 -fstatat64: 1 -futex: 1 -munmap: 1 -faccessat: 1 -_llseek: 1 -lseek: 1 -clone: 1 -sigaltstack: 1 -setpriority: 1 -restart_syscall: 1 -exit: 1 -exit_group: 1 -rt_sigreturn: 1 -ugetrlimit: 1 -readlinkat: 1 -_llseek: 1 -fstatfs64: 1 -pread64: 1 -mremap: 1 -dup: 1 -set_tid_address: 1 -write: 1 -nanosleep: 1 - -# Required by AddressSanitizer -gettid: 1 -sched_yield: 1 -getpid: 1 -gettid: 1 - -@include /system/etc/seccomp_policy/crash_dump.x86.policy - diff --git a/codec2/include/C2Component.h b/codec2/include/C2Component.h index 8810725..7624851 100644 --- a/codec2/include/C2Component.h +++ b/codec2/include/C2Component.h @@ -409,6 +409,7 @@ public: kind_t kind; ///< component kind rank_t rank; ///< component rank C2String mediaType; ///< media type supported by the component + C2String owner; ///< name of the component store owning this component /** * name alias(es) for backward compatibility. diff --git a/codec2/include/C2Config.h b/codec2/include/C2Config.h index 838ae49..be5f128 100644 --- a/codec2/include/C2Config.h +++ b/codec2/include/C2Config.h @@ -180,6 +180,7 @@ enum C2ParamIndexKind : C2Param::type_index_t { kParamIndexPictureTypeMask, kParamIndexPictureType, + kParamIndexHdr10PlusMetadata, /* ------------------------------------ video components ------------------------------------ */ @@ -238,6 +239,8 @@ enum C2ParamIndexKind : C2Param::type_index_t { kParamIndexMinFrameRate, // input-surface, float kParamIndexTimestampGapAdjustment, // input-surface, struct + kParamIndexSurfaceAllocator, // u32 + // deprecated indices due to renaming kParamIndexAacStreamFormat = kParamIndexAacPackaging, kParamIndexCsd = kParamIndexInitData, @@ -904,6 +907,18 @@ typedef C2GlobalParam<C2Tuning, C2SimpleArrayStruct<C2Allocator::id_t>, kParamIn constexpr char C2_PARAMKEY_PRIVATE_ALLOCATORS[] = "algo.buffers.allocator-ids"; /** + * Allocator to use for outputting to surface. + * + * Components can optionally request allocator type for outputting to surface. + * + * If none specified, client will use the default BufferQueue-backed allocator ID for outputting to + * surface. + */ +typedef C2PortParam<C2Tuning, C2Uint32Value, kParamIndexSurfaceAllocator> + C2PortSurfaceAllocatorTuning; +constexpr char C2_PARAMKEY_OUTPUT_SURFACE_ALLOCATOR[] = "output.buffers.surface-allocator-id"; + +/** * Block pools to use. * * These are allocated by the client for the component using the allocator IDs specified by the @@ -1546,6 +1561,14 @@ typedef C2StreamParam<C2Info, C2HdrStaticMetadataStruct, kParamIndexHdrStaticMet C2StreamHdrStaticInfo; constexpr char C2_PARAMKEY_HDR_STATIC_INFO[] = "raw.hdr-static-info"; +/** + * HDR10+ Metadata Info. + */ +typedef C2StreamParam<C2Info, C2BlobValue, kParamIndexHdr10PlusMetadata> + C2StreamHdr10PlusInfo; +constexpr char C2_PARAMKEY_INPUT_HDR10_PLUS_INFO[] = "input.hdr10-plus-info"; +constexpr char C2_PARAMKEY_OUTPUT_HDR10_PLUS_INFO[] = "output.hdr10-plus-info"; + /* ------------------------------------ block-based coding ----------------------------------- */ /** diff --git a/codec2/include/C2Enum.h b/codec2/include/C2Enum.h index a7bbcdc..b0fad8f 100644 --- a/codec2/include/C2Enum.h +++ b/codec2/include/C2Enum.h @@ -63,7 +63,7 @@ class _C2EnumUtils { static std::vector<C2String> sanitizeEnumValueNames( const std::vector<C2StringLiteral> names, - C2StringLiteral _prefix = NULL); + C2StringLiteral _prefix = nullptr); friend class C2UtilTest_EnumUtilsTest_Test; @@ -75,7 +75,7 @@ public: static C2_HIDE C2FieldDescriptor::NamedValuesType sanitizeEnumValues( std::vector<T> values, std::vector<C2StringLiteral> names, - C2StringLiteral prefix = NULL) { + C2StringLiteral prefix = nullptr) { C2FieldDescriptor::NamedValuesType namedValues; std::vector<C2String> sanitizedNames = sanitizeEnumValueNames(names, prefix); for (size_t i = 0; i < values.size() && i < sanitizedNames.size(); ++i) { @@ -147,7 +147,7 @@ C2FieldDescriptor::NamedValuesType C2FieldDescriptor::namedValuesFor(const name */ #define C2ENUM(name, type, ...) \ enum name : type { __VA_ARGS__ }; \ -DEFINE_C2_ENUM_VALUE_AUTO_HELPER(name, type, NULL, __VA_ARGS__) +DEFINE_C2_ENUM_VALUE_AUTO_HELPER(name, type, nullptr, __VA_ARGS__) /** * Defines an enum type with the default named value mapper but custom prefix. The default diff --git a/codec2/include/media/stagefright/codec2/1.0/InputSurface.h b/codec2/include/media/stagefright/codec2/1.0/InputSurface.h index b011a06..226dcc1 100644 --- a/codec2/include/media/stagefright/codec2/1.0/InputSurface.h +++ b/codec2/include/media/stagefright/codec2/1.0/InputSurface.h @@ -20,7 +20,7 @@ #include <memory> #include <C2Component.h> -#include <media/stagefright/bqhelper/WGraphicBufferProducer.h> +#include <gui/bufferqueue/1.0/WGraphicBufferProducer.h> #include <media/stagefright/codec2/1.0/InputSurfaceConnection.h> namespace android { diff --git a/codec2/include/media/stagefright/codec2/1.0/InputSurfaceConnection.h b/codec2/include/media/stagefright/codec2/1.0/InputSurfaceConnection.h index b24a416..06e52cf 100644 --- a/codec2/include/media/stagefright/codec2/1.0/InputSurfaceConnection.h +++ b/codec2/include/media/stagefright/codec2/1.0/InputSurfaceConnection.h @@ -20,8 +20,8 @@ #include <memory> #include <C2Component.h> +#include <gui/bufferqueue/1.0/WGraphicBufferProducer.h> #include <media/stagefright/bqhelper/GraphicBufferSource.h> -#include <media/stagefright/bqhelper/WGraphicBufferProducer.h> #include <media/stagefright/codec2/1.0/InputSurfaceConnection.h> namespace android { diff --git a/codec2/tests/Android.bp b/codec2/tests/Android.bp index 9f167bf..f54c039 100644 --- a/codec2/tests/Android.bp +++ b/codec2/tests/Android.bp @@ -20,7 +20,6 @@ cc_test { cflags: [ "-Werror", "-Wall", - "-std=c++14", ], } @@ -48,7 +47,6 @@ cc_test { cflags: [ "-Werror", "-Wall", - "-std=c++14", ], } @@ -74,6 +72,5 @@ cc_test { cflags: [ "-Werror", "-Wall", - "-std=c++14", ], } diff --git a/codec2/tests/C2SampleComponent_test.cpp b/codec2/tests/C2SampleComponent_test.cpp index ea445e8..cd354ad 100644 --- a/codec2/tests/C2SampleComponent_test.cpp +++ b/codec2/tests/C2SampleComponent_test.cpp @@ -361,11 +361,11 @@ void dumpStruct(const C2StructDescriptor &sd) { for (const C2FieldDescriptor::NamedValueType &p : f.namedValues()) { cout << sep << p.first << "="; switch (f.type()) { - case C2Value::INT32: cout << get(p.second, (int32_t *)0); break; - case C2Value::INT64: cout << get(p.second, (int64_t *)0); break; - case C2Value::UINT32: cout << get(p.second, (uint32_t *)0); break; - case C2Value::UINT64: cout << get(p.second, (uint64_t *)0); break; - case C2Value::FLOAT: cout << get(p.second, (float *)0); break; + case C2FieldDescriptor::INT32: cout << get(p.second, (int32_t *)0); break; + case C2FieldDescriptor::INT64: cout << get(p.second, (int64_t *)0); break; + case C2FieldDescriptor::UINT32: cout << get(p.second, (uint32_t *)0); break; + case C2FieldDescriptor::UINT64: cout << get(p.second, (uint64_t *)0); break; + case C2FieldDescriptor::FLOAT: cout << get(p.second, (float *)0); break; default: cout << "???"; break; } sep = ","; diff --git a/codec2/vndk/Android.bp b/codec2/vndk/Android.bp index cc1db02..845d113 100644 --- a/codec2/vndk/Android.bp +++ b/codec2/vndk/Android.bp @@ -36,6 +36,7 @@ cc_library_shared { export_shared_lib_headers: [ "libbase", "android.hardware.media.bufferpool@1.0", + "android.hidl.token@1.0-utils", ], local_include_dirs: [ @@ -47,11 +48,16 @@ cc_library_shared { "hardware/google/av/codec2/include", ], + header_libs: [ + "libgui_headers", + ], + shared_libs: [ "android.hardware.graphics.allocator@2.0", "android.hardware.graphics.bufferqueue@1.0", "android.hardware.graphics.mapper@2.0", "android.hardware.media.bufferpool@1.0", + "android.hidl.token@1.0-utils", "libbase", "libbinder", "libcutils", @@ -71,7 +77,6 @@ cc_library_shared { cflags: [ "-Werror", "-Wall", - "-std=c++14", ], } @@ -101,6 +106,3 @@ cc_defaults { // TODO: separate internal headers so they can be exposed here } -subdirs = [ - "bufferpool", -] diff --git a/codec2/vndk/C2AllocatorGralloc.cpp b/codec2/vndk/C2AllocatorGralloc.cpp index b0ec5f1..d3fbbd0 100644 --- a/codec2/vndk/C2AllocatorGralloc.cpp +++ b/codec2/vndk/C2AllocatorGralloc.cpp @@ -159,7 +159,7 @@ public: return xd != nullptr && xd->magic == MAGIC; } - static C2HandleGralloc* WrapNativeHandle( + static C2HandleGralloc* WrapAndMoveNativeHandle( const native_handle_t *const handle, uint32_t width, uint32_t height, uint32_t format, uint64_t usage, uint32_t stride, uint32_t generation, uint64_t igbp_id = 0, uint32_t igbp_slot = 0) { @@ -181,6 +181,26 @@ public: return reinterpret_cast<C2HandleGralloc *>(res); } + static C2HandleGralloc* WrapNativeHandle( + const native_handle_t *const handle, + uint32_t width, uint32_t height, uint32_t format, uint64_t usage, + uint32_t stride, uint32_t generation, uint64_t igbp_id = 0, uint32_t igbp_slot = 0) { + if (handle == nullptr) { + return nullptr; + } + native_handle_t *clone = native_handle_clone(handle); + if (clone == nullptr) { + return nullptr; + } + C2HandleGralloc *res = WrapAndMoveNativeHandle( + clone, width, height, format, usage, stride, generation, igbp_id, igbp_slot); + if (res == nullptr) { + native_handle_close(clone); + } + native_handle_delete(clone); + return res; + } + static native_handle_t* UnwrapNativeHandle( const C2Handle *const handle) { const ExtraData *xd = getExtraData(handle); @@ -283,6 +303,7 @@ private: const C2HandleGralloc *mLockedHandle; bool mLocked; C2Allocator::id_t mAllocatorId; + std::mutex mMappedLock; }; C2AllocationGralloc::C2AllocationGralloc( @@ -315,6 +336,11 @@ C2AllocationGralloc::~C2AllocationGralloc() { native_handle_delete( const_cast<native_handle_t *>(reinterpret_cast<const native_handle_t *>(mHandle))); } + if (mLockedHandle) { + native_handle_delete( + const_cast<native_handle_t *>( + reinterpret_cast<const native_handle_t *>(mLockedHandle))); + } } c2_status_t C2AllocationGralloc::map( @@ -327,6 +353,7 @@ c2_status_t C2AllocationGralloc::map( // TODO (void) fence; + std::lock_guard<std::mutex> lock(mMappedLock); if (mBuffer && mLocked) { ALOGD("already mapped"); return C2_DUPLICATE; @@ -359,7 +386,7 @@ c2_status_t C2AllocationGralloc::map( if (mHandle) { mHandle->getIgbpData(&generation, &igbp_id, &igbp_slot); } - mLockedHandle = C2HandleGralloc::WrapNativeHandle( + mLockedHandle = C2HandleGralloc::WrapAndMoveNativeHandle( mBuffer, mInfo.mapperInfo.width, mInfo.mapperInfo.height, (uint32_t)mInfo.mapperInfo.format, mInfo.mapperInfo.usage, mInfo.stride, generation, igbp_id, igbp_slot); @@ -533,6 +560,8 @@ c2_status_t C2AllocationGralloc::unmap( // TODO: check addr and size, use fence (void)addr; (void)rect; + + std::lock_guard<std::mutex> lock(mMappedLock); c2_status_t err = C2_OK; mMapper->unlock( const_cast<native_handle_t *>(mBuffer), @@ -658,7 +687,7 @@ c2_status_t C2AllocatorGralloc::Impl::newGraphicAllocation( return; } info.stride = stride; - buffer = std::move(buffers[0]); + buffer = buffers[0]; }); if (err != C2_OK) { return err; @@ -667,7 +696,7 @@ c2_status_t C2AllocatorGralloc::Impl::newGraphicAllocation( allocation->reset(new C2AllocationGralloc( info, mMapper, buffer, - C2HandleGralloc::WrapNativeHandle( + C2HandleGralloc::WrapAndMoveNativeHandle( buffer.getNativeHandle(), info.mapperInfo.width, info.mapperInfo.height, (uint32_t)info.mapperInfo.format, info.mapperInfo.usage, info.stride, diff --git a/codec2/vndk/bufferpool/Accessor.cpp b/codec2/vndk/bufferpool/Accessor.cpp deleted file mode 100644 index b1dfc08..0000000 --- a/codec2/vndk/bufferpool/Accessor.cpp +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#define LOG_TAG "BufferPoolConnection" - -#include "Accessor.h" -#include "AccessorImpl.h" -#include "Connection.h" - -namespace android { -namespace hardware { -namespace media { -namespace bufferpool { -namespace V1_0 { -namespace implementation { - -void ConnectionDeathRecipient::add( - int64_t connectionId, - const sp<Accessor> &accessor) { - std::lock_guard<std::mutex> lock(mLock); - if (mAccessors.find(connectionId) == mAccessors.end()) { - mAccessors.insert(std::make_pair(connectionId, accessor)); - } -} - -void ConnectionDeathRecipient::remove(int64_t connectionId) { - std::lock_guard<std::mutex> lock(mLock); - mAccessors.erase(connectionId); - auto it = mConnectionToCookie.find(connectionId); - if (it != mConnectionToCookie.end()) { - uint64_t cookie = it->second; - mConnectionToCookie.erase(it); - auto cit = mCookieToConnections.find(cookie); - if (cit != mCookieToConnections.end()) { - cit->second.erase(connectionId); - if (cit->second.size() == 0) { - mCookieToConnections.erase(cit); - } - } - } -} - -void ConnectionDeathRecipient::addCookieToConnection( - uint64_t cookie, - int64_t connectionId) { - std::lock_guard<std::mutex> lock(mLock); - if (mAccessors.find(connectionId) == mAccessors.end()) { - return; - } - mConnectionToCookie.insert(std::make_pair(connectionId, cookie)); - auto it = mCookieToConnections.find(cookie); - if (it != mCookieToConnections.end()) { - it->second.insert(connectionId); - } else { - mCookieToConnections.insert(std::make_pair( - cookie, std::set<int64_t>{connectionId})); - } -} - -void ConnectionDeathRecipient::serviceDied( - uint64_t cookie, - const wp<::android::hidl::base::V1_0::IBase>& /* who */ - ) { - std::map<int64_t, const wp<Accessor>> connectionsToClose; - { - std::lock_guard<std::mutex> lock(mLock); - - auto it = mCookieToConnections.find(cookie); - if (it != mCookieToConnections.end()) { - for (auto conIt = it->second.begin(); conIt != it->second.end(); ++conIt) { - auto accessorIt = mAccessors.find(*conIt); - if (accessorIt != mAccessors.end()) { - connectionsToClose.insert(std::make_pair(*conIt, accessorIt->second)); - mAccessors.erase(accessorIt); - } - mConnectionToCookie.erase(*conIt); - } - mCookieToConnections.erase(it); - } - } - - if (connectionsToClose.size() > 0) { - sp<Accessor> accessor; - for (auto it = connectionsToClose.begin(); it != connectionsToClose.end(); ++it) { - accessor = it->second.promote(); - - if (accessor) { - accessor->close(it->first); - ALOGD("connection %lld closed on death", (long long)it->first); - } - } - } -} - -namespace { -static sp<ConnectionDeathRecipient> sConnectionDeathRecipient = - new ConnectionDeathRecipient(); -} - -sp<ConnectionDeathRecipient> Accessor::getConnectionDeathRecipient() { - return sConnectionDeathRecipient; -} - -// Methods from ::android::hardware::media::bufferpool::V1_0::IAccessor follow. -Return<void> Accessor::connect(connect_cb _hidl_cb) { - sp<Connection> connection; - ConnectionId connectionId; - const QueueDescriptor* fmqDesc; - - ResultStatus status = connect(&connection, &connectionId, &fmqDesc, false); - if (status == ResultStatus::OK) { - _hidl_cb(status, connection, connectionId, *fmqDesc); - } else { - _hidl_cb(status, nullptr, -1LL, - android::hardware::MQDescriptorSync<BufferStatusMessage>( - std::vector<android::hardware::GrantorDescriptor>(), - nullptr /* nhandle */, 0 /* size */)); - } - return Void(); -} - -Accessor::Accessor(const std::shared_ptr<BufferPoolAllocator> &allocator) - : mImpl(new Impl(allocator)) {} - -Accessor::~Accessor() { -} - -bool Accessor::isValid() { - return (bool)mImpl; -} - -ResultStatus Accessor::allocate( - ConnectionId connectionId, - const std::vector<uint8_t> ¶ms, - BufferId *bufferId, const native_handle_t** handle) { - if (mImpl) { - return mImpl->allocate(connectionId, params, bufferId, handle); - } - return ResultStatus::CRITICAL_ERROR; -} - -ResultStatus Accessor::fetch( - ConnectionId connectionId, TransactionId transactionId, - BufferId bufferId, const native_handle_t** handle) { - if (mImpl) { - return mImpl->fetch(connectionId, transactionId, bufferId, handle); - } - return ResultStatus::CRITICAL_ERROR; -} - -ResultStatus Accessor::connect( - sp<Connection> *connection, ConnectionId *pConnectionId, - const QueueDescriptor** fmqDescPtr, bool local) { - if (mImpl) { - ResultStatus status = mImpl->connect(this, connection, pConnectionId, fmqDescPtr); - if (!local && status == ResultStatus::OK) { - sp<Accessor> accessor(this); - sConnectionDeathRecipient->add(*pConnectionId, accessor); - } - return status; - } - return ResultStatus::CRITICAL_ERROR; -} - -ResultStatus Accessor::close(ConnectionId connectionId) { - if (mImpl) { - ResultStatus status = mImpl->close(connectionId); - sConnectionDeathRecipient->remove(connectionId); - return status; - } - return ResultStatus::CRITICAL_ERROR; -} - -void Accessor::cleanUp(bool clearCache) { - if (mImpl) { - mImpl->cleanUp(clearCache); - } -} - -//IAccessor* HIDL_FETCH_IAccessor(const char* /* name */) { -// return new Accessor(); -//} - -} // namespace implementation -} // namespace V1_0 -} // namespace bufferpool -} // namespace media -} // namespace hardware -} // namespace android diff --git a/codec2/vndk/bufferpool/Accessor.h b/codec2/vndk/bufferpool/Accessor.h deleted file mode 100644 index 2f86f7b..0000000 --- a/codec2/vndk/bufferpool/Accessor.h +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ANDROID_HARDWARE_MEDIA_BUFFERPOOL_V1_0_ACCESSOR_H -#define ANDROID_HARDWARE_MEDIA_BUFFERPOOL_V1_0_ACCESSOR_H - -#include <android/hardware/media/bufferpool/1.0/IAccessor.h> -#include <bufferpool/BufferPoolTypes.h> -#include <hidl/MQDescriptor.h> -#include <hidl/Status.h> -#include "BufferStatus.h" - -#include <set> - -namespace android { -namespace hardware { -namespace media { -namespace bufferpool { -namespace V1_0 { -namespace implementation { - -using ::android::hardware::hidl_array; -using ::android::hardware::hidl_memory; -using ::android::hardware::hidl_string; -using ::android::hardware::hidl_vec; -using ::android::hardware::Return; -using ::android::hardware::Void; -using ::android::sp; - -struct Accessor; -struct Connection; - -/** - * Receives death notifications from remote connections. - * On death notifications, the connections are closed and used resources - * are released. - */ -struct ConnectionDeathRecipient : public hardware::hidl_death_recipient { - /** - * Registers a newly connected connection from remote processes. - */ - void add(int64_t connectionId, const sp<Accessor> &accessor); - - /** - * Removes a connection. - */ - void remove(int64_t connectionId); - - void addCookieToConnection(uint64_t cookie, int64_t connectionId); - - virtual void serviceDied( - uint64_t /* cookie */, - const wp<::android::hidl::base::V1_0::IBase>& /* who */ - ) override; - -private: - std::mutex mLock; - std::map<uint64_t, std::set<int64_t>> mCookieToConnections; - std::map<int64_t, uint64_t> mConnectionToCookie; - std::map<int64_t, const wp<Accessor>> mAccessors; -}; - -/** - * A buffer pool accessor which enables a buffer pool to communicate with buffer - * pool clients. 1:1 correspondense holds between a buffer pool and an accessor. - */ -struct Accessor : public IAccessor { - // Methods from ::android::hardware::media::bufferpool::V1_0::IAccessor follow. - Return<void> connect(connect_cb _hidl_cb) override; - - /** - * Creates a buffer pool accessor which uses the specified allocator. - * - * @param allocator buffer allocator. - */ - explicit Accessor(const std::shared_ptr<BufferPoolAllocator> &allocator); - - /** Destructs a buffer pool accessor. */ - ~Accessor(); - - /** Returns whether the accessor is valid. */ - bool isValid(); - - /** Allocates a buffer from a buffer pool. - * - * @param connectionId the connection id of the client. - * @param params the allocation parameters. - * @param bufferId the id of the allocated buffer. - * @param handle the native handle of the allocated buffer. - * - * @return OK when a buffer is successfully allocated. - * NO_MEMORY when there is no memory. - * CRITICAL_ERROR otherwise. - */ - ResultStatus allocate( - ConnectionId connectionId, - const std::vector<uint8_t>& params, - BufferId *bufferId, - const native_handle_t** handle); - - /** - * Fetches a buffer for the specified transaction. - * - * @param connectionId the id of receiving connection(client). - * @param transactionId the id of the transfer transaction. - * @param bufferId the id of the buffer to be fetched. - * @param handle the native handle of the fetched buffer. - * - * @return OK when a buffer is successfully fetched. - * NO_MEMORY when there is no memory. - * CRITICAL_ERROR otherwise. - */ - ResultStatus fetch( - ConnectionId connectionId, - TransactionId transactionId, - BufferId bufferId, - const native_handle_t** handle); - - /** - * Makes a connection to the buffer pool. The buffer pool client uses the - * created connection in order to communicate with the buffer pool. An - * FMQ for buffer status message is also created for the client. - * - * @param connection created connection - * @param pConnectionId the id of the created connection - * @param fmqDescPtr FMQ descriptor for shared buffer status message - * queue between a buffer pool and the client. - * @param local true when a connection request comes from local process, - * false otherwise. - * - * @return OK when a connection is successfully made. - * NO_MEMORY when there is no memory. - * CRITICAL_ERROR otherwise. - */ - ResultStatus connect( - sp<Connection> *connection, ConnectionId *pConnectionId, - const QueueDescriptor** fmqDescPtr, bool local); - - /** - * Closes the specified connection to the client. - * - * @param connectionId the id of the connection. - * - * @return OK when the connection is closed. - * CRITICAL_ERROR otherwise. - */ - ResultStatus close(ConnectionId connectionId); - - /** - * Processes pending buffer status messages and perfoms periodic cache - * cleaning. - * - * @param clearCache if clearCache is true, it frees all buffers waiting - * to be recycled. - */ - void cleanUp(bool clearCache); - - /** - * Gets a hidl_death_recipient for remote connection death. - */ - static sp<ConnectionDeathRecipient> getConnectionDeathRecipient(); - -private: - class Impl; - std::unique_ptr<Impl> mImpl; -}; - -} // namespace implementation -} // namespace V1_0 -} // namespace bufferpool -} // namespace media -} // namespace hardware -} // namespace android - -#endif // ANDROID_HARDWARE_MEDIA_BUFFERPOOL_V1_0_ACCESSOR_H diff --git a/codec2/vndk/bufferpool/AccessorImpl.cpp b/codec2/vndk/bufferpool/AccessorImpl.cpp deleted file mode 100644 index 06d49c1..0000000 --- a/codec2/vndk/bufferpool/AccessorImpl.cpp +++ /dev/null @@ -1,543 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#define LOG_TAG "BufferPoolAccessor" -//#define LOG_NDEBUG 0 - -#include <sys/types.h> -#include <time.h> -#include <unistd.h> -#include <utils/Log.h> -#include "AccessorImpl.h" -#include "Connection.h" - -namespace android { -namespace hardware { -namespace media { -namespace bufferpool { -namespace V1_0 { -namespace implementation { - -namespace { - static constexpr int64_t kCleanUpDurationUs = 500000; // TODO tune 0.5 sec - static constexpr int64_t kLogDurationUs = 5000000; // 5 secs - - static constexpr size_t kMinAllocBytesForEviction = 1024*1024*15; - static constexpr size_t kMinBufferCountForEviction = 40; -} - -// Buffer structure in bufferpool process -struct InternalBuffer { - BufferId mId; - size_t mOwnerCount; - size_t mTransactionCount; - const std::shared_ptr<BufferPoolAllocation> mAllocation; - const size_t mAllocSize; - const std::vector<uint8_t> mConfig; - - InternalBuffer( - BufferId id, - const std::shared_ptr<BufferPoolAllocation> &alloc, - const size_t allocSize, - const std::vector<uint8_t> &allocConfig) - : mId(id), mOwnerCount(0), mTransactionCount(0), - mAllocation(alloc), mAllocSize(allocSize), mConfig(allocConfig) {} - - const native_handle_t *handle() { - return mAllocation->handle(); - } -}; - -struct TransactionStatus { - TransactionId mId; - BufferId mBufferId; - ConnectionId mSender; - ConnectionId mReceiver; - BufferStatus mStatus; - int64_t mTimestampUs; - bool mSenderValidated; - - TransactionStatus(const BufferStatusMessage &message, int64_t timestampUs) { - mId = message.transactionId; - mBufferId = message.bufferId; - mStatus = message.newStatus; - mTimestampUs = timestampUs; - if (mStatus == BufferStatus::TRANSFER_TO) { - mSender = message.connectionId; - mReceiver = message.targetConnectionId; - mSenderValidated = true; - } else { - mSender = -1LL; - mReceiver = message.connectionId; - mSenderValidated = false; - } - } -}; - -// Helper template methods for handling map of set. -template<class T, class U> -bool insert(std::map<T, std::set<U>> *mapOfSet, T key, U value) { - auto iter = mapOfSet->find(key); - if (iter == mapOfSet->end()) { - std::set<U> valueSet{value}; - mapOfSet->insert(std::make_pair(key, valueSet)); - return true; - } else if (iter->second.find(value) == iter->second.end()) { - iter->second.insert(value); - return true; - } - return false; -} - -template<class T, class U> -bool erase(std::map<T, std::set<U>> *mapOfSet, T key, U value) { - bool ret = false; - auto iter = mapOfSet->find(key); - if (iter != mapOfSet->end()) { - if (iter->second.erase(value) > 0) { - ret = true; - } - if (iter->second.size() == 0) { - mapOfSet->erase(iter); - } - } - return ret; -} - -template<class T, class U> -bool contains(std::map<T, std::set<U>> *mapOfSet, T key, U value) { - auto iter = mapOfSet->find(key); - if (iter != mapOfSet->end()) { - auto setIter = iter->second.find(value); - return setIter != iter->second.end(); - } - return false; -} - -int32_t Accessor::Impl::sPid = getpid(); -uint32_t Accessor::Impl::sSeqId = time(NULL); - -Accessor::Impl::Impl( - const std::shared_ptr<BufferPoolAllocator> &allocator) - : mAllocator(allocator) {} - -Accessor::Impl::~Impl() { -} - -ResultStatus Accessor::Impl::connect( - const sp<Accessor> &accessor, sp<Connection> *connection, - ConnectionId *pConnectionId, const QueueDescriptor** fmqDescPtr) { - sp<Connection> newConnection = new Connection(); - ResultStatus status = ResultStatus::CRITICAL_ERROR; - { - std::lock_guard<std::mutex> lock(mBufferPool.mMutex); - if (newConnection) { - ConnectionId id = (int64_t)sPid << 32 | sSeqId; - status = mBufferPool.mObserver.open(id, fmqDescPtr); - if (status == ResultStatus::OK) { - newConnection->initialize(accessor, id); - *connection = newConnection; - *pConnectionId = id; - ++sSeqId; - } - } - mBufferPool.processStatusMessages(); - mBufferPool.cleanUp(); - } - return status; -} - -ResultStatus Accessor::Impl::close(ConnectionId connectionId) { - std::lock_guard<std::mutex> lock(mBufferPool.mMutex); - mBufferPool.processStatusMessages(); - mBufferPool.handleClose(connectionId); - mBufferPool.mObserver.close(connectionId); - // Since close# will be called after all works are finished, it is OK to - // evict unused buffers. - mBufferPool.cleanUp(true); - return ResultStatus::OK; -} - -ResultStatus Accessor::Impl::allocate( - ConnectionId connectionId, const std::vector<uint8_t>& params, - BufferId *bufferId, const native_handle_t** handle) { - std::unique_lock<std::mutex> lock(mBufferPool.mMutex); - mBufferPool.processStatusMessages(); - ResultStatus status = ResultStatus::OK; - if (!mBufferPool.getFreeBuffer(mAllocator, params, bufferId, handle)) { - lock.unlock(); - std::shared_ptr<BufferPoolAllocation> alloc; - size_t allocSize; - status = mAllocator->allocate(params, &alloc, &allocSize); - lock.lock(); - if (status == ResultStatus::OK) { - status = mBufferPool.addNewBuffer(alloc, allocSize, params, bufferId, handle); - } - ALOGV("create a buffer %d : %u %p", - status == ResultStatus::OK, *bufferId, *handle); - } - if (status == ResultStatus::OK) { - // TODO: handle ownBuffer failure - mBufferPool.handleOwnBuffer(connectionId, *bufferId); - } - mBufferPool.cleanUp(); - return status; -} - -ResultStatus Accessor::Impl::fetch( - ConnectionId connectionId, TransactionId transactionId, - BufferId bufferId, const native_handle_t** handle) { - std::lock_guard<std::mutex> lock(mBufferPool.mMutex); - mBufferPool.processStatusMessages(); - auto found = mBufferPool.mTransactions.find(transactionId); - if (found != mBufferPool.mTransactions.end() && - contains(&mBufferPool.mPendingTransactions, - connectionId, transactionId)) { - if (found->second->mSenderValidated && - found->second->mStatus == BufferStatus::TRANSFER_FROM && - found->second->mBufferId == bufferId) { - found->second->mStatus = BufferStatus::TRANSFER_FETCH; - auto bufferIt = mBufferPool.mBuffers.find(bufferId); - if (bufferIt != mBufferPool.mBuffers.end()) { - mBufferPool.mStats.onBufferFetched(); - *handle = bufferIt->second->handle(); - return ResultStatus::OK; - } - } - } - mBufferPool.cleanUp(); - return ResultStatus::CRITICAL_ERROR; -} - -void Accessor::Impl::cleanUp(bool clearCache) { - // transaction timeout, buffer cacheing TTL handling - std::lock_guard<std::mutex> lock(mBufferPool.mMutex); - mBufferPool.processStatusMessages(); - mBufferPool.cleanUp(clearCache); -} - -Accessor::Impl::Impl::BufferPool::BufferPool() - : mTimestampUs(getTimestampNow()), - mLastCleanUpUs(mTimestampUs), - mLastLogUs(mTimestampUs), - mSeq(0) {} - - -// Statistics helper -template<typename T, typename S> -int percentage(T base, S total) { - return int(total ? 0.5 + 100. * static_cast<S>(base) / total : 0); -} - -Accessor::Impl::Impl::BufferPool::~BufferPool() { - std::lock_guard<std::mutex> lock(mMutex); - ALOGD("Destruction - bufferpool %p " - "cached: %zu/%zuM, %zu/%d%% in use; " - "allocs: %zu, %d%% recycled; " - "transfers: %zu, %d%% unfetced", - this, mStats.mBuffersCached, mStats.mSizeCached >> 20, - mStats.mBuffersInUse, percentage(mStats.mBuffersInUse, mStats.mBuffersCached), - mStats.mTotalAllocations, percentage(mStats.mTotalRecycles, mStats.mTotalAllocations), - mStats.mTotalTransfers, - percentage(mStats.mTotalTransfers - mStats.mTotalFetches, mStats.mTotalTransfers)); -} - -bool Accessor::Impl::BufferPool::handleOwnBuffer( - ConnectionId connectionId, BufferId bufferId) { - - bool added = insert(&mUsingBuffers, connectionId, bufferId); - if (added) { - auto iter = mBuffers.find(bufferId); - iter->second->mOwnerCount++; - } - insert(&mUsingConnections, bufferId, connectionId); - return added; -} - -bool Accessor::Impl::BufferPool::handleReleaseBuffer( - ConnectionId connectionId, BufferId bufferId) { - bool deleted = erase(&mUsingBuffers, connectionId, bufferId); - if (deleted) { - auto iter = mBuffers.find(bufferId); - iter->second->mOwnerCount--; - if (iter->second->mOwnerCount == 0 && - iter->second->mTransactionCount == 0) { - mStats.onBufferUnused(iter->second->mAllocSize); - mFreeBuffers.insert(bufferId); - } - } - erase(&mUsingConnections, bufferId, connectionId); - ALOGV("release buffer %u : %d", bufferId, deleted); - return deleted; -} - -bool Accessor::Impl::BufferPool::handleTransferTo(const BufferStatusMessage &message) { - auto completed = mCompletedTransactions.find( - message.transactionId); - if (completed != mCompletedTransactions.end()) { - // already completed - mCompletedTransactions.erase(completed); - return true; - } - // the buffer should exist and be owned. - auto bufferIter = mBuffers.find(message.bufferId); - if (bufferIter == mBuffers.end() || - !contains(&mUsingBuffers, message.connectionId, message.bufferId)) { - return false; - } - auto found = mTransactions.find(message.transactionId); - if (found != mTransactions.end()) { - // transfer_from was received earlier. - found->second->mSender = message.connectionId; - found->second->mSenderValidated = true; - return true; - } - // TODO: verify there is target connection Id - mStats.onBufferSent(); - mTransactions.insert(std::make_pair( - message.transactionId, - std::make_unique<TransactionStatus>(message, mTimestampUs))); - insert(&mPendingTransactions, message.targetConnectionId, - message.transactionId); - bufferIter->second->mTransactionCount++; - return true; -} - -bool Accessor::Impl::BufferPool::handleTransferFrom(const BufferStatusMessage &message) { - auto found = mTransactions.find(message.transactionId); - if (found == mTransactions.end()) { - // TODO: is it feasible to check ownership here? - mStats.onBufferSent(); - mTransactions.insert(std::make_pair( - message.transactionId, - std::make_unique<TransactionStatus>(message, mTimestampUs))); - insert(&mPendingTransactions, message.connectionId, - message.transactionId); - auto bufferIter = mBuffers.find(message.bufferId); - bufferIter->second->mTransactionCount++; - } else { - if (message.connectionId == found->second->mReceiver) { - found->second->mStatus = BufferStatus::TRANSFER_FROM; - } - } - return true; -} - -bool Accessor::Impl::BufferPool::handleTransferResult(const BufferStatusMessage &message) { - auto found = mTransactions.find(message.transactionId); - if (found != mTransactions.end()) { - bool deleted = erase(&mPendingTransactions, message.connectionId, - message.transactionId); - if (deleted) { - if (!found->second->mSenderValidated) { - mCompletedTransactions.insert(message.transactionId); - } - auto bufferIter = mBuffers.find(message.bufferId); - if (message.newStatus == BufferStatus::TRANSFER_OK) { - handleOwnBuffer(message.connectionId, message.bufferId); - } - bufferIter->second->mTransactionCount--; - if (bufferIter->second->mOwnerCount == 0 - && bufferIter->second->mTransactionCount == 0) { - mStats.onBufferUnused(bufferIter->second->mAllocSize); - mFreeBuffers.insert(message.bufferId); - } - mTransactions.erase(found); - } - ALOGV("transfer finished %llu %u - %d", (unsigned long long)message.transactionId, - message.bufferId, deleted); - return deleted; - } - ALOGV("transfer not found %llu %u", (unsigned long long)message.transactionId, - message.bufferId); - return false; -} - -void Accessor::Impl::BufferPool::processStatusMessages() { - std::vector<BufferStatusMessage> messages; - mObserver.getBufferStatusChanges(messages); - mTimestampUs = getTimestampNow(); - for (BufferStatusMessage& message: messages) { - bool ret = false; - switch (message.newStatus) { - case BufferStatus::NOT_USED: - ret = handleReleaseBuffer( - message.connectionId, message.bufferId); - break; - case BufferStatus::USED: - // not happening - break; - case BufferStatus::TRANSFER_TO: - ret = handleTransferTo(message); - break; - case BufferStatus::TRANSFER_FROM: - ret = handleTransferFrom(message); - break; - case BufferStatus::TRANSFER_TIMEOUT: - // TODO - break; - case BufferStatus::TRANSFER_LOST: - // TODO - break; - case BufferStatus::TRANSFER_FETCH: - // not happening - break; - case BufferStatus::TRANSFER_OK: - case BufferStatus::TRANSFER_ERROR: - ret = handleTransferResult(message); - break; - } - if (ret == false) { - ALOGW("buffer status message processing failure - message : %d connection : %lld", - message.newStatus, (long long)message.connectionId); - } - } - messages.clear(); -} - -bool Accessor::Impl::BufferPool::handleClose(ConnectionId connectionId) { - // Cleaning buffers - auto buffers = mUsingBuffers.find(connectionId); - if (buffers != mUsingBuffers.end()) { - for (const BufferId& bufferId : buffers->second) { - bool deleted = erase(&mUsingConnections, bufferId, connectionId); - if (deleted) { - auto bufferIter = mBuffers.find(bufferId); - bufferIter->second->mOwnerCount--; - if (bufferIter->second->mOwnerCount == 0 && - bufferIter->second->mTransactionCount == 0) { - // TODO: handle freebuffer insert fail - mStats.onBufferUnused(bufferIter->second->mAllocSize); - mFreeBuffers.insert(bufferId); - } - } - } - mUsingBuffers.erase(buffers); - } - - // Cleaning transactions - auto pending = mPendingTransactions.find(connectionId); - if (pending != mPendingTransactions.end()) { - for (const TransactionId& transactionId : pending->second) { - auto iter = mTransactions.find(transactionId); - if (iter != mTransactions.end()) { - if (!iter->second->mSenderValidated) { - mCompletedTransactions.insert(transactionId); - } - BufferId bufferId = iter->second->mBufferId; - auto bufferIter = mBuffers.find(bufferId); - bufferIter->second->mTransactionCount--; - if (bufferIter->second->mOwnerCount == 0 && - bufferIter->second->mTransactionCount == 0) { - // TODO: handle freebuffer insert fail - mStats.onBufferUnused(bufferIter->second->mAllocSize); - mFreeBuffers.insert(bufferId); - } - mTransactions.erase(iter); - } - } - } - return true; -} - -bool Accessor::Impl::BufferPool::getFreeBuffer( - const std::shared_ptr<BufferPoolAllocator> &allocator, - const std::vector<uint8_t> ¶ms, BufferId *pId, - const native_handle_t** handle) { - auto bufferIt = mFreeBuffers.begin(); - for (;bufferIt != mFreeBuffers.end(); ++bufferIt) { - BufferId bufferId = *bufferIt; - if (allocator->compatible(params, mBuffers[bufferId]->mConfig)) { - break; - } - } - if (bufferIt != mFreeBuffers.end()) { - BufferId id = *bufferIt; - mFreeBuffers.erase(bufferIt); - mStats.onBufferRecycled(mBuffers[id]->mAllocSize); - *handle = mBuffers[id]->handle(); - *pId = id; - ALOGV("recycle a buffer %u %p", id, *handle); - return true; - } - return false; -} - -ResultStatus Accessor::Impl::BufferPool::addNewBuffer( - const std::shared_ptr<BufferPoolAllocation> &alloc, - const size_t allocSize, - const std::vector<uint8_t> ¶ms, - BufferId *pId, - const native_handle_t** handle) { - - BufferId bufferId = mSeq++; - if (mSeq == Connection::SYNC_BUFFERID) { - mSeq = 0; - } - std::unique_ptr<InternalBuffer> buffer = - std::make_unique<InternalBuffer>( - bufferId, alloc, allocSize, params); - if (buffer) { - auto res = mBuffers.insert(std::make_pair( - bufferId, std::move(buffer))); - if (res.second) { - mStats.onBufferAllocated(allocSize); - *handle = alloc->handle(); - *pId = bufferId; - return ResultStatus::OK; - } - } - return ResultStatus::NO_MEMORY; -} - -void Accessor::Impl::BufferPool::cleanUp(bool clearCache) { - if (clearCache || mTimestampUs > mLastCleanUpUs + kCleanUpDurationUs) { - mLastCleanUpUs = mTimestampUs; - if (mTimestampUs > mLastLogUs + kLogDurationUs) { - mLastLogUs = mTimestampUs; - ALOGD("bufferpool %p : %zu(%zu size) total buffers - " - "%zu(%zu size) used buffers - %zu/%zu (recycle/alloc) - " - "%zu/%zu (fetch/transfer)", - this, mStats.mBuffersCached, mStats.mSizeCached, - mStats.mBuffersInUse, mStats.mSizeInUse, - mStats.mTotalRecycles, mStats.mTotalAllocations, - mStats.mTotalFetches, mStats.mTotalTransfers); - } - for (auto freeIt = mFreeBuffers.begin(); freeIt != mFreeBuffers.end();) { - if (!clearCache && mStats.mSizeCached < kMinAllocBytesForEviction - && mBuffers.size() < kMinBufferCountForEviction) { - break; - } - auto it = mBuffers.find(*freeIt); - if (it != mBuffers.end() && - it->second->mOwnerCount == 0 && it->second->mTransactionCount == 0) { - mStats.onBufferEvicted(it->second->mAllocSize); - mBuffers.erase(it); - freeIt = mFreeBuffers.erase(freeIt); - } else { - ++freeIt; - ALOGW("bufferpool inconsistent!"); - } - } - } -} - -} // namespace implementation -} // namespace V1_0 -} // namespace bufferpool -} // namespace media -} // namespace hardware -} // namespace android diff --git a/codec2/vndk/bufferpool/AccessorImpl.h b/codec2/vndk/bufferpool/AccessorImpl.h deleted file mode 100644 index c04dbf3..0000000 --- a/codec2/vndk/bufferpool/AccessorImpl.h +++ /dev/null @@ -1,300 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ANDROID_HARDWARE_MEDIA_BUFFERPOOL_V1_0_ACCESSORIMPL_H -#define ANDROID_HARDWARE_MEDIA_BUFFERPOOL_V1_0_ACCESSORIMPL_H - -#include <map> -#include <set> -#include "Accessor.h" - -namespace android { -namespace hardware { -namespace media { -namespace bufferpool { -namespace V1_0 { -namespace implementation { - -struct InternalBuffer; -struct TransactionStatus; - -/** - * An implementation of a buffer pool accessor(or a buffer pool implementation.) */ -class Accessor::Impl { -public: - Impl(const std::shared_ptr<BufferPoolAllocator> &allocator); - - ~Impl(); - - ResultStatus connect( - const sp<Accessor> &accessor, sp<Connection> *connection, - ConnectionId *pConnectionId, const QueueDescriptor** fmqDescPtr); - - ResultStatus close(ConnectionId connectionId); - - ResultStatus allocate(ConnectionId connectionId, - const std::vector<uint8_t>& params, - BufferId *bufferId, - const native_handle_t** handle); - - ResultStatus fetch(ConnectionId connectionId, - TransactionId transactionId, - BufferId bufferId, - const native_handle_t** handle); - - void cleanUp(bool clearCache); - -private: - // ConnectionId = pid : (timestamp_created + seqId) - // in order to guarantee uniqueness for each connection - static uint32_t sSeqId; - static int32_t sPid; - - const std::shared_ptr<BufferPoolAllocator> mAllocator; - - /** - * Buffer pool implementation. - * - * Handles buffer status messages. Handles buffer allocation/recycling. - * Handles buffer transfer between buffer pool clients. - */ - struct BufferPool { - private: - std::mutex mMutex; - int64_t mTimestampUs; - int64_t mLastCleanUpUs; - int64_t mLastLogUs; - BufferId mSeq; - BufferStatusObserver mObserver; - - std::map<ConnectionId, std::set<BufferId>> mUsingBuffers; - std::map<BufferId, std::set<ConnectionId>> mUsingConnections; - - std::map<ConnectionId, std::set<TransactionId>> mPendingTransactions; - // Transactions completed before TRANSFER_TO message arrival. - // Fetch does not occur for the transactions. - // Only transaction id is kept for the transactions in short duration. - std::set<TransactionId> mCompletedTransactions; - // Currently active(pending) transations' status & information. - std::map<TransactionId, std::unique_ptr<TransactionStatus>> - mTransactions; - - std::map<BufferId, std::unique_ptr<InternalBuffer>> mBuffers; - std::set<BufferId> mFreeBuffers; - - /// Buffer pool statistics which tracks allocation and transfer statistics. - struct Stats { - /// Total size of allocations which are used or available to use. - /// (bytes or pixels) - size_t mSizeCached; - /// # of cached buffers which are used or available to use. - size_t mBuffersCached; - /// Total size of allocations which are currently used. (bytes or pixels) - size_t mSizeInUse; - /// # of currently used buffers - size_t mBuffersInUse; - - /// # of allocations called on bufferpool. (# of fetched from BlockPool) - size_t mTotalAllocations; - /// # of allocations that were served from the cache. - /// (# of allocator alloc prevented) - size_t mTotalRecycles; - /// # of buffer transfers initiated. - size_t mTotalTransfers; - /// # of transfers that had to be fetched. - size_t mTotalFetches; - - Stats() - : mSizeCached(0), mBuffersCached(0), mSizeInUse(0), mBuffersInUse(0), - mTotalAllocations(0), mTotalRecycles(0), mTotalTransfers(0), mTotalFetches(0) {} - - /// A new buffer is allocated on an allocation request. - void onBufferAllocated(size_t allocSize) { - mSizeCached += allocSize; - mBuffersCached++; - - mSizeInUse += allocSize; - mBuffersInUse++; - - mTotalAllocations++; - } - - /// A buffer is evicted and destroyed. - void onBufferEvicted(size_t allocSize) { - mSizeCached -= allocSize; - mBuffersCached--; - } - - /// A buffer is recycled on an allocation request. - void onBufferRecycled(size_t allocSize) { - mSizeInUse += allocSize; - mBuffersInUse++; - - mTotalAllocations++; - mTotalRecycles++; - } - - /// A buffer is available to be recycled. - void onBufferUnused(size_t allocSize) { - mSizeInUse -= allocSize; - mBuffersInUse--; - } - - /// A buffer transfer is initiated. - void onBufferSent() { - mTotalTransfers++; - } - - /// A buffer fetch is invoked by a buffer transfer. - void onBufferFetched() { - mTotalFetches++; - } - } mStats; - - public: - /** Creates a buffer pool. */ - BufferPool(); - - /** Destroys a buffer pool. */ - ~BufferPool(); - - /** - * Processes all pending buffer status messages, and returns the result. - * Each status message is handled by methods with 'handle' prefix. - */ - void processStatusMessages(); - - /** - * Handles a buffer being owned by a connection. - * - * @param connectionId the id of the buffer owning connection. - * @param bufferId the id of the buffer. - * - * @return {@code true} when the buffer is owned, - * {@code false} otherwise. - */ - bool handleOwnBuffer(ConnectionId connectionId, BufferId bufferId); - - /** - * Handles a buffer being released by a connection. - * - * @param connectionId the id of the buffer owning connection. - * @param bufferId the id of the buffer. - * - * @return {@code true} when the buffer ownership is released, - * {@code false} otherwise. - */ - bool handleReleaseBuffer(ConnectionId connectionId, BufferId bufferId); - - /** - * Handles a transfer transaction start message from the sender. - * - * @param message a buffer status message for the transaction. - * - * @result {@code true} when transfer_to message is acknowledged, - * {@code false} otherwise. - */ - bool handleTransferTo(const BufferStatusMessage &message); - - /** - * Handles a transfer transaction being acked by the receiver. - * - * @param message a buffer status message for the transaction. - * - * @result {@code true} when transfer_from message is acknowledged, - * {@code false} otherwise. - */ - bool handleTransferFrom(const BufferStatusMessage &message); - - /** - * Handles a transfer transaction result message from the receiver. - * - * @param message a buffer status message for the transaction. - * - * @result {@code true} when the exisitng transaction is finished, - * {@code false} otherwise. - */ - bool handleTransferResult(const BufferStatusMessage &message); - - /** - * Handles a connection being closed, and returns the result. All the - * buffers and transactions owned by the connection will be cleaned up. - * The related FMQ will be cleaned up too. - * - * @param connectionId the id of the connection. - * - * @result {@code true} when the connection existed, - * {@code false} otherwise. - */ - bool handleClose(ConnectionId connectionId); - - /** - * Recycles a existing free buffer if it is possible. - * - * @param allocator the buffer allocator - * @param params the allocation parameters. - * @param pId the id of the recycled buffer. - * @param handle the native handle of the recycled buffer. - * - * @return {@code true} when a buffer is recycled, {@code false} - * otherwise. - */ - bool getFreeBuffer( - const std::shared_ptr<BufferPoolAllocator> &allocator, - const std::vector<uint8_t> ¶ms, - BufferId *pId, const native_handle_t **handle); - - /** - * Adds a newly allocated buffer to bufferpool. - * - * @param alloc the newly allocated buffer. - * @param allocSize the size of the newly allocated buffer. - * @param params the allocation parameters. - * @param pId the buffer id for the newly allocated buffer. - * @param handle the native handle for the newly allocated buffer. - * - * @return OK when an allocation is successfully allocated. - * NO_MEMORY when there is no memory. - * CRITICAL_ERROR otherwise. - */ - ResultStatus addNewBuffer( - const std::shared_ptr<BufferPoolAllocation> &alloc, - const size_t allocSize, - const std::vector<uint8_t> ¶ms, - BufferId *pId, - const native_handle_t **handle); - - /** - * Processes pending buffer status messages and performs periodic cache - * cleaning. - * - * @param clearCache if clearCache is true, it frees all buffers - * waiting to be recycled. - */ - void cleanUp(bool clearCache = false); - - friend class Accessor::Impl; - } mBufferPool; -}; - -} // namespace implementation -} // namespace V1_0 -} // namespace ufferpool -} // namespace media -} // namespace hardware -} // namespace android - -#endif // ANDROID_HARDWARE_MEDIA_BUFFERPOOL_V1_0_ACCESSORIMPL_H diff --git a/codec2/vndk/bufferpool/Android.bp b/codec2/vndk/bufferpool/Android.bp deleted file mode 100644 index c7ea70f..0000000 --- a/codec2/vndk/bufferpool/Android.bp +++ /dev/null @@ -1,29 +0,0 @@ -cc_library { - name: "libstagefright_bufferpool@1.0", - vendor_available: true, - srcs: [ - "Accessor.cpp", - "AccessorImpl.cpp", - "BufferPoolClient.cpp", - "BufferStatus.cpp", - "ClientManager.cpp", - "Connection.cpp", - ], - export_include_dirs: [ - "include", - ], - shared_libs: [ - "libcutils", - "libfmq", - "libhidlbase", - "libhwbinder", - "libhidltransport", - "liblog", - "libutils", - "android.hardware.media.bufferpool@1.0", - ], - export_shared_lib_headers: [ - "libfmq", - "android.hardware.media.bufferpool@1.0", - ], -} diff --git a/codec2/vndk/bufferpool/BufferPoolClient.cpp b/codec2/vndk/bufferpool/BufferPoolClient.cpp deleted file mode 100644 index 3626004..0000000 --- a/codec2/vndk/bufferpool/BufferPoolClient.cpp +++ /dev/null @@ -1,708 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#define LOG_TAG "BufferPoolClient" -//#define LOG_NDEBUG 0 - -#include <thread> -#include <utils/Log.h> -#include "BufferPoolClient.h" -#include "Connection.h" - -namespace android { -namespace hardware { -namespace media { -namespace bufferpool { -namespace V1_0 { -namespace implementation { - -static constexpr int64_t kReceiveTimeoutUs = 1000000; // 100ms -static constexpr int kPostMaxRetry = 3; -static constexpr int kCacheTtlUs = 1000000; // TODO: tune - -class BufferPoolClient::Impl - : public std::enable_shared_from_this<BufferPoolClient::Impl> { -public: - explicit Impl(const sp<Accessor> &accessor); - - explicit Impl(const sp<IAccessor> &accessor); - - bool isValid() { - return mValid; - } - - bool isLocal() { - return mValid && mLocal; - } - - ConnectionId getConnectionId() { - return mConnectionId; - } - - sp<IAccessor> &getAccessor() { - return mAccessor; - } - - bool isActive(int64_t *lastTransactionUs, bool clearCache); - - ResultStatus allocate(const std::vector<uint8_t> ¶ms, - native_handle_t **handle, - std::shared_ptr<BufferPoolData> *buffer); - - ResultStatus receive( - TransactionId transactionId, BufferId bufferId, - int64_t timestampUs, - native_handle_t **handle, std::shared_ptr<BufferPoolData> *buffer); - - void postBufferRelease(BufferId bufferId); - - bool postSend( - BufferId bufferId, ConnectionId receiver, - TransactionId *transactionId, int64_t *timestampUs); -private: - - bool postReceive( - BufferId bufferId, TransactionId transactionId, - int64_t timestampUs); - - bool postReceiveResult( - BufferId bufferId, TransactionId transactionId, bool result, bool *needsSync); - - void trySyncFromRemote(); - - bool syncReleased(); - - void evictCaches(bool clearCache = false); - - ResultStatus allocateBufferHandle( - const std::vector<uint8_t>& params, BufferId *bufferId, - native_handle_t **handle); - - ResultStatus fetchBufferHandle( - TransactionId transactionId, BufferId bufferId, - native_handle_t **handle); - - struct BlockPoolDataDtor; - struct ClientBuffer; - - bool mLocal; - bool mValid; - sp<IAccessor> mAccessor; - sp<Connection> mLocalConnection; - sp<IConnection> mRemoteConnection; - uint32_t mSeqId; - ConnectionId mConnectionId; - int64_t mLastEvictCacheUs; - - // CachedBuffers - struct BufferCache { - std::mutex mLock; - bool mCreating; - std::condition_variable mCreateCv; - std::map<BufferId, std::unique_ptr<ClientBuffer>> mBuffers; - int mActive; - int64_t mLastChangeUs; - - BufferCache() : mCreating(false), mActive(0), mLastChangeUs(getTimestampNow()) {} - - void incActive_l() { - ++mActive; - mLastChangeUs = getTimestampNow(); - } - - void decActive_l() { - --mActive; - mLastChangeUs = getTimestampNow(); - } - } mCache; - - // FMQ - release notifier - struct { - std::mutex mLock; - // TODO: use only one list?(using one list may dealy sending messages?) - std::list<BufferId> mReleasingIds; - std::list<BufferId> mReleasedIds; - std::unique_ptr<BufferStatusChannel> mStatusChannel; - } mReleasing; - - // This lock is held during synchronization from remote side. - // In order to minimize remote calls and locking durtaion, this lock is held - // by best effort approach using try_lock(). - std::mutex mRemoteSyncLock; -}; - -struct BufferPoolClient::Impl::BlockPoolDataDtor { - BlockPoolDataDtor(const std::shared_ptr<BufferPoolClient::Impl> &impl) - : mImpl(impl) {} - - void operator()(BufferPoolData *buffer) { - BufferId id = buffer->mId; - delete buffer; - - auto impl = mImpl.lock(); - if (impl && impl->isValid()) { - impl->postBufferRelease(id); - } - } - const std::weak_ptr<BufferPoolClient::Impl> mImpl; -}; - -struct BufferPoolClient::Impl::ClientBuffer { -private: - bool mInvalidated; // TODO: implement - int64_t mExpireUs; - bool mHasCache; - ConnectionId mConnectionId; - BufferId mId; - native_handle_t *mHandle; - std::weak_ptr<BufferPoolData> mCache; - - void updateExpire() { - mExpireUs = getTimestampNow() + kCacheTtlUs; - } - -public: - ClientBuffer( - ConnectionId connectionId, BufferId id, native_handle_t *handle) - : mInvalidated(false), mHasCache(false), - mConnectionId(connectionId), mId(id), mHandle(handle) { - (void)mInvalidated; - mExpireUs = getTimestampNow() + kCacheTtlUs; - } - - ~ClientBuffer() { - if (mHandle) { - native_handle_close(mHandle); - native_handle_delete(mHandle); - } - } - - bool expire() const { - int64_t now = getTimestampNow(); - return now >= mExpireUs; - } - - bool hasCache() const { - return mHasCache; - } - - std::shared_ptr<BufferPoolData> fetchCache(native_handle_t **pHandle) { - if (mHasCache) { - std::shared_ptr<BufferPoolData> cache = mCache.lock(); - if (cache) { - *pHandle = mHandle; - } - return cache; - } - return nullptr; - } - - std::shared_ptr<BufferPoolData> createCache( - const std::shared_ptr<BufferPoolClient::Impl> &impl, - native_handle_t **pHandle) { - if (!mHasCache) { - // Allocates a raw ptr in order to avoid sending #postBufferRelease - // from deleter, in case of native_handle_clone failure. - BufferPoolData *ptr = new BufferPoolData(mConnectionId, mId); - if (ptr) { - std::shared_ptr<BufferPoolData> cache(ptr, BlockPoolDataDtor(impl)); - if (cache) { - mCache = cache; - mHasCache = true; - *pHandle = mHandle; - return cache; - } - } - if (ptr) { - delete ptr; - } - } - return nullptr; - } - - bool onCacheRelease() { - if (mHasCache) { - // TODO: verify mCache is not valid; - updateExpire(); - mHasCache = false; - return true; - } - return false; - } -}; - -BufferPoolClient::Impl::Impl(const sp<Accessor> &accessor) - : mLocal(true), mValid(false), mAccessor(accessor), mSeqId(0), - mLastEvictCacheUs(getTimestampNow()) { - const QueueDescriptor *fmqDesc; - ResultStatus status = accessor->connect( - &mLocalConnection, &mConnectionId, &fmqDesc, true); - if (status == ResultStatus::OK) { - mReleasing.mStatusChannel = - std::make_unique<BufferStatusChannel>(*fmqDesc); - mValid = mReleasing.mStatusChannel && - mReleasing.mStatusChannel->isValid(); - } -} - -BufferPoolClient::Impl::Impl(const sp<IAccessor> &accessor) - : mLocal(false), mValid(false), mAccessor(accessor), mSeqId(0), - mLastEvictCacheUs(getTimestampNow()) { - bool valid = false; - sp<IConnection>& outConnection = mRemoteConnection; - ConnectionId& id = mConnectionId; - std::unique_ptr<BufferStatusChannel>& outChannel = - mReleasing.mStatusChannel; - Return<void> transResult = accessor->connect( - [&valid, &outConnection, &id, &outChannel] - (ResultStatus status, sp<IConnection> connection, - ConnectionId connectionId, const QueueDescriptor& desc) { - if (status == ResultStatus::OK) { - outConnection = connection; - id = connectionId; - outChannel = std::make_unique<BufferStatusChannel>(desc); - if (outChannel && outChannel->isValid()) { - valid = true; - } - } - }); - mValid = transResult.isOk() && valid; -} - -bool BufferPoolClient::Impl::isActive(int64_t *lastTransactionUs, bool clearCache) { - bool active = false; - { - std::lock_guard<std::mutex> lock(mCache.mLock); - syncReleased(); - evictCaches(clearCache); - *lastTransactionUs = mCache.mLastChangeUs; - active = mCache.mActive > 0; - } - if (mValid && mLocal && mLocalConnection) { - mLocalConnection->cleanUp(clearCache); - return true; - } - return active; -} - -ResultStatus BufferPoolClient::Impl::allocate( - const std::vector<uint8_t> ¶ms, - native_handle_t **pHandle, - std::shared_ptr<BufferPoolData> *buffer) { - if (!mLocal || !mLocalConnection || !mValid) { - return ResultStatus::CRITICAL_ERROR; - } - BufferId bufferId; - native_handle_t *handle = NULL; - buffer->reset(); - ResultStatus status = allocateBufferHandle(params, &bufferId, &handle); - if (status == ResultStatus::OK) { - if (handle) { - std::unique_lock<std::mutex> lock(mCache.mLock); - syncReleased(); - evictCaches(); - auto cacheIt = mCache.mBuffers.find(bufferId); - if (cacheIt != mCache.mBuffers.end()) { - // TODO: verify it is recycled. (not having active ref) - mCache.mBuffers.erase(cacheIt); - } - auto clientBuffer = std::make_unique<ClientBuffer>( - mConnectionId, bufferId, handle); - if (clientBuffer) { - auto result = mCache.mBuffers.insert(std::make_pair( - bufferId, std::move(clientBuffer))); - if (result.second) { - *buffer = result.first->second->createCache( - shared_from_this(), pHandle); - if (*buffer) { - mCache.incActive_l(); - } - } - } - } - if (!*buffer) { - ALOGV("client cache creation failure %d: %lld", - handle != NULL, (long long)mConnectionId); - status = ResultStatus::NO_MEMORY; - postBufferRelease(bufferId); - } - } - return status; -} - -ResultStatus BufferPoolClient::Impl::receive( - TransactionId transactionId, BufferId bufferId, int64_t timestampUs, - native_handle_t **pHandle, - std::shared_ptr<BufferPoolData> *buffer) { - if (!mValid) { - return ResultStatus::CRITICAL_ERROR; - } - if (timestampUs != 0) { - timestampUs += kReceiveTimeoutUs; - } - if (!postReceive(bufferId, transactionId, timestampUs)) { - return ResultStatus::CRITICAL_ERROR; - } - ResultStatus status = ResultStatus::CRITICAL_ERROR; - buffer->reset(); - while(1) { - std::unique_lock<std::mutex> lock(mCache.mLock); - syncReleased(); - evictCaches(); - auto cacheIt = mCache.mBuffers.find(bufferId); - if (cacheIt != mCache.mBuffers.end()) { - if (cacheIt->second->hasCache()) { - *buffer = cacheIt->second->fetchCache(pHandle); - if (!*buffer) { - // check transfer time_out - lock.unlock(); - std::this_thread::yield(); - continue; - } - ALOGV("client receive from reference %lld", (long long)mConnectionId); - break; - } else { - *buffer = cacheIt->second->createCache(shared_from_this(), pHandle); - if (*buffer) { - mCache.incActive_l(); - } - ALOGV("client receive from cache %lld", (long long)mConnectionId); - break; - } - } else { - if (!mCache.mCreating) { - mCache.mCreating = true; - lock.unlock(); - native_handle_t* handle = NULL; - status = fetchBufferHandle(transactionId, bufferId, &handle); - lock.lock(); - if (status == ResultStatus::OK) { - if (handle) { - auto clientBuffer = std::make_unique<ClientBuffer>( - mConnectionId, bufferId, handle); - if (clientBuffer) { - auto result = mCache.mBuffers.insert( - std::make_pair(bufferId, std::move( - clientBuffer))); - if (result.second) { - *buffer = result.first->second->createCache( - shared_from_this(), pHandle); - if (*buffer) { - mCache.incActive_l(); - } - } - } - } - if (!*buffer) { - status = ResultStatus::NO_MEMORY; - } - } - mCache.mCreating = false; - lock.unlock(); - mCache.mCreateCv.notify_all(); - break; - } - mCache.mCreateCv.wait(lock); - } - } - bool needsSync = false; - bool posted = postReceiveResult(bufferId, transactionId, - *buffer ? true : false, &needsSync); - ALOGV("client receive %lld - %u : %s (%d)", (long long)mConnectionId, bufferId, - *buffer ? "ok" : "fail", posted); - if (mValid && mLocal && mLocalConnection) { - mLocalConnection->cleanUp(false); - } - if (needsSync && mRemoteConnection) { - trySyncFromRemote(); - } - if (*buffer) { - if (!posted) { - buffer->reset(); - return ResultStatus::CRITICAL_ERROR; - } - return ResultStatus::OK; - } - return status; -} - - -void BufferPoolClient::Impl::postBufferRelease(BufferId bufferId) { - std::lock_guard<std::mutex> lock(mReleasing.mLock); - mReleasing.mReleasingIds.push_back(bufferId); - mReleasing.mStatusChannel->postBufferRelease( - mConnectionId, mReleasing.mReleasingIds, mReleasing.mReleasedIds); -} - -// TODO: revise ad-hoc posting data structure -bool BufferPoolClient::Impl::postSend( - BufferId bufferId, ConnectionId receiver, - TransactionId *transactionId, int64_t *timestampUs) { - bool ret = false; - bool needsSync = false; - { - std::lock_guard<std::mutex> lock(mReleasing.mLock); - *timestampUs = getTimestampNow(); - *transactionId = (mConnectionId << 32) | mSeqId++; - // TODO: retry, add timeout, target? - ret = mReleasing.mStatusChannel->postBufferStatusMessage( - *transactionId, bufferId, BufferStatus::TRANSFER_TO, mConnectionId, - receiver, mReleasing.mReleasingIds, mReleasing.mReleasedIds); - needsSync = !mLocal && mReleasing.mStatusChannel->needsSync(); - } - if (mValid && mLocal && mLocalConnection) { - mLocalConnection->cleanUp(false); - } - if (needsSync && mRemoteConnection) { - trySyncFromRemote(); - } - return ret; -} - -bool BufferPoolClient::Impl::postReceive( - BufferId bufferId, TransactionId transactionId, int64_t timestampUs) { - for (int i = 0; i < kPostMaxRetry; ++i) { - std::unique_lock<std::mutex> lock(mReleasing.mLock); - int64_t now = getTimestampNow(); - if (timestampUs == 0 || now < timestampUs) { - bool result = mReleasing.mStatusChannel->postBufferStatusMessage( - transactionId, bufferId, BufferStatus::TRANSFER_FROM, - mConnectionId, -1, mReleasing.mReleasingIds, - mReleasing.mReleasedIds); - if (result) { - return true; - } - lock.unlock(); - std::this_thread::yield(); - } else { - mReleasing.mStatusChannel->postBufferStatusMessage( - transactionId, bufferId, BufferStatus::TRANSFER_TIMEOUT, - mConnectionId, -1, mReleasing.mReleasingIds, - mReleasing.mReleasedIds); - return false; - } - } - return false; -} - -bool BufferPoolClient::Impl::postReceiveResult( - BufferId bufferId, TransactionId transactionId, bool result, bool *needsSync) { - std::lock_guard<std::mutex> lock(mReleasing.mLock); - // TODO: retry, add timeout - bool ret = mReleasing.mStatusChannel->postBufferStatusMessage( - transactionId, bufferId, - result ? BufferStatus::TRANSFER_OK : BufferStatus::TRANSFER_ERROR, - mConnectionId, -1, mReleasing.mReleasingIds, - mReleasing.mReleasedIds); - *needsSync = !mLocal && mReleasing.mStatusChannel->needsSync(); - return ret; -} - -void BufferPoolClient::Impl::trySyncFromRemote() { - if (mRemoteSyncLock.try_lock()) { - bool needsSync = false; - { - std::lock_guard<std::mutex> lock(mReleasing.mLock); - needsSync = mReleasing.mStatusChannel->needsSync(); - } - if (needsSync) { - TransactionId transactionId = (mConnectionId << 32); - BufferId bufferId = Connection::SYNC_BUFFERID; - Return<void> transResult = mRemoteConnection->fetch( - transactionId, bufferId, - [] - (ResultStatus outStatus, Buffer outBuffer) { - (void) outStatus; - (void) outBuffer; - }); - } - mRemoteSyncLock.unlock(); - } -} - -// should have mCache.mLock -bool BufferPoolClient::Impl::syncReleased() { - std::lock_guard<std::mutex> lock(mReleasing.mLock); - if (mReleasing.mReleasingIds.size() > 0) { - mReleasing.mStatusChannel->postBufferRelease( - mConnectionId, mReleasing.mReleasingIds, - mReleasing.mReleasedIds); - } - if (mReleasing.mReleasedIds.size() > 0) { - for (BufferId& id: mReleasing.mReleasedIds) { - ALOGV("client release buffer %lld - %u", (long long)mConnectionId, id); - auto found = mCache.mBuffers.find(id); - if (found != mCache.mBuffers.end()) { - if (found->second->onCacheRelease()) { - mCache.decActive_l(); - } else { - // should not happen! - ALOGW("client %lld cache release status inconsitent!", - (long long)mConnectionId); - } - } else { - // should not happen! - ALOGW("client %lld cache status inconsitent!", (long long)mConnectionId); - } - } - mReleasing.mReleasedIds.clear(); - return true; - } - return false; -} - -// should have mCache.mLock -void BufferPoolClient::Impl::evictCaches(bool clearCache) { - int64_t now = getTimestampNow(); - if (now >= mLastEvictCacheUs + kCacheTtlUs || clearCache) { - size_t evicted = 0; - for (auto it = mCache.mBuffers.begin(); it != mCache.mBuffers.end();) { - if (!it->second->hasCache() && (it->second->expire() || clearCache)) { - it = mCache.mBuffers.erase(it); - ++evicted; - } else { - ++it; - } - } - ALOGV("cache count %lld : total %zu, active %d, evicted %zu", - (long long)mConnectionId, mCache.mBuffers.size(), mCache.mActive, evicted); - mLastEvictCacheUs = now; - } -} - -ResultStatus BufferPoolClient::Impl::allocateBufferHandle( - const std::vector<uint8_t>& params, BufferId *bufferId, - native_handle_t** handle) { - if (mLocalConnection) { - const native_handle_t* allocHandle = NULL; - ResultStatus status = mLocalConnection->allocate( - params, bufferId, &allocHandle); - if (status == ResultStatus::OK) { - *handle = native_handle_clone(allocHandle); - } - ALOGV("client allocate result %lld %d : %u clone %p", - (long long)mConnectionId, status == ResultStatus::OK, - *handle ? *bufferId : 0 , *handle); - return status; - } - return ResultStatus::CRITICAL_ERROR; -} - -ResultStatus BufferPoolClient::Impl::fetchBufferHandle( - TransactionId transactionId, BufferId bufferId, - native_handle_t **handle) { - sp<IConnection> connection; - if (mLocal) { - connection = mLocalConnection; - } else { - connection = mRemoteConnection; - } - ResultStatus status; - Return<void> transResult = connection->fetch( - transactionId, bufferId, - [&status, &handle] - (ResultStatus outStatus, Buffer outBuffer) { - status = outStatus; - if (status == ResultStatus::OK) { - *handle = native_handle_clone( - outBuffer.buffer.getNativeHandle()); - } - }); - return transResult.isOk() ? status : ResultStatus::CRITICAL_ERROR; -} - - -BufferPoolClient::BufferPoolClient(const sp<Accessor> &accessor) { - mImpl = std::make_shared<Impl>(accessor); -} - -BufferPoolClient::BufferPoolClient(const sp<IAccessor> &accessor) { - mImpl = std::make_shared<Impl>(accessor); -} - -BufferPoolClient::~BufferPoolClient() { - // TODO: how to handle orphaned buffers? -} - -bool BufferPoolClient::isValid() { - return mImpl && mImpl->isValid(); -} - -bool BufferPoolClient::isLocal() { - return mImpl && mImpl->isLocal(); -} - -bool BufferPoolClient::isActive(int64_t *lastTransactionUs, bool clearCache) { - if (!isValid()) { - *lastTransactionUs = 0; - return false; - } - return mImpl->isActive(lastTransactionUs, clearCache); -} - -ConnectionId BufferPoolClient::getConnectionId() { - if (isValid()) { - return mImpl->getConnectionId(); - } - return -1; -} - -ResultStatus BufferPoolClient::getAccessor(sp<IAccessor> *accessor) { - if (isValid()) { - *accessor = mImpl->getAccessor(); - return ResultStatus::OK; - } - return ResultStatus::CRITICAL_ERROR; -} - -ResultStatus BufferPoolClient::allocate( - const std::vector<uint8_t> ¶ms, - native_handle_t **handle, - std::shared_ptr<BufferPoolData> *buffer) { - if (isValid()) { - return mImpl->allocate(params, handle, buffer); - } - return ResultStatus::CRITICAL_ERROR; -} - -ResultStatus BufferPoolClient::receive( - TransactionId transactionId, BufferId bufferId, int64_t timestampUs, - native_handle_t **handle, std::shared_ptr<BufferPoolData> *buffer) { - if (isValid()) { - return mImpl->receive(transactionId, bufferId, timestampUs, handle, buffer); - } - return ResultStatus::CRITICAL_ERROR; -} - -ResultStatus BufferPoolClient::postSend( - ConnectionId receiverId, - const std::shared_ptr<BufferPoolData> &buffer, - TransactionId *transactionId, - int64_t *timestampUs) { - if (isValid()) { - bool result = mImpl->postSend( - buffer->mId, receiverId, transactionId, timestampUs); - return result ? ResultStatus::OK : ResultStatus::CRITICAL_ERROR; - } - return ResultStatus::CRITICAL_ERROR; -} - -} // namespace implementation -} // namespace V1_0 -} // namespace bufferpool -} // namespace media -} // namespace hardware -} // namespace android diff --git a/codec2/vndk/bufferpool/BufferPoolClient.h b/codec2/vndk/bufferpool/BufferPoolClient.h deleted file mode 100644 index 577efed..0000000 --- a/codec2/vndk/bufferpool/BufferPoolClient.h +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ANDROID_HARDWARE_MEDIA_BUFFERPOOL_V1_0_BUFFERPOOLCLIENT_H -#define ANDROID_HARDWARE_MEDIA_BUFFERPOOL_V1_0_BUFFERPOOLCLIENT_H - -#include <memory> -#include <android/hardware/media/bufferpool/1.0/IAccessor.h> -#include <android/hardware/media/bufferpool/1.0/IConnection.h> -#include <bufferpool/BufferPoolTypes.h> -#include <cutils/native_handle.h> -#include "Accessor.h" - -namespace android { -namespace hardware { -namespace media { -namespace bufferpool { -namespace V1_0 { -namespace implementation { - -using ::android::hardware::media::bufferpool::V1_0::IAccessor; -using ::android::hardware::media::bufferpool::V1_0::IConnection; -using ::android::hardware::media::bufferpool::V1_0::ResultStatus; -using ::android::sp; - -/** - * A buffer pool client for a buffer pool. For a specific buffer pool, at most - * one buffer pool client exists per process. This class will not be exposed - * outside. A buffer pool client will be used via ClientManager. - */ -class BufferPoolClient { -public: - /** - * Creates a buffer pool client from a local buffer pool - * (via ClientManager#create). - */ - explicit BufferPoolClient(const sp<Accessor> &accessor); - - /** - * Creates a buffer pool client from a remote buffer pool - * (via ClientManager#registerSender). - * Note: A buffer pool client created with remote buffer pool cannot - * allocate a buffer. - */ - explicit BufferPoolClient(const sp<IAccessor> &accessor); - - /** Destructs a buffer pool client. */ - ~BufferPoolClient(); - -private: - bool isValid(); - - bool isLocal(); - - bool isActive(int64_t *lastTransactionUs, bool clearCache); - - ConnectionId getConnectionId(); - - ResultStatus getAccessor(sp<IAccessor> *accessor); - - ResultStatus allocate(const std::vector<uint8_t> ¶ms, - native_handle_t **handle, - std::shared_ptr<BufferPoolData> *buffer); - - ResultStatus receive(TransactionId transactionId, - BufferId bufferId, - int64_t timestampUs, - native_handle_t **handle, - std::shared_ptr<BufferPoolData> *buffer); - - ResultStatus postSend(ConnectionId receiver, - const std::shared_ptr<BufferPoolData> &buffer, - TransactionId *transactionId, - int64_t *timestampUs); - - class Impl; - std::shared_ptr<Impl> mImpl; - - friend struct ClientManager; -}; - -} // namespace implementation -} // namespace V1_0 -} // namespace bufferpool -} // namespace media -} // namespace hardware -} // namespace android - -#endif // ANDROID_HARDWARE_MEDIA_BUFFERPOOL_V1_0_BUFFERPOOLCLIENT_H diff --git a/codec2/vndk/bufferpool/BufferStatus.cpp b/codec2/vndk/bufferpool/BufferStatus.cpp deleted file mode 100644 index 596cfa5..0000000 --- a/codec2/vndk/bufferpool/BufferStatus.cpp +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#define LOG_TAG "BufferPoolStatus" -//#define LOG_NDEBUG 0 - -#include <time.h> -#include "BufferStatus.h" - -namespace android { -namespace hardware { -namespace media { -namespace bufferpool { -namespace V1_0 { -namespace implementation { - -int64_t getTimestampNow() { - int64_t stamp; - struct timespec ts; - // TODO: CLOCK_MONOTONIC_COARSE? - clock_gettime(CLOCK_MONOTONIC, &ts); - stamp = ts.tv_nsec / 1000; - stamp += (ts.tv_sec * 1000000LL); - return stamp; -} - -static constexpr int kNumElementsInQueue = 1024*16; -static constexpr int kMinElementsToSyncInQueue = 128; - -ResultStatus BufferStatusObserver::open( - ConnectionId id, const QueueDescriptor** fmqDescPtr) { - if (mBufferStatusQueues.find(id) != mBufferStatusQueues.end()) { - // TODO: id collision log? - return ResultStatus::CRITICAL_ERROR; - } - std::unique_ptr<BufferStatusQueue> queue = - std::make_unique<BufferStatusQueue>(kNumElementsInQueue); - if (!queue || queue->isValid() == false) { - *fmqDescPtr = NULL; - return ResultStatus::NO_MEMORY; - } else { - *fmqDescPtr = queue->getDesc(); - } - auto result = mBufferStatusQueues.insert( - std::make_pair(id, std::move(queue))); - if (!result.second) { - *fmqDescPtr = NULL; - return ResultStatus::NO_MEMORY; - } - return ResultStatus::OK; -} - -ResultStatus BufferStatusObserver::close(ConnectionId id) { - if (mBufferStatusQueues.find(id) == mBufferStatusQueues.end()) { - return ResultStatus::CRITICAL_ERROR; - } - mBufferStatusQueues.erase(id); - return ResultStatus::OK; -} - -void BufferStatusObserver::getBufferStatusChanges(std::vector<BufferStatusMessage> &messages) { - for (auto it = mBufferStatusQueues.begin(); it != mBufferStatusQueues.end(); ++it) { - BufferStatusMessage message; - size_t avail = it->second->availableToRead(); - while (avail > 0) { - if (!it->second->read(&message, 1)) { - // Since avaliable # of reads are already confirmed, - // this should not happen. - // TODO: error handling (spurious client?) - ALOGW("FMQ message cannot be read from %lld", (long long)it->first); - return; - } - message.connectionId = it->first; - messages.push_back(message); - --avail; - } - } -} - -BufferStatusChannel::BufferStatusChannel( - const QueueDescriptor &fmqDesc) { - std::unique_ptr<BufferStatusQueue> queue = - std::make_unique<BufferStatusQueue>(fmqDesc); - if (!queue || queue->isValid() == false) { - mValid = false; - return; - } - mValid = true; - mBufferStatusQueue = std::move(queue); -} - -bool BufferStatusChannel::isValid() { - return mValid; -} - -bool BufferStatusChannel::needsSync() { - if (mValid) { - size_t avail = mBufferStatusQueue->availableToWrite(); - return avail + kMinElementsToSyncInQueue < kNumElementsInQueue; - } - return false; -} - -void BufferStatusChannel::postBufferRelease( - ConnectionId connectionId, - std::list<BufferId> &pending, std::list<BufferId> &posted) { - if (mValid && pending.size() > 0) { - size_t avail = mBufferStatusQueue->availableToWrite(); - avail = std::min(avail, pending.size()); - BufferStatusMessage message; - for (size_t i = 0 ; i < avail; ++i) { - BufferId id = pending.front(); - message.newStatus = BufferStatus::NOT_USED; - message.bufferId = id; - message.connectionId = connectionId; - if (!mBufferStatusQueue->write(&message, 1)) { - // Since avaliable # of writes are already confirmed, - // this should not happen. - // TODO: error handing? - ALOGW("FMQ message cannot be sent from %lld", (long long)connectionId); - return; - } - pending.pop_front(); - posted.push_back(id); - } - } -} - -bool BufferStatusChannel::postBufferStatusMessage( - TransactionId transactionId, BufferId bufferId, - BufferStatus status, ConnectionId connectionId, ConnectionId targetId, - std::list<BufferId> &pending, std::list<BufferId> &posted) { - if (mValid) { - size_t avail = mBufferStatusQueue->availableToWrite(); - size_t numPending = pending.size(); - if (avail >= numPending + 1) { - BufferStatusMessage release, message; - for (size_t i = 0; i < numPending; ++i) { - BufferId id = pending.front(); - release.newStatus = BufferStatus::NOT_USED; - release.bufferId = id; - release.connectionId = connectionId; - if (!mBufferStatusQueue->write(&release, 1)) { - // Since avaliable # of writes are already confirmed, - // this should not happen. - // TODO: error handling? - ALOGW("FMQ message cannot be sent from %lld", (long long)connectionId); - return false; - } - pending.pop_front(); - posted.push_back(id); - } - message.transactionId = transactionId; - message.bufferId = bufferId; - message.newStatus = status; - message.connectionId = connectionId; - message.targetConnectionId = targetId; - // TODO : timesatamp - message.timestampUs = 0; - if (!mBufferStatusQueue->write(&message, 1)) { - // Since avaliable # of writes are already confirmed, - // this should not happen. - ALOGW("FMQ message cannot be sent from %lld", (long long)connectionId); - return false; - } - return true; - } - } - return false; -} - -} // namespace implementation -} // namespace V1_0 -} // namespace bufferpool -} // namespace media -} // namespace hardware -} // namespace android - diff --git a/codec2/vndk/bufferpool/BufferStatus.h b/codec2/vndk/bufferpool/BufferStatus.h deleted file mode 100644 index a18a921..0000000 --- a/codec2/vndk/bufferpool/BufferStatus.h +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ANDROID_HARDWARE_MEDIA_BUFFERPOOL_V1_0_BUFFERSTATUS_H -#define ANDROID_HARDWARE_MEDIA_BUFFERPOOL_V1_0_BUFFERSTATUS_H - -#include <android/hardware/media/bufferpool/1.0/types.h> -#include <bufferpool/BufferPoolTypes.h> -#include <fmq/MessageQueue.h> -#include <hidl/MQDescriptor.h> -#include <hidl/Status.h> -#include <memory> -#include <mutex> -#include <vector> -#include <list> - -namespace android { -namespace hardware { -namespace media { -namespace bufferpool { -namespace V1_0 { -namespace implementation { - -/** Returns monotonic timestamp in Us since fixed point in time. */ -int64_t getTimestampNow(); - -/** - * A collection of FMQ for a buffer pool. buffer ownership/status change - * messages are sent via the FMQs from the clients. - */ -class BufferStatusObserver { -private: - std::map<ConnectionId, std::unique_ptr<BufferStatusQueue>> - mBufferStatusQueues; - -public: - /** Creates an FMQ for the specified connection(client). - * - * @param connectionId connection Id of the specified client. - * @param fmqDescPtr double ptr of created FMQ's descriptor. - * - * @return OK if FMQ is created successfully. - * NO_MEMORY when there is no memory. - * CRITICAL_ERROR otherwise. - */ - ResultStatus open(ConnectionId id, const QueueDescriptor** fmqDescPtr); - - /** Closes an FMQ for the specified connection(client). - * - * @param connectionId connection Id of the specified client. - * - * @return OK if the specified connection is closed successfully. - * CRITICAL_ERROR otherwise. - */ - ResultStatus close(ConnectionId id); - - /** Retrieves all pending FMQ buffer status messages from clients. - * - * @param messages retrieved pending messages. - */ - void getBufferStatusChanges(std::vector<BufferStatusMessage> &messages); -}; - -/** - * An FMQ for a buffer pool client. Buffer ownership/status change messages - * are sent via the fmq to the buffer pool. - */ -class BufferStatusChannel { -private: - bool mValid; - std::unique_ptr<BufferStatusQueue> mBufferStatusQueue; - -public: - /** - * Connects to an FMQ from a descriptor of the created FMQ. - * - * @param fmqDesc Descriptor of the created FMQ. - */ - BufferStatusChannel(const QueueDescriptor &fmqDesc); - - /** Returns whether the FMQ is connected successfully. */ - bool isValid(); - - /** Returns whether the FMQ needs to be synced from the buffer pool */ - bool needsSync(); - - /** - * Posts a buffer release message to the buffer pool. - * - * @param connectionId connection Id of the client. - * @param pending currently pending buffer release messages. - * @param posted posted buffer release messages. - */ - void postBufferRelease( - ConnectionId connectionId, - std::list<BufferId> &pending, std::list<BufferId> &posted); - - /** - * Posts a buffer status message regarding the specified buffer - * transfer transaction. - * - * @param transactionId Id of the specified transaction. - * @param bufferId buffer Id of the specified transaction. - * @param status new status of the buffer. - * @param connectionId connection Id of the client. - * @param targetId connection Id of the receiver(only when the sender - * posts a status message). - * @param pending currently pending buffer release messages. - * @param posted posted buffer release messages. - * - * @return {@code true} when the specified message is posted, - * {@code false} otherwise. - */ - bool postBufferStatusMessage( - TransactionId transactionId, - BufferId bufferId, - BufferStatus status, - ConnectionId connectionId, - ConnectionId targetId, - std::list<BufferId> &pending, std::list<BufferId> &posted); -}; - -} // namespace implementation -} // namespace V1_0 -} // namespace bufferpool -} // namespace media -} // namespace hardware -} // namespace android - -#endif // ANDROID_HARDWARE_MEDIA_BUFFERPOOL_V1_0_BUFFERSTATUS_H diff --git a/codec2/vndk/bufferpool/ClientManager.cpp b/codec2/vndk/bufferpool/ClientManager.cpp deleted file mode 100644 index ecea0a4..0000000 --- a/codec2/vndk/bufferpool/ClientManager.cpp +++ /dev/null @@ -1,504 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#define LOG_TAG "BufferPoolManager" -//#define LOG_NDEBUG 0 - -#include <bufferpool/ClientManager.h> -#include <hidl/HidlTransportSupport.h> -#include <sys/types.h> -#include <time.h> -#include <unistd.h> -#include <utils/Log.h> -#include "BufferPoolClient.h" - -namespace android { -namespace hardware { -namespace media { -namespace bufferpool { -namespace V1_0 { -namespace implementation { - -static constexpr int64_t kRegisterTimeoutUs = 500000; // 0.5 sec -static constexpr int64_t kCleanUpDurationUs = 1000000; // TODO: 1 sec tune -static constexpr int64_t kClientTimeoutUs = 5000000; // TODO: 5 secs tune - -/** - * The holder of the cookie of remote IClientManager. - * The cookie is process locally unique for each IClientManager. - * (The cookie is used to notify death of clients to bufferpool process.) - */ -class ClientManagerCookieHolder { -public: - /** - * Creates a cookie holder for remote IClientManager(s). - */ - ClientManagerCookieHolder(); - - /** - * Gets a cookie for a remote IClientManager. - * - * @param manager the specified remote IClientManager. - * @param added true when the specified remote IClientManager is added - * newly, false otherwise. - * - * @return the process locally unique cookie for the specified IClientManager. - */ - uint64_t getCookie(const sp<IClientManager> &manager, bool *added); - -private: - uint64_t mSeqId; - std::mutex mLock; - std::list<std::pair<const wp<IClientManager>, uint64_t>> mManagers; -}; - -ClientManagerCookieHolder::ClientManagerCookieHolder() : mSeqId(0){} - -uint64_t ClientManagerCookieHolder::getCookie( - const sp<IClientManager> &manager, - bool *added) { - std::lock_guard<std::mutex> lock(mLock); - for (auto it = mManagers.begin(); it != mManagers.end();) { - const sp<IClientManager> key = it->first.promote(); - if (key) { - if (interfacesEqual(key, manager)) { - *added = false; - return it->second; - } - ++it; - } else { - it = mManagers.erase(it); - } - } - uint64_t id = mSeqId++; - *added = true; - mManagers.push_back(std::make_pair(manager, id)); - return id; -} - -class ClientManager::Impl { -public: - Impl(); - - // BnRegisterSender - ResultStatus registerSender(const sp<IAccessor> &accessor, - ConnectionId *pConnectionId); - - // BpRegisterSender - ResultStatus registerSender(const sp<IClientManager> &receiver, - ConnectionId senderId, - ConnectionId *receiverId); - - ResultStatus create(const std::shared_ptr<BufferPoolAllocator> &allocator, - ConnectionId *pConnectionId); - - ResultStatus close(ConnectionId connectionId); - - ResultStatus allocate(ConnectionId connectionId, - const std::vector<uint8_t> ¶ms, - native_handle_t **handle, - std::shared_ptr<BufferPoolData> *buffer); - - ResultStatus receive(ConnectionId connectionId, - TransactionId transactionId, - BufferId bufferId, - int64_t timestampUs, - native_handle_t **handle, - std::shared_ptr<BufferPoolData> *buffer); - - ResultStatus postSend(ConnectionId receiverId, - const std::shared_ptr<BufferPoolData> &buffer, - TransactionId *transactionId, - int64_t *timestampUs); - - ResultStatus getAccessor(ConnectionId connectionId, - sp<IAccessor> *accessor); - - void cleanUp(bool clearCache = false); - -private: - // In order to prevent deadlock between multiple locks, - // always lock ClientCache.lock before locking ActiveClients.lock. - struct ClientCache { - // This lock is held for brief duration. - // Blocking operation is not performed while holding the lock. - std::mutex mMutex; - std::list<std::pair<const wp<IAccessor>, const std::weak_ptr<BufferPoolClient>>> - mClients; - std::condition_variable mConnectCv; - bool mConnecting; - int64_t mLastCleanUpUs; - - ClientCache() : mConnecting(false), mLastCleanUpUs(getTimestampNow()) {} - } mCache; - - // Active clients which can be retrieved via ConnectionId - struct ActiveClients { - // This lock is held for brief duration. - // Blocking operation is not performed holding the lock. - std::mutex mMutex; - std::map<ConnectionId, const std::shared_ptr<BufferPoolClient>> - mClients; - } mActive; - - ClientManagerCookieHolder mRemoteClientCookies; -}; - -ClientManager::Impl::Impl() {} - -ResultStatus ClientManager::Impl::registerSender( - const sp<IAccessor> &accessor, ConnectionId *pConnectionId) { - cleanUp(); - int64_t timeoutUs = getTimestampNow() + kRegisterTimeoutUs; - do { - std::unique_lock<std::mutex> lock(mCache.mMutex); - for (auto it = mCache.mClients.begin(); it != mCache.mClients.end(); ++it) { - sp<IAccessor> sAccessor = it->first.promote(); - if (sAccessor && interfacesEqual(sAccessor, accessor)) { - const std::shared_ptr<BufferPoolClient> client = it->second.lock(); - if (client) { - std::lock_guard<std::mutex> lock(mActive.mMutex); - *pConnectionId = client->getConnectionId(); - if (mActive.mClients.find(*pConnectionId) != mActive.mClients.end()) { - ALOGV("register existing connection %lld", (long long)*pConnectionId); - return ResultStatus::ALREADY_EXISTS; - } - } - mCache.mClients.erase(it); - break; - } - } - if (!mCache.mConnecting) { - mCache.mConnecting = true; - lock.unlock(); - ResultStatus result = ResultStatus::OK; - const std::shared_ptr<BufferPoolClient> client = - std::make_shared<BufferPoolClient>(accessor); - lock.lock(); - if (!client) { - result = ResultStatus::NO_MEMORY; - } else if (!client->isValid()) { - result = ResultStatus::CRITICAL_ERROR; - } - if (result == ResultStatus::OK) { - // TODO: handle insert fail. (malloc fail) - const std::weak_ptr<BufferPoolClient> wclient = client; - mCache.mClients.push_back(std::make_pair(accessor, wclient)); - ConnectionId conId = client->getConnectionId(); - { - std::lock_guard<std::mutex> lock(mActive.mMutex); - mActive.mClients.insert(std::make_pair(conId, client)); - } - *pConnectionId = conId; - ALOGV("register new connection %lld", (long long)*pConnectionId); - } - mCache.mConnecting = false; - lock.unlock(); - mCache.mConnectCv.notify_all(); - return result; - } - mCache.mConnectCv.wait_for( - lock, std::chrono::microseconds(kRegisterTimeoutUs)); - } while (getTimestampNow() < timeoutUs); - // TODO: return timeout error - return ResultStatus::CRITICAL_ERROR; -} - -ResultStatus ClientManager::Impl::registerSender( - const sp<IClientManager> &receiver, - ConnectionId senderId, - ConnectionId *receiverId) { - sp<IAccessor> accessor; - bool local = false; - { - std::lock_guard<std::mutex> lock(mActive.mMutex); - auto it = mActive.mClients.find(senderId); - if (it == mActive.mClients.end()) { - return ResultStatus::NOT_FOUND; - } - it->second->getAccessor(&accessor); - local = it->second->isLocal(); - } - ResultStatus rs = ResultStatus::CRITICAL_ERROR; - if (accessor) { - Return<void> transResult = receiver->registerSender( - accessor, - [&rs, receiverId]( - ResultStatus status, - int64_t connectionId) { - rs = status; - *receiverId = connectionId; - }); - if (!transResult.isOk()) { - return ResultStatus::CRITICAL_ERROR; - } else if (local && rs == ResultStatus::OK) { - sp<ConnectionDeathRecipient> recipient = Accessor::getConnectionDeathRecipient(); - if (recipient) { - ALOGV("client death recipient registered %lld", (long long)*receiverId); - bool added; - uint64_t cookie = mRemoteClientCookies.getCookie(receiver, &added); - recipient->addCookieToConnection(cookie, *receiverId); - if (added) { - Return<bool> transResult = receiver->linkToDeath(recipient, cookie); - } - } - } - } - return rs; -} - -ResultStatus ClientManager::Impl::create( - const std::shared_ptr<BufferPoolAllocator> &allocator, - ConnectionId *pConnectionId) { - const sp<Accessor> accessor = new Accessor(allocator); - if (!accessor || !accessor->isValid()) { - return ResultStatus::CRITICAL_ERROR; - } - std::shared_ptr<BufferPoolClient> client = - std::make_shared<BufferPoolClient>(accessor); - if (!client || !client->isValid()) { - return ResultStatus::CRITICAL_ERROR; - } - // Since a new bufferpool is created, evict memories which are used by - // existing bufferpools and clients. - cleanUp(true); - { - // TODO: handle insert fail. (malloc fail) - std::lock_guard<std::mutex> lock(mCache.mMutex); - const std::weak_ptr<BufferPoolClient> wclient = client; - mCache.mClients.push_back(std::make_pair(accessor, wclient)); - ConnectionId conId = client->getConnectionId(); - { - std::lock_guard<std::mutex> lock(mActive.mMutex); - mActive.mClients.insert(std::make_pair(conId, client)); - } - *pConnectionId = conId; - ALOGV("create new connection %lld", (long long)*pConnectionId); - } - return ResultStatus::OK; -} - -ResultStatus ClientManager::Impl::close(ConnectionId connectionId) { - std::lock_guard<std::mutex> lock1(mCache.mMutex); - std::lock_guard<std::mutex> lock2(mActive.mMutex); - auto it = mActive.mClients.find(connectionId); - if (it != mActive.mClients.end()) { - sp<IAccessor> accessor; - it->second->getAccessor(&accessor); - mActive.mClients.erase(connectionId); - for (auto cit = mCache.mClients.begin(); cit != mCache.mClients.end();) { - // clean up dead client caches - sp<IAccessor> cAccessor = cit->first.promote(); - if (!cAccessor || (accessor && interfacesEqual(cAccessor, accessor))) { - cit = mCache.mClients.erase(cit); - } else { - cit++; - } - } - return ResultStatus::OK; - } - return ResultStatus::NOT_FOUND; -} - -ResultStatus ClientManager::Impl::allocate( - ConnectionId connectionId, const std::vector<uint8_t> ¶ms, - native_handle_t **handle, std::shared_ptr<BufferPoolData> *buffer) { - std::shared_ptr<BufferPoolClient> client; - { - std::lock_guard<std::mutex> lock(mActive.mMutex); - auto it = mActive.mClients.find(connectionId); - if (it == mActive.mClients.end()) { - return ResultStatus::NOT_FOUND; - } - client = it->second; - } - return client->allocate(params, handle, buffer); -} - -ResultStatus ClientManager::Impl::receive( - ConnectionId connectionId, TransactionId transactionId, - BufferId bufferId, int64_t timestampUs, - native_handle_t **handle, std::shared_ptr<BufferPoolData> *buffer) { - std::shared_ptr<BufferPoolClient> client; - { - std::lock_guard<std::mutex> lock(mActive.mMutex); - auto it = mActive.mClients.find(connectionId); - if (it == mActive.mClients.end()) { - return ResultStatus::NOT_FOUND; - } - client = it->second; - } - return client->receive(transactionId, bufferId, timestampUs, handle, buffer); -} - -ResultStatus ClientManager::Impl::postSend( - ConnectionId receiverId, const std::shared_ptr<BufferPoolData> &buffer, - TransactionId *transactionId, int64_t *timestampUs) { - ConnectionId connectionId = buffer->mConnectionId; - std::shared_ptr<BufferPoolClient> client; - { - std::lock_guard<std::mutex> lock(mActive.mMutex); - auto it = mActive.mClients.find(connectionId); - if (it == mActive.mClients.end()) { - return ResultStatus::NOT_FOUND; - } - client = it->second; - } - return client->postSend(receiverId, buffer, transactionId, timestampUs); -} - -ResultStatus ClientManager::Impl::getAccessor( - ConnectionId connectionId, sp<IAccessor> *accessor) { - std::shared_ptr<BufferPoolClient> client; - { - std::lock_guard<std::mutex> lock(mActive.mMutex); - auto it = mActive.mClients.find(connectionId); - if (it == mActive.mClients.end()) { - return ResultStatus::NOT_FOUND; - } - client = it->second; - } - return client->getAccessor(accessor); -} - -void ClientManager::Impl::cleanUp(bool clearCache) { - int64_t now = getTimestampNow(); - int64_t lastTransactionUs; - std::lock_guard<std::mutex> lock1(mCache.mMutex); - if (clearCache || mCache.mLastCleanUpUs + kCleanUpDurationUs < now) { - std::lock_guard<std::mutex> lock2(mActive.mMutex); - int cleaned = 0; - for (auto it = mActive.mClients.begin(); it != mActive.mClients.end();) { - if (!it->second->isActive(&lastTransactionUs, clearCache)) { - if (lastTransactionUs + kClientTimeoutUs < now) { - sp<IAccessor> accessor; - it->second->getAccessor(&accessor); - it = mActive.mClients.erase(it); - ++cleaned; - continue; - } - } - ++it; - } - for (auto cit = mCache.mClients.begin(); cit != mCache.mClients.end();) { - // clean up dead client caches - sp<IAccessor> cAccessor = cit->first.promote(); - if (!cAccessor) { - cit = mCache.mClients.erase(cit); - } else { - ++cit; - } - } - ALOGV("# of cleaned connections: %d", cleaned); - mCache.mLastCleanUpUs = now; - } -} - -// Methods from ::android::hardware::media::bufferpool::V1_0::IClientManager follow. -Return<void> ClientManager::registerSender(const sp<::android::hardware::media::bufferpool::V1_0::IAccessor>& bufferPool, registerSender_cb _hidl_cb) { - if (mImpl) { - ConnectionId connectionId = -1; - ResultStatus status = mImpl->registerSender(bufferPool, &connectionId); - _hidl_cb(status, connectionId); - } else { - _hidl_cb(ResultStatus::CRITICAL_ERROR, -1); - } - return Void(); -} - -// Methods for local use. -sp<ClientManager> ClientManager::sInstance; -std::mutex ClientManager::sInstanceLock; - -sp<ClientManager> ClientManager::getInstance() { - std::lock_guard<std::mutex> lock(sInstanceLock); - if (!sInstance) { - sInstance = new ClientManager(); - } - return sInstance; -} - -ClientManager::ClientManager() : mImpl(new Impl()) {} - -ClientManager::~ClientManager() { -} - -ResultStatus ClientManager::create( - const std::shared_ptr<BufferPoolAllocator> &allocator, - ConnectionId *pConnectionId) { - if (mImpl) { - return mImpl->create(allocator, pConnectionId); - } - return ResultStatus::CRITICAL_ERROR; -} - -ResultStatus ClientManager::registerSender( - const sp<IClientManager> &receiver, - ConnectionId senderId, - ConnectionId *receiverId) { - if (mImpl) { - return mImpl->registerSender(receiver, senderId, receiverId); - } - return ResultStatus::CRITICAL_ERROR; -} - -ResultStatus ClientManager::close(ConnectionId connectionId) { - if (mImpl) { - return mImpl->close(connectionId); - } - return ResultStatus::CRITICAL_ERROR; -} - -ResultStatus ClientManager::allocate( - ConnectionId connectionId, const std::vector<uint8_t> ¶ms, - native_handle_t **handle, std::shared_ptr<BufferPoolData> *buffer) { - if (mImpl) { - return mImpl->allocate(connectionId, params, handle, buffer); - } - return ResultStatus::CRITICAL_ERROR; -} - -ResultStatus ClientManager::receive( - ConnectionId connectionId, TransactionId transactionId, - BufferId bufferId, int64_t timestampUs, - native_handle_t **handle, std::shared_ptr<BufferPoolData> *buffer) { - if (mImpl) { - return mImpl->receive(connectionId, transactionId, bufferId, - timestampUs, handle, buffer); - } - return ResultStatus::CRITICAL_ERROR; -} - -ResultStatus ClientManager::postSend( - ConnectionId receiverId, const std::shared_ptr<BufferPoolData> &buffer, - TransactionId *transactionId, int64_t* timestampUs) { - if (mImpl && buffer) { - return mImpl->postSend(receiverId, buffer, transactionId, timestampUs); - } - return ResultStatus::CRITICAL_ERROR; -} - -void ClientManager::cleanUp() { - if (mImpl) { - mImpl->cleanUp(true); - } -} - -} // namespace implementation -} // namespace V1_0 -} // namespace bufferpool -} // namespace media -} // namespace hardware -} // namespace android diff --git a/codec2/vndk/bufferpool/Connection.cpp b/codec2/vndk/bufferpool/Connection.cpp deleted file mode 100644 index 9e741e7..0000000 --- a/codec2/vndk/bufferpool/Connection.cpp +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "Connection.h" - -namespace android { -namespace hardware { -namespace media { -namespace bufferpool { -namespace V1_0 { -namespace implementation { - -// Methods from ::android::hardware::media::bufferpool::V1_0::IConnection follow. -Return<void> Connection::fetch(uint64_t transactionId, uint32_t bufferId, fetch_cb _hidl_cb) { - ResultStatus status = ResultStatus::CRITICAL_ERROR; - if (mInitialized && mAccessor) { - if (bufferId != SYNC_BUFFERID) { - const native_handle_t *handle = NULL; - status = mAccessor->fetch( - mConnectionId, transactionId, bufferId, &handle); - if (status == ResultStatus::OK) { - _hidl_cb(status, Buffer{bufferId, handle}); - return Void(); - } - } else { - mAccessor->cleanUp(false); - } - } - _hidl_cb(status, Buffer{0, nullptr}); - return Void(); -} - -Connection::Connection() : mInitialized(false), mConnectionId(-1LL) {} - -Connection::~Connection() { - if (mInitialized && mAccessor) { - mAccessor->close(mConnectionId); - } -} - -void Connection::initialize( - const sp<Accessor>& accessor, ConnectionId connectionId) { - if (!mInitialized) { - mAccessor = accessor; - mConnectionId = connectionId; - mInitialized = true; - } -} - -ResultStatus Connection::allocate( - const std::vector<uint8_t> ¶ms, BufferId *bufferId, - const native_handle_t **handle) { - if (mInitialized && mAccessor) { - return mAccessor->allocate(mConnectionId, params, bufferId, handle); - } - return ResultStatus::CRITICAL_ERROR; -} - -void Connection::cleanUp(bool clearCache) { - if (mInitialized && mAccessor) { - mAccessor->cleanUp(clearCache); - } -} - -// Methods from ::android::hidl::base::V1_0::IBase follow. - -//IConnection* HIDL_FETCH_IConnection(const char* /* name */) { -// return new Connection(); -//} - -} // namespace implementation -} // namespace V1_0 -} // namespace bufferpool -} // namespace media -} // namespace hardware -} // namespace android diff --git a/codec2/vndk/bufferpool/Connection.h b/codec2/vndk/bufferpool/Connection.h deleted file mode 100644 index e19cb67..0000000 --- a/codec2/vndk/bufferpool/Connection.h +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ANDROID_HARDWARE_MEDIA_BUFFERPOOL_V1_0_CONNECTION_H -#define ANDROID_HARDWARE_MEDIA_BUFFERPOOL_V1_0_CONNECTION_H - -#include <android/hardware/media/bufferpool/1.0/IConnection.h> -#include <bufferpool/BufferPoolTypes.h> -#include <hidl/MQDescriptor.h> -#include <hidl/Status.h> -#include "Accessor.h" - -namespace android { -namespace hardware { -namespace media { -namespace bufferpool { -namespace V1_0 { -namespace implementation { - -using ::android::hardware::hidl_array; -using ::android::hardware::hidl_memory; -using ::android::hardware::hidl_string; -using ::android::hardware::hidl_vec; -using ::android::hardware::media::bufferpool::V1_0::implementation::Accessor; -using ::android::hardware::Return; -using ::android::hardware::Void; -using ::android::sp; - -struct Connection : public IConnection { - // Methods from ::android::hardware::media::bufferpool::V1_0::IConnection follow. - Return<void> fetch(uint64_t transactionId, uint32_t bufferId, fetch_cb _hidl_cb) override; - - /** - * Allocates a buffer using the specified parameters. Recycles a buffer if - * it is possible. The returned buffer can be transferred to other remote - * clients(Connection). - * - * @param params allocation parameters. - * @param bufferId Id of the allocated buffer. - * @param handle native handle of the allocated buffer. - * - * @return OK if a buffer is successfully allocated. - * NO_MEMORY when there is no memory. - * CRITICAL_ERROR otherwise. - */ - ResultStatus allocate(const std::vector<uint8_t> ¶ms, - BufferId *bufferId, const native_handle_t **handle); - - /** - * Processes pending buffer status messages and performs periodic cache cleaning - * from bufferpool. - * - * @param clearCache if clearCache is true, bufferpool frees all buffers - * waiting to be recycled. - */ - void cleanUp(bool clearCache); - - /** Destructs a connection. */ - ~Connection(); - - /** Creates a connection. */ - Connection(); - - /** - * Initializes with the specified buffer pool and the connection id. - * The connection id should be unique in the whole system. - * - * @param accessor the specified buffer pool. - * @param connectionId Id. - */ - void initialize(const sp<Accessor> &accessor, ConnectionId connectionId); - - enum : uint32_t { - SYNC_BUFFERID = UINT32_MAX, - }; - -private: - bool mInitialized; - sp<Accessor> mAccessor; - ConnectionId mConnectionId; -}; - -} // namespace implementation -} // namespace V1_0 -} // namespace bufferpool -} // namespace media -} // namespace hardware -} // namespace android - -#endif // ANDROID_HARDWARE_MEDIA_BUFFERPOOL_V1_0_CONNECTION_H diff --git a/codec2/vndk/bufferpool/include/bufferpool/BufferPoolTypes.h b/codec2/vndk/bufferpool/include/bufferpool/BufferPoolTypes.h deleted file mode 100644 index 710f015..0000000 --- a/codec2/vndk/bufferpool/include/bufferpool/BufferPoolTypes.h +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ANDROID_HARDWARE_MEDIA_BUFFERPOOL_V1_0_BUFFERPOOLTYPES_H -#define ANDROID_HARDWARE_MEDIA_BUFFERPOOL_V1_0_BUFFERPOOLTYPES_H - -#include <android/hardware/media/bufferpool/1.0/types.h> -#include <cutils/native_handle.h> -#include <fmq/MessageQueue.h> -#include <hidl/MQDescriptor.h> -#include <hidl/Status.h> - -namespace android { -namespace hardware { -namespace media { -namespace bufferpool { - -struct BufferPoolData { - // For local use, to specify a bufferpool (client connection) for buffers. - // Return value from connect#IAccessor(android.hardware.media.bufferpool@1.0). - int64_t mConnectionId; - // BufferId - uint32_t mId; - - BufferPoolData() : mConnectionId(0), mId(0) {} - - BufferPoolData( - int64_t connectionId, uint32_t id) - : mConnectionId(connectionId), mId(id) {} - - ~BufferPoolData() {} -}; - -namespace V1_0 { -namespace implementation { - -using ::android::hardware::kSynchronizedReadWrite; - -typedef uint32_t BufferId; -typedef uint64_t TransactionId; -typedef int64_t ConnectionId; - -enum : ConnectionId { - INVALID_CONNECTIONID = 0, -}; - -typedef android::hardware::MessageQueue<BufferStatusMessage, kSynchronizedReadWrite> BufferStatusQueue; -typedef BufferStatusQueue::Descriptor QueueDescriptor; - -/** - * Allocation wrapper class for buffer pool. - */ -struct BufferPoolAllocation { - const native_handle_t *mHandle; - - const native_handle_t *handle() { - return mHandle; - } - - BufferPoolAllocation(const native_handle_t *handle) : mHandle(handle) {} - - ~BufferPoolAllocation() {}; -}; - -/** - * Allocator wrapper class for buffer pool. - */ -class BufferPoolAllocator { -public: - - /** - * Allocate an allocation(buffer) for buffer pool. - * - * @param params allocation parameters - * @param alloc created allocation - * @param allocSize size of created allocation - * - * @return OK when an allocation is created successfully. - */ - virtual ResultStatus allocate( - const std::vector<uint8_t> ¶ms, - std::shared_ptr<BufferPoolAllocation> *alloc, - size_t *allocSize) = 0; - - /** - * Returns whether allocation parameters of an old allocation are - * compatible with new allocation parameters. - */ - virtual bool compatible(const std::vector<uint8_t> &newParams, - const std::vector<uint8_t> &oldParams) = 0; - -protected: - BufferPoolAllocator() = default; - - virtual ~BufferPoolAllocator() = default; -}; - -} // namespace implementation -} // namespace V1_0 -} // namespace bufferpool -} // namespace media -} // namespace hardware -} // namespace android - -#endif // ANDROID_HARDWARE_MEDIA_BUFFERPOOL_V1_0_BUFFERPOOLTYPES_H diff --git a/codec2/vndk/bufferpool/include/bufferpool/ClientManager.h b/codec2/vndk/bufferpool/include/bufferpool/ClientManager.h deleted file mode 100644 index be5779f..0000000 --- a/codec2/vndk/bufferpool/include/bufferpool/ClientManager.h +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ANDROID_HARDWARE_MEDIA_BUFFERPOOL_V1_0_CLIENTMANAGER_H -#define ANDROID_HARDWARE_MEDIA_BUFFERPOOL_V1_0_CLIENTMANAGER_H - -#include <android/hardware/media/bufferpool/1.0/IClientManager.h> -#include <hidl/MQDescriptor.h> -#include <hidl/Status.h> -#include <memory> -#include "BufferPoolTypes.h" - -namespace android { -namespace hardware { -namespace media { -namespace bufferpool { -namespace V1_0 { -namespace implementation { - -using ::android::hardware::hidl_array; -using ::android::hardware::hidl_memory; -using ::android::hardware::hidl_string; -using ::android::hardware::hidl_vec; -using ::android::hardware::media::bufferpool::V1_0::IAccessor; -using ::android::hardware::media::bufferpool::V1_0::ResultStatus; -using ::android::hardware::Return; -using ::android::hardware::Void; -using ::android::sp; - -struct ClientManager : public IClientManager { - // Methods from ::android::hardware::media::bufferpool::V1_0::IClientManager follow. - Return<void> registerSender(const sp<::android::hardware::media::bufferpool::V1_0::IAccessor>& bufferPool, registerSender_cb _hidl_cb) override; - - /** Gets an instance. */ - static sp<ClientManager> getInstance(); - - /** - * Creates a local connection with a newly created buffer pool. - * - * @param allocator for new buffer allocation. - * @param pConnectionId Id of the created connection. This is - * system-wide unique. - * - * @return OK when a buffer pool and a local connection is successfully - * created. - * NO_MEMORY when there is no memory. - * CRITICAL_ERROR otherwise. - */ - ResultStatus create(const std::shared_ptr<BufferPoolAllocator> &allocator, - ConnectionId *pConnectionId); - - /** - * Register a created connection as sender for remote process. - * - * @param receiver The remote receiving process. - * @param senderId A local connection which will send buffers to. - * @param receiverId Id of the created receiving connection on the receiver - * process. - * - * @return OK when the receiving connection is successfully created on the - * receiver process. - * NOT_FOUND when the sender connection was not found. - * ALREADY_EXISTS the receiving connection is already made. - * CRITICAL_ERROR otherwise. - */ - ResultStatus registerSender(const sp<IClientManager> &receiver, - ConnectionId senderId, - ConnectionId *receiverId); - - /** - * Closes the specified connection. - * - * @param connectionId The id of the connection. - * - * @return OK when the connection is closed. - * NOT_FOUND when the specified connection was not found. - * CRITICAL_ERROR otherwise. - */ - ResultStatus close(ConnectionId connectionId); - - /** - * Allocates a buffer from the specified connection. - * - * @param connectionId The id of the connection. - * @param params The allocation parameters. - * @param handle The native handle to the allocated buffer. handle - * should be cloned before use. - * @param buffer The allocated buffer. - * - * @return OK when a buffer was allocated successfully. - * NOT_FOUND when the specified connection was not found. - * NO_MEMORY when there is no memory. - * CRITICAL_ERROR otherwise. - */ - ResultStatus allocate(ConnectionId connectionId, - const std::vector<uint8_t> ¶ms, - native_handle_t **handle, - std::shared_ptr<BufferPoolData> *buffer); - - /** - * Receives a buffer for the transaction. - * - * @param connectionId The id of the receiving connection. - * @param transactionId The id for the transaction. - * @param bufferId The id for the buffer. - * @param timestampUs The timestamp of the buffer is being sent. - * @param handle The native handle to the allocated buffer. handle - * should be cloned before use. - * @param buffer The received buffer. - * - * @return OK when a buffer was received successfully. - * NOT_FOUND when the specified connection was not found. - * NO_MEMORY when there is no memory. - * CRITICAL_ERROR otherwise. - */ - ResultStatus receive(ConnectionId connectionId, - TransactionId transactionId, - BufferId bufferId, - int64_t timestampUs, - native_handle_t **handle, - std::shared_ptr<BufferPoolData> *buffer); - - /** - * Posts a buffer transfer transaction to the buffer pool. Sends a buffer - * to other remote clients(connection) after this call has been succeeded. - * - * @param receiverId The id of the receiving connection. - * @param buffer to transfer - * @param transactionId Id of the transfer transaction. - * @param timestampUs The timestamp of the buffer transaction is being - * posted. - * - * @return OK when a buffer transaction was posted successfully. - * NOT_FOUND when the sending connection was not found. - * CRITICAL_ERROR otherwise. - */ - ResultStatus postSend(ConnectionId receiverId, - const std::shared_ptr<BufferPoolData> &buffer, - TransactionId *transactionId, - int64_t *timestampUs); - - /** - * Time out inactive lingering connections and close. - */ - void cleanUp(); - - /** Destructs the manager of buffer pool clients. */ - ~ClientManager(); -private: - static sp<ClientManager> sInstance; - static std::mutex sInstanceLock; - - class Impl; - const std::unique_ptr<Impl> mImpl; - - ClientManager(); -}; - -} // namespace implementation -} // namespace V1_0 -} // namespace bufferpool -} // namespace media -} // namespace hardware -} // namespace android - -#endif // ANDROID_HARDWARE_MEDIA_BUFFERPOOL_V1_0_CLIENTMANAGER_H diff --git a/codec2/vndk/bufferpool/vts/Android.bp b/codec2/vndk/bufferpool/vts/Android.bp deleted file mode 100644 index 62286f3..0000000 --- a/codec2/vndk/bufferpool/vts/Android.bp +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -cc_test { - name: "VtsVndkHidlBufferpoolV1_0TargetSingleTest", - defaults: ["VtsHalTargetTestDefaults"], - srcs: [ - "allocator.cpp", - "single.cpp", - ], - static_libs: [ - "android.hardware.media.bufferpool@1.0", - "libion", - "libstagefright_bufferpool@1.0", - ], - shared_libs: [ - "libfmq", - "libstagefright_codec2", - "libstagefright_codec2_vndk", - ], - compile_multilib: "both", -} - -cc_test { - name: "VtsVndkHidlBufferpoolV1_0TargetMultiTest", - defaults: ["VtsHalTargetTestDefaults"], - srcs: [ - "allocator.cpp", - "multi.cpp", - ], - static_libs: [ - "android.hardware.media.bufferpool@1.0", - "libion", - "libstagefright_bufferpool@1.0", - ], - shared_libs: [ - "libfmq", - "libstagefright_codec2", - "libstagefright_codec2_vndk", - ], - compile_multilib: "both", -} diff --git a/codec2/vndk/bufferpool/vts/OWNERS b/codec2/vndk/bufferpool/vts/OWNERS deleted file mode 100644 index 4c3f7a1..0000000 --- a/codec2/vndk/bufferpool/vts/OWNERS +++ /dev/null @@ -1,7 +0,0 @@ -# Media team -taklee@google.com -lajos@google.com - -# VTS team -yim@google.com -zhuoyao@google.com diff --git a/codec2/vndk/bufferpool/vts/allocator.cpp b/codec2/vndk/bufferpool/vts/allocator.cpp deleted file mode 100644 index 04e3b4e..0000000 --- a/codec2/vndk/bufferpool/vts/allocator.cpp +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <C2Buffer.h> -#include "allocator.h" - -union Params { - struct { - uint32_t capacity; - C2MemoryUsage usage; - } data; - uint8_t array[0]; - Params() : data{0, {0, 0}} {} - Params(uint32_t size) - : data{size, {C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE}} {} -}; - -struct AllocationDtor { - AllocationDtor(const std::shared_ptr<C2LinearAllocation> &alloc) - : mAlloc(alloc) {} - - void operator()(BufferPoolAllocation *poolAlloc) { delete poolAlloc; } - - const std::shared_ptr<C2LinearAllocation> mAlloc; -}; - -ResultStatus VtsBufferPoolAllocator::allocate( - const std::vector<uint8_t> ¶ms, - std::shared_ptr<BufferPoolAllocation> *alloc, - size_t *allocSize) { - Params ionParams; - memcpy(&ionParams, params.data(), std::min(sizeof(Params), params.size())); - - std::shared_ptr<C2LinearAllocation> linearAlloc; - c2_status_t status = mAllocator->newLinearAllocation( - ionParams.data.capacity, ionParams.data.usage, &linearAlloc); - if (status == C2_OK && linearAlloc) { - BufferPoolAllocation *ptr = new BufferPoolAllocation(linearAlloc->handle()); - if (ptr) { - *alloc = std::shared_ptr<BufferPoolAllocation>( - ptr, AllocationDtor(linearAlloc)); - if (*alloc) { - *allocSize = ionParams.data.capacity; - return ResultStatus::OK; - } - delete ptr; - return ResultStatus::NO_MEMORY; - } - } - return ResultStatus::CRITICAL_ERROR; -} - -bool VtsBufferPoolAllocator::compatible(const std::vector<uint8_t> &newParams, - const std::vector<uint8_t> &oldParams) { - size_t newSize = newParams.size(); - size_t oldSize = oldParams.size(); - if (newSize == oldSize) { - for (size_t i = 0; i < newSize; ++i) { - if (newParams[i] != oldParams[i]) { - return false; - } - } - return true; - } - return false; -} - -void getVtsAllocatorParams(std::vector<uint8_t> *params) { - constexpr static int kAllocationSize = 1024 * 10; - Params ionParams(kAllocationSize); - - params->assign(ionParams.array, ionParams.array + sizeof(ionParams)); -} diff --git a/codec2/vndk/bufferpool/vts/allocator.h b/codec2/vndk/bufferpool/vts/allocator.h deleted file mode 100644 index b88d074..0000000 --- a/codec2/vndk/bufferpool/vts/allocator.h +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef VTS_VNDK_HIDL_BUFFERPOOL_V1_0_ALLOCATOR_H -#define VTS_VNDK_HIDL_BUFFERPOOL_V1_0_ALLOCATOR_H - -#include <bufferpool/BufferPoolTypes.h> - -using android::hardware::media::bufferpool::V1_0::ResultStatus; -using android::hardware::media::bufferpool::V1_0::implementation:: - BufferPoolAllocation; -using android::hardware::media::bufferpool::V1_0::implementation:: - BufferPoolAllocator; - -// buffer allocator for the tests -class VtsBufferPoolAllocator : public BufferPoolAllocator { - public: - VtsBufferPoolAllocator(const std::shared_ptr<C2Allocator> &allocator) - : mAllocator(allocator) {} - - ~VtsBufferPoolAllocator() override {} - - ResultStatus allocate(const std::vector<uint8_t> ¶ms, - std::shared_ptr<BufferPoolAllocation> *alloc, - size_t *allocSize) override; - - bool compatible(const std::vector<uint8_t> &newParams, - const std::vector<uint8_t> &oldParams) override; - - private: - const std::shared_ptr<C2Allocator> mAllocator; -}; - -// retrieve buffer allocator paramters -void getVtsAllocatorParams(std::vector<uint8_t> *params); - -#endif // VTS_VNDK_HIDL_BUFFERPOOL_V1_0_ALLOCATOR_H diff --git a/codec2/vndk/bufferpool/vts/multi.cpp b/codec2/vndk/bufferpool/vts/multi.cpp deleted file mode 100644 index 71ca6f1..0000000 --- a/codec2/vndk/bufferpool/vts/multi.cpp +++ /dev/null @@ -1,228 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#define LOG_TAG "buffferpool_unit_test" - -#include <gtest/gtest.h> - -#include <C2AllocatorIon.h> -#include <C2Buffer.h> -#include <C2PlatformSupport.h> -#include <android-base/logging.h> -#include <binder/ProcessState.h> -#include <bufferpool/ClientManager.h> -#include <hidl/HidlSupport.h> -#include <hidl/HidlTransportSupport.h> -#include <hidl/LegacySupport.h> -#include <hidl/Status.h> -#include <signal.h> -#include <sys/types.h> -#include <sys/wait.h> -#include <unistd.h> -#include <iostream> -#include <memory> -#include <vector> -#include "allocator.h" - -using android::C2AllocatorIon; -using android::C2PlatformAllocatorStore; -using android::hardware::configureRpcThreadpool; -using android::hardware::hidl_handle; -using android::hardware::media::bufferpool::V1_0::IClientManager; -using android::hardware::media::bufferpool::V1_0::ResultStatus; -using android::hardware::media::bufferpool::V1_0::implementation::BufferId; -using android::hardware::media::bufferpool::V1_0::implementation::ClientManager; -using android::hardware::media::bufferpool::V1_0::implementation::ConnectionId; -using android::hardware::media::bufferpool::V1_0::implementation::TransactionId; -using android::hardware::media::bufferpool::BufferPoolData; - -namespace { - -// communication message types between processes. -enum PipeCommand : int32_t { - INIT_OK = 0, - INIT_ERROR, - SEND, - RECEIVE_OK, - RECEIVE_ERROR, -}; - -// communication message between processes. -union PipeMessage { - struct { - int32_t command; - BufferId bufferId; - ConnectionId connectionId; - TransactionId transactionId; - int64_t timestampUs; - } data; - char array[0]; -}; - -// media.bufferpool test setup -class BufferpoolMultiTest : public ::testing::Test { - public: - virtual void SetUp() override { - ResultStatus status; - mReceiverPid = -1; - mConnectionValid = false; - - ASSERT_TRUE(pipe(mCommandPipeFds) == 0); - ASSERT_TRUE(pipe(mResultPipeFds) == 0); - - mReceiverPid = fork(); - ASSERT_TRUE(mReceiverPid >= 0); - - if (mReceiverPid == 0) { - doReceiver(); - // In order to ignore gtest behaviour, wait for being killed from - // tearDown - pause(); - } - - mManager = ClientManager::getInstance(); - ASSERT_NE(mManager, nullptr); - - std::shared_ptr<C2Allocator> allocator = - std::make_shared<C2AllocatorIon>(C2PlatformAllocatorStore::ION); - ASSERT_TRUE((bool)allocator); - - mAllocator = std::make_shared<VtsBufferPoolAllocator>(allocator); - ASSERT_TRUE((bool)mAllocator); - - status = mManager->create(mAllocator, &mConnectionId); - ASSERT_TRUE(status == ResultStatus::OK); - mConnectionValid = true; - } - - virtual void TearDown() override { - if (mReceiverPid > 0) { - kill(mReceiverPid, SIGKILL); - int wstatus; - wait(&wstatus); - } - - if (mConnectionValid) { - mManager->close(mConnectionId); - } - } - - protected: - static void description(const std::string& description) { - RecordProperty("description", description); - } - - android::sp<ClientManager> mManager; - std::shared_ptr<BufferPoolAllocator> mAllocator; - bool mConnectionValid; - ConnectionId mConnectionId; - pid_t mReceiverPid; - int mCommandPipeFds[2]; - int mResultPipeFds[2]; - - bool sendMessage(int *pipes, const PipeMessage &message) { - int ret = write(pipes[1], message.array, sizeof(PipeMessage)); - return ret == sizeof(PipeMessage); - } - - bool receiveMessage(int *pipes, PipeMessage *message) { - int ret = read(pipes[0], message->array, sizeof(PipeMessage)); - return ret == sizeof(PipeMessage); - } - - void doReceiver() { - configureRpcThreadpool(1, false); - PipeMessage message; - mManager = ClientManager::getInstance(); - if (!mManager) { - message.data.command = PipeCommand::INIT_ERROR; - sendMessage(mResultPipeFds, message); - return; - } - android::status_t status = mManager->registerAsService(); - if (status != android::OK) { - message.data.command = PipeCommand::INIT_ERROR; - sendMessage(mResultPipeFds, message); - return; - } - message.data.command = PipeCommand::INIT_OK; - sendMessage(mResultPipeFds, message); - - receiveMessage(mCommandPipeFds, &message); - { - native_handle_t *rhandle = nullptr; - std::shared_ptr<BufferPoolData> rbuffer; - ResultStatus status = mManager->receive( - message.data.connectionId, message.data.transactionId, - message.data.bufferId, message.data.timestampUs, &rhandle, &rbuffer); - mManager->close(message.data.connectionId); - if (status != ResultStatus::OK) { - message.data.command = PipeCommand::RECEIVE_ERROR; - sendMessage(mResultPipeFds, message); - return; - } - } - message.data.command = PipeCommand::RECEIVE_OK; - sendMessage(mResultPipeFds, message); - } -}; - -// Buffer transfer test between processes. -TEST_F(BufferpoolMultiTest, TransferBuffer) { - ResultStatus status; - PipeMessage message; - - ASSERT_TRUE(receiveMessage(mResultPipeFds, &message)); - - android::sp<IClientManager> receiver = IClientManager::getService(); - ConnectionId receiverId; - ASSERT_TRUE((bool)receiver); - - status = mManager->registerSender(receiver, mConnectionId, &receiverId); - ASSERT_TRUE(status == ResultStatus::OK); - { - native_handle_t *shandle = nullptr; - std::shared_ptr<BufferPoolData> sbuffer; - TransactionId transactionId; - int64_t postUs; - std::vector<uint8_t> vecParams; - - getVtsAllocatorParams(&vecParams); - status = mManager->allocate(mConnectionId, vecParams, &shandle, &sbuffer); - ASSERT_TRUE(status == ResultStatus::OK); - - status = mManager->postSend(receiverId, sbuffer, &transactionId, &postUs); - ASSERT_TRUE(status == ResultStatus::OK); - - message.data.command = PipeCommand::SEND; - message.data.bufferId = sbuffer->mId; - message.data.connectionId = receiverId; - message.data.transactionId = transactionId; - message.data.timestampUs = postUs; - sendMessage(mCommandPipeFds, message); - } - EXPECT_TRUE(receiveMessage(mResultPipeFds, &message)); -} - -} // anonymous namespace - -int main(int argc, char** argv) { - setenv("TREBLE_TESTING_OVERRIDE", "true", true); - ::testing::InitGoogleTest(&argc, argv); - int status = RUN_ALL_TESTS(); - LOG(INFO) << "Test result = " << status; - return status; -} diff --git a/codec2/vndk/bufferpool/vts/single.cpp b/codec2/vndk/bufferpool/vts/single.cpp deleted file mode 100644 index 474f6b6..0000000 --- a/codec2/vndk/bufferpool/vts/single.cpp +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#define LOG_TAG "buffferpool_unit_test" - -#include <gtest/gtest.h> - -#include <C2AllocatorIon.h> -#include <C2Buffer.h> -#include <C2PlatformSupport.h> -#include <android-base/logging.h> -#include <binder/ProcessState.h> -#include <bufferpool/ClientManager.h> -#include <hidl/HidlSupport.h> -#include <hidl/HidlTransportSupport.h> -#include <hidl/LegacySupport.h> -#include <hidl/Status.h> -#include <unistd.h> -#include <iostream> -#include <memory> -#include <vector> -#include "allocator.h" - -using android::C2AllocatorIon; -using android::C2PlatformAllocatorStore; -using android::hardware::hidl_handle; -using android::hardware::media::bufferpool::V1_0::ResultStatus; -using android::hardware::media::bufferpool::V1_0::implementation::BufferId; -using android::hardware::media::bufferpool::V1_0::implementation::ClientManager; -using android::hardware::media::bufferpool::V1_0::implementation::ConnectionId; -using android::hardware::media::bufferpool::V1_0::implementation::TransactionId; -using android::hardware::media::bufferpool::BufferPoolData; - -namespace { - -// Number of iteration for buffer allocation test. -constexpr static int kNumAllocationTest = 3; - -// Number of iteration for buffer recycling test. -constexpr static int kNumRecycleTest = 3; - -// media.bufferpool test setup -class BufferpoolSingleTest : public ::testing::Test { - public: - virtual void SetUp() override { - ResultStatus status; - mConnectionValid = false; - - mManager = ClientManager::getInstance(); - ASSERT_NE(mManager, nullptr); - - std::shared_ptr<C2Allocator> allocator = - std::make_shared<C2AllocatorIon>(C2PlatformAllocatorStore::ION); - ASSERT_TRUE((bool)allocator); - - mAllocator = std::make_shared<VtsBufferPoolAllocator>(allocator); - ASSERT_TRUE((bool)mAllocator); - - status = mManager->create(mAllocator, &mConnectionId); - ASSERT_TRUE(status == ResultStatus::OK); - - mConnectionValid = true; - - status = mManager->registerSender(mManager, mConnectionId, &mReceiverId); - ASSERT_TRUE(status == ResultStatus::ALREADY_EXISTS && - mReceiverId == mConnectionId); - } - - virtual void TearDown() override { - if (mConnectionValid) { - mManager->close(mConnectionId); - } - } - - protected: - static void description(const std::string& description) { - RecordProperty("description", description); - } - - android::sp<ClientManager> mManager; - std::shared_ptr<BufferPoolAllocator> mAllocator; - bool mConnectionValid; - ConnectionId mConnectionId; - ConnectionId mReceiverId; - -}; - -// Buffer allocation test. -// Check whether each buffer allocation is done successfully with -// unique buffer id. -TEST_F(BufferpoolSingleTest, AllocateBuffer) { - ResultStatus status; - std::vector<uint8_t> vecParams; - getVtsAllocatorParams(&vecParams); - - std::shared_ptr<BufferPoolData> buffer[kNumAllocationTest]; - native_handle_t *allocHandle = nullptr; - for (int i = 0; i < kNumAllocationTest; ++i) { - status = mManager->allocate(mConnectionId, vecParams, &allocHandle, &buffer[i]); - ASSERT_TRUE(status == ResultStatus::OK); - } - for (int i = 0; i < kNumAllocationTest; ++i) { - for (int j = i + 1; j < kNumAllocationTest; ++j) { - ASSERT_TRUE(buffer[i]->mId != buffer[j]->mId); - } - } - EXPECT_TRUE(kNumAllocationTest > 1); -} - -// Buffer recycle test. -// Check whether de-allocated buffers are recycled. -TEST_F(BufferpoolSingleTest, RecycleBuffer) { - ResultStatus status; - std::vector<uint8_t> vecParams; - getVtsAllocatorParams(&vecParams); - - BufferId bid[kNumRecycleTest]; - for (int i = 0; i < kNumRecycleTest; ++i) { - std::shared_ptr<BufferPoolData> buffer; - native_handle_t *allocHandle = nullptr; - status = mManager->allocate(mConnectionId, vecParams, &allocHandle, &buffer); - ASSERT_TRUE(status == ResultStatus::OK); - bid[i] = buffer->mId; - } - for (int i = 1; i < kNumRecycleTest; ++i) { - ASSERT_TRUE(bid[i - 1] == bid[i]); - } - EXPECT_TRUE(kNumRecycleTest > 1); -} - -// Buffer transfer test. -// Check whether buffer is transferred to another client successfully. -TEST_F(BufferpoolSingleTest, TransferBuffer) { - ResultStatus status; - std::vector<uint8_t> vecParams; - getVtsAllocatorParams(&vecParams); - std::shared_ptr<BufferPoolData> sbuffer, rbuffer; - native_handle_t *allocHandle = nullptr; - native_handle_t *recvHandle = nullptr; - - TransactionId transactionId; - int64_t postUs; - - status = mManager->allocate(mConnectionId, vecParams, &allocHandle, &sbuffer); - ASSERT_TRUE(status == ResultStatus::OK); - status = mManager->postSend(mReceiverId, sbuffer, &transactionId, &postUs); - ASSERT_TRUE(status == ResultStatus::OK); - status = mManager->receive(mReceiverId, transactionId, sbuffer->mId, postUs, - &recvHandle, &rbuffer); - EXPECT_TRUE(status == ResultStatus::OK); -} - -} // anonymous namespace - -int main(int argc, char** argv) { - ::testing::InitGoogleTest(&argc, argv); - int status = RUN_ALL_TESTS(); - LOG(INFO) << "Test result = " << status; - return status; -} diff --git a/codec2/vndk/include/C2BqBufferPriv.h b/codec2/vndk/include/C2BqBufferPriv.h index 9271a71..0141f62 100644 --- a/codec2/vndk/include/C2BqBufferPriv.h +++ b/codec2/vndk/include/C2BqBufferPriv.h @@ -20,7 +20,7 @@ #include <functional> #include <C2Buffer.h> -#include <media/stagefright/bqhelper/WGraphicBufferProducer.h> +#include <gui/bufferqueue/1.0/WGraphicBufferProducer.h> class C2BufferQueueBlockPool : public C2BlockPool { public: diff --git a/codec2/vndk/platform/C2BqBuffer.cpp b/codec2/vndk/platform/C2BqBuffer.cpp index cbacd97..90eba49 100644 --- a/codec2/vndk/platform/C2BqBuffer.cpp +++ b/codec2/vndk/platform/C2BqBuffer.cpp @@ -18,7 +18,7 @@ #define LOG_TAG "C2BqBuffer" #include <utils/Log.h> -#include <gui/BufferQueueDefs.h> +#include <ui/BufferQueueDefs.h> #include <list> #include <map> #include <mutex> @@ -269,36 +269,28 @@ private: } } if (slotBuffer) { - native_handle_t *grallocHandle = native_handle_clone(slotBuffer->handle); - - if (grallocHandle) { - ALOGV("buffer wraps %llu %d", (unsigned long long)mProducerId, slot); - C2Handle *c2Handle = android::WrapNativeCodec2GrallocHandle( - grallocHandle, - slotBuffer->width, - slotBuffer->height, - slotBuffer->format, - slotBuffer->usage, - slotBuffer->stride, - slotBuffer->getGenerationNumber(), - mProducerId, slot); - if (c2Handle) { - // Moved everything to c2Handle. - native_handle_delete(grallocHandle); - std::shared_ptr<C2GraphicAllocation> alloc; - c2_status_t err = mAllocator->priorGraphicAllocation(c2Handle, &alloc); - if (err != C2_OK) { - return err; - } - std::shared_ptr<C2BufferQueueBlockPoolData> poolData = - std::make_shared<C2BufferQueueBlockPoolData>( - slotBuffer->getGenerationNumber(), - mProducerId, slot, shared_from_this()); - *block = _C2BlockFactory::CreateGraphicBlock(alloc, poolData); - return C2_OK; + ALOGV("buffer wraps %llu %d", (unsigned long long)mProducerId, slot); + C2Handle *c2Handle = android::WrapNativeCodec2GrallocHandle( + slotBuffer->handle, + slotBuffer->width, + slotBuffer->height, + slotBuffer->format, + slotBuffer->usage, + slotBuffer->stride, + slotBuffer->getGenerationNumber(), + mProducerId, slot); + if (c2Handle) { + std::shared_ptr<C2GraphicAllocation> alloc; + c2_status_t err = mAllocator->priorGraphicAllocation(c2Handle, &alloc); + if (err != C2_OK) { + return err; } - native_handle_close(grallocHandle); - native_handle_delete(grallocHandle); + std::shared_ptr<C2BufferQueueBlockPoolData> poolData = + std::make_shared<C2BufferQueueBlockPoolData>( + slotBuffer->getGenerationNumber(), + mProducerId, slot, shared_from_this()); + *block = _C2BlockFactory::CreateGraphicBlock(alloc, poolData); + return C2_OK; } // Block was not created. call requestBuffer# again next time. slotBuffer.clear(); diff --git a/media/Android.bp b/media/Android.bp deleted file mode 100644 index 573d3ce..0000000 --- a/media/Android.bp +++ /dev/null @@ -1,4 +0,0 @@ -subdirs = [ - "codecs", - "sfplugin", -] diff --git a/media/codecs/Android.bp b/media/codecs/Android.bp deleted file mode 100644 index e8176cf..0000000 --- a/media/codecs/Android.bp +++ /dev/null @@ -1,3 +0,0 @@ -subdirs = [ - "*", -] diff --git a/media/codecs/aac/C2SoftAacDec.cpp b/media/codecs/aac/C2SoftAacDec.cpp index 02be0ea..c7c8442 100644 --- a/media/codecs/aac/C2SoftAacDec.cpp +++ b/media/codecs/aac/C2SoftAacDec.cpp @@ -239,10 +239,10 @@ C2SoftAacDec::C2SoftAacDec( const std::shared_ptr<IntfImpl> &intfImpl) : SimpleC2Component(std::make_shared<SimpleInterface<IntfImpl>>(name, id, intfImpl)), mIntf(intfImpl), - mAACDecoder(NULL), - mStreamInfo(NULL), + mAACDecoder(nullptr), + mStreamInfo(nullptr), mSignalledError(false), - mOutputDelayRingBuffer(NULL) { + mOutputDelayRingBuffer(nullptr) { } C2SoftAacDec::~C2SoftAacDec() { @@ -279,11 +279,11 @@ void C2SoftAacDec::onReset() { void C2SoftAacDec::onRelease() { if (mAACDecoder) { aacDecoder_Close(mAACDecoder); - mAACDecoder = NULL; + mAACDecoder = nullptr; } if (mOutputDelayRingBuffer) { delete[] mOutputDelayRingBuffer; - mOutputDelayRingBuffer = NULL; + mOutputDelayRingBuffer = nullptr; } } @@ -291,9 +291,9 @@ status_t C2SoftAacDec::initDecoder() { ALOGV("initDecoder()"); status_t status = UNKNOWN_ERROR; mAACDecoder = aacDecoder_Open(TT_MP4_ADIF, /* num layers */ 1); - if (mAACDecoder != NULL) { + if (mAACDecoder != nullptr) { mStreamInfo = aacDecoder_GetStreamInfo(mAACDecoder); - if (mStreamInfo != NULL) { + if (mStreamInfo != nullptr) { status = OK; } } @@ -305,7 +305,7 @@ status_t C2SoftAacDec::initDecoder() { mOutputDelayRingBufferReadPos = 0; mOutputDelayRingBufferFilled = 0; - if (mAACDecoder == NULL) { + if (mAACDecoder == nullptr) { ALOGE("AAC decoder is null. TODO: Can not call aacDecoder_SetParam in the following code"); } @@ -401,7 +401,7 @@ int32_t C2SoftAacDec::outputDelayRingBufferGetSamples(INT_PCM *samples, int32_t && (mOutputDelayRingBufferWritePos < mOutputDelayRingBufferReadPos || mOutputDelayRingBufferWritePos >= mOutputDelayRingBufferReadPos + numSamples)) { // faster memcopy loop without checks, if the preconditions allow this - if (samples != 0) { + if (samples != nullptr) { for (int32_t i = 0; i < numSamples; i++) { samples[i] = mOutputDelayRingBuffer[mOutputDelayRingBufferReadPos++]; } @@ -415,7 +415,7 @@ int32_t C2SoftAacDec::outputDelayRingBufferGetSamples(INT_PCM *samples, int32_t ALOGV("slow C2SoftAacDec::outputDelayRingBufferGetSamples()"); for (int32_t i = 0; i < numSamples; i++) { - if (samples != 0) { + if (samples != nullptr) { samples[i] = mOutputDelayRingBuffer[mOutputDelayRingBufferReadPos]; } mOutputDelayRingBufferReadPos++; @@ -486,7 +486,6 @@ void C2SoftAacDec::drainRingBuffer( numSamples * sizeof(int16_t), usage, &block); if (err != C2_OK) { ALOGD("failed to fetch a linear block (%d)", err); - mSignalledError = true; return std::bind(fillEmptyWork, _1, C2_NO_MEMORY); } C2WriteView wView = block->map().get(); @@ -524,9 +523,12 @@ void C2SoftAacDec::drainRingBuffer( void C2SoftAacDec::process( const std::unique_ptr<C2Work> &work, const std::shared_ptr<C2BlockPool> &pool) { - work->workletsProcessed = 0u; + // Initialize output work work->result = C2_OK; + work->workletsProcessed = 1u; work->worklets.front()->output.configUpdate.clear(); + work->worklets.front()->output.flags = work->input.flags; + if (mSignalledError) { return; } @@ -567,13 +569,12 @@ void C2SoftAacDec::process( if (decoderErr != AAC_DEC_OK) { ALOGE("aacDecoder_ConfigRaw decoderErr = 0x%4.4x", decoderErr); mSignalledError = true; - // TODO: error + work->result = C2_CORRUPTED; return; } work->worklets.front()->output.flags = work->input.flags; work->worklets.front()->output.ordinal = work->input.ordinal; work->worklets.front()->output.buffers.clear(); - work->workletsProcessed = 1u; return; } @@ -629,7 +630,7 @@ void C2SoftAacDec::process( if (signalError) { mSignalledError = true; - // TODO: notify(OMX_EventError, OMX_ErrorStreamCorrupt, ERROR_MALFORMED, NULL); + work->result = C2_CORRUPTED; return; } } else { @@ -687,7 +688,7 @@ void C2SoftAacDec::process( if (bytesValid[0] != 0) { ALOGE("bytesValid[0] != 0 should never happen"); mSignalledError = true; - // TODO: notify(OMX_EventError, OMX_ErrorUndefined, 0, NULL); + work->result = C2_CORRUPTED; return; } @@ -698,7 +699,7 @@ void C2SoftAacDec::process( if (!outputDelayRingBufferPutSamples(tmpOutBuffer, mStreamInfo->frameSize * mStreamInfo->numChannels)) { mSignalledError = true; - // TODO: notify(OMX_EventError, OMX_ErrorUndefined, decoderErr, NULL); + work->result = C2_CORRUPTED; return; } } else { @@ -709,7 +710,7 @@ void C2SoftAacDec::process( if (!outputDelayRingBufferPutSamples(tmpOutBuffer, mStreamInfo->frameSize * mStreamInfo->numChannels)) { mSignalledError = true; - // TODO: notify(OMX_EventError, OMX_ErrorUndefined, decoderErr, NULL); + work->result = C2_CORRUPTED; return; } @@ -766,8 +767,12 @@ void C2SoftAacDec::process( C2FrameData &output = work->worklets.front()->output; output.configUpdate.push_back(C2Param::Copy(sampleRateInfo)); output.configUpdate.push_back(C2Param::Copy(channelCountInfo)); + } else { + ALOGE("Config Update failed"); + mSignalledError = true; + work->result = C2_CORRUPTED; + return; } - // TODO: error handling } ALOGV("size = %zu", size); } while (decoderErr == AAC_DEC_OK); @@ -776,7 +781,7 @@ void C2SoftAacDec::process( int32_t outputDelay = mStreamInfo->outputDelay * mStreamInfo->numChannels; mBuffersInfo.push_back(std::move(inInfo)); - + work->workletsProcessed = 0u; if (!eos && mOutputDelayCompensated < outputDelay) { // discard outputDelay at the beginning int32_t toCompensate = outputDelay - mOutputDelayCompensated; @@ -784,7 +789,7 @@ void C2SoftAacDec::process( if (discard > toCompensate) { discard = toCompensate; } - int32_t discarded = outputDelayRingBufferGetSamples(0, discard); + int32_t discarded = outputDelayRingBufferGetSamples(nullptr, discard); mOutputDelayCompensated += discarded; return; } @@ -849,7 +854,7 @@ c2_status_t C2SoftAacDec::onFlush_sm() { if (avail > mStreamInfo->frameSize * mStreamInfo->numChannels) { avail = mStreamInfo->frameSize * mStreamInfo->numChannels; } - int32_t ns = outputDelayRingBufferGetSamples(0, avail); + int32_t ns = outputDelayRingBufferGetSamples(nullptr, avail); if (ns != avail) { ALOGW("not a complete frame of samples available"); break; diff --git a/media/codecs/aac/C2SoftAacEnc.cpp b/media/codecs/aac/C2SoftAacEnc.cpp index 630b249..87730ae 100644 --- a/media/codecs/aac/C2SoftAacEnc.cpp +++ b/media/codecs/aac/C2SoftAacEnc.cpp @@ -144,14 +144,13 @@ C2SoftAacEnc::C2SoftAacEnc( const std::shared_ptr<IntfImpl> &intfImpl) : SimpleC2Component(std::make_shared<SimpleInterface<IntfImpl>>(name, id, intfImpl)), mIntf(intfImpl), - mAACEncoder(NULL), + mAACEncoder(nullptr), mSBRMode(-1), mSBRRatio(0), mAACProfile(AOT_AAC_LC), mNumBytesPerInputFrame(0u), mOutBufferSize(0u), mSentCodecSpecificData(false), - mInputTimeSet(false), mInputSize(0), mInputTimeUs(-1ll), mSignalledError(false), @@ -177,7 +176,6 @@ status_t C2SoftAacEnc::initEncoder() { c2_status_t C2SoftAacEnc::onStop() { mSentCodecSpecificData = false; - mInputTimeSet = false; mInputSize = 0u; mInputTimeUs = -1ll; mSignalledError = false; @@ -195,7 +193,6 @@ void C2SoftAacEnc::onRelease() { c2_status_t C2SoftAacEnc::onFlush_sm() { mSentCodecSpecificData = false; - mInputTimeSet = false; mInputSize = 0u; return C2_OK; } @@ -285,8 +282,10 @@ status_t C2SoftAacEnc::setAudioParams() { void C2SoftAacEnc::process( const std::unique_ptr<C2Work> &work, const std::shared_ptr<C2BlockPool> &pool) { + // Initialize output work work->result = C2_OK; - work->workletsProcessed = 0u; + work->workletsProcessed = 1u; + work->worklets.front()->output.flags = work->input.flags; if (mSignalledError) { return; @@ -300,10 +299,10 @@ void C2SoftAacEnc::process( // The very first thing we want to output is the codec specific // data. - if (AACENC_OK != aacEncEncode(mAACEncoder, NULL, NULL, NULL, NULL)) { + if (AACENC_OK != aacEncEncode(mAACEncoder, nullptr, nullptr, nullptr, nullptr)) { ALOGE("Unable to initialize encoder for profile / sample-rate / bit-rate / channels"); - // TODO: notify(OMX_EventError, OMX_ErrorUndefined, 0, NULL); mSignalledError = true; + work->result = C2_CORRUPTED; return; } @@ -316,14 +315,19 @@ void C2SoftAacEnc::process( AACENC_InfoStruct encInfo; if (AACENC_OK != aacEncInfo(mAACEncoder, &encInfo)) { ALOGE("Failed to get AAC encoder info"); - // TODO: notify(OMX_EventError, OMX_ErrorUndefined, 0, NULL); mSignalledError = true; + work->result = C2_CORRUPTED; return; } std::unique_ptr<C2StreamCsdInfo::output> csd = C2StreamCsdInfo::output::AllocUnique(encInfo.confSize, 0u); - // TODO: check NO_MEMORY + if (!csd) { + ALOGE("CSD allocation failed"); + mSignalledError = true; + work->result = C2_NO_MEMORY; + return; + } memcpy(csd->m.value, encInfo.confBuf, encInfo.confSize); ALOGV("put csd"); #if defined(LOG_NDEBUG) && !LOG_NDEBUG @@ -333,6 +337,7 @@ void C2SoftAacEnc::process( mOutBufferSize = encInfo.maxOutBufBytes; mNumBytesPerInputFrame = encInfo.frameLength * channelCount * sizeof(int16_t); + mInputTimeUs = work->input.ordinal.timestamp; mSentCodecSpecificData = true; } @@ -346,10 +351,6 @@ void C2SoftAacEnc::process( data = view.data(); capacity = view.capacity(); } - if (!mInputTimeSet && capacity > 0) { - mInputTimeUs = work->input.ordinal.timestamp; - mInputTimeSet = true; - } size_t numFrames = (capacity + mInputSize + (eos ? mNumBytesPerInputFrame - 1 : 0)) / mNumBytesPerInputFrame; @@ -432,7 +433,9 @@ void C2SoftAacEnc::process( // TODO: error handling, proper usage, etc. c2_status_t err = pool->fetchLinearBlock(mOutBufferSize, usage, &block); if (err != C2_OK) { - ALOGE("err = %d", err); + ALOGE("fetchLinearBlock failed : err = %d", err); + work->result = C2_NO_MEMORY; + return; } wView.reset(new C2WriteView(block->map().get())); @@ -496,7 +499,9 @@ void C2SoftAacEnc::process( // TODO: error handling, proper usage, etc. c2_status_t err = pool->fetchLinearBlock(mOutBufferSize, usage, &block); if (err != C2_OK) { - ALOGE("err = %d", err); + ALOGE("fetchLinearBlock failed : err = %d", err); + work->result = C2_NO_MEMORY; + return; } wView.reset(new C2WriteView(block->map().get())); @@ -545,7 +550,6 @@ c2_status_t C2SoftAacEnc::drain( (void)pool; mSentCodecSpecificData = false; - mInputTimeSet = false; mInputSize = 0u; // TODO: we don't have any pending work at this time to drain. diff --git a/media/codecs/aac/C2SoftAacEnc.h b/media/codecs/aac/C2SoftAacEnc.h index 779365b..82fb438 100644 --- a/media/codecs/aac/C2SoftAacEnc.h +++ b/media/codecs/aac/C2SoftAacEnc.h @@ -57,7 +57,6 @@ private: UINT mOutBufferSize; bool mSentCodecSpecificData; - bool mInputTimeSet; size_t mInputSize; c2_cntr64_t mInputTimeUs; diff --git a/media/codecs/amr_nb_wb/C2SoftAmrDec.cpp b/media/codecs/amr_nb_wb/C2SoftAmrDec.cpp index 400149a..c591e21 100644 --- a/media/codecs/amr_nb_wb/C2SoftAmrDec.cpp +++ b/media/codecs/amr_nb_wb/C2SoftAmrDec.cpp @@ -245,8 +245,11 @@ static status_t calculateNumFrames(const uint8 *input, size_t inSize, void C2SoftAmrDec::process( const std::unique_ptr<C2Work> &work, const std::shared_ptr<C2BlockPool> &pool) { + // Initialize output work work->result = C2_OK; - work->workletsProcessed = 0u; + work->workletsProcessed = 1u; + work->worklets.front()->output.flags = work->input.flags; + if (mSignalledError || mSignalledOutputEos) { work->result = C2_BAD_VALUE; return; @@ -270,7 +273,6 @@ void C2SoftAmrDec::process( work->worklets.front()->output.flags = work->input.flags; work->worklets.front()->output.buffers.clear(); work->worklets.front()->output.ordinal = work->input.ordinal; - work->workletsProcessed = 1u; if (eos) { mSignalledOutputEos = true; ALOGV("signalled EOS"); @@ -368,7 +370,6 @@ void C2SoftAmrDec::process( work->worklets.front()->output.buffers.clear(); work->worklets.front()->output.buffers.push_back(createLinearBuffer(block)); work->worklets.front()->output.ordinal = work->input.ordinal; - work->workletsProcessed = 1u; if (eos) { mSignalledOutputEos = true; ALOGV("signalled EOS"); diff --git a/media/codecs/amr_nb_wb/C2SoftAmrNbEnc.cpp b/media/codecs/amr_nb_wb/C2SoftAmrNbEnc.cpp index b34c3a9..ca21480 100644 --- a/media/codecs/amr_nb_wb/C2SoftAmrNbEnc.cpp +++ b/media/codecs/amr_nb_wb/C2SoftAmrNbEnc.cpp @@ -188,8 +188,11 @@ static void fillEmptyWork(const std::unique_ptr<C2Work> &work) { void C2SoftAmrNbEnc::process( const std::unique_ptr<C2Work> &work, const std::shared_ptr<C2BlockPool> &pool) { + // Initialize output work work->result = C2_OK; - work->workletsProcessed = 0u; + work->workletsProcessed = 1u; + work->worklets.front()->output.flags = work->input.flags; + if (mSignalledError || mSignalledOutputEos) { work->result = C2_BAD_VALUE; return; diff --git a/media/codecs/amr_nb_wb/C2SoftAmrWbEnc.cpp b/media/codecs/amr_nb_wb/C2SoftAmrWbEnc.cpp index 1e5378c..be3892f 100644 --- a/media/codecs/amr_nb_wb/C2SoftAmrWbEnc.cpp +++ b/media/codecs/amr_nb_wb/C2SoftAmrWbEnc.cpp @@ -271,8 +271,11 @@ static void fillEmptyWork(const std::unique_ptr<C2Work> &work) { void C2SoftAmrWbEnc::process( const std::unique_ptr<C2Work> &work, const std::shared_ptr<C2BlockPool> &pool) { + // Initialize output work work->result = C2_OK; - work->workletsProcessed = 0u; + work->workletsProcessed = 1u; + work->worklets.front()->output.flags = work->input.flags; + if (mSignalledError || mSignalledOutputEos) { work->result = C2_BAD_VALUE; return; diff --git a/media/codecs/avc/C2SoftAvcDec.cpp b/media/codecs/avc/C2SoftAvcDec.cpp index 7f0b725..3e62744 100644 --- a/media/codecs/avc/C2SoftAvcDec.cpp +++ b/media/codecs/avc/C2SoftAvcDec.cpp @@ -328,7 +328,8 @@ C2SoftAvcDec::C2SoftAvcDec( mOutBufferFlush(nullptr), mIvColorFormat(IV_YUV_420P), mWidth(320), - mHeight(240) { + mHeight(240), + mHeaderDecoded(false) { GENERATE_FILE_NAMES(); CREATE_DUMP_FILE(mInFile); } @@ -385,8 +386,10 @@ c2_status_t C2SoftAvcDec::onFlush_sm() { } } - ivd_aligned_free(nullptr, mOutBufferFlush); - mOutBufferFlush = nullptr; + if (mOutBufferFlush) { + ivd_aligned_free(nullptr, mOutBufferFlush); + mOutBufferFlush = nullptr; + } return C2_OK; } @@ -438,7 +441,7 @@ status_t C2SoftAvcDec::setNumCores() { return OK; } -status_t C2SoftAvcDec::setParams(size_t stride) { +status_t C2SoftAvcDec::setParams(size_t stride, IVD_VIDEO_DECODE_MODE_T dec_mode) { ivd_ctl_set_config_ip_t s_set_dyn_params_ip; ivd_ctl_set_config_op_t s_set_dyn_params_op; @@ -448,7 +451,7 @@ status_t C2SoftAvcDec::setParams(size_t stride) { s_set_dyn_params_ip.u4_disp_wd = (UWORD32) stride; s_set_dyn_params_ip.e_frm_skip_mode = IVD_SKIP_NONE; s_set_dyn_params_ip.e_frm_out_mode = IVD_DISPLAY_FRAME_OUT; - s_set_dyn_params_ip.e_vid_dec_mode = IVD_DECODE_FRAME; + s_set_dyn_params_ip.e_vid_dec_mode = dec_mode; s_set_dyn_params_op.u4_size = sizeof(ivd_ctl_set_config_op_t); IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle, &s_set_dyn_params_ip, @@ -491,7 +494,7 @@ status_t C2SoftAvcDec::initDecoder() { mSignalledError = false; resetPlugin(); (void) setNumCores(); - if (OK != setParams(mStride)) return UNKNOWN_ERROR; + if (OK != setParams(mStride, IVD_DECODE_FRAME)) return UNKNOWN_ERROR; (void) getVersion(); return OK; @@ -629,6 +632,7 @@ status_t C2SoftAvcDec::resetDecoder() { mStride = 0; (void) setNumCores(); mSignalledError = false; + mHeaderDecoded = false; return OK; } @@ -682,14 +686,8 @@ void C2SoftAvcDec::finishWork(uint64_t index, const std::unique_ptr<C2Work> &wor buffer->setInfo(mIntf->getColorAspects_l()); } - auto fillWork = [buffer, index](const std::unique_ptr<C2Work> &work) { - uint32_t flags = 0; - if ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) && - (c2_cntr64_t(index) == work->input.ordinal.frameIndex)) { - flags |= C2FrameData::FLAG_END_OF_STREAM; - ALOGV("signalling eos"); - } - work->worklets.front()->output.flags = (C2FrameData::flags_t)flags; + auto fillWork = [buffer](const std::unique_ptr<C2Work> &work) { + work->worklets.front()->output.flags = (C2FrameData::flags_t)0; work->worklets.front()->output.buffers.clear(); work->worklets.front()->output.buffers.push_back(buffer); work->worklets.front()->output.ordinal = work->input.ordinal; @@ -709,7 +707,7 @@ c2_status_t C2SoftAvcDec::ensureDecoderState(const std::shared_ptr<C2BlockPool> } if (mStride != ALIGN64(mWidth)) { mStride = ALIGN64(mWidth); - if (OK != setParams(mStride)) return C2_CORRUPTED; + if (OK != setParams(mStride, IVD_DECODE_FRAME)) return C2_CORRUPTED; } if (mOutBlock && (mOutBlock->width() != mStride || mOutBlock->height() != mHeight)) { @@ -739,8 +737,10 @@ c2_status_t C2SoftAvcDec::ensureDecoderState(const std::shared_ptr<C2BlockPool> void C2SoftAvcDec::process( const std::unique_ptr<C2Work> &work, const std::shared_ptr<C2BlockPool> &pool) { + // Initialize output work work->result = C2_OK; work->workletsProcessed = 0u; + work->worklets.front()->output.flags = work->input.flags; if (mSignalledError || mSignalledOutputEos) { work->result = C2_BAD_VALUE; return; @@ -769,6 +769,7 @@ void C2SoftAvcDec::process( while (inPos < inSize) { if (C2_OK != ensureDecoderState(pool)) { mSignalledError = true; + work->workletsProcessed = 1u; work->result = C2_CORRUPTED; return; } @@ -785,11 +786,18 @@ void C2SoftAvcDec::process( if (!setDecodeArgs(&s_decode_ip, &s_decode_op, &rView, &wView, inOffset + inPos, inSize - inPos, workIndex)) { mSignalledError = true; + work->workletsProcessed = 1u; work->result = C2_CORRUPTED; return; } + + if (false == mHeaderDecoded) { + /* Decode header and get dimensions */ + setParams(mStride, IVD_DECODE_HEADER); + } + WORD32 delay; - GETTIME(&mTimeStart, NULL); + GETTIME(&mTimeStart, nullptr); TIME_DIFF(mTimeEnd, mTimeStart, delay); (void) ivdec_api_function(mDecHandle, &s_decode_ip, &s_decode_op); WORD32 decodeTime; @@ -800,22 +808,32 @@ void C2SoftAvcDec::process( } if (IVD_MEM_ALLOC_FAILED == (s_decode_op.u4_error_code & 0xFF)) { ALOGE("allocation failure in decoder"); - work->result = C2_CORRUPTED; mSignalledError = true; + work->workletsProcessed = 1u; + work->result = C2_CORRUPTED; return; } else if (IVD_STREAM_WIDTH_HEIGHT_NOT_SUPPORTED == (s_decode_op.u4_error_code & 0xFF)) { ALOGE("unsupported resolution : %dx%d", mWidth, mHeight); - work->result = C2_CORRUPTED; mSignalledError = true; + work->workletsProcessed = 1u; + work->result = C2_CORRUPTED; return; } else if (IVD_RES_CHANGED == (s_decode_op.u4_error_code & 0xFF)) { ALOGV("resolution changed"); drainInternal(DRAIN_COMPONENT_NO_EOS, pool, work); resetDecoder(); resetPlugin(); - continue; + work->workletsProcessed = 0u; + + /* Decode header and get new dimensions */ + setParams(mStride, IVD_DECODE_HEADER); + (void) ivdec_api_function(mDecHandle, &s_decode_ip, &s_decode_op); } if (0 < s_decode_op.u4_pic_wd && 0 < s_decode_op.u4_pic_ht) { + if (mHeaderDecoded == false) { + mHeaderDecoded = true; + setParams(ALIGN64(s_decode_op.u4_pic_wd), IVD_DECODE_FRAME); + } if (s_decode_op.u4_pic_wd != mWidth || s_decode_op.u4_pic_ht != mHeight) { mWidth = s_decode_op.u4_pic_wd; mHeight = s_decode_op.u4_pic_ht; @@ -823,8 +841,17 @@ void C2SoftAvcDec::process( C2VideoSizeStreamInfo::output size(0u, mWidth, mHeight); std::vector<std::unique_ptr<C2SettingResult>> failures; - (void)mIntf->config({&size}, C2_MAY_BLOCK, &failures); - work->worklets.front()->output.configUpdate.push_back(C2Param::Copy(size)); + c2_status_t err = mIntf->config({&size}, C2_MAY_BLOCK, &failures); + if (err == OK) { + work->worklets.front()->output.configUpdate.push_back( + C2Param::Copy(size)); + } else { + ALOGE("Cannot set width and height"); + mSignalledError = true; + work->workletsProcessed = 1u; + work->result = C2_CORRUPTED; + return; + } continue; } } @@ -833,6 +860,10 @@ void C2SoftAvcDec::process( if (s_decode_op.u4_output_present) { finishWork(s_decode_op.u4_ts, work); } + if (0 == s_decode_op.u4_num_bytes_consumed) { + ALOGD("Bytes consumed is zero. Ignoring remaining bytes"); + break; + } inPos += s_decode_op.u4_num_bytes_consumed; if (hasPicture && (inSize - inPos)) { ALOGD("decoded frame in current access nal, ignoring further trailing bytes %d", @@ -865,6 +896,7 @@ c2_status_t C2SoftAvcDec::drainInternal( while (true) { if (C2_OK != ensureDecoderState(pool)) { mSignalledError = true; + work->workletsProcessed = 1u; work->result = C2_CORRUPTED; return C2_CORRUPTED; } @@ -877,21 +909,18 @@ c2_status_t C2SoftAvcDec::drainInternal( ivd_video_decode_op_t s_decode_op; if (!setDecodeArgs(&s_decode_ip, &s_decode_op, nullptr, &wView, 0, 0, 0)) { mSignalledError = true; + work->workletsProcessed = 1u; return C2_CORRUPTED; } (void) ivdec_api_function(mDecHandle, &s_decode_ip, &s_decode_op); if (s_decode_op.u4_output_present) { finishWork(s_decode_op.u4_ts, work); } else { + fillEmptyWork(work); break; } } - if (drainMode == DRAIN_COMPONENT_WITH_EOS && - work && work->workletsProcessed == 0u) { - fillEmptyWork(work); - } - return C2_OK; } diff --git a/media/codecs/avc/C2SoftAvcDec.h b/media/codecs/avc/C2SoftAvcDec.h index abf0fc6..2127a93 100644 --- a/media/codecs/avc/C2SoftAvcDec.h +++ b/media/codecs/avc/C2SoftAvcDec.h @@ -117,7 +117,7 @@ public: private: status_t createDecoder(); status_t setNumCores(); - status_t setParams(size_t stride); + status_t setParams(size_t stride, IVD_VIDEO_DECODE_MODE_T dec_mode); void getVersion(); status_t initDecoder(); bool setDecodeArgs(ivd_video_decode_ip_t *ps_decode_ip, @@ -162,7 +162,7 @@ private: uint32_t mStride; bool mSignalledOutputEos; bool mSignalledError; - + bool mHeaderDecoded; // Color aspects. These are ISO values and are meant to detect changes in aspects to avoid // converting them to C2 values for each frame struct VuiColorAspects { diff --git a/media/codecs/avc/C2SoftAvcEnc.cpp b/media/codecs/avc/C2SoftAvcEnc.cpp index c10de61..bfe745c 100644 --- a/media/codecs/avc/C2SoftAvcEnc.cpp +++ b/media/codecs/avc/C2SoftAvcEnc.cpp @@ -216,7 +216,7 @@ public: }; uint64_t mbs = uint64_t((size.v.width + 15) / 16) * ((size.v.height + 15) / 16); - float mbsPerSec = float(mbs) / frameRate.v.value; + float mbsPerSec = float(mbs) * frameRate.v.value; // Check if the supplied level meets the MB / bitrate requirements. If // not, update the level with the lowest level meeting the requirements. @@ -374,7 +374,7 @@ C2SoftAvcEnc::C2SoftAvcEnc( mSawInputEOS(false), mSawOutputEOS(false), mSignalledError(false), - mCodecCtx(NULL), + mCodecCtx(nullptr), // TODO: output buffer size mOutBufferSize(524288) { @@ -414,8 +414,8 @@ c2_status_t C2SoftAvcEnc::onFlush_sm() { } void C2SoftAvcEnc::initEncParams() { - mCodecCtx = NULL; - mMemRecords = NULL; + mCodecCtx = nullptr; + mMemRecords = nullptr; mNumMemRecords = DEFAULT_MEM_REC_CNT; mHeaderGenerated = 0; mNumCores = GetCPUCoreCount(); @@ -436,8 +436,8 @@ void C2SoftAvcEnc::initEncParams() { mEntropyMode = DEFAULT_ENTROPY_MODE; mBframes = DEFAULT_B_FRAMES; - gettimeofday(&mTimeStart, NULL); - gettimeofday(&mTimeEnd, NULL); + gettimeofday(&mTimeStart, nullptr); + gettimeofday(&mTimeEnd, nullptr); } c2_status_t C2SoftAvcEnc::setDimensions() { @@ -872,7 +872,7 @@ c2_status_t C2SoftAvcEnc::initEncoder() { s_num_mem_rec_ip.e_cmd = IV_CMD_GET_NUM_MEM_REC; - status = ive_api_function(0, &s_num_mem_rec_ip, &s_num_mem_rec_op); + status = ive_api_function(nullptr, &s_num_mem_rec_ip, &s_num_mem_rec_op); if (status != IV_SUCCESS) { ALOGE("Get number of memory records failed = 0x%x\n", @@ -889,7 +889,7 @@ c2_status_t C2SoftAvcEnc::initEncoder() { return C2_CORRUPTED; } mMemRecords = (iv_mem_rec_t *)malloc(mNumMemRecords * sizeof(iv_mem_rec_t)); - if (NULL == mMemRecords) { + if (nullptr == mMemRecords) { ALOGE("Unable to allocate memory for hold memory records: Size %zu", mNumMemRecords * sizeof(iv_mem_rec_t)); mSignalledError = true; @@ -901,7 +901,7 @@ c2_status_t C2SoftAvcEnc::initEncoder() { ps_mem_rec = mMemRecords; for (size_t i = 0; i < mNumMemRecords; i++) { ps_mem_rec->u4_size = sizeof(iv_mem_rec_t); - ps_mem_rec->pv_base = NULL; + ps_mem_rec->pv_base = nullptr; ps_mem_rec->u4_mem_size = 0; ps_mem_rec->u4_mem_alignment = 0; ps_mem_rec->e_mem_type = IV_NA_MEM_TYPE; @@ -930,7 +930,7 @@ c2_status_t C2SoftAvcEnc::initEncoder() { s_fill_mem_rec_ip.u4_max_srch_rng_x = DEFAULT_MAX_SRCH_RANGE_X; s_fill_mem_rec_ip.u4_max_srch_rng_y = DEFAULT_MAX_SRCH_RANGE_Y; - status = ive_api_function(0, &s_fill_mem_rec_ip, &s_fill_mem_rec_op); + status = ive_api_function(nullptr, &s_fill_mem_rec_ip, &s_fill_mem_rec_op); if (status != IV_SUCCESS) { ALOGE("Fill memory records failed = 0x%x\n", @@ -949,7 +949,7 @@ c2_status_t C2SoftAvcEnc::initEncoder() { for (size_t i = 0; i < mNumMemRecords; i++) { ps_mem_rec->pv_base = ive_aligned_malloc( ps_mem_rec->u4_mem_alignment, ps_mem_rec->u4_mem_size); - if (ps_mem_rec->pv_base == NULL) { + if (ps_mem_rec->pv_base == nullptr) { ALOGE("Allocation failure for mem record id %zu size %u\n", i, ps_mem_rec->u4_mem_size); return C2_CORRUPTED; @@ -1085,14 +1085,18 @@ c2_status_t C2SoftAvcEnc::releaseEncoder() { /* Free memory records */ ps_mem_rec = mMemRecords; for (size_t i = 0; i < s_retrieve_mem_op.u4_num_mem_rec_filled; i++) { - ive_aligned_free(ps_mem_rec->pv_base); + if (ps_mem_rec) ive_aligned_free(ps_mem_rec->pv_base); + else { + ALOGE("memory record is null."); + return C2_CORRUPTED; + } ps_mem_rec++; } - free(mMemRecords); + if (mMemRecords) free(mMemRecords); // clear other pointers into the space being free()d - mCodecCtx = NULL; + mCodecCtx = nullptr; mStarted = false; @@ -1116,15 +1120,15 @@ c2_status_t C2SoftAvcEnc::setEncodeArgs( ps_encode_op->u4_size = sizeof(ive_video_encode_op_t); ps_encode_ip->e_cmd = IVE_CMD_VIDEO_ENCODE; - ps_encode_ip->pv_bufs = NULL; - ps_encode_ip->pv_mb_info = NULL; - ps_encode_ip->pv_pic_info = NULL; + ps_encode_ip->pv_bufs = nullptr; + ps_encode_ip->pv_mb_info = nullptr; + ps_encode_ip->pv_pic_info = nullptr; ps_encode_ip->u4_mb_info_type = 0; ps_encode_ip->u4_pic_info_type = 0; ps_encode_ip->u4_is_last = 0; ps_encode_ip->u4_timestamp_high = timestamp >> 32; ps_encode_ip->u4_timestamp_low = timestamp & 0xFFFFFFFF; - ps_encode_op->s_out_buf.pv_buf = NULL; + ps_encode_op->s_out_buf.pv_buf = nullptr; /* Initialize color formats */ memset(ps_inp_raw_buf, 0, sizeof(iv_raw_buf_t)); @@ -1273,18 +1277,20 @@ c2_status_t C2SoftAvcEnc::setEncodeArgs( void C2SoftAvcEnc::process( const std::unique_ptr<C2Work> &work, const std::shared_ptr<C2BlockPool> &pool) { + // Initialize output work work->result = C2_OK; - work->workletsProcessed = 0u; + work->workletsProcessed = 1u; + work->worklets.front()->output.flags = work->input.flags; IV_STATUS_T status; WORD32 timeDelay, timeTaken; uint64_t timestamp = work->input.ordinal.timestamp.peekull(); // Initialize encoder if not already initialized - if (mCodecCtx == NULL) { + if (mCodecCtx == nullptr) { if (C2_OK != initEncoder()) { ALOGE("Failed to initialize encoder"); - work->workletsProcessed = 1u; + mSignalledError = true; work->result = C2_CORRUPTED; return; } @@ -1302,12 +1308,11 @@ void C2SoftAvcEnc::process( constexpr uint32_t kHeaderLength = MIN_STREAM_SIZE; uint8_t header[kHeaderLength]; error = setEncodeArgs( - &s_encode_ip, &s_encode_op, NULL, header, kHeaderLength, timestamp); + &s_encode_ip, &s_encode_op, nullptr, header, kHeaderLength, timestamp); if (error != C2_OK) { ALOGE("setEncodeArgs failed: %d", error); mSignalledError = true; - work->workletsProcessed = 1u; - work->result = error; + work->result = C2_CORRUPTED; return; } status = ive_api_function(mCodecCtx, &s_encode_ip, &s_encode_op); @@ -1325,6 +1330,12 @@ void C2SoftAvcEnc::process( std::unique_ptr<C2StreamCsdInfo::output> csd = C2StreamCsdInfo::output::AllocUnique(s_encode_op.s_out_buf.u4_bytes, 0u); + if (!csd) { + ALOGE("CSD allocation failed"); + mSignalledError = true; + work->result = C2_NO_MEMORY; + return; + } memcpy(csd->m.value, header, s_encode_op.s_out_buf.u4_bytes); work->worklets.front()->output.configUpdate.push_back(std::move(csd)); @@ -1398,14 +1409,12 @@ void C2SoftAvcEnc::process( c2_status_t err = pool->fetchLinearBlock(mOutBufferSize, usage, &block); if (err != C2_OK) { ALOGE("fetch linear block err = %d", err); - work->workletsProcessed = 1u; work->result = err; return; } C2WriteView wView = block->map().get(); if (wView.error() != C2_OK) { ALOGE("write view map err = %d", wView.error()); - work->workletsProcessed = 1u; work->result = wView.error(); return; } @@ -1413,9 +1422,8 @@ void C2SoftAvcEnc::process( error = setEncodeArgs( &s_encode_ip, &s_encode_op, view.get(), wView.base(), wView.capacity(), timestamp); if (error != C2_OK) { - mSignalledError = true; ALOGE("setEncodeArgs failed : %d", error); - work->workletsProcessed = 1u; + mSignalledError = true; work->result = error; return; } @@ -1424,7 +1432,7 @@ void C2SoftAvcEnc::process( // mInFile, s_encode_ip.s_inp_buf.apv_bufs[0], // (mHeight * mStride * 3 / 2)); - GETTIME(&mTimeStart, NULL); + GETTIME(&mTimeStart, nullptr); /* Compute time elapsed between end of previous decode() * to start of current decode() */ TIME_DIFF(mTimeEnd, mTimeStart, timeDelay); @@ -1439,7 +1447,6 @@ void C2SoftAvcEnc::process( ALOGE("Encode Frame failed = 0x%x\n", s_encode_op.u4_error_code); mSignalledError = true; - work->workletsProcessed = 1u; work->result = C2_CORRUPTED; return; } @@ -1450,7 +1457,7 @@ void C2SoftAvcEnc::process( mBuffers[s_encode_ip.s_inp_buf.apv_bufs[0]] = inputBuffer; } - GETTIME(&mTimeEnd, NULL); + GETTIME(&mTimeEnd, nullptr); /* Compute time taken for decode() */ TIME_DIFF(mTimeStart, mTimeEnd, timeTaken); @@ -1459,7 +1466,7 @@ void C2SoftAvcEnc::process( void *freed = s_encode_op.s_inp_buf.apv_bufs[0]; /* If encoder frees up an input buffer, mark it as free */ - if (freed != NULL) { + if (freed != nullptr) { if (mBuffers.count(freed) == 0u) { ALOGD("buffer not tracked"); } else { @@ -1485,7 +1492,6 @@ void C2SoftAvcEnc::process( } work->worklets.front()->output.buffers.push_back(buffer); } - work->workletsProcessed = 1u; if (s_encode_op.u4_is_last) { // outputBufferHeader->nFlags |= OMX_BUFFERFLAG_EOS; diff --git a/media/codecs/base/Android.bp b/media/codecs/base/Android.bp index 2aa84f6..473cb4d 100644 --- a/media/codecs/base/Android.bp +++ b/media/codecs/base/Android.bp @@ -74,9 +74,6 @@ cc_defaults { "signed-integer-overflow", ], cfi: true, - diag: { - cfi: true, - }, }, } @@ -90,9 +87,6 @@ cc_defaults { "signed-integer-overflow", ], cfi: true, - diag: { - cfi: true, - }, }, } @@ -128,9 +122,6 @@ cc_library_shared { "signed-integer-overflow", ], cfi: true, - diag: { - cfi: true, - }, }, ldflags: ["-Wl,-Bsymbolic"], diff --git a/media/codecs/base/SimpleC2Component.cpp b/media/codecs/base/SimpleC2Component.cpp index 6129543..50b4d20 100644 --- a/media/codecs/base/SimpleC2Component.cpp +++ b/media/codecs/base/SimpleC2Component.cpp @@ -132,56 +132,6 @@ void SimpleC2Component::WorkHandler::onMessageReceived(const sp<AMessage> &msg) } } -class SimpleC2Component::BlockingBlockPool : public C2BlockPool { -public: - BlockingBlockPool(const std::shared_ptr<C2BlockPool>& base): mBase{base} {} - - virtual local_id_t getLocalId() const override { - return mBase->getLocalId(); - } - - virtual C2Allocator::id_t getAllocatorId() const override { - return mBase->getAllocatorId(); - } - - virtual c2_status_t fetchLinearBlock( - uint32_t capacity, - C2MemoryUsage usage, - std::shared_ptr<C2LinearBlock>* block) { - c2_status_t status; - do { - status = mBase->fetchLinearBlock(capacity, usage, block); - } while (status == C2_TIMED_OUT); - return status; - } - - virtual c2_status_t fetchCircularBlock( - uint32_t capacity, - C2MemoryUsage usage, - std::shared_ptr<C2CircularBlock>* block) { - c2_status_t status; - do { - status = mBase->fetchCircularBlock(capacity, usage, block); - } while (status == C2_TIMED_OUT); - return status; - } - - virtual c2_status_t fetchGraphicBlock( - uint32_t width, uint32_t height, uint32_t format, - C2MemoryUsage usage, - std::shared_ptr<C2GraphicBlock>* block) { - c2_status_t status; - do { - status = mBase->fetchGraphicBlock(width, height, format, usage, - block); - } while (status == C2_TIMED_OUT); - return status; - } - -private: - std::shared_ptr<C2BlockPool> mBase; -}; - //////////////////////////////////////////////////////////////////////////////// namespace { @@ -496,16 +446,12 @@ bool SimpleC2Component::processQueue() { } } - std::shared_ptr<C2BlockPool> blockPool; - err = GetCodec2BlockPool(poolId, shared_from_this(), &blockPool); + err = GetCodec2BlockPool(poolId, shared_from_this(), &mOutputBlockPool); ALOGD("Using output block pool with poolID %llu => got %llu - %d", (unsigned long long)poolId, (unsigned long long)( - blockPool ? blockPool->getLocalId() : 111000111), + mOutputBlockPool ? mOutputBlockPool->getLocalId() : 111000111), err); - if (err == C2_OK) { - mOutputBlockPool = std::make_shared<BlockingBlockPool>(blockPool); - } return err; }(); if (err != C2_OK) { @@ -543,6 +489,13 @@ bool SimpleC2Component::processQueue() { } ALOGV("start processing frame #%" PRIu64, work->input.ordinal.frameIndex.peeku()); + // If input buffer list is not empty, it means we have some input to process on. + // However, input could be a null buffer. In such case, clear the buffer list + // before making call to process(). + if (!work->input.buffers.empty() && !work->input.buffers[0]) { + ALOGD("Encountered null input buffer. Clearing the input buffer"); + work->input.buffers.clear(); + } process(work, mOutputBlockPool); ALOGV("processed frame #%" PRIu64, work->input.ordinal.frameIndex.peeku()); { diff --git a/media/codecs/base/include/SimpleC2Component.h b/media/codecs/base/include/SimpleC2Component.h index 43029a9..b3a98f4 100644 --- a/media/codecs/base/include/SimpleC2Component.h +++ b/media/codecs/base/include/SimpleC2Component.h @@ -234,8 +234,7 @@ private: typedef std::unordered_map<uint64_t, std::unique_ptr<C2Work>> PendingWork; Mutexed<PendingWork> mPendingWork; - class BlockingBlockPool; - std::shared_ptr<BlockingBlockPool> mOutputBlockPool; + std::shared_ptr<C2BlockPool> mOutputBlockPool; SimpleC2Component() = delete; }; diff --git a/media/codecs/cmds/Android.bp b/media/codecs/cmds/Android.bp index 4019fbe..6a4ca8c 100644 --- a/media/codecs/cmds/Android.bp +++ b/media/codecs/cmds/Android.bp @@ -15,7 +15,6 @@ cc_binary { "libcutils", "libgui", "liblog", - "libmediaextractor", "libstagefright", "libstagefright_foundation", "libui", @@ -25,7 +24,6 @@ cc_binary { cflags: [ "-Werror", "-Wall", - "-std=c++14", ], sanitize: { diff --git a/media/codecs/cmds/codec2.cpp b/media/codecs/cmds/codec2.cpp index 4d0fdd4..f2cf545 100644 --- a/media/codecs/cmds/codec2.cpp +++ b/media/codecs/cmds/codec2.cpp @@ -33,7 +33,6 @@ #include <media/DataSource.h> #include <media/ICrypto.h> #include <media/IMediaHTTPService.h> -#include <media/MediaExtractor.h> #include <media/MediaSource.h> #include <media/stagefright/foundation/ABuffer.h> #include <media/stagefright/foundation/ALooper.h> @@ -155,7 +154,7 @@ SimplePlayer::SimplePlayer() HAL_PIXEL_FORMAT_YV12); //PIXEL_FORMAT_RGB_565); - CHECK(mControl != NULL); + CHECK(mControl != nullptr); CHECK(mControl->isValid()); SurfaceComposerClient::Transaction{} @@ -164,7 +163,7 @@ SimplePlayer::SimplePlayer() .apply(); mSurface = mControl->getSurface(); - CHECK(mSurface != NULL); + CHECK(mSurface != nullptr); mSurface->connect(NATIVE_WINDOW_API_CPU, mProducerListener); } @@ -305,7 +304,7 @@ void SimplePlayer::play(const sp<IMediaSource> &source) { } else { status_t err = source->read(&buffer); if (err != OK) { - CHECK(buffer == NULL); + CHECK(buffer == nullptr); if (err == INFO_FORMAT_CHANGED) { continue; @@ -367,7 +366,7 @@ void SimplePlayer::play(const sp<IMediaSource> &source) { if (buffer) { buffer->release(); - buffer = NULL; + buffer = nullptr; } ++numFrames; @@ -419,9 +418,9 @@ int main(int argc, char **argv) { const char *filename = argv[k]; sp<DataSource> dataSource = - DataSourceFactory::CreateFromURI(NULL /* httpService */, filename); + DataSourceFactory::CreateFromURI(nullptr /* httpService */, filename); - if (strncasecmp(filename, "sine:", 5) && dataSource == NULL) { + if (strncasecmp(filename, "sine:", 5) && dataSource == nullptr) { fprintf(stderr, "Unable to create data source.\n"); return 1; } @@ -431,14 +430,14 @@ int main(int argc, char **argv) { sp<IMediaExtractor> extractor = MediaExtractorFactory::Create(dataSource); - if (extractor == NULL) { + if (extractor == nullptr) { fprintf(stderr, "could not create extractor.\n"); return -1; } sp<MetaData> meta = extractor->getMetaData(); - if (meta != NULL) { + if (meta != nullptr) { const char *mime; if (!meta->findCString(kKeyMIMEType, &mime)) { fprintf(stderr, "extractor did not provide MIME type.\n"); @@ -450,10 +449,9 @@ int main(int argc, char **argv) { size_t i; for (i = 0; i < numTracks; ++i) { - meta = extractor->getTrackMetaData( - i, MediaExtractor::kIncludeExtensiveMetaData); + meta = extractor->getTrackMetaData(i, 0); - if (meta == NULL) { + if (meta == nullptr) { break; } const char *mime; @@ -464,10 +462,10 @@ int main(int argc, char **argv) { break; } - meta = NULL; + meta = nullptr; } - if (meta == NULL) { + if (meta == nullptr) { fprintf(stderr, "No AVC track found.\n"); return -1; } diff --git a/media/codecs/flac/C2SoftFlacDec.cpp b/media/codecs/flac/C2SoftFlacDec.cpp index 1358d66..f1e2f51 100644 --- a/media/codecs/flac/C2SoftFlacDec.cpp +++ b/media/codecs/flac/C2SoftFlacDec.cpp @@ -169,9 +169,12 @@ static void fillEmptyWork(const std::unique_ptr<C2Work> &work) { void C2SoftFlacDec::process( const std::unique_ptr<C2Work> &work, const std::shared_ptr<C2BlockPool> &pool) { + // Initialize output work work->result = C2_OK; - work->workletsProcessed = 0u; + work->workletsProcessed = 1u; work->worklets.front()->output.configUpdate.clear(); + work->worklets.front()->output.flags = work->input.flags; + if (mSignalledError || mSignalledOutputEos) { work->result = C2_BAD_VALUE; return; @@ -297,7 +300,6 @@ void C2SoftFlacDec::process( work->worklets.front()->output.buffers.clear(); work->worklets.front()->output.buffers.push_back(createLinearBuffer(block, 0, outSize)); work->worklets.front()->output.ordinal = work->input.ordinal; - work->workletsProcessed = 1u; if (eos) { mSignalledOutputEos = true; ALOGV("signalled EOS"); diff --git a/media/codecs/flac/C2SoftFlacEnc.cpp b/media/codecs/flac/C2SoftFlacEnc.cpp index 13c888e..e4192c7 100644 --- a/media/codecs/flac/C2SoftFlacEnc.cpp +++ b/media/codecs/flac/C2SoftFlacEnc.cpp @@ -176,8 +176,11 @@ static void fillEmptyWork(const std::unique_ptr<C2Work> &work) { void C2SoftFlacEnc::process( const std::unique_ptr<C2Work> &work, const std::shared_ptr<C2BlockPool> &pool) { + // Initialize output work work->result = C2_OK; work->workletsProcessed = 1u; + work->worklets.front()->output.flags = work->input.flags; + if (mSignalledError || mSignalledOutputEos) { work->result = C2_BAD_VALUE; return; @@ -208,7 +211,12 @@ void C2SoftFlacEnc::process( if (!mWroteHeader) { std::unique_ptr<C2StreamCsdInfo::output> csd = C2StreamCsdInfo::output::AllocUnique(mHeaderOffset, 0u); - // TODO: check NO_MEMORY + if (!csd) { + ALOGE("CSD allocation failed"); + mSignalledError = true; + work->result = C2_NO_MEMORY; + return; + } memcpy(csd->m.value, mHeader, mHeaderOffset); ALOGV("put csd, %d bytes", mHeaderOffset); diff --git a/media/codecs/g711/C2SoftG711Dec.cpp b/media/codecs/g711/C2SoftG711Dec.cpp index 2f84b46..1c71d45 100644 --- a/media/codecs/g711/C2SoftG711Dec.cpp +++ b/media/codecs/g711/C2SoftG711Dec.cpp @@ -140,8 +140,11 @@ c2_status_t C2SoftG711Dec::onFlush_sm() { void C2SoftG711Dec::process( const std::unique_ptr<C2Work> &work, const std::shared_ptr<C2BlockPool> &pool) { + // Initialize output work work->result = C2_OK; - work->workletsProcessed = 0u; + work->workletsProcessed = 1u; + work->worklets.front()->output.flags = work->input.flags; + if (mSignalledOutputEos) { work->result = C2_BAD_VALUE; return; @@ -169,7 +172,6 @@ void C2SoftG711Dec::process( work->worklets.front()->output.flags = work->input.flags; work->worklets.front()->output.buffers.clear(); work->worklets.front()->output.ordinal = work->input.ordinal; - work->workletsProcessed = 1u; if (eos) { mSignalledOutputEos = true; ALOGV("signalled EOS"); @@ -205,7 +207,6 @@ void C2SoftG711Dec::process( work->worklets.front()->output.buffers.clear(); work->worklets.front()->output.buffers.push_back(createLinearBuffer(block)); work->worklets.front()->output.ordinal = work->input.ordinal; - work->workletsProcessed = 1u; if (eos) { mSignalledOutputEos = true; diff --git a/media/codecs/gsm/C2SoftGsmDec.cpp b/media/codecs/gsm/C2SoftGsmDec.cpp index eedaa14..7101c79 100644 --- a/media/codecs/gsm/C2SoftGsmDec.cpp +++ b/media/codecs/gsm/C2SoftGsmDec.cpp @@ -174,8 +174,11 @@ static size_t decodeGSM(gsm handle, int16_t *out, size_t outCapacity, void C2SoftGsmDec::process( const std::unique_ptr<C2Work> &work, const std::shared_ptr<C2BlockPool> &pool) { + // Initialize output work work->result = C2_OK; - work->workletsProcessed = 0u; + work->workletsProcessed = 1u; + work->worklets.front()->output.flags = work->input.flags; + if (mSignalledError || mSignalledEos) { work->result = C2_BAD_VALUE; return; @@ -199,7 +202,6 @@ void C2SoftGsmDec::process( work->worklets.front()->output.flags = work->input.flags; work->worklets.front()->output.buffers.clear(); work->worklets.front()->output.ordinal = work->input.ordinal; - work->workletsProcessed = 1u; if (eos) { mSignalledEos = true; ALOGV("signalled EOS"); @@ -239,7 +241,6 @@ void C2SoftGsmDec::process( work->worklets.front()->output.buffers.clear(); work->worklets.front()->output.buffers.push_back(createLinearBuffer(block, 0, outSize)); work->worklets.front()->output.ordinal = work->input.ordinal; - work->workletsProcessed = 1u; if (eos) { mSignalledEos = true; ALOGV("signalled EOS"); diff --git a/media/codecs/hevc/C2SoftHevcDec.cpp b/media/codecs/hevc/C2SoftHevcDec.cpp index f71a7c5..99892ce 100644 --- a/media/codecs/hevc/C2SoftHevcDec.cpp +++ b/media/codecs/hevc/C2SoftHevcDec.cpp @@ -324,7 +324,8 @@ C2SoftHevcDec::C2SoftHevcDec( mOutBufferFlush(nullptr), mIvColorformat(IV_YUV_420P), mWidth(320), - mHeight(240) { + mHeight(240), + mHeaderDecoded(false) { } C2SoftHevcDec::~C2SoftHevcDec() { @@ -381,8 +382,10 @@ c2_status_t C2SoftHevcDec::onFlush_sm() { } } - ivd_aligned_free(nullptr, mOutBufferFlush); - mOutBufferFlush = nullptr; + if (mOutBufferFlush) { + ivd_aligned_free(nullptr, mOutBufferFlush); + mOutBufferFlush = nullptr; + } return C2_OK; } @@ -434,7 +437,7 @@ status_t C2SoftHevcDec::setNumCores() { return OK; } -status_t C2SoftHevcDec::setParams(size_t stride) { +status_t C2SoftHevcDec::setParams(size_t stride, IVD_VIDEO_DECODE_MODE_T dec_mode) { ivd_ctl_set_config_ip_t s_set_dyn_params_ip; ivd_ctl_set_config_op_t s_set_dyn_params_op; @@ -444,7 +447,7 @@ status_t C2SoftHevcDec::setParams(size_t stride) { s_set_dyn_params_ip.u4_disp_wd = (UWORD32) stride; s_set_dyn_params_ip.e_frm_skip_mode = IVD_SKIP_NONE; s_set_dyn_params_ip.e_frm_out_mode = IVD_DISPLAY_FRAME_OUT; - s_set_dyn_params_ip.e_vid_dec_mode = IVD_DECODE_FRAME; + s_set_dyn_params_ip.e_vid_dec_mode = dec_mode; s_set_dyn_params_op.u4_size = sizeof(ivd_ctl_set_config_op_t); IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle, &s_set_dyn_params_ip, @@ -489,7 +492,7 @@ status_t C2SoftHevcDec::initDecoder() { mSignalledError = false; resetPlugin(); (void) setNumCores(); - if (OK != setParams(mStride)) return UNKNOWN_ERROR; + if (OK != setParams(mStride, IVD_DECODE_FRAME)) return UNKNOWN_ERROR; (void) getVersion(); return OK; @@ -628,7 +631,7 @@ status_t C2SoftHevcDec::resetDecoder() { mStride = 0; (void) setNumCores(); mSignalledError = false; - + mHeaderDecoded = false; return OK; } @@ -681,14 +684,8 @@ void C2SoftHevcDec::finishWork(uint64_t index, const std::unique_ptr<C2Work> &wo buffer->setInfo(mIntf->getColorAspects_l()); } - auto fillWork = [buffer, index](const std::unique_ptr<C2Work> &work) { - uint32_t flags = 0; - if ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) && - (c2_cntr64_t(index) == work->input.ordinal.frameIndex)) { - flags |= C2FrameData::FLAG_END_OF_STREAM; - ALOGV("signalling eos"); - } - work->worklets.front()->output.flags = (C2FrameData::flags_t)flags; + auto fillWork = [buffer](const std::unique_ptr<C2Work> &work) { + work->worklets.front()->output.flags = (C2FrameData::flags_t)0; work->worklets.front()->output.buffers.clear(); work->worklets.front()->output.buffers.push_back(buffer); work->worklets.front()->output.ordinal = work->input.ordinal; @@ -708,7 +705,7 @@ c2_status_t C2SoftHevcDec::ensureDecoderState(const std::shared_ptr<C2BlockPool> } if (mStride != ALIGN64(mWidth)) { mStride = ALIGN64(mWidth); - if (OK != setParams(mStride)) return C2_CORRUPTED; + if (OK != setParams(mStride, IVD_DECODE_FRAME)) return C2_CORRUPTED; } if (mOutBlock && (mOutBlock->width() != mStride || mOutBlock->height() != mHeight)) { @@ -738,9 +735,12 @@ c2_status_t C2SoftHevcDec::ensureDecoderState(const std::shared_ptr<C2BlockPool> void C2SoftHevcDec::process( const std::unique_ptr<C2Work> &work, const std::shared_ptr<C2BlockPool> &pool) { + // Initialize output work work->result = C2_OK; work->workletsProcessed = 0u; work->worklets.front()->output.configUpdate.clear(); + work->worklets.front()->output.flags = work->input.flags; + if (mSignalledError || mSignalledOutputEos) { work->result = C2_BAD_VALUE; return; @@ -769,6 +769,7 @@ void C2SoftHevcDec::process( while (inPos < inSize) { if (C2_OK != ensureDecoderState(pool)) { mSignalledError = true; + work->workletsProcessed = 1u; work->result = C2_CORRUPTED; return; } @@ -783,11 +784,17 @@ void C2SoftHevcDec::process( if (!setDecodeArgs(&s_decode_ip, &s_decode_op, &rView, &wView, inOffset + inPos, inSize - inPos, workIndex)) { mSignalledError = true; + work->workletsProcessed = 1u; work->result = C2_CORRUPTED; return; } + + if (false == mHeaderDecoded) { + /* Decode header and get dimensions */ + setParams(mStride, IVD_DECODE_HEADER); + } WORD32 delay; - GETTIME(&mTimeStart, NULL); + GETTIME(&mTimeStart, nullptr); TIME_DIFF(mTimeEnd, mTimeStart, delay); (void) ivdec_api_function(mDecHandle, &s_decode_ip, &s_decode_op); WORD32 decodeTime; @@ -797,22 +804,32 @@ void C2SoftHevcDec::process( s_decode_op.u4_num_bytes_consumed); if (IVD_MEM_ALLOC_FAILED == (s_decode_op.u4_error_code & 0xFF)) { ALOGE("allocation failure in decoder"); - work->result = C2_CORRUPTED; mSignalledError = true; + work->workletsProcessed = 1u; + work->result = C2_CORRUPTED; return; } else if (IVD_STREAM_WIDTH_HEIGHT_NOT_SUPPORTED == (s_decode_op.u4_error_code & 0xFF)) { ALOGE("unsupported resolution : %dx%d", mWidth, mHeight); - work->result = C2_CORRUPTED; mSignalledError = true; + work->workletsProcessed = 1u; + work->result = C2_CORRUPTED; return; } else if (IVD_RES_CHANGED == (s_decode_op.u4_error_code & 0xFF)) { ALOGV("resolution changed"); drainInternal(DRAIN_COMPONENT_NO_EOS, pool, work); resetDecoder(); resetPlugin(); - continue; + work->workletsProcessed = 0u; + + /* Decode header and get new dimensions */ + setParams(mStride, IVD_DECODE_HEADER); + (void) ivdec_api_function(mDecHandle, &s_decode_ip, &s_decode_op); } if (0 < s_decode_op.u4_pic_wd && 0 < s_decode_op.u4_pic_ht) { + if (mHeaderDecoded == false) { + mHeaderDecoded = true; + setParams(ALIGN64(s_decode_op.u4_pic_wd), IVD_DECODE_FRAME); + } if (s_decode_op.u4_pic_wd != mWidth || s_decode_op.u4_pic_ht != mHeight) { mWidth = s_decode_op.u4_pic_wd; mHeight = s_decode_op.u4_pic_ht; @@ -828,9 +845,11 @@ void C2SoftHevcDec::process( } else { ALOGE("Cannot set width and height"); mSignalledError = true; + work->workletsProcessed = 1u; work->result = C2_CORRUPTED; return; } + continue; } } (void) getVuiParams(); @@ -838,6 +857,10 @@ void C2SoftHevcDec::process( if (s_decode_op.u4_output_present) { finishWork(s_decode_op.u4_ts, work); } + if (0 == s_decode_op.u4_num_bytes_consumed) { + ALOGD("Bytes consumed is zero. Ignoring remaining bytes"); + break; + } inPos += s_decode_op.u4_num_bytes_consumed; if (hasPicture && (inSize - inPos)) { ALOGD("decoded frame in current access nal, ignoring further trailing bytes %d", @@ -871,6 +894,7 @@ c2_status_t C2SoftHevcDec::drainInternal( while (true) { if (C2_OK != ensureDecoderState(pool)) { mSignalledError = true; + work->workletsProcessed = 1u; work->result = C2_CORRUPTED; return C2_CORRUPTED; } @@ -883,21 +907,18 @@ c2_status_t C2SoftHevcDec::drainInternal( ivd_video_decode_op_t s_decode_op; if (!setDecodeArgs(&s_decode_ip, &s_decode_op, nullptr, &wView, 0, 0, 0)) { mSignalledError = true; + work->workletsProcessed = 1u; return C2_CORRUPTED; } (void) ivdec_api_function(mDecHandle, &s_decode_ip, &s_decode_op); if (s_decode_op.u4_output_present) { finishWork(s_decode_op.u4_ts, work); } else { + fillEmptyWork(work); break; } } - if (drainMode == DRAIN_COMPONENT_WITH_EOS && - work && work->workletsProcessed == 0u) { - fillEmptyWork(work); - } - return C2_OK; } diff --git a/media/codecs/hevc/C2SoftHevcDec.h b/media/codecs/hevc/C2SoftHevcDec.h index 2b3154d..75111fc 100644 --- a/media/codecs/hevc/C2SoftHevcDec.h +++ b/media/codecs/hevc/C2SoftHevcDec.h @@ -69,7 +69,7 @@ struct C2SoftHevcDec : public SimpleC2Component { private: status_t createDecoder(); status_t setNumCores(); - status_t setParams(size_t stride); + status_t setParams(size_t stride, IVD_VIDEO_DECODE_MODE_T dec_mode); status_t getVersion(); status_t initDecoder(); bool setDecodeArgs(ivd_video_decode_ip_t *ps_decode_ip, @@ -120,6 +120,7 @@ struct C2SoftHevcDec : public SimpleC2Component { uint32_t mStride; bool mSignalledOutputEos; bool mSignalledError; + bool mHeaderDecoded; // Color aspects. These are ISO values and are meant to detect changes in aspects to avoid // converting them to C2 values for each frame diff --git a/media/codecs/mp3/C2SoftMp3Dec.cpp b/media/codecs/mp3/C2SoftMp3Dec.cpp index 48de625..c8b8397 100644 --- a/media/codecs/mp3/C2SoftMp3Dec.cpp +++ b/media/codecs/mp3/C2SoftMp3Dec.cpp @@ -336,9 +336,12 @@ c2_status_t C2SoftMP3::drain( void C2SoftMP3::process( const std::unique_ptr<C2Work> &work, const std::shared_ptr<C2BlockPool> &pool) { + // Initialize output work work->result = C2_OK; - work->workletsProcessed = 0u; + work->workletsProcessed = 1u; work->worklets.front()->output.configUpdate.clear(); + work->worklets.front()->output.flags = work->input.flags; + if (mSignalledError || mSignalledOutputEos) { work->result = C2_BAD_VALUE; return; @@ -361,7 +364,6 @@ void C2SoftMP3::process( work->worklets.front()->output.flags = work->input.flags; work->worklets.front()->output.buffers.clear(); work->worklets.front()->output.ordinal = work->input.ordinal; - work->workletsProcessed = 1u; return; } ALOGV("in buffer attr. size %zu timestamp %d frameindex %d", inSize, @@ -494,7 +496,6 @@ void C2SoftMP3::process( mProcessedSamples += ((outSize - outOffset) / (numChannels * sizeof(int16_t))); ALOGV("out buffer attr. offset %d size %d timestamp %u", outOffset, outSize - outOffset, (uint32_t)(mAnchorTimeStamp + outTimeStamp)); - decodedSizes.clear(); work->worklets.front()->output.flags = work->input.flags; work->worklets.front()->output.buffers.clear(); @@ -502,7 +503,6 @@ void C2SoftMP3::process( createLinearBuffer(block, outOffset, outSize - outOffset)); work->worklets.front()->output.ordinal = work->input.ordinal; work->worklets.front()->output.ordinal.timestamp = mAnchorTimeStamp + outTimeStamp; - work->workletsProcessed = 1u; if (eos) { mSignalledOutputEos = true; ALOGV("signalled EOS"); diff --git a/media/codecs/mpeg2/C2SoftMpeg2Dec.cpp b/media/codecs/mpeg2/C2SoftMpeg2Dec.cpp index 855598d..da32ec0 100644 --- a/media/codecs/mpeg2/C2SoftMpeg2Dec.cpp +++ b/media/codecs/mpeg2/C2SoftMpeg2Dec.cpp @@ -379,8 +379,10 @@ c2_status_t C2SoftMpeg2Dec::onFlush_sm() { } } - ivd_aligned_free(mOutBufferDrain); - mOutBufferDrain = nullptr; + if (mOutBufferDrain) { + ivd_aligned_free(mOutBufferDrain); + mOutBufferDrain = nullptr; + } return C2_OK; } @@ -764,14 +766,8 @@ void C2SoftMpeg2Dec::finishWork(uint64_t index, const std::unique_ptr<C2Work> &w buffer->setInfo(mIntf->getColorAspects_l()); } - auto fillWork = [buffer, index](const std::unique_ptr<C2Work> &work) { - uint32_t flags = 0; - if ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) && - (c2_cntr64_t(index) == work->input.ordinal.frameIndex)) { - flags |= C2FrameData::FLAG_END_OF_STREAM; - ALOGV("signalling eos"); - } - work->worklets.front()->output.flags = (C2FrameData::flags_t)flags; + auto fillWork = [buffer](const std::unique_ptr<C2Work> &work) { + work->worklets.front()->output.flags = (C2FrameData::flags_t)0; work->worklets.front()->output.buffers.clear(); work->worklets.front()->output.buffers.push_back(buffer); work->worklets.front()->output.ordinal = work->input.ordinal; @@ -812,29 +808,6 @@ c2_status_t C2SoftMpeg2Dec::ensureDecoderState(const std::shared_ptr<C2BlockPool return C2_OK; } -void C2SoftMpeg2Dec::setTimeStampFrameIndexMap(uint64_t frameIndex, uint64_t timeStamp) { - mFrameIndices[timeStamp] = frameIndex; - return; -} - -uint32_t C2SoftMpeg2Dec::getMinTimeStampFrameIndex() { - uint64_t frameIndex = 0; - uint64_t minTimeStamp = 0; - std::map<uint64_t , uint64_t >::iterator it = mFrameIndices.begin(); - - minTimeStamp = it->first; - while(it != mFrameIndices.end()) { - if(minTimeStamp > it->first) minTimeStamp = it->first; - it++; - } - if (mFrameIndices.count(minTimeStamp) == 0u) { - ALOGV("timestamp %d not tracked", (int)minTimeStamp); - } else { - frameIndex = mFrameIndices[minTimeStamp]; - mFrameIndices.erase(minTimeStamp); - } - return frameIndex & 0xFFFFFFFF; -} // TODO: can overall error checking be improved? // TODO: allow configuration of color format and usage for graphic buffers instead // of hard coding them to HAL_PIXEL_FORMAT_YV12 @@ -844,9 +817,12 @@ uint32_t C2SoftMpeg2Dec::getMinTimeStampFrameIndex() { void C2SoftMpeg2Dec::process( const std::unique_ptr<C2Work> &work, const std::shared_ptr<C2BlockPool> &pool) { + // Initialize output work work->result = C2_OK; work->workletsProcessed = 0u; work->worklets.front()->output.configUpdate.clear(); + work->worklets.front()->output.flags = work->input.flags; + if (mSignalledError || mSignalledOutputEos) { work->result = C2_BAD_VALUE; return; @@ -864,8 +840,6 @@ void C2SoftMpeg2Dec::process( work->result = C2_CORRUPTED; return; } - setTimeStampFrameIndexMap((uint64_t)work->input.ordinal.frameIndex.peeku(), - (uint64_t)work->input.ordinal.timestamp.peeku()); } bool eos = ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0); bool hasPicture = false; @@ -877,6 +851,7 @@ void C2SoftMpeg2Dec::process( while (inPos < inSize) { if (C2_OK != ensureDecoderState(pool)) { mSignalledError = true; + work->workletsProcessed = 1u; work->result = C2_CORRUPTED; return; } @@ -892,13 +867,14 @@ void C2SoftMpeg2Dec::process( if (!setDecodeArgs(&s_decode_ip, &s_decode_op, &rView, &wView, inOffset + inPos, inSize - inPos, workIndex)) { mSignalledError = true; + work->workletsProcessed = 1u; work->result = C2_CORRUPTED; return; } // If input dump is enabled, then write to file DUMP_TO_FILE(mInFile, s_decode_ip.pv_stream_buffer, s_decode_ip.u4_num_Bytes); WORD32 delay; - GETTIME(&mTimeStart, NULL); + GETTIME(&mTimeStart, nullptr); TIME_DIFF(mTimeEnd, mTimeStart, delay); (void) ivdec_api_function(mDecHandle, &s_decode_ip, &s_decode_op); WORD32 decodeTime; @@ -910,6 +886,7 @@ void C2SoftMpeg2Dec::process( ALOGV("unsupported resolution : %dx%d", s_decode_op.u4_pic_wd, s_decode_op.u4_pic_ht); drainInternal(DRAIN_COMPONENT_NO_EOS, pool, work); resetPlugin(); + work->workletsProcessed = 0u; mWidth = s_decode_op.u4_pic_wd; mHeight = s_decode_op.u4_pic_ht; @@ -925,6 +902,7 @@ void C2SoftMpeg2Dec::process( } else { ALOGE("Cannot set width and height"); mSignalledError = true; + work->workletsProcessed = 1u; work->result = C2_CORRUPTED; return; } @@ -932,6 +910,7 @@ void C2SoftMpeg2Dec::process( if (OK != reInitDecoder()) { ALOGE("Failed to reinitialize decoder"); mSignalledError = true; + work->workletsProcessed = 1u; work->result = C2_CORRUPTED; return; } @@ -941,6 +920,7 @@ void C2SoftMpeg2Dec::process( drainInternal(DRAIN_COMPONENT_NO_EOS, pool, work); resetDecoder(); resetPlugin(); + work->workletsProcessed = 0u; continue; } if (0 < s_decode_op.u4_pic_wd && 0 < s_decode_op.u4_pic_ht) { @@ -961,6 +941,7 @@ void C2SoftMpeg2Dec::process( } else { ALOGE("Cannot set width and height"); mSignalledError = true; + work->workletsProcessed = 1u; work->result = C2_CORRUPTED; return; } @@ -970,8 +951,9 @@ void C2SoftMpeg2Dec::process( (void) getSeqInfo(); hasPicture |= (1 == s_decode_op.u4_frame_decoded_flag); if (s_decode_op.u4_output_present) { - finishWork(getMinTimeStampFrameIndex(), work); + finishWork(s_decode_op.u4_ts, work); } + inPos += s_decode_op.u4_num_bytes_consumed; if (hasPicture && (inSize - inPos) != 0) { ALOGD("decoded frame in current access nal, ignoring further trailing bytes %d", @@ -1005,6 +987,7 @@ c2_status_t C2SoftMpeg2Dec::drainInternal( while (true) { if (C2_OK != ensureDecoderState(pool)) { mSignalledError = true; + work->workletsProcessed = 1u; work->result = C2_CORRUPTED; return C2_CORRUPTED; } @@ -1017,19 +1000,17 @@ c2_status_t C2SoftMpeg2Dec::drainInternal( ivd_video_decode_op_t s_decode_op; if (!setDecodeArgs(&s_decode_ip, &s_decode_op, nullptr, &wView, 0, 0, 0)) { mSignalledError = true; + work->workletsProcessed = 1u; return C2_CORRUPTED; } (void) ivdec_api_function(mDecHandle, &s_decode_ip, &s_decode_op); if (s_decode_op.u4_output_present) { - finishWork(getMinTimeStampFrameIndex(), work); + finishWork(s_decode_op.u4_ts, work); } else { + fillEmptyWork(work); break; } } - if (drainMode == DRAIN_COMPONENT_WITH_EOS && - work && work->workletsProcessed == 0u) { - fillEmptyWork(work); - } return C2_OK; } diff --git a/media/codecs/mpeg2/C2SoftMpeg2Dec.h b/media/codecs/mpeg2/C2SoftMpeg2Dec.h index 2ea3d85..9999872 100644 --- a/media/codecs/mpeg2/C2SoftMpeg2Dec.h +++ b/media/codecs/mpeg2/C2SoftMpeg2Dec.h @@ -136,8 +136,7 @@ struct C2SoftMpeg2Dec : public SimpleC2Component { void resetPlugin(); status_t deleteDecoder(); status_t reInitDecoder(); - uint32_t getMinTimeStampFrameIndex(); - void setTimeStampFrameIndexMap(uint64_t frameIndex, uint64_t timeStamp); + // TODO:This is not the right place for this enum. These should // be part of c2-vndk so that they can be accessed by all video plugins // until then, make them feel at home @@ -181,8 +180,6 @@ struct C2SoftMpeg2Dec : public SimpleC2Component { } } mBitstreamColorAspects; - std::map<uint64_t , uint64_t > mFrameIndices; - // profile struct timeval mTimeStart; struct timeval mTimeEnd; diff --git a/media/codecs/mpeg4_h263/C2SoftMpeg4Dec.cpp b/media/codecs/mpeg4_h263/C2SoftMpeg4Dec.cpp index 793a515..901f5ed 100644 --- a/media/codecs/mpeg4_h263/C2SoftMpeg4Dec.cpp +++ b/media/codecs/mpeg4_h263/C2SoftMpeg4Dec.cpp @@ -324,6 +324,10 @@ status_t C2SoftMpeg4Dec::initDecoder() { if (!mDecHandle) { mDecHandle = new tagvideoDecControls; } + if (!mDecHandle) { + ALOGE("mDecHandle is null"); + return NO_MEMORY; + } memset(mDecHandle, 0, sizeof(tagvideoDecControls)); /* TODO: bring these values to 352 and 288. It cannot be done as of now @@ -446,8 +450,8 @@ bool C2SoftMpeg4Dec::handleResChange(const std::unique_ptr<C2Work> &work) { if (!PVInitVideoDecoder( mDecHandle, vol_data, &vol_size, 1, mIntf->getMaxWidth(), mIntf->getMaxHeight(), H263_MODE)) { ALOGE("Error in PVInitVideoDecoder H263_MODE while resChanged was set to true"); - work->result = C2_CORRUPTED; mSignalledError = true; + work->result = C2_CORRUPTED; return true; } } @@ -491,12 +495,14 @@ static void copyOutputBufferToYV12Frame(uint8_t *dst, uint8_t *src, size_t dstYS void C2SoftMpeg4Dec::process( const std::unique_ptr<C2Work> &work, const std::shared_ptr<C2BlockPool> &pool) { + // Initialize output work work->result = C2_OK; - work->workletsProcessed = 0u; + work->workletsProcessed = 1u; work->worklets.front()->output.configUpdate.clear(); + work->worklets.front()->output.flags = work->input.flags; + if (mSignalledError || mSignalledOutputEos) { work->result = C2_BAD_VALUE; - work->workletsProcessed = 1u; return; } @@ -548,18 +554,16 @@ void C2SoftMpeg4Dec::process( mDecHandle, vol_data, &vol_size, 1, mIntf->getMaxWidth(), mIntf->getMaxHeight(), mode)) { ALOGE("PVInitVideoDecoder failed. Unsupported content?"); - work->result = C2_CORRUPTED; - work->workletsProcessed = 1u; mSignalledError = true; + work->result = C2_CORRUPTED; return; } mInitialized = true; MP4DecodingMode actualMode = PVGetDecBitstreamMode(mDecHandle); if (mode != actualMode) { ALOGE("Decoded mode not same as actual mode of the decoder"); - work->result = C2_CORRUPTED; - work->workletsProcessed = 1u; mSignalledError = true; + work->result = C2_CORRUPTED; return; } @@ -576,7 +580,6 @@ void C2SoftMpeg4Dec::process( ALOGE("Config update size failed"); mSignalledError = true; work->result = C2_CORRUPTED; - work->workletsProcessed = 1u; return; } } @@ -592,23 +595,20 @@ void C2SoftMpeg4Dec::process( if (C2_OK != err) { mSignalledError = true; work->result = err; - work->workletsProcessed = 1u; return; } C2GraphicView wView = mOutBlock->map().get(); if (wView.error()) { ALOGE("graphic view map failed %d", wView.error()); work->result = C2_CORRUPTED; - work->workletsProcessed = 1u; return; } uint32_t yFrameSize = sizeof(uint8) * mDecHandle->size; if (mOutputBufferSize < yFrameSize * 3 / 2){ ALOGE("Too small output buffer: %zu bytes", mOutputBufferSize); - work->result = C2_NO_MEMORY; - work->workletsProcessed = 1u; mSignalledError = true; + work->result = C2_NO_MEMORY; return; } @@ -628,9 +628,8 @@ void C2SoftMpeg4Dec::process( &header_info, &useExtTimestamp, mOutputBuffer[mNumSamplesOutput & 1]) != PV_TRUE) { ALOGE("failed to decode vop header."); - work->result = C2_CORRUPTED; - work->workletsProcessed = 1u; mSignalledError = true; + work->result = C2_CORRUPTED; return; } @@ -638,9 +637,8 @@ void C2SoftMpeg4Dec::process( // decoder may detect size change after PVDecodeVopHeader. bool resChange = handleResChange(work); if (mIsMpeg4 && resChange) { - work->result = C2_CORRUPTED; - work->workletsProcessed = 1u; mSignalledError = true; + work->result = C2_CORRUPTED; return; } else if (resChange) { ALOGI("Setting width and height"); @@ -653,7 +651,6 @@ void C2SoftMpeg4Dec::process( ALOGE("Config update size failed"); mSignalledError = true; work->result = C2_CORRUPTED; - work->workletsProcessed = 1u; return; } continue; @@ -661,15 +658,13 @@ void C2SoftMpeg4Dec::process( if (PVDecodeVopBody(mDecHandle, &tmpInSize) != PV_TRUE) { ALOGE("failed to decode video frame."); - work->result = C2_CORRUPTED; - work->workletsProcessed = 1u; mSignalledError = true; + work->result = C2_CORRUPTED; return; } if (handleResChange(work)) { - work->result = C2_CORRUPTED; - work->workletsProcessed = 1u; mSignalledError = true; + work->result = C2_CORRUPTED; return; } diff --git a/media/codecs/mpeg4_h263/C2SoftMpeg4Enc.cpp b/media/codecs/mpeg4_h263/C2SoftMpeg4Enc.cpp index dd9d94b..c8796f3 100644 --- a/media/codecs/mpeg4_h263/C2SoftMpeg4Enc.cpp +++ b/media/codecs/mpeg4_h263/C2SoftMpeg4Enc.cpp @@ -93,8 +93,8 @@ class C2SoftMpeg4Enc::IntfImpl : public C2InterfaceHelper { C2F(mSize, width).inRange(16, 176, 16), C2F(mSize, height).inRange(16, 144, 16), #else - C2F(mSize, width).inRange(176, 176, 16), - C2F(mSize, height).inRange(144, 144, 16), + C2F(mSize, width).oneOf({176, 352}), + C2F(mSize, height).oneOf({144, 288}), #endif }) .withSetter(SizeSetter) @@ -253,9 +253,13 @@ c2_status_t C2SoftMpeg4Enc::onInit() { if (!mHandle) { mHandle = new tagvideoEncControls; } + if (!mEncParams) { mEncParams = new tagvideoEncOptions; } + + if (!(mEncParams && mHandle)) return C2_NO_MEMORY; + mSignalledOutputEos = false; mSignalledError = false; @@ -395,8 +399,10 @@ c2_status_t C2SoftMpeg4Enc::initEncoder() { void C2SoftMpeg4Enc::process( const std::unique_ptr<C2Work> &work, const std::shared_ptr<C2BlockPool> &pool) { + // Initialize output work work->result = C2_OK; work->workletsProcessed = 1u; + work->worklets.front()->output.flags = work->input.flags; if (mSignalledError || mSignalledOutputEos) { work->result = C2_BAD_VALUE; return; @@ -432,8 +438,8 @@ void C2SoftMpeg4Enc::process( int32_t outputSize = mOutBufferSize; if (!PVGetVolHeader(mHandle, outPtr, &outputSize, 0)) { ALOGE("Failed to get VOL header"); - work->result = C2_CORRUPTED; mSignalledError = true; + work->result = C2_CORRUPTED; return; } else { ALOGV("Bytes Generated in header %d\n", outputSize); @@ -442,6 +448,12 @@ void C2SoftMpeg4Enc::process( ++mNumInputFrames; std::unique_ptr<C2StreamCsdInfo::output> csd = C2StreamCsdInfo::output::AllocUnique(outputSize, 0u); + if (!csd) { + ALOGE("CSD allocation failed"); + mSignalledError = true; + work->result = C2_NO_MEMORY; + return; + } memcpy(csd->m.value, outPtr, outputSize); work->worklets.front()->output.configUpdate.push_back(std::move(csd)); } diff --git a/media/codecs/opus/C2SoftOpusDec.cpp b/media/codecs/opus/C2SoftOpusDec.cpp index 996a87c..2439c3c 100644 --- a/media/codecs/opus/C2SoftOpusDec.cpp +++ b/media/codecs/opus/C2SoftOpusDec.cpp @@ -300,9 +300,12 @@ static uint64_t ns_to_samples(uint64_t ns, int rate) { void C2SoftOpusDec::process( const std::unique_ptr<C2Work> &work, const std::shared_ptr<C2BlockPool> &pool) { + // Initialize output work work->result = C2_OK; - work->workletsProcessed = 0u; + work->workletsProcessed = 1u; work->worklets.front()->output.configUpdate.clear(); + work->worklets.front()->output.flags = work->input.flags; + if (mSignalledError || mSignalledOutputEos) { work->result = C2_BAD_VALUE; return; diff --git a/media/codecs/raw/C2SoftRawDec.cpp b/media/codecs/raw/C2SoftRawDec.cpp index 27895c4..5c83481 100644 --- a/media/codecs/raw/C2SoftRawDec.cpp +++ b/media/codecs/raw/C2SoftRawDec.cpp @@ -83,6 +83,18 @@ public: DefineParam(mInputMaxBufSize, C2_PARAMKEY_INPUT_MAX_BUFFER_SIZE) .withConstValue(new C2StreamMaxBufferSizeInfo::input(0u, 64 * 1024)) .build()); + + addParameter( + DefineParam(mPcmEncodingInfo, C2_PARAMKEY_PCM_ENCODING) + .withDefault(new C2StreamPcmEncodingInfo::output(0u, C2Config::PCM_16)) + .withFields({C2F(mPcmEncodingInfo, value).oneOf({ + C2Config::PCM_16, + C2Config::PCM_8, + C2Config::PCM_FLOAT}) + }) + .withSetter((Setter<decltype(*mPcmEncodingInfo)>::StrictValueWithNoDeps)) + .build()); + } private: @@ -94,6 +106,7 @@ private: std::shared_ptr<C2StreamChannelCountInfo::output> mChannelCount; std::shared_ptr<C2BitrateTuning::input> mBitrate; std::shared_ptr<C2StreamMaxBufferSizeInfo::input> mInputMaxBufSize; + std::shared_ptr<C2StreamPcmEncodingInfo::output> mPcmEncodingInfo; }; C2SoftRawDec::C2SoftRawDec( @@ -134,7 +147,8 @@ void C2SoftRawDec::process( const std::shared_ptr<C2BlockPool> &pool) { (void)pool; work->result = C2_OK; - work->workletsProcessed = 0u; + work->workletsProcessed = 1u; + if (mSignalledEos) { work->result = C2_BAD_VALUE; return; @@ -149,7 +163,6 @@ void C2SoftRawDec::process( if (!work->input.buffers.empty()) { work->worklets.front()->output.buffers.push_back(work->input.buffers[0]); } - work->workletsProcessed = 1u; if (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) { mSignalledEos = true; ALOGV("signalled EOS"); diff --git a/media/codecs/vorbis/C2SoftVorbisDec.cpp b/media/codecs/vorbis/C2SoftVorbisDec.cpp index f67cf72..280ae36 100644 --- a/media/codecs/vorbis/C2SoftVorbisDec.cpp +++ b/media/codecs/vorbis/C2SoftVorbisDec.cpp @@ -231,9 +231,12 @@ static void makeBitReader( void C2SoftVorbisDec::process( const std::unique_ptr<C2Work> &work, const std::shared_ptr<C2BlockPool> &pool) { + // Initialize output work work->result = C2_OK; - work->workletsProcessed = 0u; + work->workletsProcessed = 1u; work->worklets.front()->output.configUpdate.clear(); + work->worklets.front()->output.flags = work->input.flags; + if (mSignalledError || mSignalledOutputEos) { work->result = C2_BAD_VALUE; return; diff --git a/media/codecs/vpx/Android.bp b/media/codecs/vpx/Android.bp index c09f365..cc83371 100644 --- a/media/codecs/vpx/Android.bp +++ b/media/codecs/vpx/Android.bp @@ -7,7 +7,7 @@ cc_library_shared { srcs: ["C2SoftVpxDec.cpp"], - static_libs: ["libvpx"], + shared_libs: ["libvpx"], cflags: [ "-DVP9", @@ -23,7 +23,7 @@ cc_library_shared { srcs: ["C2SoftVpxDec.cpp"], - static_libs: ["libvpx"], + shared_libs: ["libvpx"], } cc_library_shared { @@ -38,7 +38,7 @@ cc_library_shared { "C2SoftVpxEnc.cpp", ], - static_libs: ["libvpx"], + shared_libs: ["libvpx"], cflags: ["-DVP9"], } @@ -55,6 +55,6 @@ cc_library_shared { "C2SoftVpxEnc.cpp", ], - static_libs: ["libvpx"], + shared_libs: ["libvpx"], } diff --git a/media/codecs/vpx/C2SoftVpxDec.cpp b/media/codecs/vpx/C2SoftVpxDec.cpp index d6e49f7..8ecbf5d 100644 --- a/media/codecs/vpx/C2SoftVpxDec.cpp +++ b/media/codecs/vpx/C2SoftVpxDec.cpp @@ -97,6 +97,26 @@ public: .withSetter(ProfileLevelSetter, mSize) .build()); + mHdr10PlusInfoInput = C2StreamHdr10PlusInfo::input::AllocShared(0); + addParameter( + DefineParam(mHdr10PlusInfoInput, C2_PARAMKEY_INPUT_HDR10_PLUS_INFO) + .withDefault(mHdr10PlusInfoInput) + .withFields({ + C2F(mHdr10PlusInfoInput, m.value).any(), + }) + .withSetter(Hdr10PlusInfoInputSetter) + .build()); + + mHdr10PlusInfoOutput = C2StreamHdr10PlusInfo::output::AllocShared(0); + addParameter( + DefineParam(mHdr10PlusInfoOutput, C2_PARAMKEY_OUTPUT_HDR10_PLUS_INFO) + .withDefault(mHdr10PlusInfoOutput) + .withFields({ + C2F(mHdr10PlusInfoOutput, m.value).any(), + }) + .withSetter(Hdr10PlusInfoOutputSetter) + .build()); + #if 0 // sample BT.2020 static info mHdrStaticInfo = std::make_shared<C2StreamHdrStaticInfo::output>(); @@ -217,6 +237,18 @@ public: return C2R::Ok(); } + static C2R Hdr10PlusInfoInputSetter(bool mayBlock, C2P<C2StreamHdr10PlusInfo::input> &me) { + (void)mayBlock; + (void)me; // TODO: validate + return C2R::Ok(); + } + + static C2R Hdr10PlusInfoOutputSetter(bool mayBlock, C2P<C2StreamHdr10PlusInfo::output> &me) { + (void)mayBlock; + (void)me; // TODO: validate + return C2R::Ok(); + } + private: std::shared_ptr<C2StreamProfileLevelInfo::input> mProfileLevel; std::shared_ptr<C2StreamPictureSizeInfo::output> mSize; @@ -228,6 +260,8 @@ private: #if 0 std::shared_ptr<C2StreamHdrStaticInfo::output> mHdrStaticInfo; #endif + std::shared_ptr<C2StreamHdr10PlusInfo::input> mHdr10PlusInfoInput; + std::shared_ptr<C2StreamHdr10PlusInfo::output> mHdr10PlusInfoOutput; #endif }; @@ -320,6 +354,10 @@ status_t C2SoftVpxDec::initDecoder() { if (!mCodecCtx) { mCodecCtx = new vpx_codec_ctx_t; } + if (!mCodecCtx) { + ALOGE("mCodecCtx is null"); + return NO_MEMORY; + } vpx_codec_dec_cfg_t cfg; memset(&cfg, 0, sizeof(vpx_codec_dec_cfg_t)); @@ -366,7 +404,8 @@ void C2SoftVpxDec::finishWork(uint64_t index, const std::unique_ptr<C2Work> &wor const std::shared_ptr<C2GraphicBlock> &block) { std::shared_ptr<C2Buffer> buffer = createGraphicBuffer(block, C2Rect(mWidth, mHeight)); - auto fillWork = [buffer, index](const std::unique_ptr<C2Work> &work) { + auto fillWork = [buffer, index, intf = this->mIntf]( + const std::unique_ptr<C2Work> &work) { uint32_t flags = 0; if ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) && (c2_cntr64_t(index) == work->input.ordinal.frameIndex)) { @@ -378,6 +417,28 @@ void C2SoftVpxDec::finishWork(uint64_t index, const std::unique_ptr<C2Work> &wor work->worklets.front()->output.buffers.push_back(buffer); work->worklets.front()->output.ordinal = work->input.ordinal; work->workletsProcessed = 1u; + + for (const std::unique_ptr<C2Param> ¶m: work->input.configUpdate) { + if (param) { + C2StreamHdr10PlusInfo::input *hdr10PlusInfo = + C2StreamHdr10PlusInfo::input::From(param.get()); + + if (hdr10PlusInfo != nullptr) { + std::vector<std::unique_ptr<C2SettingResult>> failures; + std::unique_ptr<C2Param> outParam = C2Param::CopyAsStream( + *param.get(), true /*output*/, param->stream()); + c2_status_t err = intf->config( + { outParam.get() }, C2_MAY_BLOCK, &failures); + if (err == C2_OK) { + work->worklets.front()->output.configUpdate.push_back( + C2Param::Copy(*outParam.get())); + } else { + ALOGE("finishWork: Config update size failed"); + } + break; + } + } + } }; if (work && c2_cntr64_t(index) == work->input.ordinal.frameIndex) { fillWork(work); @@ -389,9 +450,12 @@ void C2SoftVpxDec::finishWork(uint64_t index, const std::unique_ptr<C2Work> &wor void C2SoftVpxDec::process( const std::unique_ptr<C2Work> &work, const std::shared_ptr<C2BlockPool> &pool) { + // Initialize output work work->result = C2_OK; work->workletsProcessed = 0u; work->worklets.front()->output.configUpdate.clear(); + work->worklets.front()->output.flags = work->input.flags; + if (mSignalledError || mSignalledOutputEos) { work->result = C2_BAD_VALUE; return; @@ -440,9 +504,9 @@ void C2SoftVpxDec::process( mCodecCtx, bitstream, inSize, &frameIndex, 0); if (err != VPX_CODEC_OK) { ALOGE("on2 decoder failed to decode frame. err: %d", err); - work->result = C2_CORRUPTED; - work->workletsProcessed = 1u; mSignalledError = true; + work->workletsProcessed = 1u; + work->result = C2_CORRUPTED; return; } } @@ -510,8 +574,8 @@ bool C2SoftVpxDec::outputBuffer( } else { ALOGE("Config update size failed"); mSignalledError = true; - work->result = C2_CORRUPTED; work->workletsProcessed = 1u; + work->result = C2_CORRUPTED; return false; } diff --git a/media/codecs/vpx/C2SoftVpxEnc.cpp b/media/codecs/vpx/C2SoftVpxEnc.cpp index 37fd59d..155a84f 100644 --- a/media/codecs/vpx/C2SoftVpxEnc.cpp +++ b/media/codecs/vpx/C2SoftVpxEnc.cpp @@ -412,8 +412,11 @@ vpx_enc_frame_flags_t C2SoftVpxEnc::getEncodeFlags() { void C2SoftVpxEnc::process( const std::unique_ptr<C2Work> &work, const std::shared_ptr<C2BlockPool> &pool) { + // Initialize output work work->result = C2_OK; work->workletsProcessed = 1u; + work->worklets.front()->output.flags = work->input.flags; + if (mSignalledError || mSignalledOutputEos) { work->result = C2_BAD_VALUE; return; @@ -457,6 +460,7 @@ void C2SoftVpxEnc::process( ALOGE("unexpected Input buffer attributes %d(%d) x %d(%d)", inBuffer.width(), mSize->width, inBuffer.height(), mSize->height); + mSignalledError = true; work->result = C2_BAD_VALUE; return; } @@ -563,6 +567,7 @@ void C2SoftVpxEnc::process( if (res != VPX_CODEC_OK) { ALOGE("vpx encoder failed to update bitrate: %s", vpx_codec_err_to_string(res)); + mSignalledError = true; work->result = C2_CORRUPTED; return; } @@ -589,6 +594,7 @@ void C2SoftVpxEnc::process( VPX_DL_REALTIME); if (codec_return != VPX_CODEC_OK) { ALOGE("vpx encoder failed to encode frame"); + mSignalledError = true; work->result = C2_CORRUPTED; return; } diff --git a/media/codecs/xaac/C2SoftXaacDec.cpp b/media/codecs/xaac/C2SoftXaacDec.cpp index ba856d1..1c0e70b 100644 --- a/media/codecs/xaac/C2SoftXaacDec.cpp +++ b/media/codecs/xaac/C2SoftXaacDec.cpp @@ -327,7 +327,10 @@ IA_ERRORCODE C2SoftXaacDec::initDecoder() { return IA_FATAL_ERROR; } - mOutputDrainBuffer = new short[kOutputDrainBufferSize]; + if (!mOutputDrainBuffer) { + mOutputDrainBuffer = new (std::nothrow) char[kOutputDrainBufferSize]; + if (!mOutputDrainBuffer) return IA_FATAL_ERROR; + } err_code = initXAACDrc(); RETURN_IF_FATAL(err_code, "initXAACDrc"); @@ -357,7 +360,11 @@ void C2SoftXaacDec::finishWork(const std::unique_ptr<C2Work>& work, // TODO: error handling, proper usage, etc. c2_status_t err = pool->fetchLinearBlock(mOutputDrainBufferWritePos, usage, &block); - if (err != C2_OK) ALOGE("err = %d", err); + if (err != C2_OK) { + ALOGE("fetchLinearBlock failed : err = %d", err); + work->result = C2_NO_MEMORY; + return; + } C2WriteView wView = block->map().get(); int16_t* outBuffer = reinterpret_cast<int16_t*>(wView.data()); memcpy(outBuffer, mOutputDrainBuffer, mOutputDrainBufferWritePos); @@ -387,9 +394,12 @@ void C2SoftXaacDec::finishWork(const std::unique_ptr<C2Work>& work, void C2SoftXaacDec::process(const std::unique_ptr<C2Work>& work, const std::shared_ptr<C2BlockPool>& pool) { - work->workletsProcessed = 0u; + // Initialize output work work->result = C2_OK; + work->workletsProcessed = 1u; work->worklets.front()->output.configUpdate.clear(); + work->worklets.front()->output.flags = work->input.flags; + if (mSignalledError || mSignalledOutputEos) { work->result = C2_BAD_VALUE; return; @@ -412,7 +422,6 @@ void C2SoftXaacDec::process(const std::unique_ptr<C2Work>& work, bool eos = (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0; bool codecConfig = (work->input.flags & C2FrameData::FLAG_CODEC_CONFIG) != 0; - if (codecConfig) { if (size == 0u) { ALOGE("empty codec config"); @@ -435,13 +444,13 @@ void C2SoftXaacDec::process(const std::unique_ptr<C2Work>& work, work->worklets.front()->output.flags = work->input.flags; work->worklets.front()->output.ordinal = work->input.ordinal; work->worklets.front()->output.buffers.clear(); - work->workletsProcessed = 1u; return; } mCurFrameIndex = work->input.ordinal.frameIndex.peeku(); mCurTimestamp = work->input.ordinal.timestamp.peeku(); mOutputDrainBufferWritePos = 0; + char* tempOutputDrainBuffer = mOutputDrainBuffer; while (size > 0u) { if ((kOutputDrainBufferSize * sizeof(int16_t) - mOutputDrainBufferWritePos) < @@ -537,7 +546,7 @@ void C2SoftXaacDec::process(const std::unique_ptr<C2Work>& work, work->worklets.front()->output.configUpdate.push_back( C2Param::Copy(channelCountInfo)); } else { - ALOGE("Cannot set width and height"); + ALOGE("Config Update failed"); mSignalledError = true; work->result = C2_CORRUPTED; return; @@ -588,7 +597,8 @@ void C2SoftXaacDec::process(const std::unique_ptr<C2Work>& work, // fall through } - memcpy(mOutputDrainBuffer, mOutputBuffer, mNumOutBytes); + memcpy(tempOutputDrainBuffer, mOutputBuffer, mNumOutBytes); + tempOutputDrainBuffer += mNumOutBytes; mOutputDrainBufferWritePos += mNumOutBytes; } @@ -623,7 +633,7 @@ IA_ERRORCODE C2SoftXaacDec::configflushDecode() { err_code = ixheaacd_dec_api(mXheaacCodecHandle, IA_API_CMD_INIT, IA_CMD_TYPE_FLUSH_MEM, - NULL); + nullptr); RETURN_IF_FATAL(err_code, "IA_CMD_TYPE_FLUSH_MEM"); err_code = ixheaacd_dec_api(mXheaacCodecHandle, @@ -635,7 +645,7 @@ IA_ERRORCODE C2SoftXaacDec::configflushDecode() { err_code = ixheaacd_dec_api(mXheaacCodecHandle, IA_API_CMD_INIT, IA_CMD_TYPE_FLUSH_MEM, - NULL); + nullptr); RETURN_IF_FATAL(err_code, "IA_CMD_TYPE_FLUSH_MEM"); err_code = ixheaacd_dec_api(mXheaacCodecHandle, @@ -695,7 +705,7 @@ IA_ERRORCODE C2SoftXaacDec::initXAACDecoder() { /* API size */ uint32_t pui_api_size; /* Get the API size */ - IA_ERRORCODE err_code = ixheaacd_dec_api(NULL, + IA_ERRORCODE err_code = ixheaacd_dec_api(nullptr, IA_API_CMD_GET_API_SIZE, 0, &pui_api_size); @@ -713,11 +723,11 @@ IA_ERRORCODE C2SoftXaacDec::initXAACDecoder() { err_code = ixheaacd_dec_api(mXheaacCodecHandle, IA_API_CMD_INIT, IA_CMD_TYPE_INIT_API_PRE_CONFIG_PARAMS, - NULL); + nullptr); RETURN_IF_FATAL(err_code, "IA_CMD_TYPE_INIT_API_PRE_CONFIG_PARAMS"); /* Get the API size */ - err_code = ia_drc_dec_api(NULL, IA_API_CMD_GET_API_SIZE, 0, &pui_api_size); + err_code = ia_drc_dec_api(nullptr, IA_API_CMD_GET_API_SIZE, 0, &pui_api_size); RETURN_IF_FATAL(err_code, "IA_API_CMD_GET_API_SIZE"); @@ -731,7 +741,7 @@ IA_ERRORCODE C2SoftXaacDec::initXAACDecoder() { /* Set the config params to default values */ err_code = ia_drc_dec_api(mMpegDDrcHandle, IA_API_CMD_INIT, - IA_CMD_TYPE_INIT_API_PRE_CONFIG_PARAMS, NULL); + IA_CMD_TYPE_INIT_API_PRE_CONFIG_PARAMS, nullptr); RETURN_IF_FATAL(err_code, "IA_CMD_TYPE_INIT_API_PRE_CONFIG_PARAMS"); @@ -775,7 +785,7 @@ IA_ERRORCODE C2SoftXaacDec::initXAACDecoder() { err_code = ixheaacd_dec_api(mXheaacCodecHandle, IA_API_CMD_INIT, IA_CMD_TYPE_INIT_API_POST_CONFIG_PARAMS, - NULL); + nullptr); RETURN_IF_FATAL(err_code, "IA_CMD_TYPE_INIT_API_POST_CONFIG_PARAMS"); /* ******************************************************************/ @@ -906,12 +916,12 @@ IA_ERRORCODE C2SoftXaacDec::deInitXAACDecoder() { err_code = ixheaacd_dec_api(mXheaacCodecHandle, IA_API_CMD_INPUT_OVER, 0, - NULL); + nullptr); } /* Irrespective of error returned in IA_API_CMD_INPUT_OVER, free allocated memory */ for (void* buf : mMemoryVec) { - free(buf); + if (buf) free(buf); } mMemoryVec.clear(); mXheaacCodecHandle = nullptr; @@ -923,7 +933,7 @@ IA_ERRORCODE C2SoftXaacDec::deInitMPEGDDDrc() { ALOGV("deInitMPEGDDDrc"); for (void* buf : mDrcMemoryVec) { - free(buf); + if (buf) free(buf); } mDrcMemoryVec.clear(); return IA_NO_ERROR; @@ -951,14 +961,14 @@ IA_ERRORCODE C2SoftXaacDec::configXAACDecoder(uint8_t* inBuffer, uint32_t inBuff err_code = ixheaacd_dec_api(mXheaacCodecHandle, IA_API_CMD_INIT, IA_CMD_TYPE_GA_HDR, - NULL); + nullptr); RETURN_IF_FATAL(err_code, "IA_CMD_TYPE_GA_HDR"); } else { /* Initialize the process */ err_code = ixheaacd_dec_api(mXheaacCodecHandle, IA_API_CMD_INIT, IA_CMD_TYPE_INIT_PROCESS, - NULL); + nullptr); RETURN_IF_FATAL(err_code, "IA_CMD_TYPE_INIT_PROCESS"); } @@ -1014,7 +1024,7 @@ IA_ERRORCODE C2SoftXaacDec::initMPEGDDDrc() { RETURN_IF_FATAL(err_code, "IA_API_CMD_GET_MEM_INFO_TYPE"); pv_alloc_ptr = memalign(4, ui_size); - if (pv_alloc_ptr == NULL) { + if (pv_alloc_ptr == nullptr) { ALOGE(" Cannot create requested memory %d", ui_size); return IA_FATAL_ERROR; } @@ -1030,7 +1040,7 @@ IA_ERRORCODE C2SoftXaacDec::initMPEGDDDrc() { ui_size = 8192 * 2; mDrcInBuf = (int8_t*)memalign(4, ui_size); - if (mDrcInBuf == NULL) { + if (mDrcInBuf == nullptr) { ALOGE(" Cannot create requested memory %d", ui_size); return IA_FATAL_ERROR; } @@ -1040,7 +1050,7 @@ IA_ERRORCODE C2SoftXaacDec::initMPEGDDDrc() { RETURN_IF_FATAL(err_code, "IA_API_CMD_SET_MEM_PTR"); mDrcOutBuf = (int8_t*)memalign(4, ui_size); - if (mDrcOutBuf == NULL) { + if (mDrcOutBuf == nullptr) { ALOGE(" Cannot create requested memory %d", ui_size); return IA_FATAL_ERROR; } @@ -1106,7 +1116,7 @@ int C2SoftXaacDec::configMPEGDDrc() { RETURN_IF_FATAL(err_code, "IA_ENHAACPLUS_DEC_CONFIG_PARAM_SBR_MODE"); err_code = ia_drc_dec_api(mMpegDDrcHandle, IA_API_CMD_INIT, - IA_CMD_TYPE_INIT_API_POST_CONFIG_PARAMS, NULL); + IA_CMD_TYPE_INIT_API_POST_CONFIG_PARAMS, nullptr); RETURN_IF_FATAL(err_code, "IA_CMD_TYPE_INIT_API_POST_CONFIG_PARAMS"); @@ -1145,7 +1155,7 @@ int C2SoftXaacDec::configMPEGDDrc() { RETURN_IF_FATAL(err_code, "IA_ENHAACPLUS_DEC_CONFIG_EXT_ELE_PTR"); err_code = - ia_drc_dec_api(mMpegDDrcHandle, IA_API_CMD_INIT, IA_CMD_TYPE_INIT_SET_BUFF_PTR, 0); + ia_drc_dec_api(mMpegDDrcHandle, IA_API_CMD_INIT, IA_CMD_TYPE_INIT_SET_BUFF_PTR, nullptr); RETURN_IF_FATAL(err_code, "IA_CMD_TYPE_INIT_SET_BUFF_PTR"); err_code = ixheaacd_dec_api(mXheaacCodecHandle, IA_API_CMD_GET_CONFIG_PARAM, @@ -1173,7 +1183,7 @@ int C2SoftXaacDec::configMPEGDDrc() { /* Execute process */ err_code = ia_drc_dec_api(mMpegDDrcHandle, IA_API_CMD_INIT, - IA_CMD_TYPE_INIT_CPY_IL_BSF_BUFF, NULL); + IA_CMD_TYPE_INIT_CPY_IL_BSF_BUFF, nullptr); RETURN_IF_FATAL(err_code, "IA_CMD_TYPE_INIT_CPY_IL_BSF_BUFF"); mDRCFlag = 1; @@ -1197,7 +1207,7 @@ int C2SoftXaacDec::configMPEGDDrc() { /* Execute process */ err_code = ia_drc_dec_api(mMpegDDrcHandle, IA_API_CMD_INIT, - IA_CMD_TYPE_INIT_CPY_IC_BSF_BUFF, NULL); + IA_CMD_TYPE_INIT_CPY_IC_BSF_BUFF, nullptr); RETURN_IF_FATAL(err_code, "IA_CMD_TYPE_INIT_CPY_IC_BSF_BUFF"); @@ -1238,11 +1248,11 @@ int C2SoftXaacDec::configMPEGDDrc() { /* Execute process */ err_code = ia_drc_dec_api(mMpegDDrcHandle, IA_API_CMD_INIT, - IA_CMD_TYPE_INIT_CPY_IN_BSF_BUFF, NULL); + IA_CMD_TYPE_INIT_CPY_IN_BSF_BUFF, nullptr); RETURN_IF_FATAL(err_code, "IA_CMD_TYPE_INIT_CPY_IN_BSF_BUFF"); err_code = ia_drc_dec_api(mMpegDDrcHandle, IA_API_CMD_INIT, - IA_CMD_TYPE_INIT_PROCESS, NULL); + IA_CMD_TYPE_INIT_PROCESS, nullptr); RETURN_IF_FATAL(err_code, "IA_CMD_TYPE_INIT_PROCESS"); err_code = ia_drc_dec_api(mMpegDDrcHandle, IA_API_CMD_GET_CONFIG_PARAM, @@ -1276,7 +1286,7 @@ IA_ERRORCODE C2SoftXaacDec::decodeXAACStream(uint8_t* inBuffer, err_code = ixheaacd_dec_api(mXheaacCodecHandle, IA_API_CMD_EXECUTE, IA_CMD_TYPE_DO_EXECUTE, - NULL); + nullptr); RETURN_IF_FATAL(err_code, "IA_CMD_TYPE_DO_EXECUTE"); /* Checking for end of processing */ @@ -1319,7 +1329,7 @@ IA_ERRORCODE C2SoftXaacDec::decodeXAACStream(uint8_t* inBuffer, /* Execute process */ err_code = ia_drc_dec_api(mMpegDDrcHandle, IA_API_CMD_INIT, - IA_CMD_TYPE_INIT_CPY_BSF_BUFF, NULL); + IA_CMD_TYPE_INIT_CPY_BSF_BUFF, nullptr); RETURN_IF_FATAL(err_code, "IA_DRC_DEC_CONFIG_PARAM_BITS_FORMAT"); mMpegDDRCPresent = 1; @@ -1346,7 +1356,7 @@ IA_ERRORCODE C2SoftXaacDec::decodeXAACStream(uint8_t* inBuffer, RETURN_IF_FATAL(err_code, "IA_API_CMD_SET_INPUT_BYTES"); err_code = - ia_drc_dec_api(mMpegDDrcHandle, IA_API_CMD_EXECUTE, IA_CMD_TYPE_DO_EXECUTE, NULL); + ia_drc_dec_api(mMpegDDrcHandle, IA_API_CMD_EXECUTE, IA_CMD_TYPE_DO_EXECUTE, nullptr); RETURN_IF_FATAL(err_code, "IA_CMD_TYPE_DO_EXECUTE"); memcpy(mOutputBuffer, mDrcOutBuf, *outBytes); diff --git a/media/codecs/xaac/C2SoftXaacDec.h b/media/codecs/xaac/C2SoftXaacDec.h index b99d2ea..5c8567f 100644 --- a/media/codecs/xaac/C2SoftXaacDec.h +++ b/media/codecs/xaac/C2SoftXaacDec.h @@ -97,7 +97,7 @@ private: size_t mOutputBufferCount __unused; bool mSignalledOutputEos; bool mSignalledError; - short* mOutputDrainBuffer; + char* mOutputDrainBuffer; uint32_t mOutputDrainBufferWritePos; IA_ERRORCODE initDecoder(); diff --git a/media/eco/.clang-format b/media/eco/.clang-format new file mode 100644 index 0000000..b043d46 --- /dev/null +++ b/media/eco/.clang-format @@ -0,0 +1,33 @@ +--- +BasedOnStyle: Google +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: true +AllowShortLoopsOnASingleLine: true +BinPackArguments: true +BinPackParameters: true +CommentPragmas: NOLINT:.* +ContinuationIndentWidth: 8 +DerivePointerAlignment: false +IndentWidth: 4 +PointerAlignment: Left +TabWidth: 4 + +# Deviations from the above file: +# "Don't indent the section label" +AccessModifierOffset: -4 +# "Each line of text in your code should be at most 100 columns long." +ColumnLimit: 100 +# "Constructor initializer lists can be all on one line or with subsequent +# lines indented eight spaces.". clang-format does not support having the colon +# on the same line as the constructor function name, so this is the best +# approximation of that rule, which makes all entries in the list (except the +# first one) have an eight space indentation. +ConstructorInitializerIndentWidth: 6 +# There is nothing in go/droidcppstyle about case labels, but there seems to be +# more code that does not indent the case labels in frameworks/base. +IndentCaseLabels: false +# There have been some bugs in which subsequent formatting operations introduce +# weird comment jumps. +ReflowComments: false +# Android does support C++11 now. +Standard: Cpp11 diff --git a/media/eco/Android.bp b/media/eco/Android.bp new file mode 100644 index 0000000..0475861 --- /dev/null +++ b/media/eco/Android.bp @@ -0,0 +1,65 @@ +filegroup { + name: "libmedia_ecoservice_aidl", + srcs: [ + "aidl/android/media/eco/IECOService.aidl", + "aidl/android/media/eco/IECOSession.aidl", + "aidl/android/media/eco/IECOServiceStatsProvider.aidl", + "aidl/android/media/eco/IECOServiceInfoListener.aidl", + ], + path: "aidl", +} + +cc_library_shared { + name: "libmedia_ecoservice", + vendor_available: true, + + srcs: [ + ":libmedia_ecoservice_aidl", + "ECOData.cpp", + "ECODebug.cpp", + "ECOService.cpp", + "ECOSession.cpp", + "ECOUtils.cpp", + ], + + aidl: { + local_include_dirs: [ + "include", + "aidl", + ], + export_aidl_headers: true, + }, + + cflags: [ + "-Wall", + "-Werror", + ], + + local_include_dirs: [ + "include", + ], + + shared_libs: [ + "libbinder", + "libcutils", + "liblog", + "libutils", + ], + + export_include_dirs: [ + "include", + ], + + sanitize: { + misc_undefined: [ + "unsigned-integer-overflow", + "signed-integer-overflow", + ], + cfi: false, // true, + diag: { + cfi: false, // true, + }, + }, + + ldflags: ["-Wl,-Bsymbolic"], +} diff --git a/media/eco/ECOData.cpp b/media/eco/ECOData.cpp new file mode 100644 index 0000000..189e609 --- /dev/null +++ b/media/eco/ECOData.cpp @@ -0,0 +1,421 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "ECOData" + +#include "eco/ECOData.h" + +#include <binder/Parcel.h> +#include <inttypes.h> +#include <utils/Errors.h> +#include <utils/Log.h> + +#include <string> + +#include "eco/ECODataKey.h" +#include "eco/ECOUtils.h" + +namespace android { +namespace media { +namespace eco { + +using namespace ::android; + +status_t ECOData::readFromParcel(const Parcel* parcel) { + if (parcel == nullptr) { + ALOGE("readFromParcel failed. Parcel pointer can not be null"); + return BAD_VALUE; + } + + // Reads the data type and time. + RETURN_STATUS_IF_ERROR(parcel->readInt32(&mDataType)); + RETURN_STATUS_IF_ERROR(parcel->readInt64(&mDataTimeUs)); + + // Reads the number of items. + uint32_t numOfItems = 0; + RETURN_STATUS_IF_ERROR(parcel->readUint32(&numOfItems)); + + // Reads the key-value pairs one by one. + for (size_t i = 0; i < numOfItems; ++i) { + // Reads the name of the key. + const char* name = parcel->readCString(); + if (name == NULL) { + ALOGE("Failed reading name for the key. Parsing aborted."); + return NAME_NOT_FOUND; + } + + int32_t type; + RETURN_STATUS_IF_ERROR(parcel->readInt32(&type)); + switch (static_cast<ValueType>(type)) { + case kTypeInt32: { + int32_t value32; + RETURN_STATUS_IF_ERROR(parcel->readInt32(&value32)); + setInt32(std::string(name), value32); + break; + } + case kTypeInt64: { + int64_t value64; + RETURN_STATUS_IF_ERROR(parcel->readInt64(&value64)); + setInt64(std::string(name), value64); + break; + } + case kTypeSize: { + int32_t valueSize; + RETURN_STATUS_IF_ERROR(parcel->readInt32(&valueSize)); + setInt32(std::string(name), valueSize); + break; + } + case kTypeFloat: { + float valueFloat; + RETURN_STATUS_IF_ERROR(parcel->readFloat(&valueFloat)); + setFloat(std::string(name), valueFloat); + break; + } + case kTypeDouble: { + double valueDouble; + RETURN_STATUS_IF_ERROR(parcel->readDouble(&valueDouble)); + setDouble(std::string(name), valueDouble); + break; + } + case kTypeString: { + const char* valueStr = parcel->readCString(); + if (valueStr == NULL) { + ALOGE("Failed reading name for the key. Parsing aborted."); + return NAME_NOT_FOUND; + } + setString(std::string(name), valueStr); + break; + } + case kTypeInt8: { + int8_t value8; + RETURN_STATUS_IF_ERROR(parcel->readByte(&value8)); + setInt8(std::string(name), value8); + break; + } + default: { + return BAD_TYPE; + } + } + } + + return NO_ERROR; +} + +status_t ECOData::writeToParcel(Parcel* parcel) const { + if (parcel == nullptr) { + ALOGE("writeToParcel failed. Parcel pointer can not be null"); + return BAD_VALUE; + } + + // Writes out the data type and time. + RETURN_STATUS_IF_ERROR(parcel->writeInt32(mDataType)); + RETURN_STATUS_IF_ERROR(parcel->writeInt64(mDataTimeUs)); + + // Writes out number of items. + RETURN_STATUS_IF_ERROR(parcel->writeUint32(int32_t(mKeyValueStore.size()))); + + // Writes out the key-value pairs one by one. + for (const auto& it : mKeyValueStore) { + // Writes out the key. + RETURN_STATUS_IF_ERROR(parcel->writeCString(it.first.c_str())); + + // Writes out the data type. + const ECODataValueType& value = it.second; + RETURN_STATUS_IF_ERROR(parcel->writeInt32(static_cast<int32_t>(value.index()))); + switch (static_cast<ValueType>(value.index())) { + case kTypeInt32: + RETURN_STATUS_IF_ERROR(parcel->writeInt32(std::get<int32_t>(it.second))); + break; + + case kTypeInt64: + RETURN_STATUS_IF_ERROR(parcel->writeInt64(std::get<int64_t>(it.second))); + break; + + case kTypeSize: + RETURN_STATUS_IF_ERROR(parcel->writeUint32(std::get<size_t>(it.second))); + break; + + case kTypeFloat: + RETURN_STATUS_IF_ERROR(parcel->writeFloat(std::get<float>(it.second))); + break; + + case kTypeDouble: + RETURN_STATUS_IF_ERROR(parcel->writeDouble(std::get<double>(it.second))); + break; + + case kTypeString: + RETURN_STATUS_IF_ERROR(parcel->writeCString(std::get<std::string>(it.second).c_str())); + break; + + case kTypeInt8: + RETURN_STATUS_IF_ERROR(parcel->writeByte(std::get<int8_t>(it.second))); + break; + + default: + return BAD_TYPE; + } + } + + return NO_ERROR; +} + +int32_t ECOData::getDataType() const { + return mDataType; +} + +int64_t ECOData::getDataTimeUs() const { + return mDataTimeUs; +} + +// Inserts a new key into store if the key does not exist yet. Otherwise, this will override the +// existing key's value. +ECODataStatus ECOData::setString(const std::string& key, const std::string& value) { + if (key.empty() || value.empty()) { + return ECODataStatus::INVALID_ARGUMENT; + } + + mKeyValueStore[key] = value; + + // TODO(hkuang): Check the valueType is valid for the key. + return ECODataStatus::OK; +} + +ECODataStatus ECOData::findString(const std::string& key, std::string* value) const { + if (key.empty()) { + return ECODataStatus::INVALID_ARGUMENT; + } + + // Check if the key exists. + if (mKeyValueStore.find(key) == mKeyValueStore.end()) { + return ECODataStatus::KEY_NOT_EXIST; + } + + // Safely access the value. + const std::string& entryValue = std::get<std::string>(mKeyValueStore.at(key)); + value->assign(entryValue); + + return ECODataStatus::OK; +} + +// Inserts a new key into store if the key does not exist yet. Otherwise, this will override the +// existing key's value. +template <typename T> +ECODataStatus ECOData::setValue(const std::string& key, T value) { + if (key.empty()) { + return ECODataStatus::INVALID_ARGUMENT; + } + + mKeyValueStore[key] = value; + return ECODataStatus::OK; +} + +template <typename T> +ECODataStatus ECOData::findValue(const std::string& key, T* out) const { + if (key.empty() || out == nullptr) { + return ECODataStatus::INVALID_ARGUMENT; + } + + if (mKeyValueStore.find(key) == mKeyValueStore.end()) { + return ECODataStatus::KEY_NOT_EXIST; + } + + // Safely access the value. + *out = std::get<T>(mKeyValueStore.at(key)); + + return ECODataStatus::OK; +} + +ECODataStatus ECOData::setInt32(const std::string& key, int32_t value) { + return setValue<int32_t>(key, value); +} + +ECODataStatus ECOData::findInt32(const std::string& key, int32_t* out) const { + return findValue<int32_t>(key, out); +} + +ECODataStatus ECOData::setInt64(const std::string& key, int64_t value) { + return setValue<int64_t>(key, value); +} + +ECODataStatus ECOData::findInt64(const std::string& key, int64_t* out) const { + return findValue<int64_t>(key, out); +} + +ECODataStatus ECOData::setDouble(const std::string& key, double value) { + return setValue<double>(key, value); +} + +ECODataStatus ECOData::findDouble(const std::string& key, double* out) const { + return findValue<double>(key, out); +} + +ECODataStatus ECOData::setSize(const std::string& key, size_t value) { + return setValue<size_t>(key, value); +} + +ECODataStatus ECOData::findSize(const std::string& key, size_t* out) const { + return findValue<size_t>(key, out); +} + +ECODataStatus ECOData::setFloat(const std::string& key, float value) { + return setValue<float>(key, value); +} + +ECODataStatus ECOData::findFloat(const std::string& key, float* out) const { + return findValue<float>(key, out); +} + +ECODataStatus ECOData::setInt8(const std::string& key, int8_t value) { + return setValue<int8_t>(key, value); +} + +ECODataStatus ECOData::findInt8(const std::string& key, int8_t* out) const { + return findValue<int8_t>(key, out); +} + +ECODataStatus ECOData::set(const std::string& key, const ECOData::ECODataValueType& value) { + if (key.empty()) { + return ECODataStatus::INVALID_ARGUMENT; + } + mKeyValueStore[key] = value; + return ECODataStatus::OK; +} + +ECODataStatus ECOData::find(const std::string& key, ECOData::ECODataValueType* out) const { + if (key.empty() || out == nullptr) { + return ECODataStatus::INVALID_ARGUMENT; + } + + if (mKeyValueStore.find(key) == mKeyValueStore.end()) { + return ECODataStatus::KEY_NOT_EXIST; + } + + // Safely access the value. + *out = mKeyValueStore.at(key); + + return ECODataStatus::OK; +} + +std::string ECOData::getDataTypeString() const { + switch (mDataType) { + case DATA_TYPE_UNKNOWN: + return "DATA_TYPE_UNKNOWN"; + case DATA_TYPE_STATS: + return "DATA_TYPE_STATS"; + case DATA_TYPE_INFO: + return "DATA_TYPE_INFO"; + case DATA_TYPE_STATS_PROVIDER_CONFIG: + return "DATA_TYPE_STATS_PROVIDER_CONFIG"; + case DATA_TYPE_INFO_LISTENER_CONFIG: + return "DATA_TYPE_INFO_LISTENER_CONFIG"; + } + return {}; +} + +// TODO(hkuang): Add test for this. +bool ECODataKeyValueIterator::hasNext() { + if (mIterator == mKeyValueStore.end()) return false; + + if (!mBeginReturned) { + // mIterator has been initialized to the beginning and + // hasn't been returned. Do not advance: + mBeginReturned = true; + } else { + std::advance(mIterator, 1); + } + return mIterator != mKeyValueStore.end(); +} + +// TODO(hkuang): Add test for this. +ECOData::ECODataKeyValuePair ECODataKeyValueIterator::next() const { + return ECOData::ECODataKeyValuePair(mIterator->first, mIterator->second); +} + +std::string ECOData::debugString() const { + std::string s = "ECOData(type = "; + + std::string tmp; + switch (mDataType) { + case DATA_TYPE_UNKNOWN: + tmp = "Unknown"; + break; + case DATA_TYPE_STATS: + tmp = "Stats"; + break; + case DATA_TYPE_INFO: + tmp = "Info"; + break; + case DATA_TYPE_STATS_PROVIDER_CONFIG: + tmp = "Stats provider config"; + break; + case DATA_TYPE_INFO_LISTENER_CONFIG: + tmp = "Info listener config"; + break; + default: + break; + } + s.append(tmp); + s.append(") = {\n "); + + // Writes out the key-value pairs one by one. + for (const auto& it : mKeyValueStore) { + const size_t SIZE = 100; + char keyValue[SIZE]; + const ECODataValueType& value = it.second; + switch (static_cast<ValueType>(value.index())) { + case kTypeInt32: + snprintf(keyValue, SIZE, "int32_t %s = %d, ", it.first.c_str(), + std::get<int32_t>(it.second)); + break; + case kTypeInt64: + snprintf(keyValue, SIZE, "int64_t %s = %" PRId64 ", ", it.first.c_str(), + std::get<int64_t>(it.second)); + break; + case kTypeSize: + snprintf(keyValue, SIZE, "size_t %s = %zu, ", it.first.c_str(), + std::get<size_t>(it.second)); + break; + case kTypeFloat: + snprintf(keyValue, SIZE, "float %s = %f, ", it.first.c_str(), + std::get<float>(it.second)); + break; + case kTypeDouble: + snprintf(keyValue, SIZE, "double %s = %f, ", it.first.c_str(), + std::get<double>(it.second)); + break; + case kTypeString: + snprintf(keyValue, SIZE, "string %s = %s, ", it.first.c_str(), + std::get<std::string>(it.second).c_str()); + break; + case kTypeInt8: + snprintf(keyValue, SIZE, "int8_t %s = %d, ", it.first.c_str(), + std::get<int8_t>(it.second)); + break; + default: + break; + } + s.append(keyValue); + } + + s.append("\n }"); + + return s; +} + +} // namespace eco +} // namespace media +} // namespace android diff --git a/media/eco/ECODebug.cpp b/media/eco/ECODebug.cpp new file mode 100644 index 0000000..5ed08ab --- /dev/null +++ b/media/eco/ECODebug.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "eco/ECODebug.h" + +namespace android { +namespace media { +namespace eco { + +uint32_t gECOLogLevel = 0; + +void updateLogLevel() { + gECOLogLevel = property_get_int32(kDebugLogsLevelProperty, 0); + ALOGI("ECOService log level is %d", gECOLogLevel); +} + +} // namespace eco +} // namespace media +} // namespace android diff --git a/media/eco/ECOService.cpp b/media/eco/ECOService.cpp new file mode 100644 index 0000000..6cc4be4 --- /dev/null +++ b/media/eco/ECOService.cpp @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "ECOService" + +#include "eco/ECOService.h" + +#include <binder/BinderService.h> +#include <cutils/atomic.h> +#include <inttypes.h> +#include <pthread.h> +#include <stdio.h> +#include <sys/types.h> + +#include <algorithm> +#include <climits> +#include <cstring> +#include <ctime> +#include <string> + +#include "eco/ECODebug.h" + +namespace android { +namespace media { +namespace eco { + +ECOService::ECOService() : BnECOService() { + ALOGD("ECOService created"); + updateLogLevel(); +} + +/*virtual*/ ::android::binder::Status ECOService::obtainSession( + int32_t width, int32_t height, bool isCameraRecording, + ::android::sp<::android::media::eco::IECOSession>* _aidl_return) { + ECOLOGI("ECOService::obtainSession w: %d, h: %d, isCameraRecording: %d", width, height, + isCameraRecording); + + if (width <= 0) { + return STATUS_ERROR(ERROR_ILLEGAL_ARGUMENT, "Width can not be <= 0"); + } + + if (height <= 0) { + return STATUS_ERROR(ERROR_ILLEGAL_ARGUMENT, "Height can not be <= 0"); + } + + SessionConfig newCfg(width, height, isCameraRecording); + + ECOLOGD("session count before is %zu", mSessionConfigToSessionMap.size()); + + Mutex::Autolock lock(mServiceLock); + bool foundSession = false; + // Instead of looking up the map directly, take the chance to scan the map and evict all the + // invalid sessions. + SanitizeSession([&](MapIterType iter) { + if (iter->first == newCfg) { + sp<ECOSession> session = iter->second.promote(); + foundSession = true; + *_aidl_return = session; + } + }); + + if (foundSession) { + return binder::Status::ok(); + } + + // Create a new session and add it to the record. + sp<ECOSession> newSession = ECOSession::createECOSession(width, height, isCameraRecording); + if (newSession == nullptr) { + ECOLOGE("ECOService failed to create ECOSession w: %d, h: %d, isCameraRecording: %d", width, + height, isCameraRecording); + return STATUS_ERROR(ERROR_UNSUPPORTED, "Failed to create eco session"); + } + *_aidl_return = newSession; + // Insert the new session into the map. + mSessionConfigToSessionMap[newCfg] = newSession; + ECOLOGD("session count after is %zu", mSessionConfigToSessionMap.size()); + + return binder::Status::ok(); +} + +/*virtual*/ ::android::binder::Status ECOService::getNumOfSessions(int32_t* _aidl_return) { + Mutex::Autolock lock(mServiceLock); + SanitizeSession(std::function<void(MapIterType it)>()); // empty callback + *_aidl_return = mSessionConfigToSessionMap.size(); + return binder::Status::ok(); +} + +/*virtual*/ ::android::binder::Status ECOService::getSessions( + ::std::vector<::android::sp<::android::IBinder>>* _aidl_return) { + // Clear all the entries in the vector. + _aidl_return->clear(); + + Mutex::Autolock lock(mServiceLock); + SanitizeSession([&](MapIterType iter) { + sp<ECOSession> session = iter->second.promote(); + _aidl_return->push_back(IInterface::asBinder(session)); + }); + return binder::Status::ok(); +} + +inline bool isEmptySession(const android::wp<ECOSession>& entry) { + sp<ECOSession> session = entry.promote(); + return session == nullptr; +} + +void ECOService::SanitizeSession( + const std::function<void(std::unordered_map<SessionConfig, wp<ECOSession>, + SessionConfigHash>::iterator it)>& callback) { + for (auto it = mSessionConfigToSessionMap.begin(), end = mSessionConfigToSessionMap.end(); + it != end;) { + if (isEmptySession(it->second)) { + it = mSessionConfigToSessionMap.erase(it); + } else { + if (callback != nullptr) { + callback(it); + }; + it++; + } + } +} + +/*virtual*/ void ECOService::binderDied(const wp<IBinder>& /*who*/) {} + +status_t ECOService::dump(int fd, const Vector<String16>& args) { + Mutex::Autolock lock(mServiceLock); + dprintf(fd, "\n== ECO Service info: ==\n\n"); + dprintf(fd, "Number of ECOServices: %zu\n", mSessionConfigToSessionMap.size()); + for (auto it = mSessionConfigToSessionMap.begin(), end = mSessionConfigToSessionMap.end(); + it != end; it++) { + sp<ECOSession> session = it->second.promote(); + if (session != nullptr) { + session->dump(fd, args); + } + } + + return NO_ERROR; +} + +} // namespace eco +} // namespace media +} // namespace android
\ No newline at end of file diff --git a/media/eco/ECOSession.cpp b/media/eco/ECOSession.cpp new file mode 100644 index 0000000..1bdfa61 --- /dev/null +++ b/media/eco/ECOSession.cpp @@ -0,0 +1,579 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "ECOSession" +//#define DEBUG_ECO_SESSION +#include "eco/ECOSession.h" + +#include <binder/BinderService.h> +#include <cutils/atomic.h> +#include <inttypes.h> +#include <pthread.h> +#include <stdio.h> +#include <sys/types.h> +#include <utils/Log.h> + +#include <algorithm> +#include <climits> +#include <cstring> +#include <ctime> +#include <string> + +#include "eco/ECODataKey.h" +#include "eco/ECODebug.h" + +namespace android { +namespace media { +namespace eco { + +using android::binder::Status; +using android::sp; + +#define RETURN_IF_ERROR(expr) \ + { \ + status_t _errorCode = (expr); \ + if (_errorCode != true) { \ + return _errorCode; \ + } \ + } + +// static +sp<ECOSession> ECOSession::createECOSession(int32_t width, int32_t height, bool isCameraRecording) { + // Only support up to 720P. + // TODO: Support the same resolution as in EAF. + if (width <= 0 || height <= 0 || width > 5120 || height > 5120 || + width > 1280 * 720 / height) { + ECOLOGE("Failed to create ECOSession with w: %d, h: %d, isCameraRecording: %d", width, + height, isCameraRecording); + return nullptr; + } + return new ECOSession(width, height, isCameraRecording); +} + +ECOSession::ECOSession(int32_t width, int32_t height, bool isCameraRecording) + : BnECOSession(), + mStopThread(false), + mLastReportedQp(0), + mListener(nullptr), + mProvider(nullptr), + mWidth(width), + mHeight(height), + mIsCameraRecording(isCameraRecording) { + ECOLOGI("ECOSession created with w: %d, h: %d, isCameraRecording: %d", mWidth, mHeight, + mIsCameraRecording); + mThread = std::thread(startThread, this); + + // Read the debug properies. + mLogStats = property_get_bool(kDebugLogStats, false); + mLogStatsEntries = mLogStats ? property_get_int32(kDebugLogStatsSize, 0) : 0; + + mLogInfo = property_get_bool(kDebugLogStats, false); + mLogInfoEntries = mLogInfo ? property_get_int32(kDebugLogInfosSize, 0) : 0; + + ECOLOGI("ECOSession debug settings: logStats: %s, entries: %d, logInfo: %s entries: %d", + mLogStats ? "true" : "false", mLogStatsEntries, mLogInfo ? "true" : "false", + mLogInfoEntries); +} + +ECOSession::~ECOSession() { + mStopThread = true; + + mWorkerWaitCV.notify_all(); + if (mThread.joinable()) { + ECOLOGD("ECOSession: join the thread"); + mThread.join(); + } + ECOLOGI("ECOSession destroyed with w: %d, h: %d, isCameraRecording: %d", mWidth, mHeight, + mIsCameraRecording); +} + +// static +void ECOSession::startThread(ECOSession* session) { + session->run(); +} + +void ECOSession::run() { + ECOLOGD("ECOSession: starting main thread"); + + while (!mStopThread) { + std::unique_lock<std::mutex> runLock(mStatsQueueLock); + + mWorkerWaitCV.wait(runLock, [this] { + return mStopThread == true || !mStatsQueue.empty() || mNewListenerAdded; + }); + + if (mStopThread) return; + + std::scoped_lock<std::mutex> lock(mSessionLock); + if (mNewListenerAdded) { + // Check if there is any session info available. + ECOData sessionInfo = generateLatestSessionInfoEcoData(); + if (!sessionInfo.isEmpty()) { + Status status = mListener->onNewInfo(sessionInfo); + if (!status.isOk()) { + ECOLOGE("%s: Failed to publish info: %s due to binder error", __FUNCTION__, + sessionInfo.debugString().c_str()); + // Remove the listener. The lock has been acquired outside this function. + mListener = nullptr; + } + } + mNewListenerAdded = false; + } + + if (!mStatsQueue.empty()) { + ECOData stats = mStatsQueue.front(); + mStatsQueue.pop_front(); + processStats(stats); // TODO: Handle the error from processStats + } + } + + ECOLOGD("ECOSession: exiting main thread"); +} + +bool ECOSession::processStats(const ECOData& stats) { + ECOLOGV("%s: receive stats: %s", __FUNCTION__, stats.debugString().c_str()); + + if (stats.getDataType() != ECOData::DATA_TYPE_STATS) { + ECOLOGE("Invalid stats. ECOData with type: %s", stats.getDataTypeString().c_str()); + return false; + } + + // Get the type of the stats. + std::string statsType; + if (stats.findString(KEY_STATS_TYPE, &statsType) != ECODataStatus::OK) { + ECOLOGE("Invalid stats ECOData without statsType"); + return false; + } + + if (statsType.compare(VALUE_STATS_TYPE_SESSION) == 0) { + processSessionStats(stats); + } else if (statsType.compare(VALUE_STATS_TYPE_FRAME) == 0) { + processFrameStats(stats); + } else { + ECOLOGE("processStats:: Failed to process stats as ECOData contains unknown stats type"); + return false; + } + + return true; +} + +void ECOSession::processSessionStats(const ECOData& stats) { + ECOLOGV("processSessionStats"); + + ECOData info(ECOData::DATA_TYPE_INFO, systemTime(SYSTEM_TIME_BOOTTIME)); + info.setString(KEY_INFO_TYPE, VALUE_INFO_TYPE_SESSION); + + ECODataKeyValueIterator iter(stats); + while (iter.hasNext()) { + ECOData::ECODataKeyValuePair entry = iter.next(); + const std::string& key = entry.first; + const ECOData::ECODataValueType value = entry.second; + ECOLOGV("Processing key: %s", key.c_str()); + if (!key.compare(KEY_STATS_TYPE)) { + // Skip the key KEY_STATS_TYPE as that has been parsed already. + continue; + } else if (!key.compare(ENCODER_TYPE)) { + mCodecType = std::get<int32_t>(value); + ECOLOGV("codec type is %d", mCodecType); + } else if (!key.compare(ENCODER_PROFILE)) { + mCodecProfile = std::get<int32_t>(value); + ECOLOGV("codec profile is %d", mCodecProfile); + } else if (!key.compare(ENCODER_LEVEL)) { + mCodecLevel = std::get<int32_t>(value); + ECOLOGV("codec level is %d", mCodecLevel); + } else if (!key.compare(ENCODER_TARGET_BITRATE_BPS)) { + mTargetBitrateBps = std::get<int32_t>(value); + ECOLOGV("codec target bitrate is %d", mTargetBitrateBps); + } else if (!key.compare(ENCODER_KFI_FRAMES)) { + mKeyFrameIntervalFrames = std::get<int32_t>(value); + ECOLOGV("codec kfi is %d", mKeyFrameIntervalFrames); + } else if (!key.compare(ENCODER_FRAMERATE_FPS)) { + mFramerateFps = std::get<float>(value); + ECOLOGV("codec framerate is %f", mFramerateFps); + } else if (!key.compare(ENCODER_INPUT_WIDTH)) { + int32_t width = std::get<int32_t>(value); + if (width != mWidth) { + ECOLOGW("Codec width: %d, expected: %d", width, mWidth); + } + ECOLOGV("codec width is %d", width); + } else if (!key.compare(ENCODER_INPUT_HEIGHT)) { + int32_t height = std::get<int32_t>(value); + if (height != mHeight) { + ECOLOGW("Codec height: %d, expected: %d", height, mHeight); + } + ECOLOGV("codec height is %d", height); + } else { + ECOLOGW("Unknown session stats key %s from provider.", key.c_str()); + continue; + } + info.set(key, value); + } + + if (mListener != nullptr) { + Status status = mListener->onNewInfo(info); + if (!status.isOk()) { + ECOLOGE("%s: Failed to publish info: %s due to binder error", __FUNCTION__, + info.debugString().c_str()); + // Remove the listener. The lock has been acquired outside this function. + mListener = nullptr; + } + } +} + +ECOData ECOSession::generateLatestSessionInfoEcoData() { + bool hasInfo = false; + + ECOData info(ECOData::DATA_TYPE_INFO, systemTime(SYSTEM_TIME_BOOTTIME)); + + if (mOutputWidth != -1) { + info.setInt32(ENCODER_OUTPUT_WIDTH, mOutputWidth); + hasInfo = true; + } + + if (mOutputHeight != -1) { + info.setInt32(ENCODER_OUTPUT_HEIGHT, mOutputHeight); + hasInfo = true; + } + + if (mCodecType != -1) { + info.setInt32(ENCODER_TYPE, mCodecType); + hasInfo = true; + } + + if (mCodecProfile != -1) { + info.setInt32(ENCODER_PROFILE, mCodecProfile); + hasInfo = true; + } + + if (mCodecLevel != -1) { + info.setInt32(ENCODER_LEVEL, mCodecLevel); + hasInfo = true; + } + + if (mTargetBitrateBps != -1) { + info.setInt32(ENCODER_TARGET_BITRATE_BPS, mTargetBitrateBps); + hasInfo = true; + } + + if (mKeyFrameIntervalFrames != -1) { + info.setInt32(ENCODER_KFI_FRAMES, mKeyFrameIntervalFrames); + hasInfo = true; + } + + if (mFramerateFps > 0) { + info.setFloat(ENCODER_FRAMERATE_FPS, mFramerateFps); + hasInfo = true; + } + + if (hasInfo) { + info.setString(KEY_INFO_TYPE, VALUE_INFO_TYPE_SESSION); + } + return info; +} + +void ECOSession::processFrameStats(const ECOData& stats) { + ECOLOGD("processFrameStats"); + + bool needToNotifyListener = false; + ECOData info(ECOData::DATA_TYPE_INFO, systemTime(SYSTEM_TIME_BOOTTIME)); + info.setString(KEY_INFO_TYPE, VALUE_INFO_TYPE_FRAME); + + ECODataKeyValueIterator iter(stats); + while (iter.hasNext()) { + ECOData::ECODataKeyValuePair entry = iter.next(); + const std::string& key = entry.first; + const ECOData::ECODataValueType value = entry.second; + ECOLOGD("Processing %s key", key.c_str()); + + if (!key.compare(KEY_STATS_TYPE)) { + // Skip the key KEY_STATS_TYPE as that has been parsed already. + continue; + } else if (!key.compare(FRAME_NUM) || !key.compare(FRAME_PTS_US) || + !key.compare(FRAME_TYPE) || !key.compare(FRAME_SIZE_BYTES) || + !key.compare(ENCODER_ACTUAL_BITRATE_BPS)) { + // Only process the keys that are supported by ECOService 1.0. + info.set(key, value); + } else if (!key.compare(FRAME_AVG_QP)) { + // Check the qp to see if need to notify the listener. + const int32_t currAverageQp = std::get<int32_t>(value); + + // Check if the delta between current QP and last reported QP is larger than the + // threshold specified by the listener. + const bool largeQPChangeDetected = + abs(currAverageQp - mLastReportedQp) > mListenerQpCondition.mQpChangeThreshold; + + // Check if the qp is going from below threshold to beyond threshold. + const bool exceedQpBlockinessThreshold = + (mLastReportedQp <= mListenerQpCondition.mQpBlocknessThreshold && + currAverageQp > mListenerQpCondition.mQpBlocknessThreshold); + + // Check if the qp is going from beyond threshold to below threshold. + const bool fallBelowQpBlockinessThreshold = + (mLastReportedQp > mListenerQpCondition.mQpBlocknessThreshold && + currAverageQp <= mListenerQpCondition.mQpBlocknessThreshold); + + // Notify the listener if any of the above three conditions met. + if (largeQPChangeDetected || exceedQpBlockinessThreshold || + fallBelowQpBlockinessThreshold) { + mLastReportedQp = currAverageQp; + needToNotifyListener = true; + } + + info.set(key, value); + } else { + ECOLOGW("Unknown frame stats key %s from provider.", key.c_str()); + } + } + + if (needToNotifyListener && mListener != nullptr) { + Status status = mListener->onNewInfo(info); + if (!status.isOk()) { + ECOLOGE("%s: Failed to publish info: %s due to binder error", __FUNCTION__, + info.debugString().c_str()); + // Remove the listener. The lock has been acquired outside this function. + mListener = nullptr; + } + } +} + +Status ECOSession::getIsCameraRecording(bool* _aidl_return) { + std::scoped_lock<std::mutex> lock(mSessionLock); + *_aidl_return = mIsCameraRecording; + return binder::Status::ok(); +} + +Status ECOSession::addStatsProvider( + const sp<::android::media::eco::IECOServiceStatsProvider>& provider, + const ::android::media::eco::ECOData& config, bool* status) { + ::android::String16 name; + Status result = provider->getName(&name); + if (!result.isOk()) { + // This binder transaction failure may due to permission issue. + *status = false; + ALOGE("Failed to get provider name"); + return STATUS_ERROR(ERROR_PERMISSION_DENIED, "Failed to get provider name"); + } + + ECOLOGV("Try to add stats provider name: %s uid: %d pid %d", ::android::String8(name).string(), + IPCThreadState::self()->getCallingUid(), IPCThreadState::self()->getCallingPid()); + + if (provider == nullptr) { + ECOLOGE("%s: provider must not be null", __FUNCTION__); + *status = false; + return STATUS_ERROR(ERROR_ILLEGAL_ARGUMENT, "Null provider given to addStatsProvider"); + } + + std::scoped_lock<std::mutex> lock(mSessionLock); + + if (mProvider != nullptr) { + ::android::String16 name; + mProvider->getName(&name); + String8 errorMsg = String8::format( + "ECOService 1.0 only supports one stats provider, current provider: %s", + ::android::String8(name).string()); + ECOLOGE("%s", errorMsg.string()); + *status = false; + return STATUS_ERROR(ERROR_ALREADY_EXISTS, errorMsg.string()); + } + + // TODO: Handle the provider config. + if (config.getDataType() != ECOData::DATA_TYPE_STATS_PROVIDER_CONFIG) { + ECOLOGE("Provider config is invalid"); + *status = false; + return STATUS_ERROR(ERROR_ILLEGAL_ARGUMENT, "Provider config is invalid"); + } + + mProvider = provider; + mProviderName = name; + *status = true; + return binder::Status::ok(); +} + +Status ECOSession::removeStatsProvider( + const sp<::android::media::eco::IECOServiceStatsProvider>& provider, bool* status) { + std::scoped_lock<std::mutex> lock(mSessionLock); + // Check if the provider is the same as current provider for the session. + if (provider.get() != mProvider.get()) { + *status = false; + return STATUS_ERROR(ERROR_ILLEGAL_ARGUMENT, "Provider does not match"); + } + + mProvider = nullptr; + *status = true; + return binder::Status::ok(); +} + +Status ECOSession::addInfoListener( + const sp<::android::media::eco::IECOServiceInfoListener>& listener, + const ::android::media::eco::ECOData& config, bool* status) { + ALOGV("%s: Add listener %p", __FUNCTION__, listener.get()); + std::scoped_lock<std::mutex> lock(mSessionLock); + + ::android::String16 name; + Status result = listener->getName(&name); + if (!result.isOk()) { + // This binder transaction failure may due to permission issue. + *status = false; + ALOGE("Failed to get listener name"); + return STATUS_ERROR(ERROR_PERMISSION_DENIED, "Failed to get listener name"); + } + + if (mListener != nullptr) { + ECOLOGE("ECOService 1.0 only supports one listener"); + *status = false; + return STATUS_ERROR(ERROR_ALREADY_EXISTS, "ECOService 1.0 only supports one listener"); + } + + if (listener == nullptr) { + ECOLOGE("%s: listener must not be null", __FUNCTION__); + *status = false; + return STATUS_ERROR(ERROR_ILLEGAL_ARGUMENT, "Null listener given to addInfoListener"); + } + + if (config.getDataType() != ECOData::DATA_TYPE_INFO_LISTENER_CONFIG) { + *status = false; + ECOLOGE("%s: listener config is invalid", __FUNCTION__); + return STATUS_ERROR(ERROR_ILLEGAL_ARGUMENT, "listener config is invalid"); + } + + if (config.isEmpty()) { + *status = false; + ECOLOGE("Listener must provide listening criterion"); + return STATUS_ERROR(ERROR_ILLEGAL_ARGUMENT, "listener config is empty"); + } + + // For ECOService 1.0, listener must specify the two threshold in order to receive info. + if (config.findInt32(KEY_LISTENER_QP_BLOCKINESS_THRESHOLD, + &mListenerQpCondition.mQpBlocknessThreshold) != ECODataStatus::OK || + config.findInt32(KEY_LISTENER_QP_CHANGE_THRESHOLD, + &mListenerQpCondition.mQpChangeThreshold) != ECODataStatus::OK || + mListenerQpCondition.mQpBlocknessThreshold < ENCODER_MIN_QP || + mListenerQpCondition.mQpBlocknessThreshold > ENCODER_MAX_QP) { + *status = false; + ECOLOGE("%s: listener config is invalid", __FUNCTION__); + return STATUS_ERROR(ERROR_ILLEGAL_ARGUMENT, "listener config is not valid"); + } + + ECOLOGD("Info listener name: %s uid: %d pid %d", ::android::String8(name).string(), + IPCThreadState::self()->getCallingUid(), IPCThreadState::self()->getCallingPid()); + + mListener = listener; + mListenerName = name; + mNewListenerAdded = true; + mWorkerWaitCV.notify_all(); + + *status = true; + return binder::Status::ok(); +} + +Status ECOSession::removeInfoListener( + const sp<::android::media::eco::IECOServiceInfoListener>& listener, bool* _aidl_return) { + std::scoped_lock<std::mutex> lock(mSessionLock); + // Check if the listener is the same as current listener for the session. + if (listener.get() != mListener.get()) { + *_aidl_return = false; + return STATUS_ERROR(ERROR_ILLEGAL_ARGUMENT, "Listener does not match"); + } + + mListener = nullptr; + mNewListenerAdded = false; + *_aidl_return = true; + return binder::Status::ok(); +} + +Status ECOSession::pushNewStats(const ::android::media::eco::ECOData& stats, bool*) { + ECOLOGV("ECOSession get new stats type: %s", stats.getDataTypeString().c_str()); + std::unique_lock<std::mutex> lock(mStatsQueueLock); + mStatsQueue.push_back(stats); + mWorkerWaitCV.notify_all(); + return binder::Status::ok(); +} + +Status ECOSession::getWidth(int32_t* _aidl_return) { + std::scoped_lock<std::mutex> lock(mSessionLock); + *_aidl_return = mWidth; + return binder::Status::ok(); +} + +Status ECOSession::getHeight(int32_t* _aidl_return) { + std::scoped_lock<std::mutex> lock(mSessionLock); + *_aidl_return = mHeight; + return binder::Status::ok(); +} + +Status ECOSession::getNumOfListeners(int32_t* _aidl_return) { + std::scoped_lock<std::mutex> lock(mSessionLock); + *_aidl_return = (mListener == nullptr ? 0 : 1); + return binder::Status::ok(); +} + +Status ECOSession::getNumOfProviders(int32_t* _aidl_return) { + std::scoped_lock<std::mutex> lock(mSessionLock); + *_aidl_return = (mProvider == nullptr ? 0 : 1); + return binder::Status::ok(); +} + +/*virtual*/ void ECOSession::binderDied(const wp<IBinder>& /*who*/) { + ECOLOGV("binderDied"); +} + +status_t ECOSession::dump(int fd, const Vector<String16>& /*args*/) { + std::scoped_lock<std::mutex> lock(mSessionLock); + dprintf(fd, "\n== Session Info: ==\n\n"); + dprintf(fd, + "Width: %d Height: %d isCameraRecording: %d, target-bitrate: %d bps codetype: %d " + "profile: %d level: %d\n", + mWidth, mHeight, mIsCameraRecording, mTargetBitrateBps, mCodecType, mCodecProfile, + mCodecLevel); + if (mProvider != nullptr) { + dprintf(fd, "Provider: %s \n", ::android::String8(mProviderName).string()); + } + if (mListener != nullptr) { + dprintf(fd, "Listener: %s \n", ::android::String8(mListenerName).string()); + } + dprintf(fd, "\n===================\n\n"); + + return NO_ERROR; +} + +void ECOSession::logStats(const ECOData& data) { + // Check if mLogStats is true; + if (!mLogStats || mLogStatsEntries == 0) return; + + // Check if we need to remove the old entry. + if (mStatsDebugBuffer.size() >= mLogStatsEntries) { + mStatsDebugBuffer.pop_front(); + } + + mStatsDebugBuffer.push_back(data); +} + +void ECOSession::logInfos(const ECOData& data) { + // Check if mLogInfo is true; + if (!mLogInfo || mLogInfoEntries == 0) return; + + // Check if we need to remove the old entry. + if (mInfosDebugBuffer.size() >= mLogInfoEntries) { + mInfosDebugBuffer.pop_front(); + } + + mInfosDebugBuffer.push_back(data); +} + +} // namespace eco +} // namespace media +} // namespace android diff --git a/media/eco/ECOUtils.cpp b/media/eco/ECOUtils.cpp new file mode 100644 index 0000000..ab82252 --- /dev/null +++ b/media/eco/ECOUtils.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "ECOUtils" + +#include "eco/ECOUtils.h" + +#include <utils/Timers.h> + +namespace android { +namespace media { +namespace eco { + +// Convert this SimpleEncoderConfig to ECOData with dataType. +ECOData SimpleEncoderConfig::toEcoData(ECOData::ECODatatype dataType) { + ECOData data(dataType, systemTime(SYSTEM_TIME_BOOTTIME)); + data.setString(KEY_STATS_TYPE, VALUE_STATS_TYPE_SESSION); + data.setInt32(ENCODER_TYPE, mCodecType); + data.setInt32(ENCODER_PROFILE, mProfile); + data.setInt32(ENCODER_LEVEL, mLevel); + data.setInt32(ENCODER_TARGET_BITRATE_BPS, mTargetBitrate); + data.setInt32(ENCODER_KFI_FRAMES, mKeyFrameIntervalFrames); + data.setFloat(ENCODER_FRAMERATE_FPS, mFrameRateFps); + return data; +} + +// Convert this SimpleEncodedFrameData to ECOData with dataType. +ECOData SimpleEncodedFrameData::toEcoData(ECOData::ECODatatype dataType) { + ECOData data(dataType, systemTime(SYSTEM_TIME_BOOTTIME)); + data.setString(KEY_STATS_TYPE, VALUE_STATS_TYPE_FRAME); + data.setInt32(FRAME_NUM, mFrameNum); + data.setInt8(FRAME_TYPE, mFrameType); + data.setInt64(FRAME_PTS_US, mFramePtsUs); + data.setInt32(FRAME_AVG_QP, mAvgQp); + data.setInt32(FRAME_SIZE_BYTES, mFrameSizeBytes); + return data; +} + +bool copyKeyValue(const ECOData& src, ECOData* dst) { + if (src.isEmpty() || dst == nullptr) return false; + dst->mKeyValueStore = src.mKeyValueStore; + return true; +} + +} // namespace eco +} // namespace media +} // namespace android
\ No newline at end of file diff --git a/media/eco/OWNERS b/media/eco/OWNERS new file mode 100644 index 0000000..2239c4f --- /dev/null +++ b/media/eco/OWNERS @@ -0,0 +1,2 @@ +hkuang@google.com +derekpang@google.com
\ No newline at end of file diff --git a/media/eco/aidl/android/media/eco/ECOData.aidl b/media/eco/aidl/android/media/eco/ECOData.aidl new file mode 100644 index 0000000..162cd89 --- /dev/null +++ b/media/eco/aidl/android/media/eco/ECOData.aidl @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.eco; + +/** @hide */ +parcelable ECOData cpp_header "eco/ECOData.h";
\ No newline at end of file diff --git a/media/eco/aidl/android/media/eco/IECOService.aidl b/media/eco/aidl/android/media/eco/IECOService.aidl new file mode 100644 index 0000000..995abad --- /dev/null +++ b/media/eco/aidl/android/media/eco/IECOService.aidl @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.eco; + +import android.media.eco.ECOData; +import android.media.eco.IECOServiceStatsProvider; +import android.media.eco.IECOServiceInfoListener; +import android.media.eco.IECOSession; + +/** + * Binder interface for ECO (Encoder Camera Optimization) service. + * @hide + */ +interface IECOService { + /** + * All ECO service Binder calls may return a ServiceSpecificException with the following error + * codes. + */ + const int ERROR_PERMISSION_DENIED = 1; + const int ERROR_ALREADY_EXISTS = 2; + const int ERROR_ILLEGAL_ARGUMENT = 3; + const int ERROR_DISCONNECTED = 4; + const int ERROR_INVALID_OPERATION = 5; + const int ERROR_UNSUPPORTED = 6; + + /** + * Obtains an IECOSession from the ECO service. + * + * <p>ECOService will first check if the requested session already existed. If not, it will + * create and return the new session. This should be called by IECOServiceStatsProvider or + * IECOServiceInfoListener to obtain an ECOSession interface upon start.</p> + * + * @param width Width of the requested video session (in pixel). + * @param height Height of the requested video session (in pixel). + * @param isCameraRecording A boolean indicates whether the session is for camera recording. + * + * @return IECOSession The session instance created by the ECOService. + */ + IECOSession obtainSession(int width, int height, boolean isCameraRecording); + + /** + * Return the total number of sessions inside ECO service. + */ + int getNumOfSessions(); + + /** + * Return a list containing all ECOSessions. + */ + List<IBinder> getSessions(); +} diff --git a/media/eco/aidl/android/media/eco/IECOServiceInfoListener.aidl b/media/eco/aidl/android/media/eco/IECOServiceInfoListener.aidl new file mode 100644 index 0000000..bf5bd88 --- /dev/null +++ b/media/eco/aidl/android/media/eco/IECOServiceInfoListener.aidl @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.eco; + +import android.media.eco.ECOData; +import android.os.IBinder; + +/** + * Binder interface for ECO service information listener. +* +* {@hide} +*/ +interface IECOServiceInfoListener { + /** + * All listener Binder calls may return a ServiceSpecificException with the following error + * codes. + */ + const int ERROR_PERMISSION_DENIED = 1; + const int ERROR_ILLEGAL_ARGUMENT = 2; + const int ERROR_INVALID_OPERATION = 3; + const int ERROR_UNSUPPORTED = 4; + + /** + * Constants for the type of the listener. + */ + const int INFO_LISTENER_TYPE_UNKNOWN = 1; + const int INFO_LISTENER_TYPE_VIDEO_ENCODER = 2; + const int INFO_LISTENER_TYPE_CAMERA = 3; + + /** + * Return the type of the listener. + */ + int getType(); + + /** + * Return the name of the listener. + */ + String getName(); + + /** + * Return the IBinder instance of the ECOSession associated the provider. + */ + IBinder getECOSession(); + + /** + * Handle the new info from ECOSession. This should only be called by ECOSession. + */ + oneway void onNewInfo(in ECOData newInfo); +}
\ No newline at end of file diff --git a/media/eco/aidl/android/media/eco/IECOServiceStatsProvider.aidl b/media/eco/aidl/android/media/eco/IECOServiceStatsProvider.aidl new file mode 100644 index 0000000..6e2f9c3 --- /dev/null +++ b/media/eco/aidl/android/media/eco/IECOServiceStatsProvider.aidl @@ -0,0 +1,56 @@ +/* +* Copyright (C) 2019 The Android Open Source Project +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +package android.media.eco; + +import android.os.IBinder; + +/** +* An interface for providers that provides various statistics to ECO service. +* {@hide} +*/ +interface IECOServiceStatsProvider { + /** + * All provider Binder calls may return a ServiceSpecificException with the following error + * codes. + */ + const int ERROR_PERMISSION_DENIED = 1; + const int ERROR_ILLEGAL_ARGUMENT = 2; + const int ERROR_INVALID_OPERATION = 3; + const int ERROR_UNSUPPORTED = 4; + + /** + * Constants for the type of the provider. + */ + const int STATS_PROVIDER_TYPE_UNKNOWN = 1; + const int STATS_PROVIDER_TYPE_VIDEO_ENCODER = 2; + const int STATS_PROVIDER_TYPE_CAMERA = 3; + + /** + * Return the type of the provider. + */ + int getType(); + + /** + * Return the name of the provider. + */ + String getName(); + + /** + * Return the IBinder instance of the ECOSession associated the listener. + */ + IBinder getECOSession(); +} diff --git a/media/eco/aidl/android/media/eco/IECOSession.aidl b/media/eco/aidl/android/media/eco/IECOSession.aidl new file mode 100644 index 0000000..e4c1136 --- /dev/null +++ b/media/eco/aidl/android/media/eco/IECOSession.aidl @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.eco; + +import android.media.eco.ECOData; +import android.media.eco.IECOServiceStatsProvider; +import android.media.eco.IECOServiceInfoListener; + +/** + * Binder interface for ECO Session. + * @hide + */ +interface IECOSession { + /** + * All ECO session Binder calls may return a ServiceSpecificException with the following error + * codes. + */ + const int ERROR_PERMISSION_DENIED = 1; + const int ERROR_ALREADY_EXISTS = 2; + const int ERROR_ILLEGAL_ARGUMENT = 3; + const int ERROR_DISCONNECTED = 4; + const int ERROR_INVALID_OPERATION = 5; + const int ERROR_UNSUPPORTED = 6; + + /** + * Adds an ECOServiceStasProvider. + * + * <p>This is called by ECOServiceStasProvider to add itself to the ECOSession.</p> + * + * @param provider Provider that implements IECOServiceStatsProvider interface. + * @param config Config that specifies the types of the stats that the provider is going to + * provide. The ECOData must be of type DATA_TYPE_STATS_PROVIDER_CONFIG. Note: + * The provider must specify the following keys when adding itself: + * KEY_PROVIDER_NAME in string and KEY_PROVIDER_TYPE as specified in + * IECOServiceStatsProvider. + * + * @return true if the provider was successfully added, false otherwise. + */ + boolean addStatsProvider(IECOServiceStatsProvider provider, in ECOData config); + + /** + * Removes an ECOServiceStasProvider from ECOSession. + * + * @param provider Provider that implements IECOServiceStatsProvider interface. + * + * @return true if the provider was successfully removed, false otherwise. + */ + boolean removeStatsProvider(IECOServiceStatsProvider provider); + + /** + * Registers an ECOServiceInfoListener. + * + * <p>This is called by IECOServiceInfoListener to add itself to the ECOSession.</p> + * + * @param listener Listener that implements IECOServiceInfoListener interface. + * @param config Config that specifies the condition for the data that the listener is + * interested in. + * + * @return true if the listener was successfully added, false otherwise. + */ + boolean addInfoListener(IECOServiceInfoListener listener, in ECOData config); + + /** + * Removes an ECOServiceInfoListener from ECOSession. + * + * @param listener Listener that implements IECOServiceInfoListener interface. + * + * @return true if the listener was successfully removed, false otherwise. + */ + boolean removeInfoListener(IECOServiceInfoListener listener); + + /** + * Push new stats to the ECOSession. This should only be called by IECOServiceStatsProvider. + */ + boolean pushNewStats(in ECOData newStats); + + /** + * Return the width in pixels. + */ + int getWidth(); + + /** + * Return the height in pixels. + */ + int getHeight(); + + /** + * Return whether the session is for camera recording. + */ + boolean getIsCameraRecording(); + + /** + * Query the number of listeners that a session has. + */ + int getNumOfListeners(); + + /** + * Query the number of providers that a session has. + */ + int getNumOfProviders(); +} diff --git a/media/eco/include/eco/ECOData.h b/media/eco/include/eco/ECOData.h new file mode 100644 index 0000000..cb42982 --- /dev/null +++ b/media/eco/include/eco/ECOData.h @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_MEDIA_ECO_DATA_H_ +#define ANDROID_MEDIA_ECO_DATA_H_ + +#include <binder/Parcel.h> +#include <binder/Parcelable.h> + +#include <string> +#include <unordered_map> +#include <variant> + +namespace android { +namespace media { +namespace eco { + +enum class ECODataStatus { + OK, + FAIL_TO_INSERT, + INVALID_ECODATA_TYPE, + KEY_NOT_EXIST, + INVALID_VALUE_TYPE, + INVALID_ARGUMENT, +}; + +/** +* ECOData is the container for all messages passed between different components in ECOService. +* All messages in ECOServices are represented by a list of key-value pairs. +* For example: +* "bit-rate" -> 22000000 +* "Provider-Name" -> "QCOM-Video-Encoder". +* "avg-frame-qp" -> 40 +* ECOData follows the same design pattern of AMessage and Metadata in Media Framework. The key +* must be non-empty string. Below are the supported data types: +* +* // Item types set/find function suffixes +* // ========================================== +* // int32_t Int32 +* // int64_t Int64 +* // size_t Size +* // float Float +* // double Double +* // String String +* +* ECOData does not support duplicate keys with different values. When inserting a key-value pair, +* a new entry will be created if the key does not exist. Othewise, they key's value will be +* overwritten with the new value. +* +* Sample usage: +* +* // Create the ECOData +* std::unique_ptr<ECOData> data = std::make_unique<ECOData>(ECOData::DATA_TYPE_STATS, 1000); +* +* // Set the encoder name. +* data->setString("stats-encoder-type", "google-avc"); +* +* // Set encoding bitrate. +* data->setInt32("stats-encoder-target-bitrate-bps", 22000000); +*/ +class ECOData : public Parcelable { +public: + using ECODataValueType = + std::variant<int32_t, int64_t, size_t, float, double, std::string, int8_t>; + using ECODataKeyValuePair = std::pair<std::string, ECODataValueType>; + + ECOData() : mDataType(0), mDataTimeUs(-1) {} + ECOData(int32_t type) : mDataType(type), mDataTimeUs(-1) {} + ECOData(int32_t type, int64_t timeUs) : mDataType(type), mDataTimeUs(timeUs) {} + + // Constants for mDataType. + typedef enum { + DATA_TYPE_UNKNOWN = 0, + /* Data sent from the ECOServiceStatsProvider to ECOService. */ + DATA_TYPE_STATS = 1, + /* Data sent from the ECOService to ECOServiceInfoListener. */ + DATA_TYPE_INFO = 2, + /* Configuration data sent by ECOServiceStatsProvider when connects with ECOService. */ + DATA_TYPE_STATS_PROVIDER_CONFIG = 3, + /* Configuration data sent by ECOServiceInfoListener when connects with ECOService. */ + DATA_TYPE_INFO_LISTENER_CONFIG = 4, + } ECODatatype; + + // set/find functions that could be used for all the value types. + ECODataStatus set(const std::string& key, const ECODataValueType& value); + ECODataStatus find(const std::string& key, ECODataValueType* out) const; + + // Convenient set/find functions for string value type. + ECODataStatus setString(const std::string& key, const std::string& value); + ECODataStatus findString(const std::string& key, std::string* out) const; + + // Convenient set/find functions for int32_t value type. + ECODataStatus setInt32(const std::string& key, int32_t value); + ECODataStatus findInt32(const std::string& key, int32_t* out) const; + + // Convenient set/find functions for int64_t value type. + ECODataStatus setInt64(const std::string& key, int64_t value); + ECODataStatus findInt64(const std::string& key, int64_t* out) const; + + // Convenient set/find functions for float value type. + ECODataStatus setFloat(const std::string& key, float value); + ECODataStatus findFloat(const std::string& key, float* out) const; + + // Convenient set/find functions for double value type. + ECODataStatus setDouble(const std::string& key, double value); + ECODataStatus findDouble(const std::string& key, double* out) const; + + // Convenient set/find functions for size_t value type. + ECODataStatus setSize(const std::string& key, size_t value); + ECODataStatus findSize(const std::string& key, size_t* out) const; + + // Convenient set/find functions for int8_t value type. + // TODO(hkuang): Add unit test. + ECODataStatus setInt8(const std::string& key, int8_t value); + ECODataStatus findInt8(const std::string& key, int8_t* out) const; + + /** + * Serialization over Binder + */ + status_t readFromParcel(const Parcel* parcel) override; + status_t writeToParcel(Parcel* parcel) const override; + + /* Returns the type of the data. */ + int32_t getDataType() const; + + /* Returns the type of the data in string. */ + std::string getDataTypeString() const; + + /* Returns the timestamp associated with the data. */ + int64_t getDataTimeUs() const; + + /* Sets the type of the data. */ + void setDataType(int32_t type); + + /* Sets the timestamp associated with the data. */ + void setDataTimeUs(); + + /* Gets the number of keys in the ECOData. */ + size_t getNumOfEntries() const { return mKeyValueStore.size(); } + + /* Whether the ECOData is empty. */ + size_t isEmpty() const { return mKeyValueStore.size() == 0; } + + friend class ECODataKeyValueIterator; + + friend bool copyKeyValue(const ECOData& src, ECOData* dst); + + // Dump the ECOData as a string. + std::string debugString() const; + +protected: + // ValueType. This must match the index in ECODataValueType. + enum ValueType { + kTypeInt32 = 0, + kTypeInt64 = 1, + kTypeSize = 2, + kTypeFloat = 3, + kTypeDouble = 4, + kTypeString = 5, + kTypeInt8 = 6, + }; + + /* The type of the data */ + int32_t mDataType; + + // The timestamp time associated with the data in microseconds. The timestamp should be in + // boottime time base. This is only used when the data type is stats or info. -1 means + // unavailable. + int64_t mDataTimeUs; + + // Internal store for the key value pairs. + std::unordered_map<std::string, ECODataValueType> mKeyValueStore; + + template <typename T> + ECODataStatus setValue(const std::string& key, T value); + + template <typename T> + ECODataStatus findValue(const std::string& key, T* out) const; +}; + +// A simple ECOData iterator that will iterate over all the key value paris in ECOData. +// To be used like: +// while (it.hasNext()) { +// entry = it.next(); +// } +class ECODataKeyValueIterator { +public: + ECODataKeyValueIterator(const ECOData& data) + : mKeyValueStore(data.mKeyValueStore), mBeginReturned(false) { + mIterator = mKeyValueStore.begin(); + } + ~ECODataKeyValueIterator() = default; + bool hasNext(); + ECOData::ECODataKeyValuePair next() const; + +private: + const std::unordered_map<std::string, ECOData::ECODataValueType>& mKeyValueStore; + std::unordered_map<std::string, ECOData::ECODataValueType>::const_iterator mIterator; + bool mBeginReturned; +}; + +} // namespace eco +} // namespace media +} // namespace android + +#endif // ANDROID_MEDIA_ECO_DATA_H_ diff --git a/media/eco/include/eco/ECODataKey.h b/media/eco/include/eco/ECODataKey.h new file mode 100644 index 0000000..c70c328 --- /dev/null +++ b/media/eco/include/eco/ECODataKey.h @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef ANDROID_MEDIA_ECO_DATA_KEY_H_ +#define ANDROID_MEDIA_ECO_DATA_KEY_H_ + +#include <android-base/unique_fd.h> +#include <binder/Parcel.h> +#include <binder/Parcelable.h> +#include <stdint.h> +#include <sys/mman.h> + +namespace android { +namespace media { +namespace eco { + +// ================================================================================================ +// Standard ECOData keys. +// ================================================================================================ +constexpr char KEY_ECO_DATA_TYPE[] = "eco-data-type"; +constexpr char KEY_ECO_DATA_TIME_US[] = "eco-data-time-us"; + +// ================================================================================================ +// Standard ECOServiceStatsProvider config keys. These keys are used in the ECOData as an config +// when StatsProvider connects with ECOService. +// ================================================================================================ +constexpr char KEY_PROVIDER_NAME[] = "provider-name"; +constexpr char KEY_PROVIDER_TYPE[] = "provider-type"; + +// ================================================================================================ +// Standard ECOServiceInfoListener config keys. These keys are used in the ECOData as config +// when ECOServiceInfoListener connects with ECOService to specify the informations that the +// listener wants to listen to. +// ================================================================================================ +constexpr char KEY_LISTENER_NAME[] = "listener-name"; +constexpr char KEY_LISTENER_TYPE[] = "listener-type"; + +// Following two keys are used together for the listener to specify the condition when it wants to +// receive notification. When a frame's avg-qp crosses KEY_LISTENER_QP_BLOCKINESS_THRESHOLD or +// the detla of qp between current frame and previous frame also goes beyond +// KEY_LISTENER_QP_CHANGE_THRESHOLD, ECOService will notify the listener. +constexpr char KEY_LISTENER_QP_BLOCKINESS_THRESHOLD[] = "listener-qp-blockness-threshold"; +constexpr char KEY_LISTENER_QP_CHANGE_THRESHOLD[] = "listener-qp-change-threshold"; + +// ================================================================================================ +// ECOService Stats keys. These key MUST BE specified when provider pushes the stats to ECOService +// to indicate the stats is session stats or frame stats. +// ================================================================================================ +constexpr char KEY_STATS_TYPE[] = "stats-type"; +constexpr char VALUE_STATS_TYPE_SESSION[] = "stats-type-session"; // value for KEY_STATS_TYPE. +constexpr char VALUE_STATS_TYPE_FRAME[] = "stats-type-frame"; // value for KEY_STATS_TYPE. + +// ================================================================================================ +// Standard ECOService Info keys. These key will be in the info provided by ECOService to indicate +// the info is session info or frame info. +// ================================================================================================ +constexpr char KEY_INFO_TYPE[] = "info-type"; +constexpr char VALUE_INFO_TYPE_SESSION[] = "info-type-session"; // value for KEY_INFO_TYPE. +constexpr char VALUE_INFO_TYPE_FRAME[] = "info-type-frame"; // value for KEY_INFO_TYPE. + +// ================================================================================================ +// General keys to be used by both stats and info in the ECOData. +// ================================================================================================ +constexpr char ENCODER_NAME[] = "encoder-name"; +constexpr char ENCODER_TYPE[] = "encoder-type"; +constexpr char ENCODER_PROFILE[] = "encoder-profile"; +constexpr char ENCODER_LEVEL[] = "encoder-level"; +constexpr char ENCODER_INPUT_WIDTH[] = "encoder-input-width"; +constexpr char ENCODER_INPUT_HEIGHT[] = "encoder-input-height"; +constexpr char ENCODER_OUTPUT_WIDTH[] = "encoder-output-width"; +constexpr char ENCODER_OUTPUT_HEIGHT[] = "encoder-output-height"; +constexpr char ENCODER_TARGET_BITRATE_BPS[] = "encoder-target-bitrate-bps"; // Session info +constexpr char ENCODER_ACTUAL_BITRATE_BPS[] = "encoder-actual-bitrate-bps"; // Frame info +constexpr char ENCODER_KFI_FRAMES[] = "encoder-kfi-frames"; +constexpr char ENCODER_FRAMERATE_FPS[] = "encoder-framerate-fps"; + +constexpr char FRAME_NUM[] = "frame-num"; +constexpr char FRAME_PTS_US[] = "frame-pts-us"; +constexpr char FRAME_AVG_QP[] = "frame-avg-qp"; +constexpr char FRAME_TYPE[] = "frame-type"; +constexpr char FRAME_SIZE_BYTES[] = "frame-size-bytes"; + +} // namespace eco +} // namespace media +} // namespace android + +#endif // ANDROID_MEDIA_ECO_DATA_KEY_H_ diff --git a/media/eco/include/eco/ECODebug.h b/media/eco/include/eco/ECODebug.h new file mode 100644 index 0000000..b250f64 --- /dev/null +++ b/media/eco/include/eco/ECODebug.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_MEDIA_ECO_DEBUG_H_ +#define ANDROID_MEDIA_ECO_DEBUG_H_ + +#include <cutils/atomic.h> +#include <cutils/properties.h> + +#include "ECOData.h" + +namespace android { +namespace media { +namespace eco { + +static const char* kDebugLogsLevelProperty = "media.ecoservice.log.level"; +static const char* kDebugLogStats = "media.ecoservice.log.stats"; +static const char* kDebugLogStatsSize = "media.ecoservice.log.stats.size"; +static const char* kDebugLogInfos = "media.ecoservice.log.info"; +static const char* kDebugLogInfosSize = "media.ecoservice.log.info.size"; + +// A debug variable that should only be accessed by ECOService through updateLogLevel. It is rare +// that this variable will have race condition. But if so, it is ok as this is just for debugging. +extern uint32_t gECOLogLevel; + +enum ECOLogLevel : uint32_t { + ECO_MSGLEVEL_DEBUG = 0x01, ///< debug logs + ECO_MSGLEVEL_VERBOSE = 0x02, ///< very detailed logs + ECO_MSGLEVEL_ALL = 0x03, ///< both debug logs and detailed logs +}; + +#define ECOLOGV(format, args...) ALOGD_IF((gECOLogLevel & ECO_MSGLEVEL_VERBOSE), format, ##args) +#define ECOLOGD(format, args...) ALOGD_IF((gECOLogLevel & ECO_MSGLEVEL_DEBUG), format, ##args) +#define ECOLOGI(format, args...) ALOGI(format, ##args) +#define ECOLOGW(format, args...) ALOGW(format, ##args) +#define ECOLOGE(format, args...) ALOGE(format, ##args) + +void updateLogLevel(); + +// Convenience methods for constructing binder::Status objects for error returns +#define STATUS_ERROR(errorCode, errorString) \ + binder::Status::fromServiceSpecificError( \ + errorCode, String8::format("%s:%d: %s", __FUNCTION__, __LINE__, errorString)) + +#define STATUS_ERROR_FMT(errorCode, errorString, ...) \ + binder::Status::fromServiceSpecificError( \ + errorCode, \ + String8::format("%s:%d: " errorString, __FUNCTION__, __LINE__, __VA_ARGS__)) + +} // namespace eco +} // namespace media +} // namespace android + +#endif // ANDROID_MEDIA_ECO_DEBUG_H_ diff --git a/media/eco/include/eco/ECOService.h b/media/eco/include/eco/ECOService.h new file mode 100644 index 0000000..f83edff --- /dev/null +++ b/media/eco/include/eco/ECOService.h @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_MEDIA_ECO_SERVICE_H_ +#define ANDROID_MEDIA_ECO_SERVICE_H_ + +#include <android/media/eco/BnECOService.h> +#include <binder/BinderService.h> + +#include <list> + +#include "eco/ECODebug.h" +#include "eco/ECOSession.h" + +namespace android { +namespace media { +namespace eco { + +using android::sp; +using android::binder::Status; +using android::media::eco::ECOSession; + +/** + * ECO (Encoder Camera Optimization) service. + * + * ECOService creates and manages EcoSession to relay feedback information between one or multiple + * ECOServiceStatsProvider and ECOServiceInfoListener. The relationship can be many-to-many. In + * general, ECOServiceStatsProvider extracts information from an encoder for a given encoding + * session. EcoSession then relays the encoder information to any subscribed + * ECOServiceInfoListener. + * + * Internally, ECOService creates an ECOSession for each encoding session. Upon start, both + * ECOServiceStatsProvider and ECOServiceInfoListener should call obtainSession to get the + * ECOSession instance. After that, ECOServiceStatsProvider should push Stats to ECOSession and + * ECOServiceInfoListener should listen to the info from ECOSession. Upon finish, both + * ECOServiceStatsProvider and ECOServiceInfoListener should remove themselves from ECOSession. + * Then ECOService will safely destroy the ECOSession. + */ +class ECOService : public BinderService<ECOService>, + public BnECOService, + public virtual IBinder::DeathRecipient { + friend class BinderService<ECOService>; + +public: + ECOService(); + + virtual ~ECOService() = default; + + virtual Status obtainSession(int32_t width, int32_t height, bool isCameraRecording, + sp<IECOSession>* _aidl_return); + + virtual Status getNumOfSessions(int32_t* _aidl_return); + + virtual Status getSessions(::std::vector<sp<IBinder>>* _aidl_return); + + // Implementation of BinderService<T> + static char const* getServiceName() { return "media.ecoservice"; } + + // IBinder::DeathRecipient implementation + virtual void binderDied(const wp<IBinder>& who); + + virtual status_t dump(int fd, const Vector<String16>& args); + +private: + // Lock guarding ECO service state + Mutex mServiceLock; + + struct SessionConfig { + int32_t mWidth; + int32_t mHeight; + bool mIsCameraRecording; + + SessionConfig(int w, int h, bool isCameraRecording) + : mWidth(w), mHeight(h), mIsCameraRecording(isCameraRecording) {} + + bool operator==(const SessionConfig& cfg) { + return mWidth == cfg.mWidth && mHeight == cfg.mHeight && + mIsCameraRecording == cfg.mIsCameraRecording; + } + }; + + friend bool operator==(const SessionConfig& p1, const SessionConfig& p2) { + return p1.mWidth == p2.mWidth && p1.mHeight == p2.mHeight && + p1.mIsCameraRecording == p2.mIsCameraRecording; + } + + // Hash function for mSessionConfigToSessionMap. + // TODO(hkuang): Add test for this hash function. + struct SessionConfigHash { + size_t operator()(const SessionConfig& cfg) const { + // Generate a hash by bit shifting and concatenation. + return cfg.mWidth | (cfg.mHeight << 16) | ((int32_t)cfg.mIsCameraRecording << 31); + } + }; + + // Map from SessionConfig to session. + std::unordered_map<SessionConfig, wp<ECOSession>, SessionConfigHash> mSessionConfigToSessionMap; + + using MapIterType = + std::unordered_map<SessionConfig, wp<ECOSession>, SessionConfigHash>::iterator; + + // A helpful function to traverse the mSessionConfigToSessionMap, remove the entry that + // does not exist any more and call |callback| when the entry is valid. + void SanitizeSession(const std::function<void(MapIterType it)>& callback); +}; + +} // namespace eco +} // namespace media +} // namespace android + +#endif // ANDROID_MEDIA_ECO_SERVICE_H_ diff --git a/media/eco/include/eco/ECOServiceConstants.h b/media/eco/include/eco/ECOServiceConstants.h new file mode 100644 index 0000000..5d753d0 --- /dev/null +++ b/media/eco/include/eco/ECOServiceConstants.h @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef ANDROID_MEDIA_ECO_SERVICE_CONSTANTS_H_ +#define ANDROID_MEDIA_ECO_SERVICE_CONSTANTS_H_ + +#include <stdint.h> +#include <sys/mman.h> + +namespace android { +namespace media { +namespace eco { + +// Codec type. +constexpr int32_t CodecTypeUnknown = 0x00; +constexpr int32_t CodecTypeAVC = 0x01; +constexpr int32_t CodecTypeHEVC = 0x02; + +// Encoded frame type. +constexpr int32_t FrameTypeUnknown = 0x0; +constexpr int32_t FrameTypeI = 0x01; +constexpr int32_t FrameTypeP = 0x02; +constexpr int32_t FrameTypeB = 0x04; + +// Below constants are borrowed from +// frameworks/av/media/libstagefright/include/media/stagefright/MediaCodecConstants.h + +// from MediaCodecInfo.java + +// Profile types: +constexpr int32_t AVCProfileBaseline = 0x01; +constexpr int32_t AVCProfileMain = 0x02; +constexpr int32_t AVCProfileExtended = 0x04; +constexpr int32_t AVCProfileHigh = 0x08; +constexpr int32_t AVCProfileHigh10 = 0x10; +constexpr int32_t AVCProfileHigh422 = 0x20; +constexpr int32_t AVCProfileHigh444 = 0x40; +constexpr int32_t AVCProfileConstrainedBaseline = 0x10000; +constexpr int32_t AVCProfileConstrainedHigh = 0x80000; + +constexpr int32_t HEVCProfileMain = 0x01; +constexpr int32_t HEVCProfileMain10 = 0x02; +constexpr int32_t HEVCProfileMainStill = 0x04; +constexpr int32_t HEVCProfileMain10HDR10 = 0x1000; +constexpr int32_t HEVCProfileMain10HDR10Plus = 0x2000; + +// Level types: +constexpr int32_t AVCLevel1 = 0x01; +constexpr int32_t AVCLevel1b = 0x02; +constexpr int32_t AVCLevel11 = 0x04; +constexpr int32_t AVCLevel12 = 0x08; +constexpr int32_t AVCLevel13 = 0x10; +constexpr int32_t AVCLevel2 = 0x20; +constexpr int32_t AVCLevel21 = 0x40; +constexpr int32_t AVCLevel22 = 0x80; +constexpr int32_t AVCLevel3 = 0x100; +constexpr int32_t AVCLevel31 = 0x200; +constexpr int32_t AVCLevel32 = 0x400; +constexpr int32_t AVCLevel4 = 0x800; +constexpr int32_t AVCLevel41 = 0x1000; +constexpr int32_t AVCLevel42 = 0x2000; +constexpr int32_t AVCLevel5 = 0x4000; +constexpr int32_t AVCLevel51 = 0x8000; +constexpr int32_t AVCLevel52 = 0x10000; +constexpr int32_t AVCLevel6 = 0x20000; +constexpr int32_t AVCLevel61 = 0x40000; +constexpr int32_t AVCLevel62 = 0x80000; + +constexpr int32_t HEVCMainTierLevel1 = 0x1; +constexpr int32_t HEVCHighTierLevel1 = 0x2; +constexpr int32_t HEVCMainTierLevel2 = 0x4; +constexpr int32_t HEVCHighTierLevel2 = 0x8; +constexpr int32_t HEVCMainTierLevel21 = 0x10; +constexpr int32_t HEVCHighTierLevel21 = 0x20; +constexpr int32_t HEVCMainTierLevel3 = 0x40; +constexpr int32_t HEVCHighTierLevel3 = 0x80; +constexpr int32_t HEVCMainTierLevel31 = 0x100; +constexpr int32_t HEVCHighTierLevel31 = 0x200; +constexpr int32_t HEVCMainTierLevel4 = 0x400; +constexpr int32_t HEVCHighTierLevel4 = 0x800; +constexpr int32_t HEVCMainTierLevel41 = 0x1000; +constexpr int32_t HEVCHighTierLevel41 = 0x2000; +constexpr int32_t HEVCMainTierLevel5 = 0x4000; +constexpr int32_t HEVCHighTierLevel5 = 0x8000; +constexpr int32_t HEVCMainTierLevel51 = 0x10000; +constexpr int32_t HEVCHighTierLevel51 = 0x20000; +constexpr int32_t HEVCMainTierLevel52 = 0x40000; +constexpr int32_t HEVCHighTierLevel52 = 0x80000; +constexpr int32_t HEVCMainTierLevel6 = 0x100000; +constexpr int32_t HEVCHighTierLevel6 = 0x200000; +constexpr int32_t HEVCMainTierLevel61 = 0x400000; +constexpr int32_t HEVCHighTierLevel61 = 0x800000; +constexpr int32_t HEVCMainTierLevel62 = 0x1000000; +constexpr int32_t HEVCHighTierLevel62 = 0x2000000; + +inline static const char* asString_AVCProfile(int32_t i, const char* def = "??") { + switch (i) { + case AVCProfileBaseline: + return "Baseline"; + case AVCProfileMain: + return "Main"; + case AVCProfileExtended: + return "Extended"; + case AVCProfileHigh: + return "High"; + case AVCProfileHigh10: + return "High10"; + case AVCProfileHigh422: + return "High422"; + case AVCProfileHigh444: + return "High444"; + case AVCProfileConstrainedBaseline: + return "ConstrainedBaseline"; + case AVCProfileConstrainedHigh: + return "ConstrainedHigh"; + default: + return def; + } +} + +inline static const char* asString_AVCLevel(int32_t i, const char* def = "??") { + switch (i) { + case AVCLevel1: + return "1"; + case AVCLevel1b: + return "1b"; + case AVCLevel11: + return "1.1"; + case AVCLevel12: + return "1.2"; + case AVCLevel13: + return "1.3"; + case AVCLevel2: + return "2"; + case AVCLevel21: + return "2.1"; + case AVCLevel22: + return "2.2"; + case AVCLevel3: + return "3"; + case AVCLevel31: + return "3.1"; + case AVCLevel32: + return "3.2"; + case AVCLevel4: + return "4"; + case AVCLevel41: + return "4.1"; + case AVCLevel42: + return "4.2"; + case AVCLevel5: + return "5"; + case AVCLevel51: + return "5.1"; + case AVCLevel52: + return "5.2"; + case AVCLevel6: + return "6"; + case AVCLevel61: + return "6.1"; + case AVCLevel62: + return "6.2"; + default: + return def; + } +} + +inline static const char* asString_HEVCProfile(int32_t i, const char* def = "??") { + switch (i) { + case HEVCProfileMain: + return "Main"; + case HEVCProfileMain10: + return "Main10"; + case HEVCProfileMainStill: + return "MainStill"; + case HEVCProfileMain10HDR10: + return "Main10HDR10"; + case HEVCProfileMain10HDR10Plus: + return "Main10HDR10Plus"; + default: + return def; + } +} + +inline static const char* asString_HEVCTierLevel(int32_t i, const char* def = "??") { + switch (i) { + case HEVCMainTierLevel1: + return "Main 1"; + case HEVCHighTierLevel1: + return "High 1"; + case HEVCMainTierLevel2: + return "Main 2"; + case HEVCHighTierLevel2: + return "High 2"; + case HEVCMainTierLevel21: + return "Main 2.1"; + case HEVCHighTierLevel21: + return "High 2.1"; + case HEVCMainTierLevel3: + return "Main 3"; + case HEVCHighTierLevel3: + return "High 3"; + case HEVCMainTierLevel31: + return "Main 3.1"; + case HEVCHighTierLevel31: + return "High 3.1"; + case HEVCMainTierLevel4: + return "Main 4"; + case HEVCHighTierLevel4: + return "High 4"; + case HEVCMainTierLevel41: + return "Main 4.1"; + case HEVCHighTierLevel41: + return "High 4.1"; + case HEVCMainTierLevel5: + return "Main 5"; + case HEVCHighTierLevel5: + return "High 5"; + case HEVCMainTierLevel51: + return "Main 5.1"; + case HEVCHighTierLevel51: + return "High 5.1"; + case HEVCMainTierLevel52: + return "Main 5.2"; + case HEVCHighTierLevel52: + return "High 5.2"; + case HEVCMainTierLevel6: + return "Main 6"; + case HEVCHighTierLevel6: + return "High 6"; + case HEVCMainTierLevel61: + return "Main 6.1"; + case HEVCHighTierLevel61: + return "High 6.1"; + case HEVCMainTierLevel62: + return "Main 6.2"; + case HEVCHighTierLevel62: + return "High 6.2"; + default: + return def; + } +} + +} // namespace eco +} // namespace media +} // namespace android + +#endif // ANDROID_MEDIA_ECO_SERVICE_CONSTANTS_H_ diff --git a/media/eco/include/eco/ECOServiceInfoListener.h b/media/eco/include/eco/ECOServiceInfoListener.h new file mode 100644 index 0000000..8a08a69 --- /dev/null +++ b/media/eco/include/eco/ECOServiceInfoListener.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_MEDIA_ECO_SERVICE_INFO_LISTENER_H_ +#define ANDROID_MEDIA_ECO_SERVICE_INFO_LISTENER_H_ + +#include <android/media/eco/BnECOServiceInfoListener.h> +#include <android/media/eco/IECOSession.h> +#include <binder/BinderService.h> + +#include <condition_variable> +#include <memory> +#include <mutex> +#include <thread> + +#include "ECOData.h" + +namespace android { +namespace media { +namespace eco { + +using ::android::binder::Status; + +/** + * ECOServiceInfoListener interface class. + */ +class ECOServiceInfoListener : public BinderService<IECOServiceInfoListener>, + public BnECOServiceInfoListener, + public virtual IBinder::DeathRecipient { + friend class BinderService<IECOServiceInfoListener>; + +public: + // Create a ECOServiceInfoListener with specifed width, height and isCameraRecording. + ECOServiceInfoListener(int32_t width, int32_t height, bool isCameraRecording); + + virtual ~ECOServiceInfoListener() {} + + virtual Status getType(int32_t* _aidl_return) = 0; + virtual Status getName(::android::String16* _aidl_return) = 0; + virtual Status getECOSession(::android::sp<::android::IBinder>* _aidl_return) = 0; + virtual Status onNewInfo(const ::android::media::eco::ECOData& newInfo) = 0; + + // IBinder::DeathRecipient implementation. + virtual void binderDied(const wp<IBinder>& who); + +private: +}; + +} // namespace eco +} // namespace media +} // namespace android + +#endif // ANDROID_MEDIA_ECO_SERVICE_INFO_LISTENER_H_ diff --git a/media/eco/include/eco/ECOServiceStatsProvider.h b/media/eco/include/eco/ECOServiceStatsProvider.h new file mode 100644 index 0000000..a6e1586 --- /dev/null +++ b/media/eco/include/eco/ECOServiceStatsProvider.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_MEDIA_ECO_SERVICE_STATS_PROVIDER_H_ +#define ANDROID_MEDIA_ECO_SERVICE_STATS_PROVIDER_H_ + +#include <android/media/eco/BnECOServiceStatsProvider.h> +#include <android/media/eco/IECOSession.h> +#include <binder/BinderService.h> + +#include <condition_variable> +#include <memory> +#include <mutex> +#include <thread> + +#include "ECOData.h" + +namespace android { +namespace media { +namespace eco { + +using ::android::binder::Status; + +/** + * ECOServiceStatsProvider interface class. + */ +class ECOServiceStatsProvider : public BinderService<IECOServiceStatsProvider>, + public BnECOServiceStatsProvider, + public virtual IBinder::DeathRecipient { + friend class BinderService<IECOServiceStatsProvider>; + +public: + // Create a ECOServiceStatsProvider with specifed width, height and isCameraRecording. + ECOServiceStatsProvider(int32_t width, int32_t height, bool isCameraRecording); + + virtual ~ECOServiceStatsProvider() {} + + virtual Status getType(int32_t* _aidl_return) = 0; + virtual Status getName(::android::String16* _aidl_return) = 0; + virtual Status getECOSession(::android::sp<::android::IBinder>* _aidl_return) = 0; + virtual Status isCameraRecording(bool* _aidl_return) = 0; + + // IBinder::DeathRecipient implementation + virtual void binderDied(const wp<IBinder>& who); + +private: +}; + +} // namespace eco +} // namespace media +} // namespace android + +#endif // ANDROID_MEDIA_ECO_SERVICE_STATS_PROVIDER_H_ diff --git a/media/eco/include/eco/ECOSession.h b/media/eco/include/eco/ECOSession.h new file mode 100644 index 0000000..90f9d94 --- /dev/null +++ b/media/eco/include/eco/ECOSession.h @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_MEDIA_ECO_SESSION_H_ +#define ANDROID_MEDIA_ECO_SESSION_H_ + +#include <android/media/eco/BnECOSession.h> +#include <android/media/eco/IECOServiceStatsProvider.h> +#include <binder/BinderService.h> + +#include <condition_variable> +#include <deque> +#include <list> +#include <memory> +#include <mutex> +#include <thread> + +#include "ECOData.h" +#include "ECOServiceInfoListener.h" +#include "ECOServiceStatsProvider.h" +#include "ECOUtils.h" + +namespace android { +namespace media { +namespace eco { + +using ::android::binder::Status; + +/** + * ECO Session. + * + * ECOSession is created by ECOService to manage an encoding session. Both the providers and + * listeners should interact with ECO session after obtain it from ECOService. For ECOService 1.0, + * it only supports resolution of up to 720P and only for camera recording use case. Also, it only + * supports encoder as the provider and camera as listener. + */ +class ECOSession : public BinderService<ECOSession>, + public BnECOSession, + public virtual IBinder::DeathRecipient { + friend class BinderService<ECOSession>; + +public: + virtual ~ECOSession(); + + virtual Status addStatsProvider(const sp<IECOServiceStatsProvider>& provider, + const ECOData& statsConfig, /*out*/ bool* status); + + virtual Status removeStatsProvider(const sp<IECOServiceStatsProvider>&, bool*); + + virtual Status addInfoListener(const sp<IECOServiceInfoListener>&, + const ECOData& listenerConfig, + /*out*/ bool* status); + + virtual Status removeInfoListener(const sp<IECOServiceInfoListener>&, bool*); + + virtual Status pushNewStats(const ECOData&, bool*); + + virtual Status getWidth(int32_t* _aidl_return); + + virtual Status getHeight(int32_t* _aidl_return); + + virtual Status getIsCameraRecording(bool*); + + virtual Status getNumOfListeners(int32_t*); + + virtual Status getNumOfProviders(int32_t*); + + // IBinder::DeathRecipient implementation + virtual void binderDied(const wp<IBinder>& who); + + // Grant permission to EcoSessionTest to run test. + friend class EcoSessionTest; + + // Let ECOService create the session. + friend class ECOService; + +protected: + static android::sp<ECOSession> createECOSession(int32_t width, int32_t height, + bool isCameraRecording); + +private: + // Only the ECOService could create ECOSession. + ECOSession(int32_t width, int32_t height, bool isCameraRecording); + + virtual status_t dump(int fd, const Vector<String16>& args); + + // Start the main thread for processing the stats and pushing info to listener. + static void startThread(ECOSession* session); + + void run(); + + bool processStats(const ECOData& stats); + + // Lock guarding ECO session state + std::mutex mSessionLock; + + // Process the session stats received from provider. + void processSessionStats(const ECOData& stats); + + // Process the frame stats received from provider. + void processFrameStats(const ECOData& stats); + + // Generate the latest session info if available. + ECOData generateLatestSessionInfoEcoData(); + + std::atomic<bool> mStopThread; + + std::mutex mStatsQueueLock; + std::deque<ECOData> mStatsQueue; // GUARDED_BY(mStatsQueueLock) + std::condition_variable mWorkerWaitCV; + + bool mNewListenerAdded = false; + + constexpr static int32_t ENCODER_MIN_QP = 0; + constexpr static int32_t ENCODER_MAX_QP = 51; + + // Save the QP last reported to the listener. Init to be 0. + int32_t mLastReportedQp; + + typedef struct QpRange { + int32_t mQpBlocknessThreshold = 50; + int32_t mQpChangeThreshold = 50; + } QpCondition; + QpCondition mListenerQpCondition; + + android::sp<IECOServiceInfoListener> mListener; + String16 mListenerName; + + android::sp<IECOServiceStatsProvider> mProvider; + String16 mProviderName; + + // Main thread for processing the events from provider. + std::thread mThread; + + // Width of the encoding session in number of pixels. + const int32_t mWidth; + + // Height of the encoding session in number of pixels. + const int32_t mHeight; + + // Whether the encoding is for camera recording. + const bool mIsCameraRecording; + + // Ouput width of the encoding session in number of pixels, -1 means not available. + int32_t mOutputWidth = -1; + + // Output height of the encoding session in number of pixels. -1 means not available. + int32_t mOutputHeight = -1; + + // Encoder codec type of the encoding session. -1 means not available. + int32_t mCodecType = -1; + + // Encoder codec profile. -1 means not available. + int32_t mCodecProfile = -1; + + // Encoder codec level. -1 means not available. + int32_t mCodecLevel = -1; + + // Target bitrate in bits per second. This should be provided by the provider. -1 means not + // available. + int32_t mTargetBitrateBps = -1; + + // Actual bitrate in bits per second. This should be provided by the provider. -1 means not + // available. + int32_t mActualBitrateBps = -1; + + // Key frame interval in number of frames. -1 means not available. + int32_t mKeyFrameIntervalFrames = -1; + + // Frame rate in frames per second. -1 means not available. + float mFramerateFps; + + // Debug related flags. + bool mLogStats; + uint32_t mLogStatsEntries; // number of stats received from the provider. + std::list<ECOData> mStatsDebugBuffer; + + // Pushes the ECOData to the debug buffer. + void logStats(const ECOData& data); + + bool mLogInfo; + uint32_t mLogInfoEntries; // number of infos sent to the listener. + std::list<ECOData> mInfosDebugBuffer; + + // Pushes the ECOData to the debug buffer. + void logInfos(const ECOData& data); +}; + +} // namespace eco +} // namespace media +} // namespace android + +#endif // ANDROID_MEDIA_ECO_SESSION_H_ diff --git a/media/eco/include/eco/ECOUtils.h b/media/eco/include/eco/ECOUtils.h new file mode 100644 index 0000000..e5bbfcc --- /dev/null +++ b/media/eco/include/eco/ECOUtils.h @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Helper classes and functions to be used by provider and listener. +#ifndef ANDROID_MEDIA_ECO_UTILS_H_ +#define ANDROID_MEDIA_ECO_UTILS_H_ + +#include <cutils/atomic.h> +#include <utils/Errors.h> + +#include "ECOData.h" +#include "ECODataKey.h" +#include "ECOServiceConstants.h" + +namespace android { +namespace media { +namespace eco { + +#define RETURN_STATUS_IF_ERROR(expr) \ + { \ + status_t _errorCode = (expr); \ + if (_errorCode != NO_ERROR) { \ + return _errorCode; \ + } \ + } + +// Helper structure to hold encoder config. This is used by EncoderProvider to provide stats to +// ECOService or by ECOService to provide info to the listener. Providers could implement their +// own more complex encoder config as long as ECOService supports parsing them. +struct SimpleEncoderConfig { + // Encoder name as defined in device/google/[device]/media_codecs.xml. + std::string mEncoderName; + + // Codec Type as defined in ECOServiceConstants.h. -1 means unavailable. + int32_t mCodecType; + + // Codec profile as defined in ECOServiceConstants.h. -1 means unavailable. + int32_t mProfile; + + // Codec level as defined in ECOServiceConstants.h. -1 means unavailable. + int32_t mLevel; + + // Target bitrate in bits per second. -1 means unavailable. + int32_t mTargetBitrate; + + // Key frame interval in frames. -1 means unavailable. + int32_t mKeyFrameIntervalFrames; + + // Frame rate in frames per second. -1 means unavailable. + float mFrameRateFps; + + SimpleEncoderConfig() + : mEncoderName(""), + mCodecType(-1), + mProfile(-1), + mLevel(-1), + mTargetBitrate(-1), + mKeyFrameIntervalFrames(-1), + mFrameRateFps(-1) {} + + SimpleEncoderConfig(const std::string& name, int32_t codecType, int32_t profile, int32_t level, + int32_t bitrate, int32_t kfi, float framerateFps) + : mEncoderName(name), + mCodecType(codecType), + mProfile(profile), + mLevel(level), + mTargetBitrate(bitrate), + mKeyFrameIntervalFrames(kfi), + mFrameRateFps(framerateFps) {} + + // Convert this SimpleEncoderConfig to ECOData with dataType. + ECOData toEcoData(ECOData::ECODatatype dataType); +}; + +// Helper structure for +struct SimpleEncodedFrameData { + // Frame sequence number starts from 0. + int32_t mFrameNum; + + // Frame type as defined in ECOServiceConstants.h. -1 means unavailable. + int8_t mFrameType; + + // Frame presentation timestamp in us. -1 means unavailable. + int64_t mFramePtsUs; + + // Avg quantization parameter of the frame. -1 means unavailable. + int32_t mAvgQp; + + // Frame size in bytes. -1 means unavailable. + int32_t mFrameSizeBytes; + + SimpleEncodedFrameData() + : mFrameNum(-1), + mFrameType(FrameTypeUnknown), + mFramePtsUs(-1), + mAvgQp(-1), + mFrameSizeBytes(-1) {} + + SimpleEncodedFrameData(int32_t frameNum, int8_t frameType, int64_t ptsUs, int32_t qp, + int32_t sizeBytes) + : mFrameNum(frameNum), + mFrameType(frameType), + mFramePtsUs(ptsUs), + mAvgQp(qp), + mFrameSizeBytes(sizeBytes) {} + + // Convert this SimpleEncoderConfig to ECOData with dataType. + ECOData toEcoData(ECOData::ECODatatype dataType); +}; + +bool copyKeyValue(const ECOData& src, ECOData* dst); + +} // namespace eco +} // namespace media +} // namespace android + +#endif // ANDROID_MEDIA_ECO_UTILS_H_ diff --git a/media/eco/tests/Android.bp b/media/eco/tests/Android.bp new file mode 100644 index 0000000..6eb3015 --- /dev/null +++ b/media/eco/tests/Android.bp @@ -0,0 +1,57 @@ +cc_defaults{ + name : "libmedia_ecoservice_tests_defaults", + cflags : [ + "-Wall", + "-Werror", + ], +} + +cc_test { + name: "EcoDataTest", + defaults: ["libmedia_ecoservice_tests_defaults"], + srcs: ["EcoDataTest.cpp"], + shared_libs: [ + "libbinder", + "libcutils", + "libutils", + "liblog", + "libmedia_ecoservice", + ], +} + +cc_test { + name: "EcoSessionTest", + defaults: ["libmedia_ecoservice_tests_defaults"], + srcs: [ + "EcoSessionTest.cpp", + "FakeECOServiceStatsProvider.cpp", + "FakeECOServiceInfoListener.cpp", + ], + + shared_libs: [ + "libbinder", + "libcutils", + "libutils", + "liblog", + "libmedia_ecoservice", + ], +} + +cc_test { + name: "EcoServiceTest", + vendor: true, + defaults: ["libmedia_ecoservice_tests_defaults"], + srcs: [ + "EcoServiceTest.cpp", + "FakeECOServiceStatsProvider.cpp", + "FakeECOServiceInfoListener.cpp", + ], + + shared_libs: [ + "libbinder", + "libcutils", + "libutils", + "liblog", + "libmedia_ecoservice", + ], +}
\ No newline at end of file diff --git a/media/eco/tests/EcoDataTest.cpp b/media/eco/tests/EcoDataTest.cpp new file mode 100644 index 0000000..f93b692 --- /dev/null +++ b/media/eco/tests/EcoDataTest.cpp @@ -0,0 +1,367 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Unit Test for EcoData. + +//#define LOG_NDEBUG 0 +#define LOG_TAG "ECODataTest" + +#include <android-base/unique_fd.h> +#include <binder/Parcel.h> +#include <binder/Parcelable.h> +#include <cutils/ashmem.h> +#include <gtest/gtest.h> +#include <math.h> +#include <stdlib.h> +#include <sys/mman.h> +#include <utils/Log.h> + +#include "eco/ECOData.h" +#include "eco/ECODataKey.h" + +namespace android { +namespace media { +namespace eco { + +TEST(EcoDataTest, TestConstructor1) { + std::unique_ptr<ECOData> data = std::make_unique<ECOData>(); + EXPECT_EQ(data->getDataType(), ECOData::DATA_TYPE_UNKNOWN); + EXPECT_EQ(data->getDataTimeUs(), -1); +} + +TEST(EcoDataTest, TestConstructor2) { + std::unique_ptr<ECOData> data = std::make_unique<ECOData>(ECOData::DATA_TYPE_STATS); + EXPECT_EQ(data->getDataType(), ECOData::DATA_TYPE_STATS); + EXPECT_EQ(data->getDataTimeUs(), -1); +} + +TEST(EcoDataTest, TestConstructor3) { + std::unique_ptr<ECOData> data = std::make_unique<ECOData>(ECOData::DATA_TYPE_STATS, 1000); + EXPECT_EQ(data->getDataType(), ECOData::DATA_TYPE_STATS); + EXPECT_EQ(data->getDataTimeUs(), 1000); +} + +TEST(EcoDataTest, TestNormalSetAndFindString) { + std::unique_ptr<ECOData> data = std::make_unique<ECOData>(ECOData::DATA_TYPE_STATS, 1000); + + data->setString(ENCODER_TYPE, "avc"); + std::string testValue; + EXPECT_TRUE(data->findString(ENCODER_TYPE, &testValue) == ECODataStatus::OK); + EXPECT_EQ(testValue, "avc"); + + // Override existing key. + data->setString(ENCODER_TYPE, "hevc"); + EXPECT_EQ(data->findString(ENCODER_TYPE, &testValue), ECODataStatus::OK); + EXPECT_EQ(testValue, "hevc"); +} + +TEST(EcoDataTest, TestSetAndFindMultipleString) { + std::unique_ptr<ECOData> data = std::make_unique<ECOData>(ECOData::DATA_TYPE_STATS, 1000); + + std::unordered_map<std::string, std::string> inputEntries = { + {"name1", "avc"}, {"name2", "avc2"}, {"name3", "avc3"}, {"name4", "avc4"}, + {"name5", "avc5"}, {"name6", "avc6"}, {"name7", "avc7"}, {"name8", "avc8"}, + {"name9", "avc9"}, {"name10", "avc10"}, {"name11", "avc11"}, {"name12", "avc12"}}; + for (auto it = inputEntries.begin(); it != inputEntries.end(); ++it) { + data->setString(it->first, it->second); + } + + // Checks if the string exist in the ECOData. + for (auto it = inputEntries.begin(); it != inputEntries.end(); ++it) { + std::string testValue; + EXPECT_TRUE(data->findString(it->first, &testValue) == ECODataStatus::OK); + EXPECT_EQ(testValue, it->second); + } +} + +TEST(EcoDataTest, TestSetAndFindInvalidString) { + std::unique_ptr<ECOData> data = std::make_unique<ECOData>(ECOData::DATA_TYPE_STATS, 1000); + + // Test read to null ptr and expect failure + EXPECT_TRUE(data->findString("encoder-name", nullptr) != ECODataStatus::OK); + + // Test find non-existing key and expect failure. + std::string testValue; + EXPECT_TRUE(data->findString("encoder-name", &testValue) != ECODataStatus::OK); + + // Test set empty key and expect failure + EXPECT_TRUE(data->setString("", "avc") != ECODataStatus::OK); + + // Test read empty key and expect failure + EXPECT_TRUE(data->findString("", &testValue) != ECODataStatus::OK); +} + +TEST(EcoDataTest, TestNormalSetAndFindInt32) { + std::unique_ptr<ECOData> data = std::make_unique<ECOData>(ECOData::DATA_TYPE_STATS, 1000); + + data->setInt32(ENCODER_TARGET_BITRATE_BPS, 2000000); + int32_t testValue; + EXPECT_TRUE(data->findInt32(ENCODER_TARGET_BITRATE_BPS, &testValue) == ECODataStatus::OK); + EXPECT_EQ(testValue, 2000000); + + // Override existing key. + data->setInt32(ENCODER_TARGET_BITRATE_BPS, 2200000); + EXPECT_EQ(data->findInt32(ENCODER_TARGET_BITRATE_BPS, &testValue), ECODataStatus::OK); + EXPECT_EQ(testValue, 2200000); +} + +TEST(EcoDataTest, TestSetAndFindMultipleInt32) { + std::unique_ptr<ECOData> data = std::make_unique<ECOData>(ECOData::DATA_TYPE_STATS, 1000); + + std::unordered_map<std::string, int32_t> inputEntries = { + {"name1", 100}, {"name2", 200}, {"name3", 300}, {"name4", 400}, + {"name5", 500}, {"name6", 600}, {"name7", 700}, {"name8", 800}, + {"name9", 900}, {"name10", 10000}, {"name11", 110000}, {"name12", 120000}}; + for (auto it = inputEntries.begin(); it != inputEntries.end(); ++it) { + data->setInt32(it->first, it->second); + } + + // Checks if the string exist in the ECOData. + for (auto it = inputEntries.begin(); it != inputEntries.end(); ++it) { + int32_t testValue; + EXPECT_TRUE(data->findInt32(it->first, &testValue) == ECODataStatus::OK); + EXPECT_EQ(testValue, it->second); + } +} + +TEST(EcoDataTest, TestSetAndFindInvalidInt32) { + std::unique_ptr<ECOData> data = std::make_unique<ECOData>(ECOData::DATA_TYPE_STATS, 1000); + + // Test read to null ptr and expect failure + EXPECT_TRUE(data->findInt32("encoder-name", nullptr) != ECODataStatus::OK); + + // Test find non-existing key and expect failure. + int32_t testValue; + EXPECT_TRUE(data->findInt32("encoder-name", &testValue) != ECODataStatus::OK); + + // Test set empty key and expect failure + EXPECT_TRUE(data->setInt32("", 1000) != ECODataStatus::OK); + + // Test read empty key and expect failure + EXPECT_TRUE(data->findInt32("", &testValue) != ECODataStatus::OK); +} + +TEST(EcoDataTest, TestNormalSetAndFindInt64) { + std::unique_ptr<ECOData> data = std::make_unique<ECOData>(ECOData::DATA_TYPE_STATS, 1000); + + data->setInt64(ENCODER_TARGET_BITRATE_BPS, 2000000); + int64_t testValue; + EXPECT_TRUE(data->findInt64(ENCODER_TARGET_BITRATE_BPS, &testValue) == ECODataStatus::OK); + EXPECT_EQ(testValue, 2000000); + + // Override existing key. + data->setInt64(ENCODER_TARGET_BITRATE_BPS, 2200000); + EXPECT_EQ(data->findInt64(ENCODER_TARGET_BITRATE_BPS, &testValue), ECODataStatus::OK); + EXPECT_EQ(testValue, 2200000); +} + +TEST(EcoDataTest, TestNormalSetAndFindMultipleInt64) { + std::unique_ptr<ECOData> data = std::make_unique<ECOData>(ECOData::DATA_TYPE_STATS, 1000); + + std::unordered_map<std::string, int64_t> inputEntries = { + {"name1", 100}, {"name2", 200}, {"name3", 300}, {"name4", 400}, + {"name5", 500}, {"name6", 600}, {"name7", 700}, {"name8", 800}, + {"name9", 900}, {"name10", 10000}, {"name11", 110000}, {"name12", 120000}}; + for (auto it = inputEntries.begin(); it != inputEntries.end(); ++it) { + data->setInt64(it->first, it->second); + } + + // Checks if the string exist in the ECOData. + for (auto it = inputEntries.begin(); it != inputEntries.end(); ++it) { + int64_t testValue; + EXPECT_TRUE(data->findInt64(it->first, &testValue) == ECODataStatus::OK); + EXPECT_EQ(testValue, it->second); + } +} + +TEST(EcoDataTest, TestSetAndFindInvalidInt64) { + std::unique_ptr<ECOData> data = std::make_unique<ECOData>(ECOData::DATA_TYPE_STATS, 1000); + + // Test read to null ptr and expect failure + EXPECT_TRUE(data->findInt64("encoder-name", nullptr) != ECODataStatus::OK); + + // Test find non-existing key and expect failure. + int64_t testValue; + EXPECT_TRUE(data->findInt64("encoder-name", &testValue) != ECODataStatus::OK); + + // Test set empty key and expect failure + EXPECT_TRUE(data->setInt64("", 1000) != ECODataStatus::OK); + + // Test read empty key and expect failure + EXPECT_TRUE(data->findInt64("", &testValue) != ECODataStatus::OK); +} + +TEST(EcoDataTest, TestNormalSetAndFindFloat) { + std::unique_ptr<ECOData> data = std::make_unique<ECOData>(ECOData::DATA_TYPE_STATS, 1000); + + data->setFloat(ENCODER_TARGET_BITRATE_BPS, 2000000.0); + float testValue; + EXPECT_TRUE(data->findFloat(ENCODER_TARGET_BITRATE_BPS, &testValue) == ECODataStatus::OK); + EXPECT_FLOAT_EQ(testValue, 2000000.0); + + // Override existing key. + data->setFloat(ENCODER_TARGET_BITRATE_BPS, 2200000.0); + EXPECT_TRUE(data->findFloat(ENCODER_TARGET_BITRATE_BPS, &testValue) == ECODataStatus::OK); + EXPECT_FLOAT_EQ(testValue, 2200000.0); +} + +TEST(EcoDataTest, TestNormalSetAndFindMultipleFloat) { + std::unique_ptr<ECOData> data = std::make_unique<ECOData>(ECOData::DATA_TYPE_STATS, 1000); + + std::unordered_map<std::string, float> inputEntries = { + {"name1", 100.0}, {"name2", 200.0}, {"name3", 300.0}, {"name4", 400.0}, + {"name5", 500.0}, {"name6", 600.0}, {"name7", 700.0}, {"name8", 800.0}, + {"name9", 900.0}, {"name10", 10000.0}, {"name11", 110000.0}, {"name12", 120000.0}}; + for (auto it = inputEntries.begin(); it != inputEntries.end(); ++it) { + data->setFloat(it->first, it->second); + } + + // Checks if the string exist in the ECOData. + for (auto it = inputEntries.begin(); it != inputEntries.end(); ++it) { + float testValue; + EXPECT_TRUE(data->findFloat(it->first, &testValue) == ECODataStatus::OK); + EXPECT_FLOAT_EQ(testValue, it->second); + } +} + +TEST(EcoDataTest, TestSetAndFindInvalidFloat) { + std::unique_ptr<ECOData> data = std::make_unique<ECOData>(ECOData::DATA_TYPE_STATS, 1000); + + // Test read to null ptr and expect failure + EXPECT_TRUE(data->findFloat("encoder-name", nullptr) != ECODataStatus::OK); + + // Test find non-existing key and expect failure. + float testValue; + EXPECT_TRUE(data->findFloat("encoder-name", &testValue) != ECODataStatus::OK); + + // Test set empty key and expect failure + EXPECT_TRUE(data->setFloat("", 1000.0) != ECODataStatus::OK); + + // Test read empty key and expect failure + EXPECT_TRUE(data->findFloat("", &testValue) != ECODataStatus::OK); +} + +TEST(EcoDataTest, TestNormalSetAndFindMixedDataType) { + std::unique_ptr<ECOData> data = std::make_unique<ECOData>(ECOData::DATA_TYPE_STATS, 1000); + + std::unordered_map<std::string, ECOData::ECODataValueType> inputEntries = { + {"name1", "google-encoder"}, {"name2", "avc"}, {"profile", 1}, {"level", 2}, + {"framerate", 4.1}, {"kfi", 30}}; + for (auto it = inputEntries.begin(); it != inputEntries.end(); ++it) { + data->set(it->first, it->second); + } + + // Checks if the string exist in the ECOData. + for (auto it = inputEntries.begin(); it != inputEntries.end(); ++it) { + ECOData::ECODataValueType testValue; + EXPECT_TRUE(data->find(it->first, &testValue) == ECODataStatus::OK); + EXPECT_EQ(testValue, it->second); + } +} + +TEST(EcoDataTest, TestSetAndFindInvalidDataType) { + std::unique_ptr<ECOData> data = std::make_unique<ECOData>(ECOData::DATA_TYPE_STATS, 1000); + + // Test read to null ptr and expect failure + EXPECT_TRUE(data->find("encoder-name", nullptr) != ECODataStatus::OK); + + // Test find non-existing key and expect failure. + ECOData::ECODataValueType testValue; + EXPECT_TRUE(data->find("encoder-name2", &testValue) != ECODataStatus::OK); + + // Test set empty key and expect failure + EXPECT_TRUE(data->set("", 1000) != ECODataStatus::OK); + + // Test read empty key and expect failure + EXPECT_TRUE(data->find("", &testValue) != ECODataStatus::OK); +} + +TEST(EcoDataTest, TestNormalWriteReadParcel) { + constexpr int32_t kDataType = ECOData::DATA_TYPE_STATS; + constexpr int64_t kDataTimeUs = 1000; + + std::unique_ptr<ECOData> sourceData = std::make_unique<ECOData>(kDataType, kDataTimeUs); + + std::unordered_map<std::string, ECOData::ECODataValueType> inputEntries = { + {"name1", "google-encoder"}, {"name2", "avc"}, {"profile", 1}, {"level", 2}, + {"framerate", 4.1}, {"kfi", 30}}; + for (auto it = inputEntries.begin(); it != inputEntries.end(); ++it) { + sourceData->set(it->first, it->second); + } + + std::unique_ptr<Parcel> parcel = std::make_unique<Parcel>(); + EXPECT_TRUE(sourceData->writeToParcel(parcel.get()) == NO_ERROR); + + // Rewind the data position of the parcel for this test. Otherwise, the following read will not + // start from the beginning. + parcel->setDataPosition(0); + + // Reads the parcel back into a new ECOData + std::unique_ptr<ECOData> dstData = std::make_unique<ECOData>(); + EXPECT_TRUE(dstData->readFromParcel(parcel.get()) == NO_ERROR); + + // Checks the data type, time and number of entries. + EXPECT_EQ(sourceData->getNumOfEntries(), dstData->getNumOfEntries()); + EXPECT_EQ(dstData->getDataType(), kDataType); + EXPECT_EQ(dstData->getDataTimeUs(), kDataTimeUs); + + for (auto it = inputEntries.begin(); it != inputEntries.end(); ++it) { + ECOData::ECODataValueType testValue; + EXPECT_TRUE(dstData->find(it->first, &testValue) == ECODataStatus::OK); + EXPECT_EQ(testValue, it->second); + } +} + +TEST(EcoDataTest, TestWriteInvalidParcel) { + constexpr int32_t kDataType = ECOData::DATA_TYPE_STATS; + constexpr int64_t kDataTimeUs = 1000; + + std::unique_ptr<ECOData> sourceData = std::make_unique<ECOData>(kDataType, kDataTimeUs); + + std::unique_ptr<Parcel> parcel = std::make_unique<Parcel>(); + EXPECT_TRUE(sourceData->writeToParcel(nullptr) != NO_ERROR); +} + +TEST(EcoDataTest, TestReadInvalidParcel) { + constexpr int32_t kDataType = ECOData::DATA_TYPE_STATS; + constexpr int64_t kDataTimeUs = 1000; + + std::unique_ptr<ECOData> sourceData = std::make_unique<ECOData>(kDataType, kDataTimeUs); + + std::unordered_map<std::string, ECOData::ECODataValueType> inputEntries = { + {"name1", "google-encoder"}, {"name2", "avc"}, {"profile", 1}, {"level", 2}, + {"framerate", 4.1}, {"kfi", 30}}; + for (auto it = inputEntries.begin(); it != inputEntries.end(); ++it) { + sourceData->set(it->first, it->second); + } + + std::unique_ptr<Parcel> parcel = std::make_unique<Parcel>(); + EXPECT_TRUE(sourceData->writeToParcel(parcel.get()) == NO_ERROR); + + // Corrupt the parcel by write random data to the beginning. + parcel->setDataPosition(4); + parcel->writeCString("invalid-data"); + + parcel->setDataPosition(0); + + // Reads the parcel back into a new ECOData + std::unique_ptr<ECOData> dstData = std::make_unique<ECOData>(); + EXPECT_TRUE(dstData->readFromParcel(parcel.get()) != NO_ERROR); +} + +} // namespace eco +} // namespace media +} // namespace android
\ No newline at end of file diff --git a/media/eco/tests/EcoServiceTest.cpp b/media/eco/tests/EcoServiceTest.cpp new file mode 100644 index 0000000..dc589dd --- /dev/null +++ b/media/eco/tests/EcoServiceTest.cpp @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Unit Test for ECOService. + +#define LOG_NDEBUG 0 +#define LOG_TAG "ECOServiceTest" + +#include <android-base/unique_fd.h> +#include <binder/Parcel.h> +#include <binder/Parcelable.h> +#include <cutils/ashmem.h> +#include <gtest/gtest.h> +#include <math.h> +#include <stdlib.h> +#include <sys/mman.h> +#include <utils/Log.h> + +#include "FakeECOServiceInfoListener.h" +#include "FakeECOServiceStatsProvider.h" +#include "eco/ECOService.h" +#include "eco/ECOUtils.h" + +namespace android { +namespace media { +namespace eco { + +using ::android::IBinder; +using android::sp; +using ::android::binder::Status; + +namespace { + +static constexpr uint32_t kTestWidth = 1280; +static constexpr uint32_t kTestHeight = 720; +static constexpr bool kIsCameraRecording = true; +static constexpr float kFrameRate = 30.0f; + +} // namespace + +// A helper class to help create ECOService and manage ECOService. +class EcoServiceTest : public ::testing::Test { +public: + EcoServiceTest() { ALOGD("EcoServiceTest created"); } + + sp<IECOService> createService() { + android::sp<android::IServiceManager> sm = android::defaultServiceManager(); + assert(sm != 0); + android::sp<android::IBinder> binder = sm->getService(String16("media.ecoservice")); + + if (binder == 0) { + ALOGE("Failed to connect to ecoservice"); + return nullptr; + } else { + ALOGD("Successfully connect to ecoservice"); + } + + mECOService = android::interface_cast<IECOService>(binder); + return mECOService; + } + + ~EcoServiceTest() { ALOGD("EcoServiceTest destroyed"); } + +private: + sp<IECOService> mECOService = nullptr; +}; + +TEST_F(EcoServiceTest, NormalObtainSessionWithInvalidWidth) { + sp<IECOService> service = createService(); + EXPECT_TRUE(service != nullptr); + + // Provider obtains the session from the service. + sp<IECOSession> session = nullptr; + + service->obtainSession(-1 /* width */, kTestHeight, kIsCameraRecording, &session); + EXPECT_FALSE(session); +} + +TEST_F(EcoServiceTest, NormalObtainSessionWithInvalidHeight) { + sp<IECOService> service = createService(); + + // Provider obtains the session from the service. + sp<IECOSession> session = nullptr; + + service->obtainSession(kTestWidth, -1 /* height */, kIsCameraRecording, &session); + EXPECT_FALSE(session); +} + +TEST_F(EcoServiceTest, NormalObtainSessionWithCameraRecordingFalse) { + sp<IECOService> service = createService(); + + // Provider obtains the session from the service. + sp<IECOSession> session = nullptr; + + service->obtainSession(kTestWidth, kTestHeight, false /* isCameraRecording */, &session); + EXPECT_TRUE(session); +} + +TEST_F(EcoServiceTest, NormalObtainSingleSession) { + sp<IECOService> service = createService(); + EXPECT_TRUE(service != nullptr); + + // Provider obtains the session from the service. + sp<IECOSession> session = nullptr; + + service->obtainSession(kTestWidth, kTestHeight, kIsCameraRecording, &session); + EXPECT_TRUE(session); +} + +TEST_F(EcoServiceTest, NormalObtainSessionTwice) { + sp<IECOService> service = createService(); + EXPECT_TRUE(service != nullptr); + + // Provider obtains the session from the service. + sp<IECOSession> session1 = nullptr; + + service->obtainSession(kTestWidth, kTestHeight, kIsCameraRecording, &session1); + EXPECT_TRUE(session1); + + sp<IECOSession> session2 = nullptr; + + service->obtainSession(kTestWidth, kTestHeight, kIsCameraRecording, &session2); + EXPECT_TRUE(session2); + + // The two session instances should be the same. + EXPECT_TRUE(IInterface::asBinder(session1) == IInterface::asBinder(session2)); +} + +TEST_F(EcoServiceTest, ObtainTwoSessions) { + sp<IECOService> service = createService(); + EXPECT_TRUE(service != nullptr); + + // Provider obtains the session from the service. + sp<IECOSession> session1 = nullptr; + + service->obtainSession(kTestWidth, kTestHeight, kIsCameraRecording, &session1); + EXPECT_TRUE(session1); + + sp<IECOSession> session2 = nullptr; + + service->obtainSession(kTestWidth - 1, kTestHeight - 1, kIsCameraRecording, &session2); + EXPECT_TRUE(session2); + + // The two session instances must not be the same. + EXPECT_TRUE(IInterface::asBinder(session1) != IInterface::asBinder(session2)); + + // Check the session number. + int32_t count = 0; + service->getNumOfSessions(&count); + EXPECT_EQ(count, 2); + + // Get the list of sessions from service. + std::vector<sp<IBinder>> sessionList; + service->getSessions(&sessionList); + bool foundFirstSession = false, foundSecondSession = false; + + for (std::vector<sp<IBinder>>::iterator it = sessionList.begin(); it != sessionList.end(); + ++it) { + if (IInterface::asBinder(session1) == it->get()) { + foundFirstSession = true; + } + if (IInterface::asBinder(session2) == it->get()) { + foundSecondSession = true; + } + } + + // Expect found both sessions. + EXPECT_TRUE(foundFirstSession); + EXPECT_TRUE(foundSecondSession); +} + +TEST_F(EcoServiceTest, TestNormalFlowWithOneListenerAndOneProvider) { + sp<IECOService> service = createService(); + EXPECT_TRUE(service != nullptr); + + // Provider obtains the session from the service. + sp<IECOSession> session = nullptr; + + service->obtainSession(kTestWidth, kTestHeight, kIsCameraRecording, &session); + EXPECT_TRUE(session); + + // Create provider and add it to the session. + sp<FakeECOServiceStatsProvider> fakeProvider = new FakeECOServiceStatsProvider( + kTestWidth, kTestHeight, kIsCameraRecording, kFrameRate); + fakeProvider->setECOSession(session); + + ECOData providerConfig(ECOData::DATA_TYPE_STATS_PROVIDER_CONFIG, + systemTime(SYSTEM_TIME_BOOTTIME)); + providerConfig.setString(KEY_PROVIDER_NAME, "FakeECOServiceStatsProvider"); + providerConfig.setInt32(KEY_PROVIDER_TYPE, + ECOServiceStatsProvider::STATS_PROVIDER_TYPE_VIDEO_ENCODER); + bool res; + Status status = session->addStatsProvider(fakeProvider, providerConfig, &res); + + // Create listener and add it to the session. + sp<FakeECOServiceInfoListener> fakeListener = + new FakeECOServiceInfoListener(kTestWidth, kTestHeight, kIsCameraRecording); + fakeListener->setECOSession(session); + + // Create the listener config. + ECOData listenerConfig(ECOData::DATA_TYPE_INFO_LISTENER_CONFIG, + systemTime(SYSTEM_TIME_BOOTTIME)); + listenerConfig.setString(KEY_LISTENER_NAME, "FakeECOServiceInfoListener"); + listenerConfig.setInt32(KEY_LISTENER_TYPE, ECOServiceInfoListener::INFO_LISTENER_TYPE_CAMERA); + + // Specify the qp thresholds for receiving notification. + listenerConfig.setInt32(KEY_LISTENER_QP_BLOCKINESS_THRESHOLD, 40); + listenerConfig.setInt32(KEY_LISTENER_QP_CHANGE_THRESHOLD, 5); + + status = session->addInfoListener(fakeListener, listenerConfig, &res); +} + +} // namespace eco +} // namespace media +} // namespace android diff --git a/media/eco/tests/EcoSessionTest.cpp b/media/eco/tests/EcoSessionTest.cpp new file mode 100644 index 0000000..33a449d --- /dev/null +++ b/media/eco/tests/EcoSessionTest.cpp @@ -0,0 +1,608 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Unit Test for ECOSession. + +//#define LOG_NDEBUG 0 +#define LOG_TAG "ECOSessionTest" + +#include <android-base/unique_fd.h> +#include <binder/Parcel.h> +#include <binder/Parcelable.h> +#include <cutils/ashmem.h> +#include <gtest/gtest.h> +#include <math.h> +#include <stdlib.h> +#include <sys/mman.h> +#include <utils/Log.h> + +#include "FakeECOServiceInfoListener.h" +#include "FakeECOServiceStatsProvider.h" +#include "eco/ECOSession.h" +#include "eco/ECOUtils.h" + +namespace android { +namespace media { +namespace eco { + +using android::sp; +using ::android::binder::Status; + +static constexpr uint32_t kTestWidth = 1280; +static constexpr uint32_t kTestHeight = 720; +static constexpr bool kIsCameraRecording = true; +static constexpr int32_t kTargetBitrateBps = 22000000; +static constexpr int32_t kKeyFrameIntervalFrames = 30; +static constexpr float kFrameRate = 30.0f; + +// A helpful class to help create ECOSession and manage ECOSession. +class EcoSessionTest : public ::testing::Test { +public: + EcoSessionTest() { ALOGD("EcoSessionTest created"); } + + sp<ECOSession> createSession(int32_t width, int32_t height, bool isCameraRecording) { + mSession = ECOSession::createECOSession(width, height, isCameraRecording); + if (mSession == nullptr) return nullptr; + return mSession; + } + +private: + sp<ECOSession> mSession = nullptr; +}; + +TEST_F(EcoSessionTest, TestConstructorWithInvalidParameters) { + // Expects failure as ECOService1.0 will only support up to 720P and camera recording case. + EXPECT_TRUE(createSession(1920 /* width */, 1080 /* height */, true /* isCameraRecording */) == + nullptr); + + // Expects failure as ECOService1.0 will only support up to 720P and camera recording case. + EXPECT_TRUE(createSession(1920 /* width */, 1080 /* height */, false /* isCameraRecording */) == + nullptr); + + EXPECT_TRUE(createSession(1920 /* width */, -1 /* height */, true /* isCameraRecording */) == + nullptr); + + EXPECT_TRUE(createSession(-1 /* width */, 1080 /* height */, true /* isCameraRecording */) == + nullptr); +} + +TEST_F(EcoSessionTest, TestConstructorWithValidParameters) { + // Expects success with <= 720P and is for camera recording. + EXPECT_TRUE(createSession(1280 /* width */, 720 /* height */, true /* isCameraRecording */) != + nullptr); + + // Expects success with <= 720P and is for camera recording. + EXPECT_TRUE(createSession(640 /* width */, 480 /* height */, true /* isCameraRecording */) != + nullptr); +} + +TEST_F(EcoSessionTest, TestAddProviderWithoutSpecifyEcoDataType) { + sp<ECOSession> ecoSession = createSession(kTestWidth, kTestHeight, kIsCameraRecording); + EXPECT_TRUE(ecoSession); + + sp<FakeECOServiceStatsProvider> fakeProvider = new FakeECOServiceStatsProvider( + kTestWidth, kTestHeight, kIsCameraRecording, kFrameRate, ecoSession); + + ECOData providerConfig; + bool res; + Status status = ecoSession->addStatsProvider(fakeProvider, providerConfig, &res); + EXPECT_FALSE(status.isOk()); +} + +TEST_F(EcoSessionTest, TestAddProviderWithWrongEcoDataType) { + sp<ECOSession> ecoSession = createSession(kTestWidth, kTestHeight, kIsCameraRecording); + EXPECT_TRUE(ecoSession); + + sp<FakeECOServiceStatsProvider> fakeProvider = new FakeECOServiceStatsProvider( + kTestWidth, kTestHeight, kIsCameraRecording, kFrameRate, ecoSession); + + ECOData providerConfig(ECOData::DATA_TYPE_INFO_LISTENER_CONFIG, + systemTime(SYSTEM_TIME_BOOTTIME)); + bool res; + Status status = ecoSession->addStatsProvider(fakeProvider, providerConfig, &res); + EXPECT_FALSE(status.isOk()); +} + +TEST_F(EcoSessionTest, TestAddNormalProvider) { + sp<ECOSession> ecoSession = createSession(kTestWidth, kTestHeight, kIsCameraRecording); + EXPECT_TRUE(ecoSession); + + sp<FakeECOServiceStatsProvider> fakeProvider = new FakeECOServiceStatsProvider( + kTestWidth, kTestHeight, kIsCameraRecording, kFrameRate, ecoSession); + + ECOData providerConfig(ECOData::DATA_TYPE_STATS_PROVIDER_CONFIG, + systemTime(SYSTEM_TIME_BOOTTIME)); + bool res; + Status status = ecoSession->addStatsProvider(fakeProvider, providerConfig, &res); + EXPECT_TRUE(status.isOk()); +} + +// Add two providers and expect failure as ECOService1.0 only supports one provider and one +// listener. +TEST_F(EcoSessionTest, TestAddTwoProvider) { + sp<ECOSession> ecoSession = createSession(kTestWidth, kTestHeight, kIsCameraRecording); + EXPECT_TRUE(ecoSession); + + sp<FakeECOServiceStatsProvider> fakeProvider1 = new FakeECOServiceStatsProvider( + kTestWidth, kTestHeight, kIsCameraRecording, kFrameRate, ecoSession); + + ECOData providerConfig(ECOData::DATA_TYPE_STATS_PROVIDER_CONFIG, + systemTime(SYSTEM_TIME_BOOTTIME)); + bool res; + Status status = ecoSession->addStatsProvider(fakeProvider1, providerConfig, &res); + EXPECT_TRUE(status.isOk()); + + sp<FakeECOServiceStatsProvider> fakeProvider2 = new FakeECOServiceStatsProvider( + kTestWidth, kTestHeight, kIsCameraRecording, kFrameRate, ecoSession); + status = ecoSession->addStatsProvider(fakeProvider2, providerConfig, &res); + EXPECT_FALSE(status.isOk()); +} + +TEST_F(EcoSessionTest, TestAddListenerWithDifferentHeight) { + sp<ECOSession> ecoSession = createSession(kTestWidth, kTestHeight, kIsCameraRecording); + EXPECT_TRUE(ecoSession); + + sp<FakeECOServiceInfoListener> fakeListener = new FakeECOServiceInfoListener( + kTestWidth - 1, kTestHeight, kIsCameraRecording, ecoSession); + + ECOData ListenerConfig(ECOData::DATA_TYPE_INFO_LISTENER_CONFIG, + systemTime(SYSTEM_TIME_BOOTTIME)); + bool res; + Status status = ecoSession->addInfoListener(fakeListener, ListenerConfig, &res); + EXPECT_FALSE(status.isOk()); +} + +TEST_F(EcoSessionTest, TestAddListenerWithDifferentWidth) { + sp<ECOSession> ecoSession = createSession(kTestWidth, kTestHeight, kIsCameraRecording); + EXPECT_TRUE(ecoSession); + + sp<FakeECOServiceInfoListener> fakeListener = new FakeECOServiceInfoListener( + kTestWidth, kTestHeight - 1, kIsCameraRecording, ecoSession); + + ECOData ListenerConfig(ECOData::DATA_TYPE_INFO_LISTENER_CONFIG, + systemTime(SYSTEM_TIME_BOOTTIME)); + bool res; + Status status = ecoSession->addInfoListener(fakeListener, ListenerConfig, &res); + EXPECT_FALSE(status.isOk()); +} + +TEST_F(EcoSessionTest, TestAddListenerWithCameraRecordingFalse) { + sp<ECOSession> ecoSession = createSession(kTestWidth, kTestHeight, kIsCameraRecording); + EXPECT_TRUE(ecoSession); + + sp<FakeECOServiceInfoListener> fakeListener = new FakeECOServiceInfoListener( + kTestWidth, kTestHeight, !kIsCameraRecording, ecoSession); + + ECOData ListenerConfig(ECOData::DATA_TYPE_INFO_LISTENER_CONFIG, + systemTime(SYSTEM_TIME_BOOTTIME)); + bool res; + Status status = ecoSession->addInfoListener(fakeListener, ListenerConfig, &res); + EXPECT_FALSE(status.isOk()); +} + +// Test the ECOSession with FakeECOServiceStatsProvider and FakeECOServiceInfoListener. Push the +// stats to ECOSession through FakeECOServiceStatsProvider and check the info received in +// from FakeECOServiceInfoListener ECOSession. +TEST_F(EcoSessionTest, TestSessionWithProviderAndListenerSimpleTest) { + // The time that listener needs to wait for the info from ECOService. + static constexpr int kServiceWaitTimeMs = 10; + + // Create the session. + sp<ECOSession> ecoSession = createSession(kTestWidth, kTestHeight, kIsCameraRecording); + + // Add provider. + sp<FakeECOServiceStatsProvider> fakeProvider = new FakeECOServiceStatsProvider( + kTestWidth, kTestHeight, kIsCameraRecording, kFrameRate, ecoSession); + ECOData providerConfig(ECOData::DATA_TYPE_STATS_PROVIDER_CONFIG, + systemTime(SYSTEM_TIME_BOOTTIME)); + providerConfig.setString(KEY_PROVIDER_NAME, "FakeECOServiceStatsProvider"); + providerConfig.setInt32(KEY_PROVIDER_TYPE, + ECOServiceStatsProvider::STATS_PROVIDER_TYPE_VIDEO_ENCODER); + bool res; + Status status = ecoSession->addStatsProvider(fakeProvider, providerConfig, &res); + + // Create listener. + sp<FakeECOServiceInfoListener> fakeListener = + new FakeECOServiceInfoListener(kTestWidth, kTestHeight, kIsCameraRecording, ecoSession); + + // Create the listener config. + ECOData listenerConfig(ECOData::DATA_TYPE_INFO_LISTENER_CONFIG, + systemTime(SYSTEM_TIME_BOOTTIME)); + listenerConfig.setString(KEY_LISTENER_NAME, "FakeECOServiceInfoListener"); + listenerConfig.setInt32(KEY_LISTENER_TYPE, ECOServiceInfoListener::INFO_LISTENER_TYPE_CAMERA); + + // Specify the qp thresholds for receiving notification. + listenerConfig.setInt32(KEY_LISTENER_QP_BLOCKINESS_THRESHOLD, 40); + listenerConfig.setInt32(KEY_LISTENER_QP_CHANGE_THRESHOLD, 5); + + status = ecoSession->addInfoListener(fakeListener, listenerConfig, &res); + + ECOData info; + bool getInfo = false; + + // Set the getInfo flag to true and copy the info from fakeListener. + fakeListener->setInfoAvailableCallback( + [&info, &getInfo](const ::android::media::eco::ECOData& newInfo) { + getInfo = true; + info = newInfo; + }); + + // Inject the session stats into the ECOSession through fakeProvider. + SimpleEncoderConfig sessionEncoderConfig("google-avc", CodecTypeAVC, AVCProfileHigh, AVCLevel52, + kTargetBitrateBps, kKeyFrameIntervalFrames, + kFrameRate); + fakeProvider->injectSessionStats(sessionEncoderConfig.toEcoData(ECOData::DATA_TYPE_STATS)); + + // Wait as ECOService may take some time to process. + std::this_thread::sleep_for(std::chrono::milliseconds(kServiceWaitTimeMs)); + // Check the Session info matches with the session stats sent by provider. + EXPECT_TRUE(getInfo); + EXPECT_TRUE(info.getDataType() == ECOData::DATA_TYPE_INFO); + + std::string infoType; + EXPECT_TRUE(info.findString(KEY_INFO_TYPE, &infoType) == ECODataStatus::OK); + EXPECT_EQ(infoType, VALUE_INFO_TYPE_SESSION); + + // Check the session info matches the session stats provided by FakeECOServiceStatsProvider. + int32_t codecType; + EXPECT_TRUE(info.findInt32(ENCODER_TYPE, &codecType) == ECODataStatus::OK); + EXPECT_EQ(codecType, CodecTypeAVC); + + int32_t profile; + EXPECT_TRUE(info.findInt32(ENCODER_PROFILE, &profile) == ECODataStatus::OK); + EXPECT_EQ(profile, AVCProfileHigh); + + int32_t level; + EXPECT_TRUE(info.findInt32(ENCODER_LEVEL, &level) == ECODataStatus::OK); + EXPECT_EQ(level, AVCLevel52); + + int32_t bitrate; + EXPECT_TRUE(info.findInt32(ENCODER_TARGET_BITRATE_BPS, &bitrate) == ECODataStatus::OK); + EXPECT_EQ(bitrate, kTargetBitrateBps); + + int32_t kfi; + EXPECT_TRUE(info.findInt32(ENCODER_KFI_FRAMES, &kfi) == ECODataStatus::OK); + EXPECT_EQ(kfi, kKeyFrameIntervalFrames); + + // ======================================================================================= + // Inject the frame stats with qp = 30. Expect receiving notification for the first frame. + SimpleEncodedFrameData frameStats(1 /* seq number */, FrameTypeI, 0 /* framePtsUs */, + 30 /* avg-qp */, 56 /* frameSize */); + + getInfo = false; + fakeProvider->injectFrameStats(frameStats.toEcoData(ECOData::DATA_TYPE_STATS)); + std::this_thread::sleep_for(std::chrono::milliseconds(kServiceWaitTimeMs)); + // Check the Session info matches with the session stats sent by provider. + EXPECT_TRUE(getInfo); + + EXPECT_TRUE(info.findString(KEY_INFO_TYPE, &infoType) == ECODataStatus::OK); + EXPECT_EQ(infoType, VALUE_INFO_TYPE_FRAME); + + int8_t frameType; + EXPECT_TRUE(info.findInt8(FRAME_TYPE, &frameType) == ECODataStatus::OK); + EXPECT_EQ(frameType, FrameTypeI); + + int32_t frameNum; + EXPECT_TRUE(info.findInt32(FRAME_NUM, &frameNum) == ECODataStatus::OK); + EXPECT_EQ(frameNum, 1); + + int64_t framePtsUs; + EXPECT_TRUE(info.findInt64(FRAME_PTS_US, &framePtsUs) == ECODataStatus::OK); + EXPECT_EQ(framePtsUs, 0); + + int32_t frameQp; + EXPECT_TRUE(info.findInt32(FRAME_AVG_QP, &frameQp) == ECODataStatus::OK); + EXPECT_EQ(frameQp, 30); + + int32_t frameSize; + EXPECT_TRUE(info.findInt32(FRAME_SIZE_BYTES, &frameSize) == ECODataStatus::OK); + EXPECT_EQ(frameSize, 56); + + // ======================================================================================= + // Inject the frame stats with qp = 35. Expect not receiving notification as 35 is below + // threshold. + frameStats = SimpleEncodedFrameData(2 /* seq number */, FrameTypeP, 333333 /* framePtsUs */, + 35 /* avg-qp */, 56 /* frameSize */); + getInfo = false; + fakeProvider->injectFrameStats(frameStats.toEcoData(ECOData::DATA_TYPE_STATS)); + // Wait as ECOService may take some time to process. + std::this_thread::sleep_for(std::chrono::milliseconds(kServiceWaitTimeMs)); + EXPECT_FALSE(getInfo); + + // ======================================================================================= + // Inject the frame stats with qp = 41. Expect receiving notification as 41 goes beyond + // threshold 40. + frameStats = SimpleEncodedFrameData(3 /* seq number */, FrameTypeP, 666666 /* framePtsUs */, + 41 /* avg-qp */, 56 /* frameSize */); + getInfo = false; + fakeProvider->injectFrameStats(frameStats.toEcoData(ECOData::DATA_TYPE_STATS)); + // Wait as ECOService may take some time to process. + std::this_thread::sleep_for(std::chrono::milliseconds(kServiceWaitTimeMs)); + + // Check the frame info matches with the frame stats sent by provider. + EXPECT_TRUE(getInfo); + EXPECT_TRUE(info.findString(KEY_INFO_TYPE, &infoType) == ECODataStatus::OK); + EXPECT_EQ(infoType, VALUE_INFO_TYPE_FRAME); + EXPECT_TRUE(info.findInt8(FRAME_TYPE, &frameType) == ECODataStatus::OK); + EXPECT_EQ(frameType, FrameTypeP); + EXPECT_TRUE(info.findInt32(FRAME_NUM, &frameNum) == ECODataStatus::OK); + EXPECT_EQ(frameNum, 3); + EXPECT_TRUE(info.findInt64(FRAME_PTS_US, &framePtsUs) == ECODataStatus::OK); + EXPECT_EQ(framePtsUs, 666666); + EXPECT_TRUE(info.findInt32(FRAME_AVG_QP, &frameQp) == ECODataStatus::OK); + EXPECT_EQ(frameQp, 41); + EXPECT_TRUE(info.findInt32(FRAME_SIZE_BYTES, &frameSize) == ECODataStatus::OK); + EXPECT_EQ(frameSize, 56); + + // ======================================================================================= + // Inject the frame stats with qp = 42. Expect not receiving notification as 42 goes beyond + // threshold 40 but delta oes not go beyond the mQpChangeThreshold threshold. + frameStats = SimpleEncodedFrameData(4 /* seq number */, FrameTypeP, 999999 /* framePtsUs */, + 42 /* avg-qp */, 56 /* frameSize */); + getInfo = false; + fakeProvider->injectFrameStats(frameStats.toEcoData(ECOData::DATA_TYPE_STATS)); + // Wait as ECOService may take some time to process. + std::this_thread::sleep_for(std::chrono::milliseconds(kServiceWaitTimeMs)); + EXPECT_FALSE(getInfo); + + // ======================================================================================= + // Inject the frame stats with qp = 10. Expect receiving notification as the detal from + // last reported QP is larger than threshold 4. + frameStats = SimpleEncodedFrameData(5 /* seq number */, FrameTypeB, 1333332 /* framePtsUs */, + 49 /* avg-qp */, 56 /* frameSize */); + getInfo = false; + fakeProvider->injectFrameStats(frameStats.toEcoData(ECOData::DATA_TYPE_STATS)); + // Wait as ECOService may take some time to process. + std::this_thread::sleep_for(std::chrono::milliseconds(kServiceWaitTimeMs)); + + // Check the frame info matches with the frame stats sent by provider. + EXPECT_TRUE(getInfo); + EXPECT_TRUE(info.findString(KEY_INFO_TYPE, &infoType) == ECODataStatus::OK); + EXPECT_EQ(infoType, VALUE_INFO_TYPE_FRAME); + EXPECT_TRUE(info.findInt8(FRAME_TYPE, &frameType) == ECODataStatus::OK); + EXPECT_EQ(frameType, FrameTypeB); + EXPECT_TRUE(info.findInt32(FRAME_NUM, &frameNum) == ECODataStatus::OK); + EXPECT_EQ(frameNum, 5); + EXPECT_TRUE(info.findInt64(FRAME_PTS_US, &framePtsUs) == ECODataStatus::OK); + EXPECT_EQ(framePtsUs, 1333332); + EXPECT_TRUE(info.findInt32(FRAME_AVG_QP, &frameQp) == ECODataStatus::OK); + EXPECT_EQ(frameQp, 49); + EXPECT_TRUE(info.findInt32(FRAME_SIZE_BYTES, &frameSize) == ECODataStatus::OK); + EXPECT_EQ(frameSize, 56); + + // ======================================================================================= + // Inject the frame stats with qp = 41. Expect receiving notification as the detal from + // last reported QP is larger than threshold 4. + frameStats = SimpleEncodedFrameData(6 /* seq number */, FrameTypeB, 1666665 /* framePtsUs */, + 41 /* avg-qp */, 56 /* frameSize */); + getInfo = false; + fakeProvider->injectFrameStats(frameStats.toEcoData(ECOData::DATA_TYPE_STATS)); + std::this_thread::sleep_for(std::chrono::milliseconds(kServiceWaitTimeMs)); + + // Check the frame info matches with the frame stats sent by provider. + EXPECT_TRUE(getInfo); + EXPECT_TRUE(info.findString(KEY_INFO_TYPE, &infoType) == ECODataStatus::OK); + EXPECT_EQ(infoType, VALUE_INFO_TYPE_FRAME); + EXPECT_TRUE(info.findInt8(FRAME_TYPE, &frameType) == ECODataStatus::OK); + EXPECT_EQ(frameType, FrameTypeB); + EXPECT_TRUE(info.findInt32(FRAME_NUM, &frameNum) == ECODataStatus::OK); + EXPECT_EQ(frameNum, 6); + EXPECT_TRUE(info.findInt64(FRAME_PTS_US, &framePtsUs) == ECODataStatus::OK); + EXPECT_EQ(framePtsUs, 1666665); + EXPECT_TRUE(info.findInt32(FRAME_AVG_QP, &frameQp) == ECODataStatus::OK); + EXPECT_EQ(frameQp, 41); + EXPECT_TRUE(info.findInt32(FRAME_SIZE_BYTES, &frameSize) == ECODataStatus::OK); + EXPECT_EQ(frameSize, 56); +} + +TEST_F(EcoSessionTest, TestRemoveMatchProvider) { + sp<ECOSession> ecoSession = createSession(kTestWidth, kTestHeight, kIsCameraRecording); + EXPECT_TRUE(ecoSession); + + sp<FakeECOServiceStatsProvider> fakeProvider1 = new FakeECOServiceStatsProvider( + kTestWidth, kTestHeight, kIsCameraRecording, kFrameRate, ecoSession); + + ECOData providerConfig(ECOData::DATA_TYPE_STATS_PROVIDER_CONFIG, + systemTime(SYSTEM_TIME_BOOTTIME)); + bool res; + Status status = ecoSession->addStatsProvider(fakeProvider1, providerConfig, &res); + EXPECT_TRUE(res); + EXPECT_TRUE(status.isOk()); + + status = ecoSession->removeStatsProvider(fakeProvider1, &res); + EXPECT_TRUE(res); + EXPECT_TRUE(status.isOk()); +} + +TEST_F(EcoSessionTest, TestRemoveMisMatchProvider) { + sp<ECOSession> ecoSession = createSession(kTestWidth, kTestHeight, kIsCameraRecording); + EXPECT_TRUE(ecoSession); + + sp<FakeECOServiceStatsProvider> fakeProvider1 = new FakeECOServiceStatsProvider( + kTestWidth, kTestHeight, kIsCameraRecording, kFrameRate, ecoSession); + + ECOData providerConfig(ECOData::DATA_TYPE_STATS_PROVIDER_CONFIG, + systemTime(SYSTEM_TIME_BOOTTIME)); + bool res; + Status status = ecoSession->addStatsProvider(fakeProvider1, providerConfig, &res); + EXPECT_TRUE(res); + EXPECT_TRUE(status.isOk()); + + sp<FakeECOServiceStatsProvider> fakeProvider2 = new FakeECOServiceStatsProvider( + kTestWidth, kTestHeight, kIsCameraRecording, kFrameRate, ecoSession); + + status = ecoSession->removeStatsProvider(fakeProvider2, &res); + EXPECT_FALSE(res); + EXPECT_FALSE(status.isOk()); +} + +TEST_F(EcoSessionTest, TestRemoveMatchListener) { + sp<ECOSession> ecoSession = createSession(kTestWidth, kTestHeight, kIsCameraRecording); + EXPECT_TRUE(ecoSession); + + // Create listener. + sp<FakeECOServiceInfoListener> fakeListener = + new FakeECOServiceInfoListener(kTestWidth, kTestHeight, kIsCameraRecording, ecoSession); + + // Create the listener config. + ECOData listenerConfig(ECOData::DATA_TYPE_INFO_LISTENER_CONFIG, + systemTime(SYSTEM_TIME_BOOTTIME)); + listenerConfig.setString(KEY_LISTENER_NAME, "FakeECOServiceInfoListener"); + listenerConfig.setInt32(KEY_LISTENER_TYPE, ECOServiceInfoListener::INFO_LISTENER_TYPE_CAMERA); + + // Specify the qp thresholds for receiving notification. + listenerConfig.setInt32(KEY_LISTENER_QP_BLOCKINESS_THRESHOLD, 40); + listenerConfig.setInt32(KEY_LISTENER_QP_CHANGE_THRESHOLD, 5); + + bool res; + Status status = ecoSession->addInfoListener(fakeListener, listenerConfig, &res); + + status = ecoSession->removeInfoListener(fakeListener, &res); + EXPECT_TRUE(res); + EXPECT_TRUE(status.isOk()); +} + +TEST_F(EcoSessionTest, TestRemoveMisMatchListener) { + sp<ECOSession> ecoSession = createSession(kTestWidth, kTestHeight, kIsCameraRecording); + EXPECT_TRUE(ecoSession); + + // Create listener. + sp<FakeECOServiceInfoListener> fakeListener = + new FakeECOServiceInfoListener(kTestWidth, kTestHeight, kIsCameraRecording, ecoSession); + + // Create the listener config. + ECOData listenerConfig(ECOData::DATA_TYPE_INFO_LISTENER_CONFIG, + systemTime(SYSTEM_TIME_BOOTTIME)); + listenerConfig.setString(KEY_LISTENER_NAME, "FakeECOServiceInfoListener"); + listenerConfig.setInt32(KEY_LISTENER_TYPE, ECOServiceInfoListener::INFO_LISTENER_TYPE_CAMERA); + + // Specify the qp thresholds for receiving notification. + listenerConfig.setInt32(KEY_LISTENER_QP_BLOCKINESS_THRESHOLD, 40); + listenerConfig.setInt32(KEY_LISTENER_QP_CHANGE_THRESHOLD, 5); + + bool res; + Status status = ecoSession->addInfoListener(fakeListener, listenerConfig, &res); + + // Create listener. + sp<FakeECOServiceInfoListener> fakeListener2 = + new FakeECOServiceInfoListener(kTestWidth, kTestHeight, kIsCameraRecording, ecoSession); + + status = ecoSession->removeInfoListener(fakeListener2, &res); + EXPECT_FALSE(res); + EXPECT_FALSE(status.isOk()); +} + +// Test the listener connects to the ECOSession after provider sends the session info. Listener +// should recieve the session info right after adding itself to the ECOSession. +TEST_F(EcoSessionTest, TestAddListenerAferProviderStarts) { + // The time that listener needs to wait for the info from ECOService. + static constexpr int kServiceWaitTimeMs = 10; + + // Create the session. + sp<ECOSession> ecoSession = createSession(kTestWidth, kTestHeight, kIsCameraRecording); + + // Add provider. + sp<FakeECOServiceStatsProvider> fakeProvider = new FakeECOServiceStatsProvider( + kTestWidth, kTestHeight, kIsCameraRecording, kFrameRate, ecoSession); + ECOData providerConfig(ECOData::DATA_TYPE_STATS_PROVIDER_CONFIG, + systemTime(SYSTEM_TIME_BOOTTIME)); + providerConfig.setString(KEY_PROVIDER_NAME, "FakeECOServiceStatsProvider"); + providerConfig.setInt32(KEY_PROVIDER_TYPE, + ECOServiceStatsProvider::STATS_PROVIDER_TYPE_VIDEO_ENCODER); + bool res; + Status status = ecoSession->addStatsProvider(fakeProvider, providerConfig, &res); + + // Inject the session stats into the ECOSession through fakeProvider. + SimpleEncoderConfig sessionEncoderConfig("google-avc", CodecTypeAVC, AVCProfileHigh, AVCLevel52, + kTargetBitrateBps, kKeyFrameIntervalFrames, + kFrameRate); + fakeProvider->injectSessionStats(sessionEncoderConfig.toEcoData(ECOData::DATA_TYPE_STATS)); + + // Wait as ECOService may take some time to process. + std::this_thread::sleep_for(std::chrono::milliseconds(kServiceWaitTimeMs)); + + // ======================================================================================= + // Inject the frame stats with qp = 30. Expect receiving notification for the first frame. + SimpleEncodedFrameData frameStats(1 /* seq number */, FrameTypeI, 0 /* framePtsUs */, + 30 /* avg-qp */, 56 /* frameSize */); + + fakeProvider->injectFrameStats(frameStats.toEcoData(ECOData::DATA_TYPE_STATS)); + std::this_thread::sleep_for(std::chrono::milliseconds(kServiceWaitTimeMs)); + + // ======================================================================================= + // Create and add the listener to the ECOSession. Expect to receive the session infor right + // after addInfoListener. + sp<FakeECOServiceInfoListener> fakeListener = + new FakeECOServiceInfoListener(kTestWidth, kTestHeight, kIsCameraRecording, ecoSession); + + // Create the listener config. + ECOData listenerConfig(ECOData::DATA_TYPE_INFO_LISTENER_CONFIG, + systemTime(SYSTEM_TIME_BOOTTIME)); + listenerConfig.setString(KEY_LISTENER_NAME, "FakeECOServiceInfoListener"); + listenerConfig.setInt32(KEY_LISTENER_TYPE, ECOServiceInfoListener::INFO_LISTENER_TYPE_CAMERA); + + // Specify the qp thresholds for receiving notification. + listenerConfig.setInt32(KEY_LISTENER_QP_BLOCKINESS_THRESHOLD, 40); + listenerConfig.setInt32(KEY_LISTENER_QP_CHANGE_THRESHOLD, 5); + + ECOData info; + bool getInfo = false; + + // Set the getInfo flag to true and copy the info from fakeListener. + fakeListener->setInfoAvailableCallback( + [&info, &getInfo](const ::android::media::eco::ECOData& newInfo) { + getInfo = true; + info = newInfo; + }); + + status = ecoSession->addInfoListener(fakeListener, listenerConfig, &res); + + // Wait as ECOService may take some time to process. + std::this_thread::sleep_for(std::chrono::milliseconds(kServiceWaitTimeMs)); + + // Check the Session info matches with the session stats sent by provider. + EXPECT_TRUE(getInfo); + EXPECT_TRUE(info.getDataType() == ECOData::DATA_TYPE_INFO); + + std::string infoType; + EXPECT_TRUE(info.findString(KEY_INFO_TYPE, &infoType) == ECODataStatus::OK); + EXPECT_EQ(infoType, VALUE_INFO_TYPE_SESSION); + + // Check the session info matches the session stats provided by FakeECOServiceStatsProvider. + int32_t codecType; + EXPECT_TRUE(info.findInt32(ENCODER_TYPE, &codecType) == ECODataStatus::OK); + EXPECT_EQ(codecType, CodecTypeAVC); + + int32_t profile; + EXPECT_TRUE(info.findInt32(ENCODER_PROFILE, &profile) == ECODataStatus::OK); + EXPECT_EQ(profile, AVCProfileHigh); + + int32_t level; + EXPECT_TRUE(info.findInt32(ENCODER_LEVEL, &level) == ECODataStatus::OK); + EXPECT_EQ(level, AVCLevel52); + + int32_t bitrate; + EXPECT_TRUE(info.findInt32(ENCODER_TARGET_BITRATE_BPS, &bitrate) == ECODataStatus::OK); + EXPECT_EQ(bitrate, kTargetBitrateBps); + + int32_t kfi; + EXPECT_TRUE(info.findInt32(ENCODER_KFI_FRAMES, &kfi) == ECODataStatus::OK); + EXPECT_EQ(kfi, kKeyFrameIntervalFrames); +} + +} // namespace eco +} // namespace media +} // namespace android diff --git a/media/eco/tests/FakeECOServiceInfoListener.cpp b/media/eco/tests/FakeECOServiceInfoListener.cpp new file mode 100644 index 0000000..2a4a285 --- /dev/null +++ b/media/eco/tests/FakeECOServiceInfoListener.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "FakeFakeECOServiceInfoListener" + +#include "FakeECOServiceInfoListener.h" + +#include <android-base/unique_fd.h> +#include <binder/IPCThreadState.h> +#include <binder/IServiceManager.h> +#include <binder/Parcel.h> +#include <binder/Parcelable.h> +#include <cutils/ashmem.h> +#include <gtest/gtest.h> +#include <math.h> +#include <stdlib.h> +#include <sys/mman.h> +#include <utils/Log.h> + +namespace android { +namespace media { +namespace eco { + +FakeECOServiceInfoListener::FakeECOServiceInfoListener(int32_t width, int32_t height, + bool isCameraRecording, + android::sp<IECOSession> session) + : mWidth(width), + mHeight(height), + mIsCameraRecording(isCameraRecording), + mECOSession(session) { + ALOGD("FakeECOServiceInfoListener construct with w: %d, h: %d, isCameraRecording: %d", mWidth, + mHeight, mIsCameraRecording); +} + +FakeECOServiceInfoListener::FakeECOServiceInfoListener(int32_t width, int32_t height, + bool isCameraRecording) + : mWidth(width), mHeight(height), mIsCameraRecording(isCameraRecording) { + ALOGD("FakeECOServiceInfoListener construct with w: %d, h: %d, isCameraRecording: %d", mWidth, + mHeight, mIsCameraRecording); +} + +FakeECOServiceInfoListener::~FakeECOServiceInfoListener() { + ALOGD("FakeECOServiceInfoListener destructor"); +} + +Status FakeECOServiceInfoListener::getType(int32_t* /*_aidl_return*/) { + return binder::Status::ok(); +} + +Status FakeECOServiceInfoListener::getName(::android::String16* _aidl_return) { + *_aidl_return = String16("FakeECOServiceInfoListener"); + return binder::Status::ok(); +} + +Status FakeECOServiceInfoListener::getECOSession(sp<::android::IBinder>* _aidl_return) { + *_aidl_return = IInterface::asBinder(mECOSession); + return binder::Status::ok(); +} + +Status FakeECOServiceInfoListener::onNewInfo(const ::android::media::eco::ECOData& newInfo) { + ALOGD("FakeECOServiceInfoListener get new info"); + mInfoAvaiableCallback(newInfo); + return binder::Status::ok(); +} + +// IBinder::DeathRecipient implementation +void FakeECOServiceInfoListener::binderDied(const wp<IBinder>& /*who*/) {} + +} // namespace eco +} // namespace media +} // namespace android diff --git a/media/eco/tests/FakeECOServiceInfoListener.h b/media/eco/tests/FakeECOServiceInfoListener.h new file mode 100644 index 0000000..9974b63 --- /dev/null +++ b/media/eco/tests/FakeECOServiceInfoListener.h @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// A fake ECOServiceInfoListener for testing ECOService and ECOSession. + +#include <android-base/unique_fd.h> +#include <android/media/eco/BnECOServiceInfoListener.h> +#include <android/media/eco/IECOSession.h> +#include <binder/BinderService.h> +#include <binder/Parcel.h> +#include <binder/Parcelable.h> +#include <cutils/ashmem.h> +#include <gtest/gtest.h> +#include <math.h> +#include <stdlib.h> +#include <sys/mman.h> +#include <utils/Log.h> + +#include "eco/ECOData.h" +#include "eco/ECODataKey.h" + +namespace android { +namespace media { +namespace eco { + +using ::android::sp; +using ::android::binder::Status; + +/** + * A fake ECOServiceInfoListener. + * + * FakeECOServiceInfoListener is a fake ECOServiceInfoListener that used for testing. + */ +class FakeECOServiceInfoListener : public BnECOServiceInfoListener { +public: + // Method called by the FakeECOServiceInfoListener when there is new info from ECOService. + // This is used by the test to verify the information is sent by the ECOService correctly. + using InfoAvailableCallback = + std::function<void(const ::android::media::eco::ECOData& newInfo)>; + + FakeECOServiceInfoListener(int32_t width, int32_t height, bool isCameraRecording, + sp<IECOSession> session); + + FakeECOServiceInfoListener(int32_t width, int32_t height, bool isCameraRecording); + + void setECOSession(android::sp<IECOSession> session) { mECOSession = session; } + + virtual ~FakeECOServiceInfoListener(); + + virtual Status getType(int32_t* _aidl_return); + virtual Status getName(::android::String16* _aidl_return); + virtual Status getECOSession(::android::sp<::android::IBinder>* _aidl_return); + virtual Status onNewInfo(const ::android::media::eco::ECOData& newInfo); + + // Helper callback to send the info to the test. + void setInfoAvailableCallback(InfoAvailableCallback callback) { + mInfoAvaiableCallback = callback; + } + + // IBinder::DeathRecipient implementation + virtual void binderDied(const wp<IBinder>& who); + +private: + int32_t mWidth; + int32_t mHeight; + bool mIsCameraRecording; + android::sp<IECOSession> mECOSession; + InfoAvailableCallback mInfoAvaiableCallback; +}; + +} // namespace eco +} // namespace media +} // namespace android diff --git a/media/eco/tests/FakeECOServiceStatsProvider.cpp b/media/eco/tests/FakeECOServiceStatsProvider.cpp new file mode 100644 index 0000000..883a830 --- /dev/null +++ b/media/eco/tests/FakeECOServiceStatsProvider.cpp @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "FakeECOServiceStatsProvider" + +#include "FakeECOServiceStatsProvider.h" + +#include <android-base/unique_fd.h> +#include <binder/IPCThreadState.h> +#include <binder/IServiceManager.h> +#include <binder/Parcel.h> +#include <binder/Parcelable.h> +#include <cutils/ashmem.h> +#include <gtest/gtest.h> +#include <math.h> +#include <stdlib.h> +#include <sys/mman.h> +#include <utils/Log.h> + +namespace android { +namespace media { +namespace eco { + +FakeECOServiceStatsProvider::FakeECOServiceStatsProvider(int32_t width, int32_t height, + bool isCameraRecording, float frameRate, + android::sp<IECOSession> session) + : mWidth(width), + mHeight(height), + mIsCameraRecording(isCameraRecording), + mFrameRate(frameRate), + mFrameNumber(0), + mECOSession(session) { + ALOGD("FakeECOServiceStatsProvider construct with w: %d, h: %d, isCameraRecording: %d, " + "frameRate: %f", + mWidth, mHeight, mIsCameraRecording, mFrameRate); +} + +FakeECOServiceStatsProvider::FakeECOServiceStatsProvider(int32_t width, int32_t height, + bool isCameraRecording, float frameRate) + : mWidth(width), + mHeight(height), + mIsCameraRecording(isCameraRecording), + mFrameRate(frameRate), + mFrameNumber(0) { + ALOGD("FakeECOServiceStatsProvider construct with w: %d, h: %d, isCameraRecording: %d, " + "frameRate: %f", + mWidth, mHeight, mIsCameraRecording, mFrameRate); +} + +FakeECOServiceStatsProvider::~FakeECOServiceStatsProvider() { + ALOGD("FakeECOServiceStatsProvider destructor"); +} + +Status FakeECOServiceStatsProvider::getType(int32_t* /*_aidl_return*/) { + return binder::Status::ok(); +} + +Status FakeECOServiceStatsProvider::getName(::android::String16* _aidl_return) { + *_aidl_return = String16("FakeECOServiceStatsProvider"); + return binder::Status::ok(); +} + +Status FakeECOServiceStatsProvider::getECOSession(sp<::android::IBinder>* _aidl_return) { + *_aidl_return = IInterface::asBinder(mECOSession); + return binder::Status::ok(); +} + +bool FakeECOServiceStatsProvider::injectSessionStats(const ECOData& stats) { + if (mECOSession == nullptr) return false; + ALOGD("injectSessionStats"); + bool res; + mECOSession->pushNewStats(stats, &res); + return res; +} + +bool FakeECOServiceStatsProvider::injectFrameStats(const ECOData& stats) { + if (mECOSession == nullptr) return false; + ALOGD("injectPerFrameStats"); + bool res; + mECOSession->pushNewStats(stats, &res); + return res; +} + +// IBinder::DeathRecipient implementation +void FakeECOServiceStatsProvider::binderDied(const wp<IBinder>& /*who*/) {} + +} // namespace eco +} // namespace media +} // namespace android diff --git a/media/eco/tests/FakeECOServiceStatsProvider.h b/media/eco/tests/FakeECOServiceStatsProvider.h new file mode 100644 index 0000000..0f60d02 --- /dev/null +++ b/media/eco/tests/FakeECOServiceStatsProvider.h @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// A fake ECOServiceStatsProvider for testing ECOService and ECOSession. + +#include <android-base/unique_fd.h> +#include <android/media/eco/BnECOServiceStatsProvider.h> +#include <binder/BinderService.h> +#include <binder/Parcel.h> +#include <binder/Parcelable.h> +#include <cutils/ashmem.h> +#include <gtest/gtest.h> +#include <math.h> +#include <stdlib.h> +#include <sys/mman.h> +#include <utils/Log.h> + +#include <condition_variable> +#include <deque> +#include <list> +#include <memory> +#include <mutex> +#include <thread> + +#include "eco/ECOData.h" +#include "eco/ECODataKey.h" +#include "eco/ECOService.h" + +namespace android { +namespace media { +namespace eco { + +using ::android::sp; +using ::android::binder::Status; + +/** + * A fake ECOServiceStatsProvider. + * + * FakeECOServiceStatsProvider is a fake ECOServiceStatsProvider that used for testing. + */ + +class FakeECOServiceStatsProvider : public BnECOServiceStatsProvider { +public: + FakeECOServiceStatsProvider(int32_t width, int32_t height, bool isCameraRecording, + float frameRate, android::sp<IECOSession> session); + + FakeECOServiceStatsProvider(int32_t width, int32_t height, bool isCameraRecording, + float frameRate); + + void setECOSession(android::sp<IECOSession> session) { mECOSession = session; } + + // Helper function to inject session stats to the FakeECOServiceStatsProvider so provider + // could push to the service. + bool injectSessionStats(const ECOData& stats); + + // Helper function to inject each frame's stats to the FakeECOServiceStatsProvider so provider + // could push to the service. + bool injectFrameStats(const ECOData& stats); + + /* Starts the FakeECOServiceStatsProvider */ + void start(); + + /* Stops FakeECOServiceStatsProvider */ + void stop(); + + virtual ~FakeECOServiceStatsProvider(); + + virtual Status getType(int32_t* _aidl_return); + virtual Status getName(::android::String16* _aidl_return); + virtual Status getECOSession(::android::sp<::android::IBinder>* _aidl_return); + + // IBinder::DeathRecipient implementation + virtual void binderDied(const wp<IBinder>& who); + +private: + int32_t mWidth; + int32_t mHeight; + bool mIsCameraRecording; + float mFrameRate; + uint32_t mFrameNumber; + + android::sp<IECOSession> mECOSession; +}; + +} // namespace eco +} // namespace media +} // namespace android diff --git a/media/eco/tests/run_all_unit_tests.sh b/media/eco/tests/run_all_unit_tests.sh new file mode 100644 index 0000000..7cbe27b --- /dev/null +++ b/media/eco/tests/run_all_unit_tests.sh @@ -0,0 +1,13 @@ +#!/bin/bash +if [ -z "$ANDROID_BUILD_TOP" ]; then + echo "Android build environment not set" + exit -1 +fi + +echo "waiting for device" +adb root && adb wait-for-device remount && adb sync + +adb shell /data/nativetest/EcoDataTest/EcoDataTest +adb shell /data/nativetest/EcoSessionTest/EcoSessionTest +#ECOService test lives in vendor side. +adb shell data/nativetest/vendor/EcoServiceTest/EcoServiceTest diff --git a/media/sfplugin/C2OMXNode.cpp b/media/sfplugin/C2OMXNode.cpp index 5b9aebb..03d859a 100644 --- a/media/sfplugin/C2OMXNode.cpp +++ b/media/sfplugin/C2OMXNode.cpp @@ -104,7 +104,7 @@ status_t C2OMXNode::getParameter(OMX_INDEXTYPE index, void *params, size_t size) status_t C2OMXNode::setParameter(OMX_INDEXTYPE index, const void *params, size_t size) { // handle max/fixed frame duration control - if (index == OMX_IndexParamMaxFrameDurationForBitrateControl + if (index == (OMX_INDEXTYPE)OMX_IndexParamMaxFrameDurationForBitrateControl && params != NULL && size == sizeof(OMX_PARAM_U32TYPE)) { // The incoming number is an int32_t contained in OMX_U32. @@ -226,7 +226,7 @@ status_t C2OMXNode::emptyBuffer( && omxBuf.mGraphicBuffer != nullptr) { std::shared_ptr<C2GraphicAllocation> alloc; handle = WrapNativeCodec2GrallocHandle( - native_handle_clone(omxBuf.mGraphicBuffer->handle), + omxBuf.mGraphicBuffer->handle, omxBuf.mGraphicBuffer->width, omxBuf.mGraphicBuffer->height, omxBuf.mGraphicBuffer->format, diff --git a/media/sfplugin/CCodec.cpp b/media/sfplugin/CCodec.cpp index 1e74658..944a8a5 100644 --- a/media/sfplugin/CCodec.cpp +++ b/media/sfplugin/CCodec.cpp @@ -1378,9 +1378,7 @@ void CCodec::signalResume() { } void CCodec::signalSetParameters(const sp<AMessage> ¶ms) { - sp<AMessage> msg = new AMessage(kWhatSetParameters, this); - msg->setMessage("params", params); - msg->post(); + setParameters(params); } void CCodec::setParameters(const sp<AMessage> ¶ms) { @@ -1509,8 +1507,7 @@ void CCodec::onMessageReceived(const sp<AMessage> &msg) { } case kWhatStart: { // C2Component::start() should return within 500ms. - // WORKAROUND: start sometimes takes longer than expected. - setDeadline(now, 2500ms, "start"); + setDeadline(now, 550ms, "start"); mQueuedWorkCount = 0; start(); break; @@ -1546,13 +1543,6 @@ void CCodec::onMessageReceived(const sp<AMessage> &msg) { setInputSurface(surface); break; } - case kWhatSetParameters: { - setDeadline(now, 50ms, "setParameters"); - sp<AMessage> params; - CHECK(msg->findMessage("params", ¶ms)); - setParameters(params); - break; - } case kWhatWorkDone: { std::unique_ptr<C2Work> work; size_t numDiscardedInputBuffers; @@ -1625,6 +1615,7 @@ void CCodec::onMessageReceived(const sp<AMessage> &msg) { C2StreamColorAspectsInfo::output::PARAM_TYPE, C2StreamDataSpaceInfo::output::PARAM_TYPE, C2StreamHdrStaticInfo::output::PARAM_TYPE, + C2StreamHdr10PlusInfo::output::PARAM_TYPE, C2StreamPixelAspectRatioInfo::output::PARAM_TYPE, C2StreamSurfaceScalingInfo::output::PARAM_TYPE }; diff --git a/media/sfplugin/CCodecBufferChannel.cpp b/media/sfplugin/CCodecBufferChannel.cpp index 69de800..247eb9b 100644 --- a/media/sfplugin/CCodecBufferChannel.cpp +++ b/media/sfplugin/CCodecBufferChannel.cpp @@ -1301,21 +1301,7 @@ public: sp<Codec2Buffer> allocateArrayBuffer() override { // TODO: proper max output size - size_t capacity = kLinearBufferSize; - int32_t width = 0, height = 0; - bool video = mFormat->findInt32(KEY_MAX_WIDTH, &width) - && mFormat->findInt32(KEY_MAX_HEIGHT, &height); - if (!video) { - video = mFormat->findInt32(KEY_WIDTH, &width) - && mFormat->findInt32(KEY_HEIGHT, &height); - } - if (video) { - // Assuming data compression ratio better than 3:1. - capacity = std::min(std::max(capacity, (size_t)width * height / 2), - kMaxLinearBufferSize); - } - ALOGD("[%s] Using linear capacity of %zu for array buffer", mName, capacity); - return new LocalLinearBuffer(mFormat, new ABuffer(capacity)); + return new LocalLinearBuffer(mFormat, new ABuffer(kLinearBufferSize)); } }; @@ -1593,8 +1579,7 @@ CCodecBufferChannel::CCodecBufferChannel( mFirstValidFrameIndex(0u), mMetaMode(MODE_NONE), mAvailablePipelineCapacity(), - mInputMetEos(false), - mPendingEosTimestamp(INT64_MIN) { + mInputMetEos(false) { Mutexed<std::unique_ptr<InputBuffers>>::Locked buffers(mInputBuffers); buffers->reset(new DummyInputBuffers("")); } @@ -1679,13 +1664,23 @@ status_t CCodecBufferChannel::queueInputBufferInternal(const sp<MediaCodecBuffer std::list<std::unique_ptr<C2Work>> items; items.push_back(std::move(work)); c2_status_t err = mComponent->queue(&items); + + if (err == C2_OK && eos && buffer->size() > 0u) { + mCCodecCallback->onWorkQueued(false); + work.reset(new C2Work); + work->input.ordinal.timestamp = timeUs; + work->input.ordinal.frameIndex = mFrameIndex++; + // WORKAROUND: keep client timestamp in customOrdinal + work->input.ordinal.customOrdinal = timeUs; + work->input.buffers.clear(); + work->input.flags = C2FrameData::FLAG_END_OF_STREAM; + + items.clear(); + items.push_back(std::move(work)); + err = mComponent->queue(&items); + } if (err == C2_OK) { - if (eos && buffer->size() > 0u) { - mCCodecCallback->onWorkQueued(false); - mPendingEosTimestamp = timeUs; - } else { - mCCodecCallback->onWorkQueued(eos); - } + mCCodecCallback->onWorkQueued(eos); Mutexed<std::unique_ptr<InputBuffers>>::Locked buffers(mInputBuffers); bool released = (*buffers)->releaseBuffer(buffer, nullptr, true); @@ -1735,6 +1730,7 @@ status_t CCodecBufferChannel::queueSecureInputBuffer( sp<EncryptedLinearBlockBuffer> encryptedBuffer((EncryptedLinearBlockBuffer *)buffer.get()); ssize_t result = -1; + ssize_t codecDataOffset = 0; if (mCrypto != nullptr) { ICrypto::DestinationBuffer destination; if (secure) { @@ -1775,9 +1771,16 @@ status_t CCodecBufferChannel::queueSecureInputBuffer( CasStatus status = CasStatus::OK; hidl_string detailedError; + ScramblingControl sctrl = ScramblingControl::UNSCRAMBLED; + + if (key != nullptr) { + sctrl = (ScramblingControl)key[0]; + // Adjust for the PES offset + codecDataOffset = key[2] | (key[3] << 8); + } auto returnVoid = mDescrambler->descramble( - key != NULL ? (ScramblingControl)key[0] : ScramblingControl::UNSCRAMBLED, + sctrl, hidlSubSamples, srcBuffer, 0, @@ -1797,6 +1800,11 @@ status_t CCodecBufferChannel::queueSecureInputBuffer( return UNKNOWN_ERROR; } + if (result < codecDataOffset) { + ALOGD("invalid codec data offset: %zd, result %zd", codecDataOffset, result); + return BAD_VALUE; + } + ALOGV("[%s] descramble succeeded, %zd bytes", mName, result); if (dstBuffer.type == BufferType::SHARED_MEMORY) { @@ -1804,7 +1812,7 @@ status_t CCodecBufferChannel::queueSecureInputBuffer( } } - buffer->setRange(0, result); + buffer->setRange(codecDataOffset, result - codecDataOffset); return queueInputBufferInternal(buffer); } @@ -1818,31 +1826,9 @@ void CCodecBufferChannel::feedInputBufferIfAvailable() { } void CCodecBufferChannel::feedInputBufferIfAvailableInternal() { - while ((!mInputMetEos || mPendingEosTimestamp != INT64_MIN) && + while (!mInputMetEos && !mReorderStash.lock()->hasPending() && mAvailablePipelineCapacity.allocate("feedInputBufferIfAvailable")) { - int64_t pendingEosTimestamp = mPendingEosTimestamp.exchange(INT64_MIN); - if (pendingEosTimestamp != INT64_MIN) { - mAvailablePipelineCapacity.freeInputSlots(1, "feedInputBufferIfAvailable: queue eos"); - - std::unique_ptr<C2Work> work(new C2Work); - work->input.ordinal.timestamp = pendingEosTimestamp; - work->input.ordinal.frameIndex = mFrameIndex++; - // WORKAROUND: keep client timestamp in customOrdinal - work->input.ordinal.customOrdinal = pendingEosTimestamp; - work->input.buffers.clear(); - work->input.flags = C2FrameData::FLAG_END_OF_STREAM; - - std::list<std::unique_ptr<C2Work>> items; - items.push_back(std::move(work)); - if (mComponent->queue(&items) == C2_OK) { - mCCodecCallback->onWorkQueued(true); - } else { - ALOGD("[%s] failed to queue EOS to the component", mName); - mCCodecCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL); - } - continue; - } sp<MediaCodecBuffer> inBuffer; size_t index; { @@ -1936,6 +1922,11 @@ status_t CCodecBufferChannel::renderOutputBuffer( std::static_pointer_cast<const C2StreamHdrStaticInfo::output>( c2Buffer->getInfo(C2StreamHdrStaticInfo::output::PARAM_TYPE)); + // HDR10 plus info + std::shared_ptr<const C2StreamHdr10PlusInfo::output> hdr10PlusInfo = + std::static_pointer_cast<const C2StreamHdr10PlusInfo::output>( + c2Buffer->getInfo(C2StreamHdr10PlusInfo::output::PARAM_TYPE)); + { Mutexed<OutputSurface>::Locked output(mOutputSurface); if (output->surface == nullptr) { @@ -1963,33 +1954,41 @@ status_t CCodecBufferChannel::renderOutputBuffer( videoScalingMode, transform, Fence::NO_FENCE, 0); - if (hdrStaticInfo) { - struct android_smpte2086_metadata smpte2086_meta = { - .displayPrimaryRed = { - hdrStaticInfo->mastering.red.x, hdrStaticInfo->mastering.red.y - }, - .displayPrimaryGreen = { - hdrStaticInfo->mastering.green.x, hdrStaticInfo->mastering.green.y - }, - .displayPrimaryBlue = { - hdrStaticInfo->mastering.blue.x, hdrStaticInfo->mastering.blue.y - }, - .whitePoint = { - hdrStaticInfo->mastering.white.x, hdrStaticInfo->mastering.white.y - }, - .maxLuminance = hdrStaticInfo->mastering.maxLuminance, - .minLuminance = hdrStaticInfo->mastering.minLuminance, - }; - - struct android_cta861_3_metadata cta861_meta = { - .maxContentLightLevel = hdrStaticInfo->maxCll, - .maxFrameAverageLightLevel = hdrStaticInfo->maxFall, - }; - + if (hdrStaticInfo || hdr10PlusInfo) { HdrMetadata hdr; - hdr.validTypes = HdrMetadata::SMPTE2086 | HdrMetadata::CTA861_3; - hdr.smpte2086 = smpte2086_meta; - hdr.cta8613 = cta861_meta; + if (hdrStaticInfo) { + struct android_smpte2086_metadata smpte2086_meta = { + .displayPrimaryRed = { + hdrStaticInfo->mastering.red.x, hdrStaticInfo->mastering.red.y + }, + .displayPrimaryGreen = { + hdrStaticInfo->mastering.green.x, hdrStaticInfo->mastering.green.y + }, + .displayPrimaryBlue = { + hdrStaticInfo->mastering.blue.x, hdrStaticInfo->mastering.blue.y + }, + .whitePoint = { + hdrStaticInfo->mastering.white.x, hdrStaticInfo->mastering.white.y + }, + .maxLuminance = hdrStaticInfo->mastering.maxLuminance, + .minLuminance = hdrStaticInfo->mastering.minLuminance, + }; + + struct android_cta861_3_metadata cta861_meta = { + .maxContentLightLevel = hdrStaticInfo->maxCll, + .maxFrameAverageLightLevel = hdrStaticInfo->maxFall, + }; + + hdr.validTypes = HdrMetadata::SMPTE2086 | HdrMetadata::CTA861_3; + hdr.smpte2086 = smpte2086_meta; + hdr.cta8613 = cta861_meta; + } + if (hdr10PlusInfo) { + hdr.validTypes |= HdrMetadata::HDR10PLUS; + hdr.hdr10plus.assign( + hdr10PlusInfo->m.value, + hdr10PlusInfo->m.value + hdr10PlusInfo->flexCount()); + } qbi.setHdrMetadata(hdr); } // we don't have dirty regions @@ -2078,6 +2077,11 @@ status_t CCodecBufferChannel::start( if (!iStreamFormat || !oStreamFormat) { return UNKNOWN_ERROR; } + } else if (err != C2_OK) { + return UNKNOWN_ERROR; + } + + { Mutexed<ReorderStash>::Locked reorder(mReorderStash); reorder->clear(); if (reorderDepth) { @@ -2086,10 +2090,7 @@ status_t CCodecBufferChannel::start( if (reorderKey) { reorder->setKey(reorderKey.value); } - } else if (err != C2_OK) { - return UNKNOWN_ERROR; } - // TODO: get this from input format bool secure = mComponent->getName().find(".secure") != std::string::npos; @@ -2238,7 +2239,7 @@ status_t CCodecBufferChannel::start( C2_DONT_BLOCK, ¶ms); if ((err != C2_OK && err != C2_BAD_INDEX) || params.size() != 1) { - ALOGD("[%s] Query input allocators returned %zu params => %s (%u)", + ALOGD("[%s] Query output allocators returned %zu params => %s (%u)", mName, params.size(), asString(err), err); } else if (err == C2_OK && params.size() == 1) { C2PortAllocatorsTuning::output *outputAllocators = @@ -2256,11 +2257,39 @@ status_t CCodecBufferChannel::start( } } - // use bufferqueue if outputting to a surface - if (pools->outputAllocatorId == C2PlatformAllocatorStore::GRALLOC - && outputSurface - && ((poolMask >> C2PlatformAllocatorStore::BUFFERQUEUE) & 1)) { - pools->outputAllocatorId = C2PlatformAllocatorStore::BUFFERQUEUE; + // use bufferqueue if outputting to a surface. + // query C2PortSurfaceAllocatorTuning::output from component, or use default allocator + // if unsuccessful. + if (outputSurface) { + params.clear(); + err = mComponent->query({ }, + { C2PortSurfaceAllocatorTuning::output::PARAM_TYPE }, + C2_DONT_BLOCK, + ¶ms); + if ((err != C2_OK && err != C2_BAD_INDEX) || params.size() != 1) { + ALOGD("[%s] Query output surface allocator returned %zu params => %s (%u)", + mName, params.size(), asString(err), err); + } else if (err == C2_OK && params.size() == 1) { + C2PortSurfaceAllocatorTuning::output *surfaceAllocator = + C2PortSurfaceAllocatorTuning::output::From(params[0].get()); + if (surfaceAllocator) { + std::shared_ptr<C2Allocator> allocator; + // verify allocator IDs and resolve default allocator + allocatorStore->fetchAllocator(surfaceAllocator->value, &allocator); + if (allocator) { + pools->outputAllocatorId = allocator->getId(); + } else { + ALOGD("[%s] component requested invalid surface output allocator ID %u", + mName, surfaceAllocator->value); + err = C2_BAD_VALUE; + } + } + } + if (pools->outputAllocatorId == C2PlatformAllocatorStore::GRALLOC + && err != C2_OK + && ((poolMask >> C2PlatformAllocatorStore::BUFFERQUEUE) & 1)) { + pools->outputAllocatorId = C2PlatformAllocatorStore::BUFFERQUEUE; + } } if ((poolMask >> pools->outputAllocatorId) & 1) { @@ -2313,11 +2342,12 @@ status_t CCodecBufferChannel::start( outputGeneration); } - (*buffers) = (*buffers)->toArrayMode(kMinOutputBufferArraySize); - if (oStreamFormat.value == C2BufferData::LINEAR) { + if (oStreamFormat.value == C2BufferData::LINEAR + && mComponentName.find("c2.qti.") == std::string::npos) { // WORKAROUND: if we're using early CSD workaround we convert to // array mode, to appease apps assuming the output // buffers to be of the same size. + (*buffers) = (*buffers)->toArrayMode(kMinOutputBufferArraySize); int32_t channelCount; int32_t sampleRate; @@ -2368,7 +2398,6 @@ status_t CCodecBufferChannel::start( #endif mInputMetEos = false; - mPendingEosTimestamp = INT64_MIN; mSync.start(); return OK; } @@ -2418,7 +2447,8 @@ status_t CCodecBufferChannel::requestInitialInputBuffers() { ALOGD("[%s] buffer capacity too small for the config (%zu < %zu)", mName, buffer->capacity(), config->size()); } - } else if (oStreamFormat.value == C2BufferData::LINEAR && i == 0) { + } else if (oStreamFormat.value == C2BufferData::LINEAR && i == 0 + && mComponentName.find("c2.qti.") == std::string::npos) { // WORKAROUND: Some apps expect CSD available without queueing // any input. Queue an empty buffer to get the CSD. buffer->setRange(0, 0); @@ -2549,6 +2579,7 @@ bool CCodecBufferChannel::handleWork( } const std::unique_ptr<C2Worklet> &worklet = work->worklets.front(); + std::shared_ptr<C2Buffer> buffer; // NOTE: MediaCodec usage supposedly have only one output stream. if (worklet->output.buffers.size() > 1u) { diff --git a/media/sfplugin/CCodecBufferChannel.h b/media/sfplugin/CCodecBufferChannel.h index e354fd0..431baaa 100644 --- a/media/sfplugin/CCodecBufferChannel.h +++ b/media/sfplugin/CCodecBufferChannel.h @@ -376,10 +376,9 @@ private: Mutexed<ReorderStash> mReorderStash; std::atomic_bool mInputMetEos; - std::atomic_int64_t mPendingEosTimestamp; inline bool hasCryptoOrDescrambler() { - return mCrypto != NULL || mDescrambler != NULL; + return mCrypto != nullptr || mDescrambler != nullptr; } }; diff --git a/media/sfplugin/CCodecConfig.cpp b/media/sfplugin/CCodecConfig.cpp index 8dbfd0e..ef02e74 100644 --- a/media/sfplugin/CCodecConfig.cpp +++ b/media/sfplugin/CCodecConfig.cpp @@ -570,6 +570,12 @@ void CCodecConfig::initializeStandardParams() { add(ConfigMapper("csd-0", C2_PARAMKEY_INIT_DATA, "value") .limitTo(D::OUTPUT & D::READ)); + add(ConfigMapper(KEY_HDR10_PLUS_INFO, C2_PARAMKEY_INPUT_HDR10_PLUS_INFO, "value") + .limitTo(D::VIDEO & D::PARAM & D::INPUT)); + + add(ConfigMapper(KEY_HDR10_PLUS_INFO, C2_PARAMKEY_OUTPUT_HDR10_PLUS_INFO, "value") + .limitTo(D::VIDEO & D::OUTPUT)); + add(ConfigMapper(C2_PARAMKEY_TEMPORAL_LAYERING, C2_PARAMKEY_TEMPORAL_LAYERING, "") .limitTo(D::ENCODER & D::VIDEO & D::OUTPUT)); @@ -624,7 +630,23 @@ void CCodecConfig::initializeStandardParams() { .limitTo(D::AUDIO & D::CODED)); add(ConfigMapper(KEY_PCM_ENCODING, C2_PARAMKEY_PCM_ENCODING, "value") - .limitTo(D::AUDIO)); + .limitTo(D::AUDIO) + .withMappers([](C2Value v) -> C2Value { + int32_t value; + C2Config::pcm_encoding_t to; + if (v.get(&value) && C2Mapper::map(value, &to)) { + return to; + } + return C2Value(); + }, [](C2Value v) -> C2Value { + C2Config::pcm_encoding_t value; + int32_t to; + using C2ValueType=typename _c2_reduce_enum_to_underlying_type<decltype(value)>::type; + if (v.get((C2ValueType*)&value) && C2Mapper::map(value, &to)) { + return to; + } + return C2Value(); + })); add(ConfigMapper(KEY_IS_ADTS, C2_PARAMKEY_AAC_PACKAGING, "value") .limitTo(D::AUDIO & D::CODED) diff --git a/media/sfplugin/Codec2Buffer.cpp b/media/sfplugin/Codec2Buffer.cpp index 5cb719e..597e8f3 100644 --- a/media/sfplugin/Codec2Buffer.cpp +++ b/media/sfplugin/Codec2Buffer.cpp @@ -109,9 +109,11 @@ bool LocalLinearBuffer::copy(const std::shared_ptr<C2Buffer> &buffer) { // DummyContainerBuffer +static uint8_t sDummyByte[1] = { 0 }; + DummyContainerBuffer::DummyContainerBuffer( const sp<AMessage> &format, const std::shared_ptr<C2Buffer> &buffer) - : Codec2Buffer(format, new ABuffer(nullptr, 1)), + : Codec2Buffer(format, new ABuffer(sDummyByte, 1)), mBufferRef(buffer) { setRange(0, buffer ? 1 : 0); } @@ -562,7 +564,7 @@ GraphicMetadataBuffer::GraphicMetadataBuffer( const std::shared_ptr<C2Allocator> &alloc) : Codec2Buffer(format, new ABuffer(sizeof(VideoNativeMetadata))), mAlloc(alloc) { - ((VideoNativeMetadata *)base())->pBuffer = 0; + ((VideoNativeMetadata *)base())->pBuffer = nullptr; } std::shared_ptr<C2Buffer> GraphicMetadataBuffer::asC2Buffer() { @@ -576,7 +578,7 @@ std::shared_ptr<C2Buffer> GraphicMetadataBuffer::asC2Buffer() { ALOGV("VideoNativeMetadata: %dx%d", buffer->width, buffer->height); C2Handle *handle = WrapNativeCodec2GrallocHandle( - native_handle_clone(buffer->handle), + buffer->handle, buffer->width, buffer->height, buffer->format, diff --git a/media/sfplugin/Codec2InfoBuilder.cpp b/media/sfplugin/Codec2InfoBuilder.cpp index d883d46..4a6d672 100644 --- a/media/sfplugin/Codec2InfoBuilder.cpp +++ b/media/sfplugin/Codec2InfoBuilder.cpp @@ -74,11 +74,11 @@ constexpr OMX_U32 kPortIndexOutput = 1; constexpr OMX_U32 kMaxIndicesToCheck = 32; status_t queryOmxCapabilities( - const char* name, const char* mime, bool isEncoder, + const char* name, const char* mediaType, bool isEncoder, MediaCodecInfo::CapabilitiesWriter* caps) { - const char *role = GetComponentRole(isEncoder, mime); - if (role == NULL) { + const char *role = GetComponentRole(isEncoder, mediaType); + if (role == nullptr) { return BAD_VALUE; } @@ -129,8 +129,8 @@ status_t queryOmxCapabilities( return err; } - bool isVideo = hasPrefix(mime, "video/") == 0; - bool isImage = hasPrefix(mime, "image/") == 0; + bool isVideo = hasPrefix(mediaType, "video/") == 0; + bool isImage = hasPrefix(mediaType, "image/") == 0; if (isVideo || isImage) { OMX_VIDEO_PARAM_PROFILELEVELTYPE param; @@ -150,7 +150,7 @@ status_t queryOmxCapabilities( // AVC components may not list the constrained profiles explicitly, but // decoders that support a profile also support its constrained version. // Encoders must explicitly support constrained profiles. - if (!isEncoder && strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC) == 0) { + if (!isEncoder && strcasecmp(mediaType, MEDIA_MIMETYPE_VIDEO_AVC) == 0) { if (param.eProfile == OMX_VIDEO_AVCProfileHigh) { caps->addProfileLevel(OMX_VIDEO_AVCProfileConstrainedHigh, param.eLevel); } else if (param.eProfile == OMX_VIDEO_AVCProfileBaseline) { @@ -194,7 +194,7 @@ status_t queryOmxCapabilities( asString(portFormat.eColorFormat), portFormat.eColorFormat); } } - } else if (strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AAC) == 0) { + } else if (strcasecmp(mediaType, MEDIA_MIMETYPE_AUDIO_AAC) == 0) { // More audio codecs if they have profiles. OMX_AUDIO_PARAM_ANDROID_PROFILETYPE param; InitOMXParams(¶m); @@ -225,18 +225,18 @@ status_t queryOmxCapabilities( } if (isVideo && !isEncoder) { - native_handle_t *sidebandHandle = NULL; + native_handle_t *sidebandHandle = nullptr; if (omxNode->configureVideoTunnelMode( kPortIndexOutput, OMX_TRUE, 0, &sidebandHandle) == OK) { // tunneled playback includes adaptive playback - caps->addFlags(MediaCodecInfo::Capabilities::kFlagSupportsAdaptivePlayback - | MediaCodecInfo::Capabilities::kFlagSupportsTunneledPlayback); + caps->addDetail(MediaCodecInfo::Capabilities::FEATURE_ADAPTIVE_PLAYBACK, 1); + caps->addDetail(MediaCodecInfo::Capabilities::FEATURE_TUNNELED_PLAYBACK, 1); } else if (omxNode->setPortMode( kPortIndexOutput, IOMX::kPortModeDynamicANWBuffer) == OK || omxNode->prepareForAdaptivePlayback( kPortIndexOutput, OMX_TRUE, 1280 /* width */, 720 /* height */) == OK) { - caps->addFlags(MediaCodecInfo::Capabilities::kFlagSupportsAdaptivePlayback); + caps->addDetail(MediaCodecInfo::Capabilities::FEATURE_ADAPTIVE_PLAYBACK, 1); } } @@ -248,7 +248,7 @@ status_t queryOmxCapabilities( if (omxNode->getConfig( (OMX_INDEXTYPE)OMX_IndexConfigAndroidIntraRefresh, ¶ms, sizeof(params)) == OK) { - caps->addFlags(MediaCodecInfo::Capabilities::kFlagSupportsIntraRefresh); + caps->addDetail(MediaCodecInfo::Capabilities::FEATURE_INTRA_REFRESH, 1); } } @@ -271,12 +271,25 @@ void buildOmxInfo(const MediaCodecsXmlParser& parser, writer->addMediaCodecInfo(); info->setName(name.c_str()); info->setOwner("default"); - info->setEncoder(encoder); + typename std::underlying_type<MediaCodecInfo::Attributes>::type attrs = 0; + if (encoder) { + attrs |= MediaCodecInfo::kFlagIsEncoder; + } + // NOTE: we don't support software-only codecs in OMX + if (!hasPrefix(name, "OMX.google.")) { + attrs |= MediaCodecInfo::kFlagIsVendor; + if (properties.quirkSet.find("attribute::software-codec") + == properties.quirkSet.end()) { + attrs |= MediaCodecInfo::kFlagIsHardwareAccelerated; + } + } + info->setAttributes(attrs); info->setRank(omxRank); - for (const MediaCodecsXmlParser::Type& type : properties.typeMap) { - const std::string &mime = type.first; + // OMX components don't have aliases + for (const MediaCodecsXmlParser::Type &type : properties.typeMap) { + const std::string &mediaType = type.first; std::unique_ptr<MediaCodecInfo::CapabilitiesWriter> caps = - info->addMime(mime.c_str()); + info->addMediaType(mediaType.c_str()); const MediaCodecsXmlParser::AttributeMap &attrMap = type.second; for (const MediaCodecsXmlParser::Attribute& attr : attrMap) { const std::string &key = attr.first; @@ -290,13 +303,13 @@ void buildOmxInfo(const MediaCodecsXmlParser& parser, } status_t err = queryOmxCapabilities( name.c_str(), - mime.c_str(), + mediaType.c_str(), encoder, caps.get()); if (err != OK) { - ALOGE("Failed to query capabilities for %s (mime: %s). Error: %d", + ALOGI("Failed to query capabilities for %s (media type: %s). Error: %d", name.c_str(), - mime.c_str(), + mediaType.c_str(), static_cast<int>(err)); } } @@ -339,12 +352,13 @@ status_t Codec2InfoBuilder::buildMediaCodecList(MediaCodecListWriter* writer) { // Obtain Codec2Client std::vector<Traits> traits = Codec2Client::ListComponents(); - MediaCodecsXmlParser parser( - MediaCodecsXmlParser::defaultSearchDirs, - option == 0 ? "media_codecs.xml" : - "media_codecs_c2.xml", - option == 0 ? "media_codecs_performance.xml" : - "media_codecs_performance_c2.xml"); + MediaCodecsXmlParser parser; + if (option == 0) { + parser.parseXmlFilesInSearchDirs(); + } else { + parser.parseXmlFilesInSearchDirs( + { "media_codecs_c2.xml", "media_codecs_performance_c2.xml" }); + } if (parser.getParsingStatus() != OK) { ALOGD("XML parser no good"); return OK; @@ -411,18 +425,37 @@ status_t Codec2InfoBuilder::buildMediaCodecList(MediaCodecListWriter* writer) { ALOGV("canonName = %s", canonName.c_str()); std::unique_ptr<MediaCodecInfoWriter> codecInfo = writer->addMediaCodecInfo(); codecInfo->setName(trait.name.c_str()); - codecInfo->setOwner("codec2"); + codecInfo->setOwner(("codec2::" + trait.owner).c_str()); + const MediaCodecsXmlParser::CodecProperties &codec = parser.getCodecMap().at(canonName); + bool encoder = trait.kind == C2Component::KIND_ENCODER; - codecInfo->setEncoder(encoder); + typename std::underlying_type<MediaCodecInfo::Attributes>::type attrs = 0; + + if (encoder) { + attrs |= MediaCodecInfo::kFlagIsEncoder; + } + if (trait.owner == "software") { + attrs |= MediaCodecInfo::kFlagIsSoftwareOnly; + } else { + attrs |= MediaCodecInfo::kFlagIsVendor; + if (trait.owner == "vendor-software") { + attrs |= MediaCodecInfo::kFlagIsSoftwareOnly; + } else if (codec.quirkSet.find("attribute::software-codec") == codec.quirkSet.end()) { + attrs |= MediaCodecInfo::kFlagIsHardwareAccelerated; + } + } + codecInfo->setAttributes(attrs); codecInfo->setRank(rank); - const MediaCodecsXmlParser::CodecProperties &codec = - parser.getCodecMap().at(canonName); + + for (const std::string &alias : codec.aliases) { + codecInfo->addAlias(alias.c_str()); + } for (auto typeIt = codec.typeMap.begin(); typeIt != codec.typeMap.end(); ++typeIt) { const std::string &mediaType = typeIt->first; const MediaCodecsXmlParser::AttributeMap &attrMap = typeIt->second; std::unique_ptr<MediaCodecInfo::CapabilitiesWriter> caps = - codecInfo->addMime(mediaType.c_str()); + codecInfo->addMediaType(mediaType.c_str()); for (auto attrIt = attrMap.begin(); attrIt != attrMap.end(); ++attrIt) { std::string key, value; std::tie(key, value) = *attrIt; diff --git a/media/sfplugin/ReflectedParamUpdater.cpp b/media/sfplugin/ReflectedParamUpdater.cpp index d2a5091..880d4a5 100644 --- a/media/sfplugin/ReflectedParamUpdater.cpp +++ b/media/sfplugin/ReflectedParamUpdater.cpp @@ -94,7 +94,7 @@ std::string ReflectedParamUpdater::Dict::debugString(size_t indent_) const { s << "string " << it.first << " = \"" << strValue.c_str() << "\""; } else if (it.second.find(&bufValue)) { s << "Buffer " << it.first << " = "; - if (bufValue != NULL && bufValue->data() != NULL && bufValue->size() <= 64) { + if (bufValue != nullptr && bufValue->data() != nullptr && bufValue->size() <= 64) { s << "{" << std::endl; AString tmp; hexdump(bufValue->data(), bufValue->size(), indent_ + 4, &tmp); diff --git a/media/sfplugin/SkipCutBuffer.cpp b/media/sfplugin/SkipCutBuffer.cpp index ee9016d..5762440 100644 --- a/media/sfplugin/SkipCutBuffer.cpp +++ b/media/sfplugin/SkipCutBuffer.cpp @@ -29,7 +29,7 @@ SkipCutBuffer::SkipCutBuffer(size_t skip, size_t cut, size_t num16BitChannels) { mWriteHead = 0; mReadHead = 0; mCapacity = 0; - mCutBuffer = NULL; + mCutBuffer = nullptr; if (num16BitChannels == 0 || num16BitChannels > INT32_MAX / 2) { ALOGW("# channels out of range: %zu, using passthrough instead", num16BitChannels); @@ -57,7 +57,7 @@ SkipCutBuffer::~SkipCutBuffer() { } void SkipCutBuffer::submit(MediaBuffer *buffer) { - if (mCutBuffer == NULL) { + if (mCutBuffer == nullptr) { // passthrough mode return; } @@ -90,7 +90,7 @@ void SkipCutBuffer::submit(MediaBuffer *buffer) { template <typename T> void SkipCutBuffer::submitInternal(const sp<T>& buffer) { - if (mCutBuffer == NULL) { + if (mCutBuffer == nullptr) { // passthrough mode return; } diff --git a/media/sfplugin/tests/Android.bp b/media/sfplugin/tests/Android.bp index f04aa41..b08d3d6 100644 --- a/media/sfplugin/tests/Android.bp +++ b/media/sfplugin/tests/Android.bp @@ -19,7 +19,6 @@ cc_test { cflags: [ "-Werror", "-Wall", - "-std=c++14", ], } @@ -49,6 +48,5 @@ cc_test { cflags: [ "-Werror", "-Wall", - "-std=c++14", ], } |