[automerger skipped] RESTRICT AUTOMERGE: Camera: fix use after free in sensor timestamp am: 1859a38c4d -s ours am skip reason: subject contains skip directive Change-Id: I5f37c2d4a6ec2854b6a2e5337c2a009817983e4d
diff --git a/Android.bp b/Android.bp deleted file mode 100644 index e4f12c8..0000000 --- a/Android.bp +++ /dev/null
@@ -1,7 +0,0 @@ -subdirs = [ - "camera", - "drm/*", - "media/*", - "services/*", - "soundtrigger", -]
diff --git a/CleanSpec.mk b/CleanSpec.mk index 793cbf4..e584ffb 100644 --- a/CleanSpec.mk +++ b/CleanSpec.mk
@@ -81,6 +81,7 @@ $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/lib/libstagefright_xmlparser@1.0.so) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/lib/libstagefright_soft_*) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/lib/vndk/libstagefright_soft_*) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/lib/libaudiopolicyengineconfig*) # ************************************************ # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
diff --git a/apex/Android.bp b/apex/Android.bp new file mode 100644 index 0000000..42a620b --- /dev/null +++ b/apex/Android.bp
@@ -0,0 +1,121 @@ +// 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. + +apex_defaults { + name: "com.android.media-defaults", + java_libs: ["updatable-media"], + multilib: { + first: { + // Extractor process runs only with the primary ABI. + native_shared_libs: [ + // Extractor plugins + "libaacextractor", + "libamrextractor", + "libflacextractor", + "libmidiextractor", + "libmkvextractor", + "libmp3extractor", + "libmp4extractor", + "libmpeg2extractor", + "liboggextractor", + "libwavextractor", + ], + }, + }, + prebuilts: [ + "mediaextractor.policy", + ], + key: "com.android.media.key", + certificate: ":com.android.media.certificate", + + // Use a custom AndroidManifest.xml used for API targeting. + androidManifest: ":com.android.media-androidManifest", +} + +apex { + name: "com.android.media", + manifest: "manifest.json", + defaults: ["com.android.media-defaults"], +} + +filegroup { + name: "com.android.media-androidManifest", + srcs: ["AndroidManifest-media.xml"], +} + +filegroup { + name: "com.android.media.swcodec-androidManifest", + srcs: ["AndroidManifest-swcodec.xml"], +} + +apex_defaults { + name: "com.android.media.swcodec-defaults", + binaries: [ + "mediaswcodec", + ], + prebuilts: [ + "com.android.media.swcodec-mediaswcodec.rc", + "com.android.media.swcodec-ld.config.txt", + "mediaswcodec.policy", + "mediaswcodec.xml", + ], + use_vendor: true, + key: "com.android.media.swcodec.key", + certificate: ":com.android.media.swcodec.certificate", + + // Use a custom AndroidManifest.xml used for API targeting. + androidManifest: ":com.android.media.swcodec-androidManifest", +} + +prebuilt_etc { + name: "com.android.media.swcodec-mediaswcodec.rc", + src: "mediaswcodec.rc", + filename: "init.rc", + installable: false, +} + +prebuilt_etc { + name: "com.android.media.swcodec-ld.config.txt", + src: "ld.config.txt", + filename: "ld.config.txt", + installable: false, +} + +apex { + name: "com.android.media.swcodec", + manifest: "manifest_codec.json", + defaults: ["com.android.media.swcodec-defaults"], +} + +apex_key { + name: "com.android.media.key", + public_key: "com.android.media.avbpubkey", + private_key: "com.android.media.pem", +} + +apex_key { + name: "com.android.media.swcodec.key", + public_key: "com.android.media.swcodec.avbpubkey", + private_key: "com.android.media.swcodec.pem", +} + +android_app_certificate { + name: "com.android.media.certificate", + certificate: "com.android.media", +} + +android_app_certificate { + name: "com.android.media.swcodec.certificate", + certificate: "com.android.media.swcodec", +}
diff --git a/apex/AndroidManifest-media.xml b/apex/AndroidManifest-media.xml new file mode 100644 index 0000000..1af4586 --- /dev/null +++ b/apex/AndroidManifest-media.xml
@@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * Copyright (C) 2017 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. + --> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.media"> + <!-- APEX does not have classes.dex --> + <application android:hasCode="false" /> + <!-- Setting maxSdk to lock the module to Q. minSdk is auto-set by build system --> + <uses-sdk + android:maxSdkVersion="29" + android:targetSdkVersion="29" + /> +</manifest>
diff --git a/apex/AndroidManifest-swcodec.xml b/apex/AndroidManifest-swcodec.xml new file mode 100644 index 0000000..de50864 --- /dev/null +++ b/apex/AndroidManifest-swcodec.xml
@@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * Copyright (C) 2017 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. + --> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.media.swcodec"> + <!-- APEX does not have classes.dex --> + <application android:hasCode="false" /> + <!-- Setting maxSdk to lock the module to Q. minSdk is auto-set by build system --> + <uses-sdk + android:maxSdkVersion="29" + android:targetSdkVersion="29" + /> +</manifest>
diff --git a/apex/OWNERS b/apex/OWNERS new file mode 100644 index 0000000..5587f5f --- /dev/null +++ b/apex/OWNERS
@@ -0,0 +1,6 @@ +chz@google.com +dwkang@google.com +jiyong@google.com +lajos@google.com +marcone@google.com +wjia@google.com
diff --git a/apex/TEST_MAPPING b/apex/TEST_MAPPING new file mode 100644 index 0000000..a2e98cc --- /dev/null +++ b/apex/TEST_MAPPING
@@ -0,0 +1,7 @@ +{ + "imports": [ + { + "path": "system/apex/tests" + } + ] +}
diff --git a/apex/com.android.media.avbpubkey b/apex/com.android.media.avbpubkey new file mode 100644 index 0000000..c0c8fd3 --- /dev/null +++ b/apex/com.android.media.avbpubkey Binary files differ
diff --git a/apex/com.android.media.pem b/apex/com.android.media.pem new file mode 100644 index 0000000..8daa50e --- /dev/null +++ b/apex/com.android.media.pem
@@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIJKAIBAAKCAgEA06dKiF+xQp36Xcosmac+DzJTXC9nbHy0Yqfy+zEC5hlwXbHZ +1gAZZu8zL9p7kbBkmtSCulU0M+cTHr74gkG9UDkM/S7Z+957FzHMqWXY03gupFP7 +lcCnKtpkzsyQrABavynoxyY6dfmKZNtEFQrikK1zs80CppRoMwZS2dLogX8qO5LU +gLe7/0PZBdbQSVA5AARE+AO6pR5Px/8QAere9TCLcm1aK9BUVOJvaAZAf7bD2f8s +3J/lANQ1tvXXZrFL1i26H6sNja11u5/M0odg3SfqKI0x/317nLkYx8QSSHVKEjBs +nzsyoFry4INEh/q7zSEX5+S1VA6ORjyof3u7CrGavrYwI2k6x3t+Dkc2dfNDaNY9 +9vGYD1nMyRqUzSIqaOz8q78tc1A391Lua8SB1E0Tx/FnsPjxPee0wZ1taGddkZxD +cvMdQJhLyE6EloimFiOhkVjnAnlPYiiPEQkwJomE9kCsP9aMmyhwBOpbbRISj1ua +edESrpTC5DHpt1owjtAfHvD8TfmPWT1KSN1iCQAuh5hnEM5LLDljc/AYvJV4L5uR +l/6t5dE8deg9ksY6lVRrUwHsXxUtBPhM82PWSrpAPNJCHLuBBVx+zDD//kOmt5oe +OyYJ2RcDsnLmDfFLHbPcAwuyfmxQ+pbFBfiL4NaKoUWRy0viR4/3zulysCsCAwEA +AQKCAgAphaNIl8VVtVpdtgED79xr7MqPxjj6/ogA5sPzZY0VCR6TMwXyRri1Ce43 +0Bv32+wQt+ohlf+UwxtsJ7jnDPCP4XFb5iobkG0DguCMxw8/hU9ZK6Sqn03sSUYH +j/g91h/3ashg8W38oQT2flGf8y+5hF2zg1+mwGykvfPZCdhVN1ZYs5h+3AzEqlHU +JG1eRJ+6EhxZr5mZNRYfvTkttx8gaPKiczOCbu9sa7PBa6CRrZBEnxv0+GVbwUX8 +a8RjQBsJnJTsC4mwJrx3H4V2M9rb6C224ORTJBHxEBr9bcjMcD4kzV0x69IlxVHq +m7YBGz5morxm4OZ15BkjTFkeEW8C8bdpRrYoY8ocmybWUf6g5IxpE33M699lWzdn ++xwPloJOA5sqsDIXGXt4+KPb2hjHLpqS4V9Rw1JQErBgB9/0EHqK4u1kwI1/djea +Ny26esGgjmupq+M+G3vQysKEX8m/KhYZKw8yqG4LrzUKp1uosEXEeE+FBnPW1fwU +OapJTAKLDAeItz2YsZ+82oTMREKR6gNoAw3yF3dxo/E4sk3IDG4y3w7A8D5dlBBM +hx2fDqnieS/OffGndbbbIIGH0Sb1MBURNlZlXwXz1hbACc8FMYmn4iyQUJfKlCfU +Rp1jOR4silFxEGYhSi0Jw7+AJe1prZRyZYp2ZQQ+trGvqQNhwQKCAQEA7CsaDxvP +L9GI8yFVznAG5SZvIut3/lLA7hd4F0LKJ277hW8YMENLjvmrtEQW1dyQqQXV+CpL +QErvgRuVj9DyV6qOKDimmpqhT+YZlbxp98N9Ba8RJ4ckw07vPNvglTjAeIZyUbRP +VX5Onr/OFw66GLzOIhsOnRlqviOKg2wm1kfBF3OBAwVczqh4PJ4gc3rbW9jPN3pn +eaouV4CdGIooXckV6XtQCGjWFNNzxmuknn1GbLbHslGuDUX89rQsRsk9qZGpi1M1 +Cf2yaQY0d+AjDdPslXKZ92EbDAmo3KGtoxy2hWWytZHbUhtWgguEwxDMkJhJ0yCM +ZSLQZ2TXlC/iCwKCAQEA5W0xuNTozb2UrwqEaDCkogxgdv5NdQvlvBM7+fD4RpOY +ItDFcudhuapnpxdbPrdl3F5bgTXZJ4jKMIKePVveugI7wNCQX7aSET3U0Qj47RzA +vVVHb0K3SZsicpK7K067Ejk1esxrPreaTnUj14jUle20Cq+3iysJnwMFs2DkmE1B +UK6MiBJ1+MbFuBATw7TWxu8yPyfa3JzUAEsfP8NqqIlXrK26DdGefd8C0hysiq4X +3matZ3SDck1mCk4LS9PCZZlDqdxLM6/UIoy4cj0qbSPdFMop8zjB8lF/1OABowkT +e/9L0dpG08G35aEJJ6vKSGnFLbstgly165PlGapeYQKCAQEAwoShuw4BsXYZIYA0 +Z4sX8secRBvDwoKwi6pi7G3DiYU8v2OIfb//zOxRg3GNiWpY8A5xdSyIvJS7/hAV +ONY1tQUyf2hhuPdhpCh2rED62up14CeYroD+Q6uRGwRTTzTmOp8qK6eirF0TLmf2 +vEESAGwKMEcu2zBjHeayIJsExftlzAYDndRd440ZM3xeaB8p69WAn0Y/UhNchg/V +1K9+nfiRBrTdb3/BzHd5ZVWlyjCOv94wTuw9uosJ1r0Btu/rzO2/wpSvG+KMfzpw +HshKtwn1VAaHUBz4JQsTvV2hYba1ktv3vNs81LzVnNkV6YC9rN7x92ZYnLh3BKIn +edOSjwKCAQAP51zeAixNLsoixCjfjBetgAwj04cNCREY04CB1/lt8wdFypEVYQK+ +OxjKVW0m0NHHz+ap81ClU+8oI7XSbQ7oeAUqXYrUh7Ria5XYE7Ylwat+tG2qQcaw +3IcryA4fd2qyXbLeW1NH2rRgofAlHcAW0I59eybPB+G32x7HC31tLVXMwPzO5fC1 +mRnVo4+rLlsBGU2zYRDj4B82Ef8NjX9URYkFWFmgYZqKAS6R4Bj52A2hhh6ZIFOI +VeMv7a8Mx5YfMtuk57dy0spyxqx2htTtEeJecZEs4g9Xu9yPpiOW6KcoHk9kMaxd +O32C9oHK9TalhGd9vw7tjX2y4eKsv8mhAoIBABEtJSjQ571YUbELvJgnVILWKExj +nURnbQ8C2j2lLdb20fi8UGJDei9g2b2oCs3SfCaMcRqoOs4uy8ZJzofPelwWY1B7 +SZezn/2d1Q/cXpaXew++krhFvJFVxya6eLD7pvEVQAsGtkp9BTi2CFIhcGo5UQdc +pym7XjFsW0LCI8mOAleuYe7DEpEU79RdsfxgQpeh1NgFJRpupHT6I2BkVijD/TI4 +kbwz11SuznkUTB4QePIwnEzV+gqDtlny4M1Qvsk8hqcU6JUOu+MOT1qnI6ZGSsdS +D6RhPukordC9y/cbiV6qqKBVZ39O8ydWvpto/g7G7fSL5SddFuVRnwAV7Vg= +-----END RSA PRIVATE KEY-----
diff --git a/apex/com.android.media.pk8 b/apex/com.android.media.pk8 new file mode 100644 index 0000000..6df741e --- /dev/null +++ b/apex/com.android.media.pk8 Binary files differ
diff --git a/apex/com.android.media.swcodec.avbpubkey b/apex/com.android.media.swcodec.avbpubkey new file mode 100644 index 0000000..0459723 --- /dev/null +++ b/apex/com.android.media.swcodec.avbpubkey Binary files differ
diff --git a/apex/com.android.media.swcodec.pem b/apex/com.android.media.swcodec.pem new file mode 100644 index 0000000..e379cd3 --- /dev/null +++ b/apex/com.android.media.swcodec.pem
@@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIJKgIBAAKCAgEAvB6lOEEOR58KMcFlayEZjsXuPgcfHi/OPxcvfpiGLCpOcK3l +OPChWUvDRcIHMB7BO+Csfxs0HgsHvvZfNyoNIm99WcjFbboiO7WrBBArIszPr14X +cfY2NxzT9LBj+EqAnbdL+4OQW1/npLHNE0qtDcxL71ipbjSuKNb58E9qGL0KwvkS +fwwueWj++bg/iz7dq0mz7iKpnxYscNm4RhJjqcG2Usmg4Ejhr8h5UmOUmTJbObC/ +vzClXQqeeuzS8NTtGVgQ/CI9gC2WN9upq2p/2T7P4U3o3CWvBytUoLKR0UyC83ey +S8XJgOa42uWR3T/eJOI1ZS4H6Srg1o2XC8Yb8EprFI/NM6/+/5DX/FgDimsslAP6 +Qq4+pSte9v/FjWGqy7QBQaefFRGRuS63xHcSZhXC9J2CFdnxo8+65QT8r4yfQEei +Ax/0Q94yB7VIL1pIJxHEonKjtd3iKdFEKQzADJ3edsmtHybERdJKCxcm9QIrDCsC +4YVT9nX0OoC9RD5d5EVD7W5I6eEnRu6igrKIKgUVppeYFQNZD+o+eiNJa4yoggRl +h8sT0/xLKjMRxAQ7fafi1j+LB2O7UgJmIDDAnidq8Aoz7h3pNi139rNWrjNfwhus +nMcZvL9dTVv26JWeESHP/zAfAX7j1rfkDwR02ocRLquwEUs1+UHA27Wi1bMCAwEA +AQKCAgAO3PT83tb7/arWh4s1zaWxTB4otHNW9Tv8bB6KiA6Bys3rxTGJMCnvXjcN +eekLekKWMoguer3BaemwwtJ/D0l+YQSsZVqD8uLliNL8PTLLSxdVqb98d5GNBTAR +8yXS5kAHNgZA1wI+1fL9ZjbnwUyu/Gc7f+vTE0J3Y5TX0c52KemBwiAd/Z5mZU9P +96i9nbfx4p7ev8pbEWttdyZCEw3gybdYDyowzlFWjCZZxhhlij7+7eIYfwVxtncT +C0cXVBtvly+wXBwz1mY5/5cGPiHfzkCqcndlfWy4ykmjcLhoqvzls51Ys0Xac2BD +m0PNEVDB5UWGuv5RA9xD12gJvBtU3D7ggMw6C5RcXJT+jSYmSFtD2klWi+It8A/N +Hv42soKskt6JqYAWE1cvJ3PEqH9ASEJNq2R0Z/PmuM000UJyzU9KId3SNwjXA1Xc +Kn9hRga4uf9elHTjkTDt79/8+Xv7hjer9sF/S/np7g04rUjIWkuFkC/7NK2tQSh1 +mljV1sD9SF4DPfVK75LwJJaQUlI7TtGd5KJ7FzZwvb+w8ODrpW3hkt6FcI6KwE/a +QT1T2Z9DknXJSYNdWGrj6vMHsYMiyz8IdAHSCrOB6eXCQxpitbn6W71Raw7f9UaZ +VDK5AhTU493hkGj1no4cJwecXInMigg/c4ywk2Ibh6IV8O0nkQKCAQEA2+dQQ0IF +vvVgmQ2WxHBD7M2mbOyf43YBY6Ka6oPBNGPVpZE8X8LoTavQLV+SgCkH8T6gY7XS +5L4Ze0JFxfua0o1rm6+L2XrOx5F/A2Y40YcPclEik5h1woSwH/J1iHGiEhY8Nqeu +9GCvjQojkgXx/Rn3Nz+lpvZ329O3H85RWWGF0l60RwLOkig0ZwUb619t8affmGIl +sxdDv2nfy7OtJX8iGDua7Kf64dvVWQKKtACWkARrlkcWX3uoESxkpSDxue+z7ndH +o7uHLfM8Tx+Rn+QvYWuRW5TPLbEDMbIYrX65ISt2r/T7v/04XdAC8YpCQRytlqPI +fpDm15htyHBizQKCAQEA2v+5otRoY56JduISaVGlsJ/IbvKzDAIyWmM14EpRCR5E +lu+MpTcRAiFQAbHGXr1tMlTFdVFD090WAzIKaKtADFVLXobIHeGspnRCq5OpVp9W +RvLtVwLxflHAc2yN9/LNtnBqHUgt+S01LBPElybdGHQRTtqAKXhkp31sM21O34Go +Pri/IxgupWxykMaW44Kig1Cr5DKvc8cwUsGuyDdJm8oBQeNPTMWqSnXtqoTWSaYg +2kxiMTFokrkSXgufb8wng6OXt/QelywrhG3hAsldPO3GdKidDSxhWZSgpUXXFdAX +y4GO0IcRJBF/WJtYTYtR+l84nQA2/1Ye4ujFlT0afwKCAQEAmXrXpSm2cvI2CnzW +hqJIdkWOa6W3bn1VOOIrt5Rfy54GZnl4pumVU2igcpqq2HJKzdDFBvLHj8kyZbn6 +ktUp2NzFhzK9q/uvyNA+0vOMoojeeg4w0MzvG+WaO6Hw8FtHH9KPEiJ01LGKtSin +bOpjXCC8T75HcsHBJBefTz6jvnt3eD2LG6jU3mPbNy/0rZG8XZaqU2PlJhsNuNI/ +VaBBL9OMy1cGqTgQvYS+YlKI1ls2uqurH4bcEaZvxhSy5iGZNQodDkoIITnofmSu +6haBgBQ2EYuPN1kkRKKwNQY1fRneQk1gmCynbPdiWO+urkCuP12xtlr3u4aM51rG +/Meb3QKCAQEA12SxZm9XhLOHLIBJ74A4YLGm50iZxXPbpn7xnHo7naZBe9p8EHtK +pTeygxggrUnOPrSVyT92YMiQP/BVwIC+a+LwUDZsWMd/ke/DKxH+eY4Zw4pm2S+x +6bXqfRwFvhr3LTr/g3FcljlalNGUh73Xs5dk9pN9fkxFY16+rw4Rh0709Uur4o6E +QnuZar+H5Ji10kXj6nvXiR4ebybEC3QlV66k8fLqKe44AShf61jfkmxs34hFA3E/ +EyAn6ouv8rtvGdArBuh5teHho0yXBLCcnbKXgGHepfhCf2LpZeR9GZ0j6iqxFnPh +7gGvqKyReyNOK9y/x9tQPG6tzit3OcNxbQKCAQEAuDheDOSkXPopXMvLAKOHBIdZ +rbQ7oFTDHt59gbucM7U8/l2YtaxuKOQFBLnzQa/amIkkrtklYwOsz8E24d7Nm39w +ykLHwX0XrmjAm6M4XKmDv66a+kSnSV6LEbKZdjvXP02DV+tGeZ5VsnNvJDquxMsD +fvRTspB8j8CpU96szekxl/tCRhqbdw/4kVTSj5BF++OaRRcJrAyj1B2qf1ynAZE1 +gUvVPkEYa914zcrxg9XIXT4M7yqB7i8KJegOtOtcWjJ7uTiP+638AvygJLMJnSrV ++HjFZWG6P9btZmLHSEBRvwGOAilp0qejXo866l0fmnlGy7ehKz8u3PzvnlPYjQ== +-----END RSA PRIVATE KEY-----
diff --git a/apex/com.android.media.swcodec.pk8 b/apex/com.android.media.swcodec.pk8 new file mode 100644 index 0000000..05a4216 --- /dev/null +++ b/apex/com.android.media.swcodec.pk8 Binary files differ
diff --git a/apex/com.android.media.swcodec.x509.pem b/apex/com.android.media.swcodec.x509.pem new file mode 100644 index 0000000..67b9b4f --- /dev/null +++ b/apex/com.android.media.swcodec.x509.pem
@@ -0,0 +1,34 @@ +-----BEGIN CERTIFICATE----- +MIIF3jCCA8agAwIBAgIJAIM72JpD4v6XMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYD +VQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4g +VmlldzEQMA4GA1UECgwHQW5kcm9pZDEQMA4GA1UECwwHQW5kcm9pZDEiMCAGA1UE +AwwZY29tLmFuZHJvaWQubWVkaWEuc3djb2RlYzAgFw0xOTAyMTEwMjExMTFaGA80 +NzU3MDEwNzAyMTExMVowgYIxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9y +bmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdBbmRyb2lkMRAw +DgYDVQQLDAdBbmRyb2lkMSIwIAYDVQQDDBljb20uYW5kcm9pZC5tZWRpYS5zd2Nv +ZGVjMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAsqXE0AIWpLW9Tgq2 +nQGph7KZ6L2Q9oxviqCVHxIaPqfhM2SwTbycADIQeqrrlRxhddVkjLuMUkJa7mev +fERmgpiOfnPIlGK6PTs2gljCkskZhF3bgfeyuHt0tsYO+UaN8MVoZD7/QdiE46w2 +OMDClG1UqgiqOBhLTEN/cHXObnUiiVXUYqN8aYZf6L6Fs3yQi2ZZgfbxTVFewqdv +aLLOqCYnVYXZH+ZxbXESA0M+WXKgRKsYTj2GYs3eko1rFi4Y6uHVLx45yaoT5u/i +SxPEkocyMCKvGJWu4XlSOd3EjSOMaqCOYVyGLxdlnQWQU7PZDqBSJ0SysWgpFHpB +I15c2jhRdXOCfQ9ZtDfPZkE0a2A8kJDAoF1mzTp6IvBAWUsl5nHPw5CWkFpNad/h +tqqGCScWbiKZuvrQ4/RQNm3f1K+mxX9TrjFigpqNO6d4pGAo1fa6sHR3xWPw/myq +h5ZJjVnXU5Yq64S4xWOssfjpOg7RfNuvzuk3ok3MYs1mbx3vhZOj5km1f3qrgX9c +mXjYnyXD0jJBm4uAJWXLdK9PlZvlXbztMCzYj832Io4pFLCtSxkzX75t1em36Nv0 +mNp6NtSSy6SFSq8l7IsXV2FNyUiyHWxS/UQm8pYg5Q5dWHvEEF78P6lV0wRa6FQl +BBSgpqTAI092KIjDDtB7GQCgV5ECAwEAAaNTMFEwHQYDVR0OBBYEFAFIdFTDEDft +ewSSAS7Fa3OZ5TXzMB8GA1UdIwQYMBaAFAFIdFTDEDftewSSAS7Fa3OZ5TXzMA8G +A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIBAC5e3zXythJCGmz1FmAV +8Y/UI+Glg6G0x/k04WaRG0DPLLjlJ1F0LM1/IReBSgXcYAL0CAgPycf/rGPOgMFm +tQxYyjBUxKdjpIqU5DJoV1feanGveIRpto1YRKNgHuzG9rZGR4AgPnt6X4Yxlq04 +lI7QpWadXe1myARJhj3niSNY9+2wEInkx4ZuCO1LtIGqnbdc8jQ8YoVqIE5N4kuM +ccyPYgsdABtopbjN92rueu8sfF8R6ROy+tNgb6OjpAAevtnBfZ2LXqfObKirHCK+ +k6w4WSB1UUoZ3Xgz8sJtXgokvYeInkN8tHuTagHYU2VQTcA0rdBGMN/1OljJpWlN +0UUq4fAYU6cN4lHxr2LM9If4WvAzdLAWvaIZrDqaU4i/zYT9l6rR4lC2KW3EHWov +nPXfgEJJ8AP1iRGibvew3i3SB6XTWFQYTUIBeJfDz/KDXQabP+yzXWISdZCUMUpx +f+Raqsb5MoKaJdVgnSL0mBunjCyJDzzg34J7oGx6/BnwoiOrwLN4Qaz5U8jbrPSx +p9LfleCcO7ZdeE8GKqx0X1T4d7tradtmxOS8Iwr4niskkHGRkzozvVvuyGKmoN2k +162Vfjq+ddj7qEpSh3BS6hHU+vlMbC9L0trGxPxFEAHDrwu0KwGNduTkiu/3jvfB +JTgH8P9mD1loYxRdo+vet8eQ +-----END CERTIFICATE-----
diff --git a/apex/com.android.media.x509.pem b/apex/com.android.media.x509.pem new file mode 100644 index 0000000..e7908fa6 --- /dev/null +++ b/apex/com.android.media.x509.pem
@@ -0,0 +1,33 @@ +-----BEGIN CERTIFICATE----- +MIIFzDCCA7SgAwIBAgIJAO05DBBusaaLMA0GCSqGSIb3DQEBCwUAMHoxCzAJBgNV +BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBW +aWV3MRAwDgYDVQQKDAdBbmRyb2lkMRAwDgYDVQQLDAdBbmRyb2lkMRowGAYDVQQD +DBFjb20uYW5kcm9pZC5tZWRpYTAgFw0xOTAxMjUxNzE3MTdaGA80NzU2MTIyMTE3 +MTcxN1owejELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNV +BAcMDU1vdW50YWluIFZpZXcxEDAOBgNVBAoMB0FuZHJvaWQxEDAOBgNVBAsMB0Fu +ZHJvaWQxGjAYBgNVBAMMEWNvbS5hbmRyb2lkLm1lZGlhMIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEAmNkVxUbp/bLbeGbvKqYXzwBycSDpmOhh///lNGYQ +/AMUD0q6EaZzU2bd4aL0rOGqfoYlhKd0kMVmMUmfdE9ODAfKxleEeEaRl2GJS8a9 +ABi770l3GHbB2xMI2sEWeOD9xsPFF6+ByPZmoUuNhMr4pUbXsDpE3h8ljrgXHtIg +bh7ofbvddruwBV0lS1k9OZ9jPVGhEKkJnhgQa67cwgdjizAMbI0Dcz9gtMMawsDj +Z2aQd1r+vxgh1/XkI/NMmXCnG2ERytXcJeC5S4gEtHfTTPoP0FuVgSB6y6dalMuZ +F0NBZw8Mvgdy3QJip0uNa36J63CMZKTJWbTdlFpPL2hk0PgaYvje8C5Xtk5282wT +dMocc8n2zIXbzbnSXGvjcNZib3Pfu55YUnX6eTqZ1BxlJ0FHZAsC4quFFWXxYBYD +LCRoNNFEtIDQpuvuHF2DuHNDULpAQjy2y6+7eot0KEsVoDmZ4H8BpuAVVu2SxYNb +gYflR9SmM0tmYeAcRT48q3xrocGyEHMqvgQRUpPfvct/8l8xVcDzOI/sJVDqmYzM +u0Cj3fkSypGDJOMF/esFSmVvoI01tS7kaNS5vvtKYib//xqKRC9f0dCsGfFLnuUK +o4KYbYWYwMyJqEd/5/ZvXyKIPAEeJL174L9+wTkc3cQpoBwJN4t+2E5MnhOEq6do +5L0CAwEAAaNTMFEwHQYDVR0OBBYEFHjNK/GZko1RdZp+8iavWXL5xz9wMB8GA1Ud +IwQYMBaAFHjNK/GZko1RdZp+8iavWXL5xz9wMA8GA1UdEwEB/wQFMAMBAf8wDQYJ +KoZIhvcNAQELBQADggIBACmPQMksuLrNV1vbI44S1f70I0FHdBxchFGB39zuLbcn +SsYom/LPtYJiD0Dl4bB4eb+ZnxkQP2XeP6pycmUH2j1EWexFwvdUvlfe8Qz+wAec +ap4AxiX4Z2Ke2ivYotIZFUHdZOLkX20js8Wex1mzY43MLQn5APl9gK1VZTxDggeR +EObH1S+JVjGwQqYZj2e6gNZH34Q25NQ698RL85GDkYtSISAifJtaJsU/B3vKm82I +k9xMiCooCH6bRdGHG1jze4SRpidjxEm8cxkiaQagfcuXeCLziXJr3qAMKYiEY6bp +0+bAqCt3S8OrrN3RQZfQrnlwitsM1jJJ/+C+WoDg4eY5AFrXDLvNeKh1qO/f8xv+ +fCXkQPcVVphLfRH9oxNrSgOWBP5/qIDH4s1YUL9luGT6H+08dlue3RkbzDbBqsQu +7fQ/BbrIG/GuVKgyEM+a7C9gv7zc86YlueVYJEyxKidnn7RxOqyDBqyyfXA3zvme +Rro7xIrMHPL7Nu3AWjwjXzbp/w0z+tEFPsfVB+OOHKsWPcUG0HUTJGkyeO/uHRjN +qPEkkf7BHHUO4V2gjOIdCsELxKwHf7vsZTOk40EV751fZ7FDHMr1eddQkgH4eqAb +DB79uP+SLfUo+42n4q6eMmoqw8d76bBXRoUhIo/Ms4sebhV0sRtAS67OQioc9UUg +-----END CERTIFICATE-----
diff --git a/apex/ld.config.txt b/apex/ld.config.txt new file mode 100644 index 0000000..a5937fd --- /dev/null +++ b/apex/ld.config.txt
@@ -0,0 +1,131 @@ +# Copyright (C) 2019 The Android Open Source Project +# +# Bionic loader config file for the media swcodec APEX. +# +# There are no versioned APEX paths here - this APEX module does not support +# having several versions mounted. + +dir.swcodec = /apex/com.android.media.swcodec/bin/ + +[swcodec] +additional.namespaces = platform,sphal + +############################################################################### +# "default" namespace +# +# This namespace is for the binaries and libraries on the swcodec APEX. +############################################################################### + +namespace.default.isolated = true +namespace.default.visible = true + +namespace.default.search.paths = /apex/com.android.media.swcodec/${LIB} +namespace.default.asan.search.paths = /apex/com.android.media.swcodec/${LIB} + +namespace.default.links = platform + +# TODO: replace the following when apex has a way to auto-generate this list +# namespace.default.link.platform.shared_libs = %LLNDK_LIBRARIES% +# namespace.default.link.platform.shared_libs += %SANITIZER_RUNTIME_LIBRARIES% +namespace.default.link.platform.shared_libs = libEGL.so:libGLESv1_CM.so:libGLESv2.so:libGLESv3.so:libRS.so:libandroid_net.so:libc.so:libcgrouprc.so:libclang_rt.asan-aarch64-android.so:libclang_rt.asan-arm-android.so:libclang_rt.hwasan-aarch64-android.so:libclang_rt.asan-i686-android.so:libclang_rt.asan-x86_64-android.so:libdl.so:libft2.so:liblog.so:libm.so:libmediandk.so:libnativewindow.so:libneuralnetworks.so:libsync.so:libvndksupport.so:libdl_android.so:libvulkan.so + +############################################################################### +# "platform" namespace +# +# This namespace is for linking to LLNDK and ASAN libraries on the system. +############################################################################### + +namespace.platform.isolated = true + +namespace.platform.search.paths = /system/${LIB} +namespace.platform.asan.search.paths = /data/asan/system/${LIB} +namespace.platform.asan.search.paths += /system/${LIB} + +# /system/lib/libc.so, etc are symlinks to /apex/com.android.lib/lib/bionic/libc.so, etc. +# Add /apex/... pat to the permitted paths because linker uses realpath(3) +# to check the accessibility of the lib. We could add this to search.paths +# instead but that makes the resolution of bionic libs be dependent on +# the order of /system/lib and /apex/... in search.paths. If /apex/... +# is after /system/lib, then /apex/... is never tried because libc.so +# is always found in /system/lib but fails to pass the accessibility test +# because of its realpath. It's better to not depend on the ordering if +# possible. +namespace.platform.permitted.paths = /apex/com.android.runtime/${LIB}/bionic +namespace.platform.asan.permitted.paths = /apex/com.android.runtime/${LIB}/bionic + +############################################################################### +# "sphal" namespace +# +############################################################################### +namespace.sphal.isolated = true +namespace.sphal.visible = true + +# Keep the below in sync with "sphal" namespace in system's /etc/ld.config.txt +# Codec2 has dependencies on some SP-hals (eg. android.hardware.graphics.mapper@2.0) +# These are dlopen'ed by libvndksupport.so. +namespace.sphal.search.paths = /odm/${LIB} +namespace.sphal.search.paths += /vendor/${LIB} + +namespace.sphal.permitted.paths = /odm/${LIB} +namespace.sphal.permitted.paths += /vendor/${LIB} +namespace.sphal.permitted.paths += /vendor/${LIB}/hw +namespace.sphal.permitted.paths += /system/vendor/${LIB} + +namespace.sphal.asan.search.paths = /data/asan/odm/${LIB} +namespace.sphal.asan.search.paths += /odm/${LIB} +namespace.sphal.asan.search.paths += /data/asan/vendor/${LIB} +namespace.sphal.asan.search.paths += /vendor/${LIB} + +namespace.sphal.asan.permitted.paths = /data/asan/odm/${LIB} +namespace.sphal.asan.permitted.paths += /odm/${LIB} +namespace.sphal.asan.permitted.paths += /data/asan/vendor/${LIB} +namespace.sphal.asan.permitted.paths += /vendor/${LIB} + +# Keep the below in sync with "vndk" namespace in system's /etc/ld.config.txt +# System's sphal namespace links to vndk namespace for %VNDK_SAMEPROCESS_LIBRARIES%, +# since we don't have a good way to auto-expand %VNDK_SAMEPROCESS_LIBRARIES%, +# we'll add the vndk paths below. + +namespace.sphal.search.paths += /odm/${LIB}/vndk-sp +namespace.sphal.search.paths += /vendor/${LIB}/vndk-sp +namespace.sphal.search.paths += /system/${LIB}/vndk-sp${VNDK_VER} + +namespace.sphal.permitted.paths += /odm/${LIB}/hw +namespace.sphal.permitted.paths += /odm/${LIB}/egl +namespace.sphal.permitted.paths += /vendor/${LIB}/hw +namespace.sphal.permitted.paths += /vendor/${LIB}/egl +namespace.sphal.permitted.paths += /system/vendor/${LIB}/hw +namespace.sphal.permitted.paths += /system/vendor/${LIB}/egl +# This is exceptionally required since android.hidl.memory@1.0-impl.so is here +namespace.sphal.permitted.paths += /system/${LIB}/vndk-sp${VNDK_VER}/hw + +namespace.sphal.asan.search.paths += /data/asan/odm/${LIB}/vndk-sp +namespace.sphal.asan.search.paths += /odm/${LIB}/vndk-sp +namespace.sphal.asan.search.paths += /data/asan/vendor/${LIB}/vndk-sp +namespace.sphal.asan.search.paths += /vendor/${LIB}/vndk-sp +namespace.sphal.asan.search.paths += /data/asan/system/${LIB}/vndk-sp${VNDK_VER} +namespace.sphal.asan.search.paths += /system/${LIB}/vndk-sp${VNDK_VER} + +namespace.sphal.asan.permitted.paths += /data/asan/odm/${LIB}/hw +namespace.sphal.asan.permitted.paths += /odm/${LIB}/hw +namespace.sphal.asan.permitted.paths += /data/asan/odm/${LIB}/egl +namespace.sphal.asan.permitted.paths += /odm/${LIB}/egl +namespace.sphal.asan.permitted.paths += /data/asan/vendor/${LIB}/hw +namespace.sphal.asan.permitted.paths += /vendor/${LIB}/hw +namespace.sphal.asan.permitted.paths += /data/asan/vendor/${LIB}/egl +namespace.sphal.asan.permitted.paths += /vendor/${LIB}/egl + +namespace.sphal.asan.permitted.paths += /data/asan/system/${LIB}/vndk-sp${VNDK_VER}/hw +namespace.sphal.asan.permitted.paths += /system/${LIB}/vndk-sp${VNDK_VER}/hw + +# Once in this namespace, access to libraries in /system/lib is restricted. Only +# libs listed here can be used. +namespace.sphal.links = platform + +# TODO: replace the following when apex has a way to auto-generate this list +# namespace.sphal.link.platform.shared_libs = %LLNDK_LIBRARIES% +# namespace.sphal.link.platform.shared_libs += %SANITIZER_RUNTIME_LIBRARIES% +namespace.sphal.link.platform.shared_libs = libEGL.so:libGLESv1_CM.so:libGLESv2.so:libGLESv3.so:libRS.so:libandroid_net.so:libc.so:libcgrouprc.so:libclang_rt.asan-aarch64-android.so:libclang_rt.asan-arm-android.so:libclang_rt.hwasan-aarch64-android.so:libclang_rt.asan-i686-android.so:libclang_rt.asan-x86_64-android.so:libdl.so:libft2.so:liblog.so:libm.so:libmediandk.so:libnativewindow.so:libneuralnetworks.so:libsync.so:libvndksupport.so:libvulkan.so + +# Add a link for libz.so which is llndk on devices where VNDK is not enforced. +namespace.sphal.link.platform.shared_libs += libz.so
diff --git a/apex/manifest.json b/apex/manifest.json new file mode 100644 index 0000000..3011ee8 --- /dev/null +++ b/apex/manifest.json
@@ -0,0 +1,4 @@ +{ + "name": "com.android.media", + "version": 290000000 +}
diff --git a/apex/manifest_codec.json b/apex/manifest_codec.json new file mode 100644 index 0000000..83a5178 --- /dev/null +++ b/apex/manifest_codec.json
@@ -0,0 +1,4 @@ +{ + "name": "com.android.media.swcodec", + "version": 290000000 +}
diff --git a/apex/mediaswcodec.rc b/apex/mediaswcodec.rc new file mode 100644 index 0000000..d17481b --- /dev/null +++ b/apex/mediaswcodec.rc
@@ -0,0 +1,7 @@ +service media.swcodec /apex/com.android.media.swcodec/bin/mediaswcodec + class main + user mediacodec + group camera drmrpc mediadrm + override + ioprio rt 4 + writepid /dev/cpuset/foreground/tasks
diff --git a/apex/testing/Android.bp b/apex/testing/Android.bp new file mode 100644 index 0000000..701ced7 --- /dev/null +++ b/apex/testing/Android.bp
@@ -0,0 +1,29 @@ +// 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. + +apex { + name: "test_com.android.media", + manifest: "test_manifest.json", + file_contexts: "com.android.media", + defaults: ["com.android.media-defaults"], + installable: false, +} + +apex { + name: "test_com.android.media.swcodec", + manifest: "test_manifest_codec.json", + file_contexts: "com.android.media.swcodec", + defaults: ["com.android.media.swcodec-defaults"], + installable: false, +}
diff --git a/apex/testing/test_manifest.json b/apex/testing/test_manifest.json new file mode 100644 index 0000000..ddd642e --- /dev/null +++ b/apex/testing/test_manifest.json
@@ -0,0 +1,4 @@ +{ + "name": "com.android.media", + "version": 300000000 +}
diff --git a/apex/testing/test_manifest_codec.json b/apex/testing/test_manifest_codec.json new file mode 100644 index 0000000..2320fd7 --- /dev/null +++ b/apex/testing/test_manifest_codec.json
@@ -0,0 +1,4 @@ +{ + "name": "com.android.media.swcodec", + "version": 300000000 +}
diff --git a/camera/Android.bp b/camera/Android.bp index 24b3918..2800595 100644 --- a/camera/Android.bp +++ b/camera/Android.bp
@@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -subdirs = ["ndk"] - cc_library_shared { name: "libcamera_client", @@ -43,6 +41,7 @@ "ICameraRecordingProxyListener.cpp", "camera2/CaptureRequest.cpp", "camera2/OutputConfiguration.cpp", + "camera2/SessionConfiguration.cpp", "camera2/SubmitInfo.cpp", "CameraBase.cpp", "CameraUtils.cpp",
diff --git a/camera/CameraMetadata.cpp b/camera/CameraMetadata.cpp index e143e05..92fe84b 100644 --- a/camera/CameraMetadata.cpp +++ b/camera/CameraMetadata.cpp
@@ -22,7 +22,6 @@ #include <binder/Parcel.h> #include <camera/CameraMetadata.h> -#include <camera/VendorTagDescriptor.h> namespace android { @@ -409,6 +408,79 @@ return res; } +status_t CameraMetadata::removePermissionEntries(metadata_vendor_id_t vendorId, + std::vector<int32_t> *tagsRemoved) { + uint32_t tagCount = 0; + std::vector<uint32_t> tagsToRemove; + + if (tagsRemoved == nullptr) { + return BAD_VALUE; + } + + sp<VendorTagDescriptor> vTags = VendorTagDescriptor::getGlobalVendorTagDescriptor(); + if ((nullptr == vTags.get()) || (0 >= vTags->getTagCount())) { + sp<VendorTagDescriptorCache> cache = + VendorTagDescriptorCache::getGlobalVendorTagCache(); + if (cache.get()) { + cache->getVendorTagDescriptor(vendorId, &vTags); + } + } + + if ((nullptr != vTags.get()) && (vTags->getTagCount() > 0)) { + tagCount = vTags->getTagCount(); + uint32_t *vendorTags = new uint32_t[tagCount]; + if (nullptr == vendorTags) { + return NO_MEMORY; + } + vTags->getTagArray(vendorTags); + + tagsToRemove.reserve(tagCount); + tagsToRemove.insert(tagsToRemove.begin(), vendorTags, vendorTags + tagCount); + + delete [] vendorTags; + tagCount = 0; + } + + auto tagsNeedingPermission = get_camera_metadata_permission_needed(&tagCount); + if (tagCount > 0) { + tagsToRemove.reserve(tagsToRemove.capacity() + tagCount); + tagsToRemove.insert(tagsToRemove.end(), tagsNeedingPermission, + tagsNeedingPermission + tagCount); + } + + tagsRemoved->reserve(tagsToRemove.size()); + for (const auto &it : tagsToRemove) { + if (exists(it)) { + auto rc = erase(it); + if (NO_ERROR != rc) { + ALOGE("%s: Failed to erase tag: %x", __func__, it); + return rc; + } + tagsRemoved->push_back(it); + } + } + + // Update the available characterstics accordingly + if (exists(ANDROID_REQUEST_AVAILABLE_CHARACTERISTICS_KEYS)) { + std::vector<uint32_t> currentKeys; + + std::sort(tagsRemoved->begin(), tagsRemoved->end()); + auto keys = find(ANDROID_REQUEST_AVAILABLE_CHARACTERISTICS_KEYS); + currentKeys.reserve(keys.count); + currentKeys.insert(currentKeys.end(), keys.data.i32, keys.data.i32 + keys.count); + std::sort(currentKeys.begin(), currentKeys.end()); + + std::vector<int32_t> newKeys(keys.count); + auto end = std::set_difference(currentKeys.begin(), currentKeys.end(), tagsRemoved->begin(), + tagsRemoved->end(), newKeys.begin()); + newKeys.resize(end - newKeys.begin()); + + update(ANDROID_REQUEST_AVAILABLE_CHARACTERISTICS_KEYS, newKeys.data(), newKeys.size()); + } + + return NO_ERROR; +} + void CameraMetadata::dump(int fd, int verbosity, int indentation) const { dump_indented_camera_metadata(mBuffer, fd, verbosity, indentation); }
diff --git a/camera/CaptureResult.cpp b/camera/CaptureResult.cpp index 928a6bc..1d8e8c4 100644 --- a/camera/CaptureResult.cpp +++ b/camera/CaptureResult.cpp
@@ -39,6 +39,16 @@ parcel->readInt64(&frameNumber); parcel->readInt32(&partialResultCount); parcel->readInt32(&errorStreamId); + auto physicalCameraIdPresent = parcel->readBool(); + if (physicalCameraIdPresent) { + String16 cameraId; + status_t res = OK; + if ((res = parcel->readString16(&cameraId)) != OK) { + ALOGE("%s: Failed to read camera id: %d", __FUNCTION__, res); + return res; + } + errorPhysicalCameraId = cameraId; + } return OK; } @@ -56,6 +66,16 @@ parcel->writeInt64(frameNumber); parcel->writeInt32(partialResultCount); parcel->writeInt32(errorStreamId); + if (errorPhysicalCameraId.size() > 0) { + parcel->writeBool(true); + status_t res = OK; + if ((res = parcel->writeString16(errorPhysicalCameraId)) != OK) { + ALOGE("%s: Failed to write physical camera ID to parcel: %d", __FUNCTION__, res); + return res; + } + } else { + parcel->writeBool(false); + } return OK; }
diff --git a/camera/OWNERS b/camera/OWNERS index 18acfee..d6b95da 100644 --- a/camera/OWNERS +++ b/camera/OWNERS
@@ -1,6 +1,8 @@ -cychen@google.com epeev@google.com etalvala@google.com +jchowdhary@google.com shuzhenwang@google.com yinchiayeh@google.com +# backup owner +cychen@google.com zhijunhe@google.com
diff --git a/camera/VendorTagDescriptor.cpp b/camera/VendorTagDescriptor.cpp index 4c28789..d713d2d 100644 --- a/camera/VendorTagDescriptor.cpp +++ b/camera/VendorTagDescriptor.cpp
@@ -315,6 +315,10 @@ return OK; } +ssize_t VendorTagDescriptor::getSectionIndex(uint32_t tag) const { + return mTagToSectionMap.valueFor(tag); +} + void VendorTagDescriptor::dump(int fd, int verbosity, int indentation) const { size_t size = mTagToNameMap.size(); @@ -407,6 +411,11 @@ return res; } +const std::unordered_map<metadata_vendor_id_t, sp<android::VendorTagDescriptor>> & + VendorTagDescriptorCache::getVendorIdsAndTagDescriptors() { + return mVendorMap; +} + int VendorTagDescriptorCache::getTagCount(metadata_vendor_id_t id) const { int ret = 0; auto desc = mVendorMap.find(id); @@ -566,7 +575,7 @@ for (size_t i = 0; i < static_cast<size_t>(tagCount); ++i) { uint32_t tag = tagArray[i]; - String8 sectionString = tagToSectionMap.valueFor(tag); + const String8& sectionString = tagToSectionMap.valueFor(tag); // Set up tag to section index map ssize_t index = sections.indexOf(sectionString);
diff --git a/camera/aidl/android/hardware/ICameraService.aidl b/camera/aidl/android/hardware/ICameraService.aidl index 9c0f28b..3e8992a 100644 --- a/camera/aidl/android/hardware/ICameraService.aidl +++ b/camera/aidl/android/hardware/ICameraService.aidl
@@ -108,7 +108,7 @@ * * Also returns the set of currently-known camera IDs and state of each device. * Adding a listener will trigger the torch status listener to fire for all - * devices that have a flash unit + * devices that have a flash unit. */ CameraStatus[] addListener(ICameraServiceListener listener); @@ -149,8 +149,10 @@ const int API_VERSION_1 = 1; const int API_VERSION_2 = 2; - // Determines if a particular API version is supported directly + // Determines if a particular API version is supported directly for a cameraId. boolean supportsCameraApi(String cameraId, int apiVersion); + // Determines if a cameraId is a hidden physical camera of a logical multi-camera. + boolean isHiddenPhysicalCamera(String cameraId); void setTorchMode(String cameraId, boolean enabled, IBinder clientBinder); @@ -160,6 +162,28 @@ * Callers require the android.permission.CAMERA_SEND_SYSTEM_EVENTS permission. */ const int EVENT_NONE = 0; - const int EVENT_USER_SWITCHED = 1; + const int EVENT_USER_SWITCHED = 1; // The argument is the set of new foreground user IDs. oneway void notifySystemEvent(int eventId, in int[] args); + + /** + * Notify the camera service of a device physical status change. May only be called from + * a privileged process. + * + * newState is a bitfield consisting of DEVICE_STATE_* values combined together. Valid state + * combinations are device-specific. At device startup, the camera service will assume the device + * state is NORMAL until otherwise notified. + * + * Callers require the android.permission.CAMERA_SEND_SYSTEM_EVENTS permission. + */ + oneway void notifyDeviceStateChange(long newState); + + // Bitfield constants for notifyDeviceStateChange + // All bits >= 32 are for custom vendor states + // Written as ints since AIDL does not support long constants. + const int DEVICE_STATE_NORMAL = 0; + const int DEVICE_STATE_BACK_COVERED = 1; + const int DEVICE_STATE_FRONT_COVERED = 2; + const int DEVICE_STATE_FOLDED = 4; + const int DEVICE_STATE_LAST_FRAMEWORK_BIT = 0x80000000; // 1 << 31; + }
diff --git a/camera/aidl/android/hardware/ICameraServiceListener.aidl b/camera/aidl/android/hardware/ICameraServiceListener.aidl index f871ce4..e9dcbdb 100644 --- a/camera/aidl/android/hardware/ICameraServiceListener.aidl +++ b/camera/aidl/android/hardware/ICameraServiceListener.aidl
@@ -76,4 +76,11 @@ const int TORCH_STATUS_UNKNOWN = -1; oneway void onTorchStatusChanged(int status, String cameraId); + + /** + * Notify registered clients about camera access priority changes. + * Clients which were previously unable to open a certain camera device + * can retry after receiving this callback. + */ + oneway void onCameraAccessPrioritiesChanged(); }
diff --git a/camera/aidl/android/hardware/camera2/ICameraDeviceUser.aidl b/camera/aidl/android/hardware/camera2/ICameraDeviceUser.aidl index 4ced08c..49dfde8 100644 --- a/camera/aidl/android/hardware/camera2/ICameraDeviceUser.aidl +++ b/camera/aidl/android/hardware/camera2/ICameraDeviceUser.aidl
@@ -19,6 +19,7 @@ import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.impl.CameraMetadataNative; import android.hardware.camera2.params.OutputConfiguration; +import android.hardware.camera2.params.SessionConfiguration; import android.hardware.camera2.utils.SubmitInfo; import android.view.Surface; @@ -83,6 +84,16 @@ */ void endConfigure(int operatingMode, in CameraMetadataNative sessionParams); + /** + * Check whether a particular session configuration has camera device + * support. + * + * @param sessionConfiguration Specific session configuration to be verified. + * @return true - in case the stream combination is supported. + * false - in case there is no device support. + */ + boolean isSessionConfigurationSupported(in SessionConfiguration sessionConfiguration); + void deleteStream(int streamId); /**
diff --git a/camera/aidl/android/hardware/camera2/params/SessionConfiguration.aidl b/camera/aidl/android/hardware/camera2/params/SessionConfiguration.aidl new file mode 100644 index 0000000..abf1556 --- /dev/null +++ b/camera/aidl/android/hardware/camera2/params/SessionConfiguration.aidl
@@ -0,0 +1,20 @@ +/* + * 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. + */ + +package android.hardware.camera2.params; + +/** @hide */ +parcelable SessionConfiguration cpp_header "camera/camera2/SessionConfiguration.h";
diff --git a/camera/camera2/OutputConfiguration.cpp b/camera/camera2/OutputConfiguration.cpp index feb04c2..4e9b27d 100644 --- a/camera/camera2/OutputConfiguration.cpp +++ b/camera/camera2/OutputConfiguration.cpp
@@ -167,14 +167,24 @@ } OutputConfiguration::OutputConfiguration(sp<IGraphicBufferProducer>& gbp, int rotation, + const String16& physicalId, int surfaceSetID, bool isShared) { mGbps.push_back(gbp); mRotation = rotation; mSurfaceSetID = surfaceSetID; mIsDeferred = false; mIsShared = isShared; + mPhysicalCameraId = physicalId; } +OutputConfiguration::OutputConfiguration( + const std::vector<sp<IGraphicBufferProducer>>& gbps, + int rotation, const String16& physicalCameraId, int surfaceSetID, int surfaceType, + int width, int height, bool isShared) + : mGbps(gbps), mRotation(rotation), mSurfaceSetID(surfaceSetID), mSurfaceType(surfaceType), + mWidth(width), mHeight(height), mIsDeferred(false), mIsShared(isShared), + mPhysicalCameraId(physicalCameraId) { } + status_t OutputConfiguration::writeToParcel(android::Parcel* parcel) const { if (parcel == nullptr) return BAD_VALUE;
diff --git a/camera/camera2/SessionConfiguration.cpp b/camera/camera2/SessionConfiguration.cpp new file mode 100644 index 0000000..a431a33 --- /dev/null +++ b/camera/camera2/SessionConfiguration.cpp
@@ -0,0 +1,133 @@ +/* +** +** Copyright 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 "SessionConfiguration" +//#define LOG_NDEBUG 0 + +#include <utils/Log.h> + +#include <camera/camera2/SessionConfiguration.h> +#include <camera/camera2/OutputConfiguration.h> +#include <binder/Parcel.h> + +namespace android { + +status_t SessionConfiguration::readFromParcel(const android::Parcel* parcel) { + status_t err = OK; + int operatingMode = 0; + + if (parcel == nullptr) return BAD_VALUE; + + if ((err = parcel->readInt32(&operatingMode)) != OK) { + ALOGE("%s: Failed to read operating mode from parcel", __FUNCTION__); + return err; + } + + int inputWidth = 0; + if ((err = parcel->readInt32(&inputWidth)) != OK) { + ALOGE("%s: Failed to read input width from parcel", __FUNCTION__); + return err; + } + + int inputHeight = 0; + if ((err = parcel->readInt32(&inputHeight)) != OK) { + ALOGE("%s: Failed to read input height from parcel", __FUNCTION__); + return err; + } + + int inputFormat = -1; + if ((err = parcel->readInt32(&inputFormat)) != OK) { + ALOGE("%s: Failed to read input format from parcel", __FUNCTION__); + return err; + } + + std::vector<OutputConfiguration> outputStreams; + if ((err = parcel->readParcelableVector(&outputStreams)) != OK) { + ALOGE("%s: Failed to read output configurations from parcel", __FUNCTION__); + return err; + } + + mOperatingMode = operatingMode; + mInputWidth = inputWidth; + mInputHeight = inputHeight; + mInputFormat = inputFormat; + for (auto& stream : outputStreams) { + mOutputStreams.push_back(stream); + } + + + return err; +} + +status_t SessionConfiguration::writeToParcel(android::Parcel* parcel) const { + + if (parcel == nullptr) return BAD_VALUE; + status_t err = OK; + + err = parcel->writeInt32(mOperatingMode); + if (err != OK) return err; + + err = parcel->writeInt32(mInputWidth); + if (err != OK) return err; + + err = parcel->writeInt32(mInputHeight); + if (err != OK) return err; + + err = parcel->writeInt32(mInputFormat); + if (err != OK) return err; + + err = parcel->writeParcelableVector(mOutputStreams); + if (err != OK) return err; + + return OK; +} + +bool SessionConfiguration::outputsEqual(const SessionConfiguration& other) const { + const std::vector<OutputConfiguration>& otherOutputStreams = + other.getOutputConfigurations(); + + if (mOutputStreams.size() != otherOutputStreams.size()) { + return false; + } + + for (size_t i = 0; i < mOutputStreams.size(); i++) { + if (mOutputStreams[i] != otherOutputStreams[i]) { + return false; + } + } + + return true; +} + +bool SessionConfiguration::outputsLessThan(const SessionConfiguration& other) const { + const std::vector<OutputConfiguration>& otherOutputStreams = + other.getOutputConfigurations(); + + if (mOutputStreams.size() != otherOutputStreams.size()) { + return mOutputStreams.size() < otherOutputStreams.size(); + } + + for (size_t i = 0; i < mOutputStreams.size(); i++) { + if (mOutputStreams[i] != otherOutputStreams[i]) { + return mOutputStreams[i] < otherOutputStreams[i]; + } + } + + return false; +} + +}; // namespace android
diff --git a/camera/cameraserver/Android.bp b/camera/cameraserver/Android.bp new file mode 100644 index 0000000..ecaba3a --- /dev/null +++ b/camera/cameraserver/Android.bp
@@ -0,0 +1,49 @@ +// Copyright 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_binary { + name: "cameraserver", + + srcs: ["main_cameraserver.cpp"], + + shared_libs: [ + "libcameraservice", + "liblog", + "libutils", + "libui", + "libgui", + "libbinder", + "libhidlbase", + "libhidltransport", + "android.hardware.camera.common@1.0", + "android.hardware.camera.provider@2.4", + "android.hardware.camera.provider@2.5", + "android.hardware.camera.device@1.0", + "android.hardware.camera.device@3.2", + "android.hardware.camera.device@3.4", + ], + compile_multilib: "32", + cflags: [ + "-Wall", + "-Wextra", + "-Werror", + "-Wno-unused-parameter", + ], + + init_rc: ["cameraserver.rc"], + + vintf_fragments: [ + "manifest_android.frameworks.cameraservice.service@2.0.xml", + ], +}
diff --git a/camera/cameraserver/Android.mk b/camera/cameraserver/Android.mk deleted file mode 100644 index b8c94e6..0000000 --- a/camera/cameraserver/Android.mk +++ /dev/null
@@ -1,42 +0,0 @@ -# Copyright 2015 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. - -LOCAL_PATH:= $(call my-dir) - -include $(CLEAR_VARS) - -LOCAL_SRC_FILES:= \ - main_cameraserver.cpp - -LOCAL_SHARED_LIBRARIES := \ - libcameraservice \ - liblog \ - libutils \ - libui \ - libgui \ - libbinder \ - libhidltransport \ - android.hardware.camera.common@1.0 \ - android.hardware.camera.provider@2.4 \ - android.hardware.camera.device@1.0 \ - android.hardware.camera.device@3.2 - -LOCAL_MODULE:= cameraserver -LOCAL_32_BIT_ONLY := true - -LOCAL_CFLAGS += -Wall -Wextra -Werror -Wno-unused-parameter - -LOCAL_INIT_RC := cameraserver.rc - -include $(BUILD_EXECUTABLE)
diff --git a/camera/cameraserver/main_cameraserver.cpp b/camera/cameraserver/main_cameraserver.cpp index 3972436..53b3d84 100644 --- a/camera/cameraserver/main_cameraserver.cpp +++ b/camera/cameraserver/main_cameraserver.cpp
@@ -26,8 +26,9 @@ { signal(SIGPIPE, SIG_IGN); - // Set 3 threads for HIDL calls - hardware::configureRpcThreadpool(3, /*willjoin*/ false); + // Set 5 threads for HIDL calls. Now cameraserver will serve HIDL calls in + // addition to consuming them from the Camera HAL as well. + hardware::configureRpcThreadpool(5, /*willjoin*/ false); sp<ProcessState> proc(ProcessState::self()); sp<IServiceManager> sm = defaultServiceManager();
diff --git a/camera/cameraserver/manifest_android.frameworks.cameraservice.service@2.0.xml b/camera/cameraserver/manifest_android.frameworks.cameraservice.service@2.0.xml new file mode 100644 index 0000000..601c717 --- /dev/null +++ b/camera/cameraserver/manifest_android.frameworks.cameraservice.service@2.0.xml
@@ -0,0 +1,11 @@ +<manifest version="1.0" type="framework"> + <hal> + <name>android.frameworks.cameraservice.service</name> + <transport>hwbinder</transport> + <version>2.0</version> + <interface> + <name>ICameraService</name> + <instance>default</instance> + </interface> + </hal> +</manifest>
diff --git a/camera/include/camera/CameraMetadata.h b/camera/include/camera/CameraMetadata.h index d284477..844bb80 100644 --- a/camera/include/camera/CameraMetadata.h +++ b/camera/include/camera/CameraMetadata.h
@@ -22,6 +22,7 @@ #include <utils/String8.h> #include <utils/Vector.h> #include <binder/Parcelable.h> +#include <camera/VendorTagDescriptor.h> namespace android { @@ -170,6 +171,12 @@ status_t erase(uint32_t tag); /** + * Remove metadata entries that need additional permissions. + */ + status_t removePermissionEntries(metadata_vendor_id_t vendorId, + std::vector<int32_t> *tagsRemoved /*out*/); + + /** * Swap the underlying camera metadata between this and the other * metadata object. */
diff --git a/camera/include/camera/CaptureResult.h b/camera/include/camera/CaptureResult.h index 56fa178..ef830b5 100644 --- a/camera/include/camera/CaptureResult.h +++ b/camera/include/camera/CaptureResult.h
@@ -70,6 +70,13 @@ int32_t errorStreamId; /** + * For capture result errors, the physical camera ID in case the respective request contains + * a reference to physical camera device. + * Empty otherwise. + */ + String16 errorPhysicalCameraId; + + /** * Constructor initializes object as invalid by setting requestId to be -1. */ CaptureResultExtras() @@ -79,7 +86,8 @@ precaptureTriggerId(0), frameNumber(0), partialResultCount(0), - errorStreamId(-1) { + errorStreamId(-1), + errorPhysicalCameraId() { } /**
diff --git a/camera/include/camera/VendorTagDescriptor.h b/camera/include/camera/VendorTagDescriptor.h index 904fba2..6f55890 100644 --- a/camera/include/camera/VendorTagDescriptor.h +++ b/camera/include/camera/VendorTagDescriptor.h
@@ -99,6 +99,11 @@ void dump(int fd, int verbosity, int indentation) const; /** + * Get Section for corresponding tag. + */ + ssize_t getSectionIndex(uint32_t tag) const; + + /** * Read values VendorTagDescriptor object from the given parcel. * * Returns OK on success, or a negative error code. @@ -206,6 +211,9 @@ */ void dump(int fd, int verbosity, int indentation) const; + const std::unordered_map<metadata_vendor_id_t, sp<android::VendorTagDescriptor>> & + getVendorIdsAndTagDescriptors(); + protected: std::unordered_map<metadata_vendor_id_t, sp<android::VendorTagDescriptor>> mVendorMap; struct vendor_tag_cache_ops mVendorCacheOps;
diff --git a/camera/include/camera/camera2/OutputConfiguration.h b/camera/include/camera/camera2/OutputConfiguration.h index a80f44b..95c4f39 100644 --- a/camera/include/camera/camera2/OutputConfiguration.h +++ b/camera/include/camera/camera2/OutputConfiguration.h
@@ -65,8 +65,15 @@ OutputConfiguration(const android::Parcel& parcel); OutputConfiguration(sp<IGraphicBufferProducer>& gbp, int rotation, + const String16& physicalCameraId, int surfaceSetID = INVALID_SET_ID, bool isShared = false); + OutputConfiguration(const std::vector<sp<IGraphicBufferProducer>>& gbps, + int rotation, const String16& physicalCameraId, + int surfaceSetID = INVALID_SET_ID, + int surfaceType = OutputConfiguration::SURFACE_TYPE_UNKNOWN, int width = 0, + int height = 0, bool isShared = false); + bool operator == (const OutputConfiguration& other) const { return ( mRotation == other.mRotation && mSurfaceSetID == other.mSurfaceSetID &&
diff --git a/camera/include/camera/camera2/SessionConfiguration.h b/camera/include/camera/camera2/SessionConfiguration.h new file mode 100644 index 0000000..64288ed --- /dev/null +++ b/camera/include/camera/camera2/SessionConfiguration.h
@@ -0,0 +1,116 @@ +/* + * 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_CAMERA2_SESSIONCONFIGURATION_H +#define ANDROID_HARDWARE_CAMERA2_SESSIONCONFIGURATION_H + +#include <binder/Parcelable.h> + +namespace android { + +namespace hardware { +namespace camera2 { +namespace params { + +class OutputConfiguration; + +class SessionConfiguration : public android::Parcelable { +public: + + const std::vector<OutputConfiguration>& getOutputConfigurations() const { + return mOutputStreams; + } + + int getInputWidth() const { return mInputWidth; } + int getInputHeight() const { return mInputHeight; } + int getInputFormat() const { return mInputFormat; } + int getOperatingMode() const { return mOperatingMode; } + + virtual status_t writeToParcel(android::Parcel* parcel) const override; + virtual status_t readFromParcel(const android::Parcel* parcel) override; + + SessionConfiguration() : + mInputWidth(0), + mInputHeight(0), + mInputFormat(-1), + mOperatingMode(-1) {} + + SessionConfiguration(const android::Parcel& parcel) { + readFromParcel(&parcel); + } + + SessionConfiguration(int inputWidth, int inputHeight, int inputFormat, int operatingMode) : + mInputWidth(inputWidth), mInputHeight(inputHeight), mInputFormat(inputFormat), + mOperatingMode(operatingMode) {} + + bool operator == (const SessionConfiguration& other) const { + return (outputsEqual(other) && + mInputWidth == other.mInputWidth && + mInputHeight == other.mInputHeight && + mInputFormat == other.mInputFormat && + mOperatingMode == other.mOperatingMode); + } + + bool operator != (const SessionConfiguration& other) const { + return !(*this == other); + } + + bool operator < (const SessionConfiguration& other) const { + if (*this == other) return false; + + if (mInputWidth != other.mInputWidth) { + return mInputWidth < other.mInputWidth; + } + + if (mInputHeight != other.mInputHeight) { + return mInputHeight < other.mInputHeight; + } + + if (mInputFormat != other.mInputFormat) { + return mInputFormat < other.mInputFormat; + } + + if (mOperatingMode != other.mOperatingMode) { + return mOperatingMode < other.mOperatingMode; + } + + return outputsLessThan(other); + } + + bool operator > (const SessionConfiguration& other) const { + return (*this != other && !(*this < other)); + } + + bool outputsEqual(const SessionConfiguration& other) const; + bool outputsLessThan(const SessionConfiguration& other) const; + void addOutputConfiguration(const OutputConfiguration &config) { + mOutputStreams.push_back(config); + } + +private: + + std::vector<OutputConfiguration> mOutputStreams; + int mInputWidth, mInputHeight, mInputFormat, mOperatingMode; +}; +} // namespace params +} // namespace camera2 +} // namespace hardware + +using hardware::camera2::params::SessionConfiguration; + +}; // namespace android + +#endif
diff --git a/camera/ndk/Android.bp b/camera/ndk/Android.bp index 97cf6bf..a2ee65d 100644 --- a/camera/ndk/Android.bp +++ b/camera/ndk/Android.bp
@@ -30,3 +30,133 @@ srcs: ["include/camera/**/*.h"], license: "NOTICE", } + +cc_library_shared { + name: "libcamera2ndk", + srcs: [ + "NdkCameraManager.cpp", + "NdkCameraMetadata.cpp", + "NdkCameraDevice.cpp", + "NdkCaptureRequest.cpp", + "NdkCameraCaptureSession.cpp", + "impl/ACameraManager.cpp", + "impl/ACameraMetadata.cpp", + "impl/ACameraDevice.cpp", + "impl/ACameraCaptureSession.cpp", + ], + shared_libs: [ + "libbinder", + "liblog", + "libgui", + "libutils", + "libandroid_runtime", + "libcamera_client", + "libstagefright_foundation", + "libcutils", + "libcamera_metadata", + "libmediandk", + "libnativewindow", + ], + cflags: [ + "-fvisibility=hidden", + "-DEXPORT=__attribute__ ((visibility (\"default\")))", + "-Wall", + "-Wextra", + "-Werror", + ], + // TODO: jchowdhary@, use header_libs instead b/131165718 + include_dirs: [ + "system/media/private/camera/include", + ], + export_include_dirs: ["include"], + export_shared_lib_headers: [ + "libnativewindow", + ], + version_script: "libcamera2ndk.map.txt", +} + +cc_library_shared { + name: "libcamera2ndk_vendor", + vendor: true, + srcs: [ + "ndk_vendor/impl/ACameraDevice.cpp", + "ndk_vendor/impl/ACameraManager.cpp", + "ndk_vendor/impl/utils.cpp", + "impl/ACameraMetadata.cpp", + "impl/ACameraCaptureSession.cpp", + "NdkCameraMetadata.cpp", + "NdkCameraCaptureSession.cpp", + "NdkCameraManager.cpp", + "NdkCameraDevice.cpp", + "NdkCaptureRequest.cpp", + ], + + export_include_dirs: ["include"], + export_shared_lib_headers: [ + "libcutils", + ], + local_include_dirs: [ + ".", + "include", + "impl", + ], + cflags: [ + "-fvisibility=hidden", + "-DEXPORT=__attribute__((visibility(\"default\")))", + "-D__ANDROID_VNDK__", + ], + + shared_libs: [ + "libhwbinder", + "libfmq", + "libhidlbase", + "libhardware", + "libnativewindow", + "liblog", + "libutils", + "libstagefright_foundation", + "libcutils", + "libcamera_metadata", + "libmediandk", + "android.frameworks.cameraservice.device@2.0", + "android.frameworks.cameraservice.common@2.0", + "android.frameworks.cameraservice.service@2.0", + ], + + static_libs: [ + "android.hardware.camera.common@1.0-helper", + "libarect", + ], + // TODO: jchowdhary@, use header_libs instead b/131165718 + include_dirs: [ + "system/media/private/camera/include", + ], + product_variables: { + pdk: { + enabled: false, + }, + }, +} + +cc_test { + name: "AImageReaderVendorTest", + vendor: true, + srcs: ["ndk_vendor/tests/AImageReaderVendorTest.cpp"], + shared_libs: [ + "libhwbinder", + "libcamera2ndk_vendor", + "libcamera_metadata", + "libmediandk", + "libnativewindow", + "libutils", + "libui", + "libcutils", + "liblog", + ], + static_libs: [ + "android.hardware.camera.common@1.0-helper", + ], + cflags: [ + "-D__ANDROID_VNDK__", + ], +}
diff --git a/camera/ndk/Android.mk b/camera/ndk/Android.mk deleted file mode 100644 index f5ff69d..0000000 --- a/camera/ndk/Android.mk +++ /dev/null
@@ -1,56 +0,0 @@ -# -# Copyright (C) 2015 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. -# - -LOCAL_PATH:= $(call my-dir) - -ifneq ($(TARGET_BUILD_PDK), true) - -include $(CLEAR_VARS) - -LOCAL_SRC_FILES:= \ - NdkCameraManager.cpp \ - NdkCameraMetadata.cpp \ - NdkCameraDevice.cpp \ - NdkCaptureRequest.cpp \ - NdkCameraCaptureSession.cpp \ - impl/ACameraManager.cpp \ - impl/ACameraMetadata.cpp \ - impl/ACameraDevice.cpp \ - impl/ACameraCaptureSession.cpp - -LOCAL_MODULE:= libcamera2ndk - -LOCAL_C_INCLUDES := $(LOCAL_PATH)/include -LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include - -LOCAL_CFLAGS += -fvisibility=hidden -D EXPORT='__attribute__ ((visibility ("default")))' -LOCAL_CFLAGS += -Wall -Wextra -Werror - -LOCAL_SHARED_LIBRARIES := \ - libbinder \ - liblog \ - libgui \ - libutils \ - libandroid_runtime \ - libcamera_client \ - libstagefright_foundation \ - libcutils \ - libcamera_metadata \ - libmediandk - -include $(BUILD_SHARED_LIBRARY) - -endif
diff --git a/camera/ndk/NdkCameraCaptureSession.cpp b/camera/ndk/NdkCameraCaptureSession.cpp index fd95296..1ac8482 100644 --- a/camera/ndk/NdkCameraCaptureSession.cpp +++ b/camera/ndk/NdkCameraCaptureSession.cpp
@@ -28,6 +28,8 @@ #include <camera/NdkCameraCaptureSession.h> #include "impl/ACameraCaptureSession.h" +#include "impl/ACameraCaptureSession.inc" + using namespace android; EXPORT @@ -78,11 +80,39 @@ if (session->isClosed()) { ALOGE("%s: session %p is already closed", __FUNCTION__, session); - *captureSequenceId = CAPTURE_SEQUENCE_ID_NONE; + if (captureSequenceId != nullptr) { + *captureSequenceId = CAPTURE_SEQUENCE_ID_NONE; + } return ACAMERA_ERROR_SESSION_CLOSED; } - return session->capture(cbs, numRequests, requests, captureSequenceId); + return session->capture( + cbs, numRequests, requests, captureSequenceId); +} + +EXPORT +camera_status_t ACameraCaptureSession_logicalCamera_capture( + ACameraCaptureSession* session, + /*optional*/ACameraCaptureSession_logicalCamera_captureCallbacks* lcbs, + int numRequests, ACaptureRequest** requests, + /*optional*/int* captureSequenceId) { + ATRACE_CALL(); + if (session == nullptr || requests == nullptr || numRequests < 1) { + ALOGE("%s: Error: invalid input: session %p, numRequest %d, requests %p", + __FUNCTION__, session, numRequests, requests); + return ACAMERA_ERROR_INVALID_PARAMETER; + } + + if (session->isClosed()) { + ALOGE("%s: session %p is already closed", __FUNCTION__, session); + if (captureSequenceId) { + *captureSequenceId = CAPTURE_SEQUENCE_ID_NONE; + } + return ACAMERA_ERROR_SESSION_CLOSED; + } + + return session->capture( + lcbs, numRequests, requests, captureSequenceId); } EXPORT @@ -99,7 +129,9 @@ if (session->isClosed()) { ALOGE("%s: session %p is already closed", __FUNCTION__, session); - *captureSequenceId = CAPTURE_SEQUENCE_ID_NONE; + if (captureSequenceId) { + *captureSequenceId = CAPTURE_SEQUENCE_ID_NONE; + } return ACAMERA_ERROR_SESSION_CLOSED; } @@ -107,6 +139,30 @@ } EXPORT +camera_status_t ACameraCaptureSession_logicalCamera_setRepeatingRequest( + ACameraCaptureSession* session, + /*optional*/ACameraCaptureSession_logicalCamera_captureCallbacks* lcbs, + int numRequests, ACaptureRequest** requests, + /*optional*/int* captureSequenceId) { + ATRACE_CALL(); + if (session == nullptr || requests == nullptr || numRequests < 1) { + ALOGE("%s: Error: invalid input: session %p, numRequest %d, requests %p", + __FUNCTION__, session, numRequests, requests); + return ACAMERA_ERROR_INVALID_PARAMETER; + } + + if (session->isClosed()) { + ALOGE("%s: session %p is already closed", __FUNCTION__, session); + if (captureSequenceId) { + *captureSequenceId = CAPTURE_SEQUENCE_ID_NONE; + } + return ACAMERA_ERROR_SESSION_CLOSED; + } + + return session->setRepeatingRequest(lcbs, numRequests, requests, captureSequenceId); +} + +EXPORT camera_status_t ACameraCaptureSession_stopRepeating(ACameraCaptureSession* session) { ATRACE_CALL(); if (session == nullptr) {
diff --git a/camera/ndk/NdkCameraDevice.cpp b/camera/ndk/NdkCameraDevice.cpp index 812a312..691996b 100644 --- a/camera/ndk/NdkCameraDevice.cpp +++ b/camera/ndk/NdkCameraDevice.cpp
@@ -24,7 +24,15 @@ #include <camera/NdkCameraDevice.h> #include "impl/ACameraCaptureSession.h" -using namespace android; +using namespace android::acam; + +bool areWindowTypesEqual(ACameraWindowType *a, ACameraWindowType *b) { +#ifdef __ANDROID_VNDK__ + return utils::isWindowNativeHandleEqual(a, b); +#else + return a == b; +#endif +} EXPORT camera_status_t ACameraDevice_close(ACameraDevice* device) { @@ -70,7 +78,34 @@ ALOGE("%s: unknown template ID %d", __FUNCTION__, templateId); return ACAMERA_ERROR_INVALID_PARAMETER; } - return device->createCaptureRequest(templateId, request); + return device->createCaptureRequest(templateId, nullptr /*physicalIdList*/, request); +} + +EXPORT +camera_status_t ACameraDevice_createCaptureRequest_withPhysicalIds( + const ACameraDevice* device, + ACameraDevice_request_template templateId, + const ACameraIdList* physicalCameraIdList, + ACaptureRequest** request) { + ATRACE_CALL(); + if (device == nullptr || request == nullptr || physicalCameraIdList == nullptr) { + ALOGE("%s: invalid argument! device %p request %p, physicalCameraIdList %p", + __FUNCTION__, device, request, physicalCameraIdList); + return ACAMERA_ERROR_INVALID_PARAMETER; + } + switch (templateId) { + case TEMPLATE_PREVIEW: + case TEMPLATE_STILL_CAPTURE: + case TEMPLATE_RECORD: + case TEMPLATE_VIDEO_SNAPSHOT: + case TEMPLATE_ZERO_SHUTTER_LAG: + case TEMPLATE_MANUAL: + break; + default: + ALOGE("%s: unknown template ID %d", __FUNCTION__, templateId); + return ACAMERA_ERROR_INVALID_PARAMETER; + } + return device->createCaptureRequest(templateId, physicalCameraIdList, request); } EXPORT @@ -96,7 +131,7 @@ EXPORT camera_status_t ACaptureSessionOutput_create( - ANativeWindow* window, /*out*/ACaptureSessionOutput** out) { + ACameraWindowType* window, /*out*/ACaptureSessionOutput** out) { ATRACE_CALL(); if (window == nullptr || out == nullptr) { ALOGE("%s: Error: bad argument. window %p, out %p", @@ -109,7 +144,7 @@ EXPORT camera_status_t ACaptureSessionSharedOutput_create( - ANativeWindow* window, /*out*/ACaptureSessionOutput** out) { + ACameraWindowType* window, /*out*/ACaptureSessionOutput** out) { ATRACE_CALL(); if (window == nullptr || out == nullptr) { ALOGE("%s: Error: bad argument. window %p, out %p", @@ -121,8 +156,22 @@ } EXPORT +camera_status_t ACaptureSessionPhysicalOutput_create( + ACameraWindowType* window, const char* physicalId, + /*out*/ACaptureSessionOutput** out) { + ATRACE_CALL(); + if (window == nullptr || physicalId == nullptr || out == nullptr) { + ALOGE("%s: Error: bad argument. window %p, physicalId %p, out %p", + __FUNCTION__, window, physicalId, out); + return ACAMERA_ERROR_INVALID_PARAMETER; + } + *out = new ACaptureSessionOutput(window, false, physicalId); + return ACAMERA_OK; +} + +EXPORT camera_status_t ACaptureSessionSharedOutput_add(ACaptureSessionOutput *out, - ANativeWindow* window) { + ACameraWindowType* window) { ATRACE_CALL(); if ((window == nullptr) || (out == nullptr)) { ALOGE("%s: Error: bad argument. window %p, out %p", @@ -134,7 +183,7 @@ __FUNCTION__); return ACAMERA_ERROR_INVALID_OPERATION; } - if (out->mWindow == window) { + if (areWindowTypesEqual(out->mWindow, window)) { ALOGE("%s: Error trying to add the same window associated with the output configuration", __FUNCTION__); return ACAMERA_ERROR_INVALID_PARAMETER; @@ -147,7 +196,7 @@ EXPORT camera_status_t ACaptureSessionSharedOutput_remove(ACaptureSessionOutput *out, - ANativeWindow* window) { + ACameraWindowType* window) { ATRACE_CALL(); if ((window == nullptr) || (out == nullptr)) { ALOGE("%s: Error: bad argument. window %p, out %p", @@ -159,7 +208,7 @@ __FUNCTION__); return ACAMERA_ERROR_INVALID_OPERATION; } - if (out->mWindow == window) { + if (areWindowTypesEqual(out->mWindow, window)) { ALOGE("%s: Error trying to remove the same window associated with the output configuration", __FUNCTION__); return ACAMERA_ERROR_INVALID_PARAMETER; @@ -238,3 +287,16 @@ } return device->createCaptureSession(outputs, sessionParameters, callbacks, session); } + +EXPORT +camera_status_t ACameraDevice_isSessionConfigurationSupported( + const ACameraDevice* device, + const ACaptureSessionOutputContainer* sessionOutputContainer) { + ATRACE_CALL(); + if (device == nullptr || sessionOutputContainer == nullptr) { + ALOGE("%s: Error: invalid input: device %p, sessionOutputContainer %p", + __FUNCTION__, device, sessionOutputContainer); + return ACAMERA_ERROR_INVALID_PARAMETER; + } + return device->isSessionConfigurationSupported(sessionOutputContainer); +}
diff --git a/camera/ndk/NdkCameraManager.cpp b/camera/ndk/NdkCameraManager.cpp index 60b4763..3d231a8 100644 --- a/camera/ndk/NdkCameraManager.cpp +++ b/camera/ndk/NdkCameraManager.cpp
@@ -22,9 +22,15 @@ #include <utils/Trace.h> #include <camera/NdkCameraManager.h> -#include "impl/ACameraManager.h" -using namespace android; +#ifdef __ANDROID_VNDK__ +#include "ndk_vendor/impl/ACameraManager.h" +#else +#include "impl/ACameraManager.h" +#endif +#include "impl/ACameraMetadata.h" + +using namespace android::acam; EXPORT ACameraManager* ACameraManager_create() { @@ -99,6 +105,60 @@ } EXPORT +camera_status_t ACameraManager_registerExtendedAvailabilityCallback( + ACameraManager* /*manager*/, const ACameraManager_ExtendedAvailabilityCallbacks *callback) { + ATRACE_CALL(); + if (callback == nullptr) { + ALOGE("%s: invalid argument! callback is null!", __FUNCTION__); + return ACAMERA_ERROR_INVALID_PARAMETER; + } + if ((callback->availabilityCallbacks.onCameraAvailable == nullptr) || + (callback->availabilityCallbacks.onCameraUnavailable == nullptr) || + (callback->onCameraAccessPrioritiesChanged == nullptr)) { + ALOGE("%s: invalid argument! callback %p, " + "onCameraAvailable %p, onCameraUnavailable %p onCameraAccessPrioritiesChanged %p", + __FUNCTION__, callback, + callback->availabilityCallbacks.onCameraAvailable, + callback->availabilityCallbacks.onCameraUnavailable, + callback->onCameraAccessPrioritiesChanged); + return ACAMERA_ERROR_INVALID_PARAMETER; + } + auto reservedEntriesCount = sizeof(callback->reserved) / sizeof(callback->reserved[0]); + for (size_t i = 0; i < reservedEntriesCount; i++) { + if (callback->reserved[i] != nullptr) { + ALOGE("%s: invalid argument! callback reserved entries must be set to NULL", + __FUNCTION__); + return ACAMERA_ERROR_INVALID_PARAMETER; + } + } + CameraManagerGlobal::getInstance().registerExtendedAvailabilityCallback(callback); + return ACAMERA_OK; +} + +EXPORT +camera_status_t ACameraManager_unregisterExtendedAvailabilityCallback( + ACameraManager* /*manager*/, const ACameraManager_ExtendedAvailabilityCallbacks *callback) { + ATRACE_CALL(); + if (callback == nullptr) { + ALOGE("%s: invalid argument! callback is null!", __FUNCTION__); + return ACAMERA_ERROR_INVALID_PARAMETER; + } + if ((callback->availabilityCallbacks.onCameraAvailable == nullptr) || + (callback->availabilityCallbacks.onCameraUnavailable == nullptr) || + (callback->onCameraAccessPrioritiesChanged == nullptr)) { + ALOGE("%s: invalid argument! callback %p, " + "onCameraAvailable %p, onCameraUnavailable %p onCameraAccessPrioritiesChanged %p", + __FUNCTION__, callback, + callback->availabilityCallbacks.onCameraAvailable, + callback->availabilityCallbacks.onCameraUnavailable, + callback->onCameraAccessPrioritiesChanged); + return ACAMERA_ERROR_INVALID_PARAMETER; + } + CameraManagerGlobal::getInstance().unregisterExtendedAvailabilityCallback(callback); + return ACAMERA_OK; +} + +EXPORT camera_status_t ACameraManager_getCameraCharacteristics( ACameraManager* mgr, const char* cameraId, ACameraMetadata** chars){ ATRACE_CALL(); @@ -107,7 +167,14 @@ __FUNCTION__, mgr, cameraId, chars); return ACAMERA_ERROR_INVALID_PARAMETER; } - return mgr->getCameraCharacteristics(cameraId, chars); + sp<ACameraMetadata> spChars; + camera_status_t status = mgr->getCameraCharacteristics(cameraId, &spChars); + if (status != ACAMERA_OK) { + return status; + } + spChars->incStrong((void*) ACameraManager_getCameraCharacteristics); + *chars = spChars.get(); + return ACAMERA_OK; } EXPORT @@ -123,3 +190,17 @@ } return mgr->openCamera(cameraId, callback, device); } + +#ifdef __ANDROID_VNDK__ +EXPORT +camera_status_t ACameraManager_getTagFromName(ACameraManager *mgr, const char* cameraId, + const char *name, /*out*/uint32_t *tag) { + ATRACE_CALL(); + if (mgr == nullptr || cameraId == nullptr || name == nullptr) { + ALOGE("%s: invalid argument! mgr %p cameraId %p name %p", + __FUNCTION__, mgr, cameraId, name); + return ACAMERA_ERROR_INVALID_PARAMETER; + } + return mgr->getTagFromName(cameraId, name, tag); +} +#endif
diff --git a/camera/ndk/NdkCameraMetadata.cpp b/camera/ndk/NdkCameraMetadata.cpp index 65de81f..9a39ed8 100644 --- a/camera/ndk/NdkCameraMetadata.cpp +++ b/camera/ndk/NdkCameraMetadata.cpp
@@ -57,13 +57,32 @@ ALOGE("%s: src is null!", __FUNCTION__); return nullptr; } - return new ACameraMetadata(*src); + ACameraMetadata* copy = new ACameraMetadata(*src); + copy->incStrong((void*) ACameraMetadata_copy); + return copy; } EXPORT void ACameraMetadata_free(ACameraMetadata* metadata) { ATRACE_CALL(); if (metadata != nullptr) { - delete metadata; + metadata->decStrong((void*) ACameraMetadata_free); } } + +EXPORT +bool ACameraMetadata_isLogicalMultiCamera(const ACameraMetadata* staticMetadata, + /*out*/size_t* numPhysicalCameras, /*out*/const char*const** physicalCameraIds) { + ATRACE_CALL(); + if (numPhysicalCameras == nullptr || physicalCameraIds == nullptr) { + ALOGE("%s: Invalid input: numPhysicalCameras %p, physicalCameraIds %p", + __FUNCTION__, numPhysicalCameras, physicalCameraIds); + return false; + } + if (staticMetadata == nullptr) { + ALOGE("%s: Invalid input: staticMetadata is null.", __FUNCTION__); + return false; + } + + return staticMetadata->isLogicalMultiCamera(numPhysicalCameras, physicalCameraIds); +}
diff --git a/camera/ndk/NdkCaptureRequest.cpp b/camera/ndk/NdkCaptureRequest.cpp index ac1856b..87de4a9 100644 --- a/camera/ndk/NdkCaptureRequest.cpp +++ b/camera/ndk/NdkCaptureRequest.cpp
@@ -27,7 +27,7 @@ EXPORT camera_status_t ACameraOutputTarget_create( - ANativeWindow* window, ACameraOutputTarget** out) { + ACameraWindowType* window, ACameraOutputTarget** out) { ATRACE_CALL(); if (window == nullptr) { ALOGE("%s: Error: input window is null", __FUNCTION__); @@ -98,6 +98,27 @@ } EXPORT +camera_status_t ACaptureRequest_getConstEntry_physicalCamera( + const ACaptureRequest* req, const char* physicalId, + uint32_t tag, ACameraMetadata_const_entry* entry) { + ATRACE_CALL(); + if (req == nullptr || entry == nullptr || physicalId == nullptr) { + ALOGE("%s: invalid argument! req %p, tag 0x%x, entry %p, physicalId %p", + __FUNCTION__, req, tag, entry, physicalId); + return ACAMERA_ERROR_INVALID_PARAMETER; + } + + const auto& physicalSettings = req->physicalSettings.find(physicalId); + if (physicalSettings == req->physicalSettings.end()) { + ALOGE("%s: Failed to find metadata for physical camera id %s", + __FUNCTION__, physicalId); + return ACAMERA_ERROR_INVALID_PARAMETER; + } + + return physicalSettings->second->getConstEntry(tag, entry); +} + +EXPORT camera_status_t ACaptureRequest_getAllTags( const ACaptureRequest* req, /*out*/int32_t* numTags, /*out*/const uint32_t** tags) { ATRACE_CALL(); @@ -131,13 +152,42 @@ #undef SET_ENTRY +#define SET_PHYSICAL_ENTRY(NAME,NDK_TYPE) \ +EXPORT \ +camera_status_t ACaptureRequest_setEntry_physicalCamera_##NAME( \ + ACaptureRequest* req, const char* physicalId, uint32_t tag, \ + uint32_t count, const NDK_TYPE* data) { \ + ATRACE_CALL(); \ + if (req == nullptr || (count > 0 && data == nullptr) || physicalId == nullptr) { \ + ALOGE("%s: invalid argument! req %p, tag 0x%x, count %d, data 0x%p, physicalId %p", \ + __FUNCTION__, req, tag, count, data, physicalId); \ + return ACAMERA_ERROR_INVALID_PARAMETER; \ + } \ + if (req->physicalSettings.find(physicalId) == req->physicalSettings.end()) { \ + ALOGE("%s: Failed to find metadata for physical camera id %s", \ + __FUNCTION__, physicalId); \ + return ACAMERA_ERROR_INVALID_PARAMETER; \ + } \ + return req->physicalSettings[physicalId]->update(tag, count, data); \ +} + +SET_PHYSICAL_ENTRY(u8,uint8_t) +SET_PHYSICAL_ENTRY(i32,int32_t) +SET_PHYSICAL_ENTRY(float,float) +SET_PHYSICAL_ENTRY(double,double) +SET_PHYSICAL_ENTRY(i64,int64_t) +SET_PHYSICAL_ENTRY(rational,ACameraMetadata_rational) + +#undef SET_PHYSICAL_ENTRY + EXPORT void ACaptureRequest_free(ACaptureRequest* request) { ATRACE_CALL(); if (request == nullptr) { return; } - delete request->settings; + request->settings.clear(); + request->physicalSettings.clear(); delete request->targets; delete request; return; @@ -174,6 +224,9 @@ ACaptureRequest* pRequest = new ACaptureRequest(); pRequest->settings = new ACameraMetadata(*(src->settings)); + for (const auto& entry : src->physicalSettings) { + pRequest->physicalSettings[entry.first] = new ACameraMetadata(*(entry.second)); + } pRequest->targets = new ACameraOutputTargets(); *(pRequest->targets) = *(src->targets); pRequest->context = src->context;
diff --git a/camera/ndk/impl/ACameraCaptureSession.cpp b/camera/ndk/impl/ACameraCaptureSession.cpp index f60e5fd..d6f1412 100644 --- a/camera/ndk/impl/ACameraCaptureSession.cpp +++ b/camera/ndk/impl/ACameraCaptureSession.cpp
@@ -23,7 +23,7 @@ ACameraCaptureSession::~ACameraCaptureSession() { ALOGV("~ACameraCaptureSession: %p notify device end of life", this); - sp<CameraDevice> dev = getDeviceSp(); + sp<acam::CameraDevice> dev = getDeviceSp(); if (dev != nullptr && !dev->isClosed()) { dev->lockDeviceForSessionOps(); { @@ -48,7 +48,7 @@ mClosedByApp = true; } - sp<CameraDevice> dev = getDeviceSp(); + sp<acam::CameraDevice> dev = getDeviceSp(); if (dev != nullptr) { dev->lockDeviceForSessionOps(); } @@ -73,7 +73,7 @@ camera_status_t ACameraCaptureSession::stopRepeating() { - sp<CameraDevice> dev = getDeviceSp(); + sp<acam::CameraDevice> dev = getDeviceSp(); if (dev == nullptr) { ALOGE("Error: Device associated with session %p has been closed!", this); return ACAMERA_ERROR_SESSION_CLOSED; @@ -91,7 +91,7 @@ camera_status_t ACameraCaptureSession::abortCaptures() { - sp<CameraDevice> dev = getDeviceSp(); + sp<acam::CameraDevice> dev = getDeviceSp(); if (dev == nullptr) { ALOGE("Error: Device associated with session %p has been closed!", this); return ACAMERA_ERROR_SESSION_CLOSED; @@ -107,49 +107,8 @@ return ret; } -camera_status_t -ACameraCaptureSession::setRepeatingRequest( - /*optional*/ACameraCaptureSession_captureCallbacks* cbs, - int numRequests, ACaptureRequest** requests, - /*optional*/int* captureSequenceId) { - sp<CameraDevice> dev = getDeviceSp(); - if (dev == nullptr) { - ALOGE("Error: Device associated with session %p has been closed!", this); - return ACAMERA_ERROR_SESSION_CLOSED; - } - - camera_status_t ret; - dev->lockDeviceForSessionOps(); - { - Mutex::Autolock _l(mSessionLock); - ret = dev->setRepeatingRequestsLocked( - this, cbs, numRequests, requests, captureSequenceId); - } - dev->unlockDevice(); - return ret; -} - -camera_status_t ACameraCaptureSession::capture( - /*optional*/ACameraCaptureSession_captureCallbacks* cbs, - int numRequests, ACaptureRequest** requests, - /*optional*/int* captureSequenceId) { - sp<CameraDevice> dev = getDeviceSp(); - if (dev == nullptr) { - ALOGE("Error: Device associated with session %p has been closed!", this); - return ACAMERA_ERROR_SESSION_CLOSED; - } - camera_status_t ret; - dev->lockDeviceForSessionOps(); - { - Mutex::Autolock _l(mSessionLock); - ret = dev->captureLocked(this, cbs, numRequests, requests, captureSequenceId); - } - dev->unlockDevice(); - return ret; -} - camera_status_t ACameraCaptureSession::updateOutputConfiguration(ACaptureSessionOutput *output) { - sp<CameraDevice> dev = getDeviceSp(); + sp<acam::CameraDevice> dev = getDeviceSp(); if (dev == nullptr) { ALOGE("Error: Device associated with session %p has been closed!", this); return ACAMERA_ERROR_SESSION_CLOSED; @@ -168,7 +127,7 @@ ACameraDevice* ACameraCaptureSession::getDevice() { Mutex::Autolock _l(mSessionLock); - sp<CameraDevice> dev = getDeviceSp(); + sp<acam::CameraDevice> dev = getDeviceSp(); if (dev == nullptr) { ALOGE("Error: Device associated with session %p has been closed!", this); return nullptr; @@ -182,9 +141,9 @@ mIsClosed = true; } -sp<CameraDevice> +sp<acam::CameraDevice> ACameraCaptureSession::getDeviceSp() { - sp<CameraDevice> device = mDevice.promote(); + sp<acam::CameraDevice> device = mDevice.promote(); if (device == nullptr || device->isClosed()) { ALOGW("Device is closed but session %d is not notified", mId); return nullptr;
diff --git a/camera/ndk/impl/ACameraCaptureSession.h b/camera/ndk/impl/ACameraCaptureSession.h index a2068e7..08a9226 100644 --- a/camera/ndk/impl/ACameraCaptureSession.h +++ b/camera/ndk/impl/ACameraCaptureSession.h
@@ -17,15 +17,22 @@ #define _ACAMERA_CAPTURE_SESSION_H #include <set> +#include <string> #include <hardware/camera3.h> #include <camera/NdkCameraDevice.h> + +#ifdef __ANDROID_VNDK__ +#include "ndk_vendor/impl/ACameraDevice.h" +#include "ndk_vendor/impl/ACameraCaptureSessionVendor.h" +#else #include "ACameraDevice.h" using namespace android; struct ACaptureSessionOutput { - explicit ACaptureSessionOutput(ANativeWindow* window, bool isShared = false) : - mWindow(window), mIsShared(isShared) {}; + explicit ACaptureSessionOutput(ACameraWindowType* window, bool isShared = false, + const char* physicalCameraId = "") : + mWindow(window), mIsShared(isShared), mPhysicalCameraId(physicalCameraId) {}; bool operator == (const ACaptureSessionOutput& other) const { return mWindow == other.mWindow; @@ -40,11 +47,13 @@ return mWindow > other.mWindow; } - ANativeWindow* mWindow; - std::set<ANativeWindow *> mSharedWindows; + ACameraWindowType* mWindow; + std::set<ACameraWindowType *> mSharedWindows; bool mIsShared; int mRotation = CAMERA3_STREAM_ROTATION_0; + std::string mPhysicalCameraId; }; +#endif struct ACaptureSessionOutputContainer { std::set<ACaptureSessionOutput> mOutputs; @@ -60,7 +69,7 @@ int id, const ACaptureSessionOutputContainer* outputs, const ACameraCaptureSession_stateCallbacks* cb, - CameraDevice* device) : + android::acam::CameraDevice* device) : mId(id), mOutput(*outputs), mUserSessionCallback(*cb), mDevice(device) {} @@ -82,13 +91,15 @@ camera_status_t abortCaptures(); + template<class T> camera_status_t setRepeatingRequest( - /*optional*/ACameraCaptureSession_captureCallbacks* cbs, + /*optional*/T* cbs, int numRequests, ACaptureRequest** requests, /*optional*/int* captureSequenceId); + template<class T> camera_status_t capture( - /*optional*/ACameraCaptureSession_captureCallbacks* cbs, + /*optional*/T* cbs, int numRequests, ACaptureRequest** requests, /*optional*/int* captureSequenceId); @@ -97,18 +108,18 @@ ACameraDevice* getDevice(); private: - friend class CameraDevice; + friend class android::acam::CameraDevice; // Close session because app close camera device, camera device got ERROR_DISCONNECTED, // or a new session is replacing this session. void closeByDevice(); - sp<CameraDevice> getDeviceSp(); + sp<android::acam::CameraDevice> getDeviceSp(); const int mId; const ACaptureSessionOutputContainer mOutput; const ACameraCaptureSession_stateCallbacks mUserSessionCallback; - const wp<CameraDevice> mDevice; + const wp<android::acam::CameraDevice> mDevice; bool mIsClosed = false; bool mClosedByApp = false; Mutex mSessionLock;
diff --git a/camera/ndk/impl/ACameraCaptureSession.inc b/camera/ndk/impl/ACameraCaptureSession.inc new file mode 100644 index 0000000..86bf8a5 --- /dev/null +++ b/camera/ndk/impl/ACameraCaptureSession.inc
@@ -0,0 +1,68 @@ +/* + * 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 "ACameraCaptureSession.h" + +#ifdef __ANDROID_VNDK__ +#include "ndk_vendor/impl/ACameraDeviceVendor.inc" +#else +#include "ACameraDevice.inc" +#endif + +using namespace android; + +template <class T> +camera_status_t +ACameraCaptureSession::setRepeatingRequest( + /*optional*/T* cbs, + int numRequests, ACaptureRequest** requests, + /*optional*/int* captureSequenceId) { + sp<acam::CameraDevice> dev = getDeviceSp(); + if (dev == nullptr) { + ALOGE("Error: Device associated with session %p has been closed!", this); + return ACAMERA_ERROR_SESSION_CLOSED; + } + + camera_status_t ret; + dev->lockDeviceForSessionOps(); + { + Mutex::Autolock _l(mSessionLock); + ret = dev->setRepeatingRequestsLocked( + this, cbs, numRequests, requests, captureSequenceId); + } + dev->unlockDevice(); + return ret; +} + +template <class T> +camera_status_t ACameraCaptureSession::capture( + /*optional*/T* cbs, + int numRequests, ACaptureRequest** requests, + /*optional*/int* captureSequenceId) { + sp<acam::CameraDevice> dev = getDeviceSp(); + if (dev == nullptr) { + ALOGE("Error: Device associated with session %p has been closed!", this); + return ACAMERA_ERROR_SESSION_CLOSED; + } + camera_status_t ret; + dev->lockDeviceForSessionOps(); + { + Mutex::Autolock _l(mSessionLock); + ret = dev->captureLocked(this, cbs, numRequests, requests, captureSequenceId); + } + dev->unlockDevice(); + return ret; +}
diff --git a/camera/ndk/impl/ACameraDevice.cpp b/camera/ndk/impl/ACameraDevice.cpp index 907debc..d24cb81 100644 --- a/camera/ndk/impl/ACameraDevice.cpp +++ b/camera/ndk/impl/ACameraDevice.cpp
@@ -20,16 +20,21 @@ #include <vector> #include <inttypes.h> #include <android/hardware/ICameraService.h> -#include <camera2/SubmitInfo.h> #include <gui/Surface.h> #include "ACameraDevice.h" #include "ACameraMetadata.h" #include "ACaptureRequest.h" #include "ACameraCaptureSession.h" -using namespace android; +#include "ACameraCaptureSession.inc" + +ACameraDevice::~ACameraDevice() { + mDevice->stopLooper(); +} namespace android { +namespace acam { + // Static member definitions const char* CameraDevice::kContextKey = "Context"; const char* CameraDevice::kDeviceKey = "Device"; @@ -39,10 +44,12 @@ const char* CameraDevice::kCaptureRequestKey = "CaptureRequest"; const char* CameraDevice::kTimeStampKey = "TimeStamp"; const char* CameraDevice::kCaptureResultKey = "CaptureResult"; +const char* CameraDevice::kPhysicalCaptureResultKey = "PhysicalCaptureResult"; const char* CameraDevice::kCaptureFailureKey = "CaptureFailure"; const char* CameraDevice::kSequenceIdKey = "SequenceId"; const char* CameraDevice::kFrameNumberKey = "FrameNumber"; const char* CameraDevice::kAnwKey = "Anw"; +const char* CameraDevice::kFailingPhysicalCameraId= "FailingPhysicalCameraId"; /** * CameraDevice Implementation @@ -50,11 +57,11 @@ CameraDevice::CameraDevice( const char* id, ACameraDevice_StateCallbacks* cb, - std::unique_ptr<ACameraMetadata> chars, + sp<ACameraMetadata> chars, ACameraDevice* wrapper) : mCameraId(id), mAppCallbacks(*cb), - mChars(std::move(chars)), + mChars(chars), mServiceCallback(new ServiceCallback(this)), mWrapper(wrapper), mInError(false), @@ -74,7 +81,7 @@ __FUNCTION__, strerror(-err), err); setCameraDeviceErrorLocked(ACAMERA_ERROR_CAMERA_DEVICE); } - mHandler = new CallbackHandler(); + mHandler = new CallbackHandler(id); mCbLooper->registerHandler(mHandler); const CameraMetadata& metadata = mChars->getInternalData(); @@ -95,6 +102,14 @@ mShadingMapSize[0] = entry.data.i32[0]; mShadingMapSize[1] = entry.data.i32[1]; } + + size_t physicalIdCnt = 0; + const char*const* physicalCameraIds; + if (mChars->isLogicalMultiCamera(&physicalIdCnt, &physicalCameraIds)) { + for (size_t i = 0; i < physicalIdCnt; i++) { + mPhysicalIds.push_back(physicalCameraIds[i]); + } + } } // Device close implementaiton @@ -105,14 +120,10 @@ if (!isClosed()) { disconnectLocked(session); } + LOG_ALWAYS_FATAL_IF(mCbLooper != nullptr, + "CameraDevice looper should've been stopped before ~CameraDevice"); mCurrentSession = nullptr; - if (mCbLooper != nullptr) { - mCbLooper->unregisterHandler(mHandler->id()); - mCbLooper->stop(); - } } - mCbLooper.clear(); - mHandler.clear(); } void @@ -127,8 +138,29 @@ camera_status_t CameraDevice::createCaptureRequest( ACameraDevice_request_template templateId, + const ACameraIdList* physicalIdList, ACaptureRequest** request) const { Mutex::Autolock _l(mDeviceLock); + + if (physicalIdList != nullptr) { + if (physicalIdList->numCameras > static_cast<int>(mPhysicalIds.size())) { + ALOGE("%s: physicalIdList size %d exceeds number of available physical cameras %zu", + __FUNCTION__, physicalIdList->numCameras, mPhysicalIds.size()); + return ACAMERA_ERROR_INVALID_PARAMETER; + } + for (auto i = 0; i < physicalIdList->numCameras; i++) { + if (physicalIdList->cameraIds[i] == nullptr) { + ALOGE("%s: physicalId is null!", __FUNCTION__); + return ACAMERA_ERROR_INVALID_PARAMETER; + } + if (mPhysicalIds.end() == std::find( + mPhysicalIds.begin(), mPhysicalIds.end(), physicalIdList->cameraIds[i])) { + ALOGE("%s: Invalid physicalId %s!", __FUNCTION__, physicalIdList->cameraIds[i]); + return ACAMERA_ERROR_INVALID_PARAMETER; + } + } + } + camera_status_t ret = checkCameraClosedOrErrorLocked(); if (ret != ACAMERA_OK) { return ret; @@ -149,6 +181,12 @@ } ACaptureRequest* outReq = new ACaptureRequest(); outReq->settings = new ACameraMetadata(rawRequest.release(), ACameraMetadata::ACM_REQUEST); + if (physicalIdList != nullptr) { + for (auto i = 0; i < physicalIdList->numCameras; i++) { + outReq->physicalSettings.emplace(physicalIdList->cameraIds[i], + new ACameraMetadata(*(outReq->settings))); + } + } outReq->targets = new ACameraOutputTargets(); *request = outReq; return ACAMERA_OK; @@ -190,104 +228,53 @@ return ACAMERA_OK; } -camera_status_t -CameraDevice::captureLocked( - sp<ACameraCaptureSession> session, - /*optional*/ACameraCaptureSession_captureCallbacks* cbs, - int numRequests, ACaptureRequest** requests, - /*optional*/int* captureSequenceId) { - return submitRequestsLocked( - session, cbs, numRequests, requests, captureSequenceId, /*isRepeating*/false); -} - -camera_status_t -CameraDevice::setRepeatingRequestsLocked( - sp<ACameraCaptureSession> session, - /*optional*/ACameraCaptureSession_captureCallbacks* cbs, - int numRequests, ACaptureRequest** requests, - /*optional*/int* captureSequenceId) { - return submitRequestsLocked( - session, cbs, numRequests, requests, captureSequenceId, /*isRepeating*/true); -} - -camera_status_t -CameraDevice::submitRequestsLocked( - sp<ACameraCaptureSession> session, - /*optional*/ACameraCaptureSession_captureCallbacks* cbs, - int numRequests, ACaptureRequest** requests, - /*optional*/int* captureSequenceId, - bool isRepeating) { +camera_status_t CameraDevice::isSessionConfigurationSupported( + const ACaptureSessionOutputContainer* sessionOutputContainer) const { + Mutex::Autolock _l(mDeviceLock); camera_status_t ret = checkCameraClosedOrErrorLocked(); if (ret != ACAMERA_OK) { - ALOGE("Camera %s submit capture request failed! ret %d", getId(), ret); return ret; } - // Form two vectors of capture request, one for internal tracking - std::vector<hardware::camera2::CaptureRequest> requestList; - Vector<sp<CaptureRequest> > requestsV; - requestsV.setCapacity(numRequests); - for (int i = 0; i < numRequests; i++) { - sp<CaptureRequest> req; - ret = allocateCaptureRequest(requests[i], req); + SessionConfiguration sessionConfiguration(0 /*inputWidth*/, 0 /*inputHeight*/, + -1 /*inputFormat*/, CAMERA3_STREAM_CONFIGURATION_NORMAL_MODE); + for (const auto& output : sessionOutputContainer->mOutputs) { + sp<IGraphicBufferProducer> iGBP(nullptr); + ret = getIGBPfromAnw(output.mWindow, iGBP); if (ret != ACAMERA_OK) { - ALOGE("Convert capture request to internal format failure! ret %d", ret); + ALOGE("Camera device %s failed to extract graphic producer from native window", + getId()); return ret; } - if (req->mSurfaceList.empty()) { - ALOGE("Capture request without output target cannot be submitted!"); - return ACAMERA_ERROR_INVALID_PARAMETER; + + String16 physicalId16(output.mPhysicalCameraId.c_str()); + OutputConfiguration outConfig(iGBP, output.mRotation, physicalId16, + OutputConfiguration::INVALID_SET_ID, true); + + for (auto& anw : output.mSharedWindows) { + ret = getIGBPfromAnw(anw, iGBP); + if (ret != ACAMERA_OK) { + ALOGE("Camera device %s failed to extract graphic producer from native window", + getId()); + return ret; + } + outConfig.addGraphicProducer(iGBP); } - requestList.push_back(*(req.get())); - requestsV.push_back(req); + + sessionConfiguration.addOutputConfiguration(outConfig); } - if (isRepeating) { - ret = stopRepeatingLocked(); - if (ret != ACAMERA_OK) { - ALOGE("Camera %s stop repeating failed! ret %d", getId(), ret); - return ret; - } - } - - binder::Status remoteRet; - hardware::camera2::utils::SubmitInfo info; - remoteRet = mRemote->submitRequestList(requestList, isRepeating, &info); - int sequenceId = info.mRequestId; - int64_t lastFrameNumber = info.mLastFrameNumber; - if (sequenceId < 0) { - ALOGE("Camera %s submit request remote failure: ret %d", getId(), sequenceId); + bool supported = false; + binder::Status remoteRet = mRemote->isSessionConfigurationSupported( + sessionConfiguration, &supported); + if (remoteRet.serviceSpecificErrorCode() == + hardware::ICameraService::ERROR_INVALID_OPERATION) { + return ACAMERA_ERROR_UNSUPPORTED_OPERATION; + } else if (!remoteRet.isOk()) { return ACAMERA_ERROR_UNKNOWN; - } - - CallbackHolder cbHolder(session, requestsV, isRepeating, cbs); - mSequenceCallbackMap.insert(std::make_pair(sequenceId, cbHolder)); - - if (isRepeating) { - // stopRepeating above should have cleanup repeating sequence id - if (mRepeatingSequenceId != REQUEST_ID_NONE) { - setCameraDeviceErrorLocked(ACAMERA_ERROR_CAMERA_DEVICE); - return ACAMERA_ERROR_CAMERA_DEVICE; - } - mRepeatingSequenceId = sequenceId; } else { - mSequenceLastFrameNumberMap.insert(std::make_pair(sequenceId, lastFrameNumber)); + return supported ? ACAMERA_OK : ACAMERA_ERROR_STREAM_CONFIGURE_FAIL; } - - if (mIdle) { - sp<AMessage> msg = new AMessage(kWhatSessionStateCb, mHandler); - msg->setPointer(kContextKey, session->mUserSessionCallback.context); - msg->setObject(kSessionSpKey, session); - msg->setPointer(kCallbackFpKey, (void*) session->mUserSessionCallback.onActive); - postSessionMsgAndCleanup(msg); - } - mIdle = false; - mBusySession = session; - - if (captureSequenceId) { - *captureSequenceId = sequenceId; - } - return ACAMERA_OK; } camera_status_t CameraDevice::updateOutputConfigurationLocked(ACaptureSessionOutput *output) { @@ -325,8 +312,9 @@ return ret; } - OutputConfiguration outConfig(iGBP, output->mRotation, OutputConfiguration::INVALID_SET_ID, - true); + String16 physicalId16(output->mPhysicalCameraId.c_str()); + OutputConfiguration outConfig(iGBP, output->mRotation, physicalId16, + OutputConfiguration::INVALID_SET_ID, true); for (auto& anw : output->mSharedWindows) { ret = getIGBPfromAnw(anw, iGBP); @@ -372,8 +360,12 @@ const ACaptureRequest* request, /*out*/sp<CaptureRequest>& outReq) { camera_status_t ret; sp<CaptureRequest> req(new CaptureRequest()); - req->mPhysicalCameraSettings.push_back({std::string(mCameraId.string()), + req->mPhysicalCameraSettings.push_back({getId(), request->settings->getInternalData()}); + for (auto& entry : request->physicalSettings) { + req->mPhysicalCameraSettings.push_back({entry.first, + entry.second->getInternalData()}); + } req->mIsReprocess = false; // NDK does not support reprocessing yet req->mContext = request->context; req->mSurfaceConverted = true; // set to true, and fill in stream/surface idx to speed up IPC @@ -417,10 +409,17 @@ } ACaptureRequest* -CameraDevice::allocateACaptureRequest(sp<CaptureRequest>& req) { +CameraDevice::allocateACaptureRequest(sp<CaptureRequest>& req, const std::string& deviceId) { ACaptureRequest* pRequest = new ACaptureRequest(); - CameraMetadata clone = req->mPhysicalCameraSettings.begin()->settings; - pRequest->settings = new ACameraMetadata(clone.release(), ACameraMetadata::ACM_REQUEST); + for (auto& entry : req->mPhysicalCameraSettings) { + CameraMetadata clone = entry.settings; + if (entry.id == deviceId) { + pRequest->settings = new ACameraMetadata(clone.release(), ACameraMetadata::ACM_REQUEST); + } else { + pRequest->physicalSettings.emplace(entry.id, + new ACameraMetadata(clone.release(), ACameraMetadata::ACM_REQUEST)); + } + } pRequest->targets = new ACameraOutputTargets(); for (size_t i = 0; i < req->mSurfaceList.size(); i++) { ANativeWindow* anw = static_cast<ANativeWindow*>(req->mSurfaceList[i].get()); @@ -436,7 +435,8 @@ if (req == nullptr) { return; } - delete req->settings; + req->settings.clear(); + req->physicalSettings.clear(); delete req->targets; delete req; } @@ -633,15 +633,16 @@ } std::set<std::pair<ANativeWindow*, OutputConfiguration>> outputSet; - for (auto outConfig : outputs->mOutputs) { + for (const auto& outConfig : outputs->mOutputs) { ANativeWindow* anw = outConfig.mWindow; sp<IGraphicBufferProducer> iGBP(nullptr); ret = getIGBPfromAnw(anw, iGBP); if (ret != ACAMERA_OK) { return ret; } + String16 physicalId16(outConfig.mPhysicalCameraId.c_str()); outputSet.insert(std::make_pair( - anw, OutputConfiguration(iGBP, outConfig.mRotation, + anw, OutputConfiguration(iGBP, outConfig.mRotation, physicalId16, OutputConfiguration::INVALID_SET_ID, outConfig.mIsShared))); } auto addSet = outputSet; @@ -706,7 +707,7 @@ } // add new streams - for (auto outputPair : addSet) { + for (const auto& outputPair : addSet) { int streamId; remoteRet = mRemote->createStream(outputPair.second, &streamId); if (!remoteRet.isOk()) { @@ -829,7 +830,7 @@ if (errorCode == hardware::camera2::ICameraDeviceCallbacks::ERROR_CAMERA_BUFFER) { int32_t streamId = resultExtras.errorStreamId; ACameraCaptureSession_captureCallback_bufferLost onBufferLost = - cbh.mCallbacks.onCaptureBufferLost; + cbh.mOnCaptureBufferLost; auto outputPairIt = mConfiguredOutputs.find(streamId); if (outputPairIt == mConfiguredOutputs.end()) { ALOGE("%s: Error: stream id %d does not exist", __FUNCTION__, streamId); @@ -839,14 +840,14 @@ const auto& gbps = outputPairIt->second.second.getGraphicBufferProducers(); for (const auto& outGbp : gbps) { - for (auto surface : request->mSurfaceList) { + for (const auto& surface : request->mSurfaceList) { if (surface->getIGraphicBufferProducer() == outGbp) { ANativeWindow* anw = static_cast<ANativeWindow*>(surface.get()); ALOGV("Camera %s Lost output buffer for ANW %p frame %" PRId64, getId(), anw, frameNumber); sp<AMessage> msg = new AMessage(kWhatCaptureBufferLost, mHandler); - msg->setPointer(kContextKey, cbh.mCallbacks.context); + msg->setPointer(kContextKey, cbh.mContext); msg->setObject(kSessionSpKey, session); msg->setPointer(kCallbackFpKey, (void*) onBufferLost); msg->setObject(kCaptureRequestKey, request); @@ -858,7 +859,7 @@ } } else { // Handle other capture failures // Fire capture failure callback if there is one registered - ACameraCaptureSession_captureCallback_failed onError = cbh.mCallbacks.onCaptureFailed; + ACameraCaptureSession_captureCallback_failed onError = cbh.mOnCaptureFailed; sp<CameraCaptureFailure> failure(new CameraCaptureFailure()); failure->frameNumber = frameNumber; // TODO: refine this when implementing flush @@ -867,10 +868,19 @@ failure->wasImageCaptured = (errorCode == hardware::camera2::ICameraDeviceCallbacks::ERROR_CAMERA_RESULT); - sp<AMessage> msg = new AMessage(kWhatCaptureFail, mHandler); - msg->setPointer(kContextKey, cbh.mCallbacks.context); + sp<AMessage> msg = new AMessage(cbh.mIsLogicalCameraCallback ? kWhatLogicalCaptureFail : + kWhatCaptureFail, mHandler); + msg->setPointer(kContextKey, cbh.mContext); msg->setObject(kSessionSpKey, session); - msg->setPointer(kCallbackFpKey, (void*) onError); + if (cbh.mIsLogicalCameraCallback) { + if (resultExtras.errorPhysicalCameraId.size() > 0) { + String8 cameraId(resultExtras.errorPhysicalCameraId); + msg->setString(kFailingPhysicalCameraId, cameraId.string(), cameraId.size()); + } + msg->setPointer(kCallbackFpKey, (void*) cbh.mOnLogicalCameraCaptureFailed); + } else { + msg->setPointer(kCallbackFpKey, (void*) onError); + } msg->setObject(kCaptureRequestKey, request); msg->setObject(kCaptureFailureKey, failure); postSessionMsgAndCleanup(msg); @@ -882,6 +892,19 @@ return; } +void CameraDevice::stopLooper() { + Mutex::Autolock _l(mDeviceLock); + if (mCbLooper != nullptr) { + mCbLooper->unregisterHandler(mHandler->id()); + mCbLooper->stop(); + } + mCbLooper.clear(); + mHandler.clear(); +} + +CameraDevice::CallbackHandler::CallbackHandler(const char* id) : mId(id) { +} + void CameraDevice::CallbackHandler::onMessageReceived( const sp<AMessage> &msg) { switch (msg->what()) { @@ -890,7 +913,9 @@ case kWhatSessionStateCb: case kWhatCaptureStart: case kWhatCaptureResult: + case kWhatLogicalCaptureResult: case kWhatCaptureFail: + case kWhatLogicalCaptureFail: case kWhatCaptureSeqEnd: case kWhatCaptureSeqAbort: case kWhatCaptureBufferLost: @@ -960,7 +985,9 @@ case kWhatSessionStateCb: case kWhatCaptureStart: case kWhatCaptureResult: + case kWhatLogicalCaptureResult: case kWhatCaptureFail: + case kWhatLogicalCaptureFail: case kWhatCaptureSeqEnd: case kWhatCaptureSeqAbort: case kWhatCaptureBufferLost: @@ -977,7 +1004,9 @@ switch (msg->what()) { case kWhatCaptureStart: case kWhatCaptureResult: + case kWhatLogicalCaptureResult: case kWhatCaptureFail: + case kWhatLogicalCaptureFail: case kWhatCaptureBufferLost: found = msg->findObject(kCaptureRequestKey, &obj); if (!found) { @@ -1020,7 +1049,7 @@ ALOGE("%s: Cannot find timestamp!", __FUNCTION__); return; } - ACaptureRequest* request = allocateACaptureRequest(requestSp); + ACaptureRequest* request = allocateACaptureRequest(requestSp, mId); (*onStart)(context, session.get(), request, timestamp); freeACaptureRequest(request); break; @@ -1043,11 +1072,69 @@ return; } sp<ACameraMetadata> result(static_cast<ACameraMetadata*>(obj.get())); - ACaptureRequest* request = allocateACaptureRequest(requestSp); + ACaptureRequest* request = allocateACaptureRequest(requestSp, mId); (*onResult)(context, session.get(), request, result.get()); freeACaptureRequest(request); break; } + case kWhatLogicalCaptureResult: + { + ACameraCaptureSession_logicalCamera_captureCallback_result onResult; + found = msg->findPointer(kCallbackFpKey, (void**) &onResult); + if (!found) { + ALOGE("%s: Cannot find logicalCamera capture result callback!", + __FUNCTION__); + return; + } + if (onResult == nullptr) { + return; + } + + found = msg->findObject(kCaptureResultKey, &obj); + if (!found) { + ALOGE("%s: Cannot find capture result!", __FUNCTION__); + return; + } + sp<ACameraMetadata> result(static_cast<ACameraMetadata*>(obj.get())); + + found = msg->findObject(kPhysicalCaptureResultKey, &obj); + if (!found) { + ALOGE("%s: Cannot find physical capture result!", __FUNCTION__); + return; + } + sp<ACameraPhysicalCaptureResultInfo> physicalResult( + static_cast<ACameraPhysicalCaptureResultInfo*>(obj.get())); + std::vector<PhysicalCaptureResultInfo>& physicalResultInfo = + physicalResult->mPhysicalResultInfo; + + std::vector<std::string> physicalCameraIds; + std::vector<sp<ACameraMetadata>> physicalMetadataCopy; + for (size_t i = 0; i < physicalResultInfo.size(); i++) { + String8 physicalId8(physicalResultInfo[i].mPhysicalCameraId); + physicalCameraIds.push_back(physicalId8.c_str()); + + CameraMetadata clone = physicalResultInfo[i].mPhysicalCameraMetadata; + clone.update(ANDROID_SYNC_FRAME_NUMBER, + &physicalResult->mFrameNumber, /*data_count*/1); + sp<ACameraMetadata> metadata = + new ACameraMetadata(clone.release(), ACameraMetadata::ACM_RESULT); + physicalMetadataCopy.push_back(metadata); + } + + std::vector<const char*> physicalCameraIdPtrs; + std::vector<const ACameraMetadata*> physicalMetadataCopyPtrs; + for (size_t i = 0; i < physicalResultInfo.size(); i++) { + physicalCameraIdPtrs.push_back(physicalCameraIds[i].c_str()); + physicalMetadataCopyPtrs.push_back(physicalMetadataCopy[i].get()); + } + + ACaptureRequest* request = allocateACaptureRequest(requestSp, mId); + (*onResult)(context, session.get(), request, result.get(), + physicalResultInfo.size(), physicalCameraIdPtrs.data(), + physicalMetadataCopyPtrs.data()); + freeACaptureRequest(request); + break; + } case kWhatCaptureFail: { ACameraCaptureSession_captureCallback_failed onFail; @@ -1069,11 +1156,44 @@ static_cast<CameraCaptureFailure*>(obj.get())); ACameraCaptureFailure* failure = static_cast<ACameraCaptureFailure*>(failureSp.get()); - ACaptureRequest* request = allocateACaptureRequest(requestSp); + ACaptureRequest* request = allocateACaptureRequest(requestSp, mId); (*onFail)(context, session.get(), request, failure); freeACaptureRequest(request); break; } + case kWhatLogicalCaptureFail: + { + ACameraCaptureSession_logicalCamera_captureCallback_failed onFail; + found = msg->findPointer(kCallbackFpKey, (void**) &onFail); + if (!found) { + ALOGE("%s: Cannot find capture fail callback!", __FUNCTION__); + return; + } + if (onFail == nullptr) { + return; + } + + found = msg->findObject(kCaptureFailureKey, &obj); + if (!found) { + ALOGE("%s: Cannot find capture failure!", __FUNCTION__); + return; + } + sp<CameraCaptureFailure> failureSp( + static_cast<CameraCaptureFailure*>(obj.get())); + ALogicalCameraCaptureFailure failure; + AString physicalCameraId; + found = msg->findString(kFailingPhysicalCameraId, &physicalCameraId); + if (found && !physicalCameraId.empty()) { + failure.physicalCameraId = physicalCameraId.c_str(); + } else { + failure.physicalCameraId = nullptr; + } + failure.captureFailure = *failureSp; + ACaptureRequest* request = allocateACaptureRequest(requestSp, mId); + (*onFail)(context, session.get(), request, &failure); + freeACaptureRequest(request); + break; + } case kWhatCaptureSeqEnd: { ACameraCaptureSession_captureCallback_sequenceEnd onSeqEnd; @@ -1146,7 +1266,7 @@ return; } - ACaptureRequest* request = allocateACaptureRequest(requestSp); + ACaptureRequest* request = allocateACaptureRequest(requestSp, mId); (*onBufferLost)(context, session.get(), request, anw, frameNumber); freeACaptureRequest(request); break; @@ -1158,12 +1278,36 @@ } CameraDevice::CallbackHolder::CallbackHolder( - sp<ACameraCaptureSession> session, - const Vector<sp<CaptureRequest> >& requests, - bool isRepeating, - ACameraCaptureSession_captureCallbacks* cbs) : - mSession(session), mRequests(requests), - mIsRepeating(isRepeating), mCallbacks(fillCb(cbs)) {} + sp<ACameraCaptureSession> session, + const Vector<sp<CaptureRequest> >& requests, + bool isRepeating, + ACameraCaptureSession_captureCallbacks* cbs) : + mSession(session), mRequests(requests), + mIsRepeating(isRepeating), + mIsLogicalCameraCallback(false) { + initCaptureCallbacks(cbs); + + if (cbs != nullptr) { + mOnCaptureCompleted = cbs->onCaptureCompleted; + mOnCaptureFailed = cbs->onCaptureFailed; + } +} + +CameraDevice::CallbackHolder::CallbackHolder( + sp<ACameraCaptureSession> session, + const Vector<sp<CaptureRequest> >& requests, + bool isRepeating, + ACameraCaptureSession_logicalCamera_captureCallbacks* lcbs) : + mSession(session), mRequests(requests), + mIsRepeating(isRepeating), + mIsLogicalCameraCallback(true) { + initCaptureCallbacks(lcbs); + + if (lcbs != nullptr) { + mOnLogicalCameraCaptureCompleted = lcbs->onLogicalCameraCaptureCompleted; + mOnLogicalCameraCaptureFailed = lcbs->onLogicalCameraCaptureFailed; + } +} void CameraDevice::checkRepeatingSequenceCompleteLocked( @@ -1180,9 +1324,9 @@ mSequenceCallbackMap.erase(cbIt); // send seq aborted callback sp<AMessage> msg = new AMessage(kWhatCaptureSeqAbort, mHandler); - msg->setPointer(kContextKey, cbh.mCallbacks.context); + msg->setPointer(kContextKey, cbh.mContext); msg->setObject(kSessionSpKey, cbh.mSession); - msg->setPointer(kCallbackFpKey, (void*) cbh.mCallbacks.onCaptureSequenceAborted); + msg->setPointer(kCallbackFpKey, (void*) cbh.mOnCaptureSequenceAborted); msg->setInt32(kSequenceIdKey, sequenceId); postSessionMsgAndCleanup(msg); } else { @@ -1230,9 +1374,9 @@ mSequenceCallbackMap.erase(cbIt); // send seq complete callback sp<AMessage> msg = new AMessage(kWhatCaptureSeqEnd, mHandler); - msg->setPointer(kContextKey, cbh.mCallbacks.context); + msg->setPointer(kContextKey, cbh.mContext); msg->setObject(kSessionSpKey, cbh.mSession); - msg->setPointer(kCallbackFpKey, (void*) cbh.mCallbacks.onCaptureSequenceCompleted); + msg->setPointer(kCallbackFpKey, (void*) cbh.mOnCaptureSequenceCompleted); msg->setInt32(kSequenceIdKey, sequenceId); msg->setInt64(kFrameNumberKey, lastFrameNumber); @@ -1290,16 +1434,21 @@ } default: ALOGE("Unknown error from camera device: %d", errorCode); - // no break + [[fallthrough]]; case ERROR_CAMERA_DEVICE: case ERROR_CAMERA_SERVICE: { + int32_t errorVal = ::ERROR_CAMERA_DEVICE; + // We keep this switch since this block might be encountered with + // more than just 2 states. The default fallthrough could have us + // handling more unmatched error cases. switch (errorCode) { case ERROR_CAMERA_DEVICE: dev->setCameraDeviceErrorLocked(ACAMERA_ERROR_CAMERA_DEVICE); break; case ERROR_CAMERA_SERVICE: dev->setCameraDeviceErrorLocked(ACAMERA_ERROR_CAMERA_SERVICE); + errorVal = ::ERROR_CAMERA_SERVICE; break; default: dev->setCameraDeviceErrorLocked(ACAMERA_ERROR_UNKNOWN); @@ -1309,7 +1458,7 @@ msg->setPointer(kContextKey, dev->mAppCallbacks.context); msg->setPointer(kDeviceKey, (void*) dev->getWrapper()); msg->setPointer(kCallbackFpKey, (void*) dev->mAppCallbacks.onError); - msg->setInt32(kErrorCodeKey, errorCode); + msg->setInt32(kErrorCodeKey, errorVal); msg->post(); break; } @@ -1384,7 +1533,7 @@ auto it = dev->mSequenceCallbackMap.find(sequenceId); if (it != dev->mSequenceCallbackMap.end()) { CallbackHolder cbh = (*it).second; - ACameraCaptureSession_captureCallback_start onStart = cbh.mCallbacks.onCaptureStarted; + ACameraCaptureSession_captureCallback_start onStart = cbh.mOnCaptureStarted; sp<ACameraCaptureSession> session = cbh.mSession; if ((size_t) burstId >= cbh.mRequests.size()) { ALOGE("%s: Error: request index %d out of bound (size %zu)", @@ -1393,7 +1542,7 @@ } sp<CaptureRequest> request = cbh.mRequests[burstId]; sp<AMessage> msg = new AMessage(kWhatCaptureStart, dev->mHandler); - msg->setPointer(kContextKey, cbh.mCallbacks.context); + msg->setPointer(kContextKey, cbh.mContext); msg->setObject(kSessionSpKey, session); msg->setPointer(kCallbackFpKey, (void*) onStart); msg->setObject(kCaptureRequestKey, request); @@ -1408,7 +1557,6 @@ const CameraMetadata& metadata, const CaptureResultExtras& resultExtras, const std::vector<PhysicalCaptureResultInfo>& physicalResultInfos) { - (void) physicalResultInfos; binder::Status ret = binder::Status::ok(); sp<CameraDevice> dev = mDevice.promote(); @@ -1444,9 +1592,6 @@ auto it = dev->mSequenceCallbackMap.find(sequenceId); if (it != dev->mSequenceCallbackMap.end()) { CallbackHolder cbh = (*it).second; - ACameraCaptureSession_captureCallback_result onResult = isPartialResult ? - cbh.mCallbacks.onCaptureProgressed : - cbh.mCallbacks.onCaptureCompleted; sp<ACameraCaptureSession> session = cbh.mSession; if ((size_t) burstId >= cbh.mRequests.size()) { ALOGE("%s: Error: request index %d out of bound (size %zu)", @@ -1456,13 +1601,27 @@ sp<CaptureRequest> request = cbh.mRequests[burstId]; sp<ACameraMetadata> result(new ACameraMetadata( metadataCopy.release(), ACameraMetadata::ACM_RESULT)); + sp<ACameraPhysicalCaptureResultInfo> physicalResult( + new ACameraPhysicalCaptureResultInfo(physicalResultInfos, frameNumber)); - sp<AMessage> msg = new AMessage(kWhatCaptureResult, dev->mHandler); - msg->setPointer(kContextKey, cbh.mCallbacks.context); + sp<AMessage> msg = new AMessage( + cbh.mIsLogicalCameraCallback ? kWhatLogicalCaptureResult : kWhatCaptureResult, + dev->mHandler); + msg->setPointer(kContextKey, cbh.mContext); msg->setObject(kSessionSpKey, session); - msg->setPointer(kCallbackFpKey, (void*) onResult); msg->setObject(kCaptureRequestKey, request); msg->setObject(kCaptureResultKey, result); + if (isPartialResult) { + msg->setPointer(kCallbackFpKey, + (void *)cbh.mOnCaptureProgressed); + } else if (cbh.mIsLogicalCameraCallback) { + msg->setPointer(kCallbackFpKey, + (void *)cbh.mOnLogicalCameraCaptureCompleted); + msg->setObject(kPhysicalCaptureResultKey, physicalResult); + } else { + msg->setPointer(kCallbackFpKey, + (void *)cbh.mOnCaptureCompleted); + } dev->postSessionMsgAndCleanup(msg); } @@ -1508,5 +1667,5 @@ return ret; } - +} // namespace acam } // namespace android
diff --git a/camera/ndk/impl/ACameraDevice.h b/camera/ndk/impl/ACameraDevice.h index 1369148..7a35bf0 100644 --- a/camera/ndk/impl/ACameraDevice.h +++ b/camera/ndk/impl/ACameraDevice.h
@@ -21,6 +21,7 @@ #include <set> #include <atomic> #include <utility> +#include <vector> #include <utils/StrongPointer.h> #include <utils/Mutex.h> #include <utils/String8.h> @@ -34,6 +35,7 @@ #include <media/stagefright/foundation/AMessage.h> #include <camera/CaptureResult.h> #include <camera/camera2/OutputConfiguration.h> +#include <camera/camera2/SessionConfiguration.h> #include <camera/camera2/CaptureRequest.h> #include <camera/NdkCameraManager.h> @@ -41,14 +43,25 @@ #include "ACameraMetadata.h" namespace android { +namespace acam { // Wrap ACameraCaptureFailure so it can be ref-counted struct CameraCaptureFailure : public RefBase, public ACameraCaptureFailure {}; +// Wrap PhysicalCaptureResultInfo so that it can be ref-counted +struct ACameraPhysicalCaptureResultInfo: public RefBase { + ACameraPhysicalCaptureResultInfo(const std::vector<PhysicalCaptureResultInfo>& info, + int64_t frameNumber) : + mPhysicalResultInfo(info), mFrameNumber(frameNumber) {} + + std::vector<PhysicalCaptureResultInfo> mPhysicalResultInfo; + int64_t mFrameNumber; +}; + class CameraDevice final : public RefBase { public: CameraDevice(const char* id, ACameraDevice_StateCallbacks* cb, - std::unique_ptr<ACameraMetadata> chars, + sp<ACameraMetadata> chars, ACameraDevice* wrapper); ~CameraDevice(); @@ -56,6 +69,7 @@ camera_status_t createCaptureRequest( ACameraDevice_request_template templateId, + const ACameraIdList* physicalIdList, ACaptureRequest** request) const; camera_status_t createCaptureSession( @@ -64,6 +78,9 @@ const ACameraCaptureSession_stateCallbacks* callbacks, /*out*/ACameraCaptureSession** session); + camera_status_t isSessionConfigurationSupported( + const ACaptureSessionOutputContainer* sessionOutputContainer) const; + // Callbacks from camera service class ServiceCallback : public hardware::camera2::BnCameraDeviceCallbacks { public: @@ -92,6 +109,9 @@ inline ACameraDevice* getWrapper() const { return mWrapper; }; + // Stop the looper thread and unregister the handler + void stopLooper(); + private: friend ACameraCaptureSession; camera_status_t checkCameraClosedOrErrorLocked() const; @@ -108,19 +128,22 @@ camera_status_t waitUntilIdleLocked(); + template<class T> camera_status_t captureLocked(sp<ACameraCaptureSession> session, - /*optional*/ACameraCaptureSession_captureCallbacks* cbs, + /*optional*/T* cbs, int numRequests, ACaptureRequest** requests, /*optional*/int* captureSequenceId); + template<class T> camera_status_t setRepeatingRequestsLocked(sp<ACameraCaptureSession> session, - /*optional*/ACameraCaptureSession_captureCallbacks* cbs, + /*optional*/T* cbs, int numRequests, ACaptureRequest** requests, /*optional*/int* captureSequenceId); + template<class T> camera_status_t submitRequestsLocked( sp<ACameraCaptureSession> session, - /*optional*/ACameraCaptureSession_captureCallbacks* cbs, + /*optional*/T* cbs, int numRequests, ACaptureRequest** requests, /*out*/int* captureSequenceId, bool isRepeating); @@ -130,7 +153,8 @@ camera_status_t allocateCaptureRequest( const ACaptureRequest* request, sp<CaptureRequest>& outReq); - static ACaptureRequest* allocateACaptureRequest(sp<CaptureRequest>& req); + static ACaptureRequest* allocateACaptureRequest(sp<CaptureRequest>& req, + const std::string& deviceId); static void freeACaptureRequest(ACaptureRequest*); // only For session to hold device lock @@ -156,7 +180,7 @@ mutable Mutex mDeviceLock; const String8 mCameraId; // Camera ID const ACameraDevice_StateCallbacks mAppCallbacks; // Callback to app - const std::unique_ptr<ACameraMetadata> mChars; // Camera characteristics + const sp<ACameraMetadata> mChars; // Camera characteristics const sp<ServiceCallback> mServiceCallback; ACameraDevice* mWrapper; @@ -191,7 +215,9 @@ // Capture callbacks kWhatCaptureStart, // onCaptureStarted kWhatCaptureResult, // onCaptureProgressed, onCaptureCompleted + kWhatLogicalCaptureResult, // onLogicalCameraCaptureCompleted kWhatCaptureFail, // onCaptureFailed + kWhatLogicalCaptureFail, // onLogicalCameraCaptureFailed kWhatCaptureSeqEnd, // onCaptureSequenceCompleted kWhatCaptureSeqAbort, // onCaptureSequenceAborted kWhatCaptureBufferLost,// onCaptureBufferLost @@ -206,16 +232,20 @@ static const char* kCaptureRequestKey; static const char* kTimeStampKey; static const char* kCaptureResultKey; + static const char* kPhysicalCaptureResultKey; static const char* kCaptureFailureKey; static const char* kSequenceIdKey; static const char* kFrameNumberKey; static const char* kAnwKey; + static const char* kFailingPhysicalCameraId; class CallbackHandler : public AHandler { public: + explicit CallbackHandler(const char* id); void onMessageReceived(const sp<AMessage> &msg) override; private: + std::string mId; // This handler will cache all capture session sp until kWhatCleanUpSessions // is processed. This is used to guarantee the last session reference is always // being removed in callback thread without holding camera device lock @@ -244,19 +274,47 @@ const Vector<sp<CaptureRequest> >& requests, bool isRepeating, ACameraCaptureSession_captureCallbacks* cbs); + CallbackHolder(sp<ACameraCaptureSession> session, + const Vector<sp<CaptureRequest> >& requests, + bool isRepeating, + ACameraCaptureSession_logicalCamera_captureCallbacks* lcbs); - static ACameraCaptureSession_captureCallbacks fillCb( - ACameraCaptureSession_captureCallbacks* cbs) { + template <class T> + void initCaptureCallbacks(T* cbs) { + mContext = nullptr; + mOnCaptureStarted = nullptr; + mOnCaptureProgressed = nullptr; + mOnCaptureCompleted = nullptr; + mOnLogicalCameraCaptureCompleted = nullptr; + mOnLogicalCameraCaptureFailed = nullptr; + mOnCaptureFailed = nullptr; + mOnCaptureSequenceCompleted = nullptr; + mOnCaptureSequenceAborted = nullptr; + mOnCaptureBufferLost = nullptr; if (cbs != nullptr) { - return *cbs; + mContext = cbs->context; + mOnCaptureStarted = cbs->onCaptureStarted; + mOnCaptureProgressed = cbs->onCaptureProgressed; + mOnCaptureSequenceCompleted = cbs->onCaptureSequenceCompleted; + mOnCaptureSequenceAborted = cbs->onCaptureSequenceAborted; + mOnCaptureBufferLost = cbs->onCaptureBufferLost; } - return { nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr }; } - sp<ACameraCaptureSession> mSession; Vector<sp<CaptureRequest> > mRequests; const bool mIsRepeating; - ACameraCaptureSession_captureCallbacks mCallbacks; + const bool mIsLogicalCameraCallback; + + void* mContext; + ACameraCaptureSession_captureCallback_start mOnCaptureStarted; + ACameraCaptureSession_captureCallback_result mOnCaptureProgressed; + ACameraCaptureSession_captureCallback_result mOnCaptureCompleted; + ACameraCaptureSession_logicalCamera_captureCallback_result mOnLogicalCameraCaptureCompleted; + ACameraCaptureSession_logicalCamera_captureCallback_failed mOnLogicalCameraCaptureFailed; + ACameraCaptureSession_captureCallback_failed mOnCaptureFailed; + ACameraCaptureSession_captureCallback_sequenceEnd mOnCaptureSequenceCompleted; + ACameraCaptureSession_captureCallback_sequenceAbort mOnCaptureSequenceAborted; + ACameraCaptureSession_captureCallback_bufferLost mOnCaptureBufferLost; }; // sequence id -> callbacks map std::map<int, CallbackHolder> mSequenceCallbackMap; @@ -283,9 +341,11 @@ // Misc variables int32_t mShadingMapSize[2]; // const after constructor int32_t mPartialResultCount; // const after constructor + std::vector<std::string> mPhysicalIds; // const after constructor }; +} // namespace acam; } // namespace android; /** @@ -294,10 +354,10 @@ */ struct ACameraDevice { ACameraDevice(const char* id, ACameraDevice_StateCallbacks* cb, - std::unique_ptr<ACameraMetadata> chars) : - mDevice(new CameraDevice(id, cb, std::move(chars), this)) {} + sp<ACameraMetadata> chars) : + mDevice(new android::acam::CameraDevice(id, cb, chars, this)) {} - ~ACameraDevice() {}; + ~ACameraDevice(); /******************* * NDK public APIs * @@ -306,8 +366,9 @@ camera_status_t createCaptureRequest( ACameraDevice_request_template templateId, + const ACameraIdList* physicalCameraIdList, ACaptureRequest** request) const { - return mDevice->createCaptureRequest(templateId, request); + return mDevice->createCaptureRequest(templateId, physicalCameraIdList, request); } camera_status_t createCaptureSession( @@ -318,6 +379,11 @@ return mDevice->createCaptureSession(outputs, sessionParameters, callbacks, session); } + camera_status_t isSessionConfigurationSupported( + const ACaptureSessionOutputContainer* sessionOutputContainer) const { + return mDevice->isSessionConfigurationSupported(sessionOutputContainer); + } + /*********************** * Device interal APIs * ***********************/ @@ -331,7 +397,7 @@ } private: - android::sp<android::CameraDevice> mDevice; + android::sp<android::acam::CameraDevice> mDevice; }; #endif // _ACAMERA_DEVICE_H
diff --git a/camera/ndk/impl/ACameraDevice.inc b/camera/ndk/impl/ACameraDevice.inc new file mode 100644 index 0000000..1fc5352 --- /dev/null +++ b/camera/ndk/impl/ACameraDevice.inc
@@ -0,0 +1,130 @@ +/* + * 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 <vector> +#include <inttypes.h> +#include "ACameraDevice.h" +#include "ACameraMetadata.h" +#include "ACaptureRequest.h" +#include "ACameraCaptureSession.h" + +namespace android { +namespace acam { + +template<class T> +camera_status_t +CameraDevice::captureLocked( + sp<ACameraCaptureSession> session, + /*optional*/T* cbs, + int numRequests, ACaptureRequest** requests, + /*optional*/int* captureSequenceId) { + return submitRequestsLocked( + session, cbs, numRequests, requests, captureSequenceId, /*isRepeating*/false); +} + +template<class T> +camera_status_t +CameraDevice::setRepeatingRequestsLocked( + sp<ACameraCaptureSession> session, + /*optional*/T* cbs, + int numRequests, ACaptureRequest** requests, + /*optional*/int* captureSequenceId) { + return submitRequestsLocked( + session, cbs, numRequests, requests, captureSequenceId, /*isRepeating*/true); +} + +template<class T> +camera_status_t CameraDevice::submitRequestsLocked( + sp<ACameraCaptureSession> session, + /*optional*/T* cbs, + int numRequests, ACaptureRequest** requests, + /*optional*/int* captureSequenceId, + bool isRepeating) { + camera_status_t ret = checkCameraClosedOrErrorLocked(); + if (ret != ACAMERA_OK) { + ALOGE("Camera %s submit capture request failed! ret %d", getId(), ret); + return ret; + } + + // Form two vectors of capture request, one for internal tracking + std::vector<hardware::camera2::CaptureRequest> requestList; + Vector<sp<CaptureRequest> > requestsV; + requestsV.setCapacity(numRequests); + for (int i = 0; i < numRequests; i++) { + sp<CaptureRequest> req; + ret = allocateCaptureRequest(requests[i], req); + if (ret != ACAMERA_OK) { + ALOGE("Convert capture request to internal format failure! ret %d", ret); + return ret; + } + if (req->mSurfaceList.empty()) { + ALOGE("Capture request without output target cannot be submitted!"); + return ACAMERA_ERROR_INVALID_PARAMETER; + } + requestList.push_back(*(req.get())); + requestsV.push_back(req); + } + + if (isRepeating) { + ret = stopRepeatingLocked(); + if (ret != ACAMERA_OK) { + ALOGE("Camera %s stop repeating failed! ret %d", getId(), ret); + return ret; + } + } + + binder::Status remoteRet; + hardware::camera2::utils::SubmitInfo info; + remoteRet = mRemote->submitRequestList(requestList, isRepeating, &info); + int sequenceId = info.mRequestId; + int64_t lastFrameNumber = info.mLastFrameNumber; + if (sequenceId < 0) { + ALOGE("Camera %s submit request remote failure: ret %d", getId(), sequenceId); + return ACAMERA_ERROR_UNKNOWN; + } + + CallbackHolder cbHolder(session, requestsV, isRepeating, cbs); + mSequenceCallbackMap.insert(std::make_pair(sequenceId, cbHolder)); + + if (isRepeating) { + // stopRepeating above should have cleanup repeating sequence id + if (mRepeatingSequenceId != REQUEST_ID_NONE) { + setCameraDeviceErrorLocked(ACAMERA_ERROR_CAMERA_DEVICE); + return ACAMERA_ERROR_CAMERA_DEVICE; + } + mRepeatingSequenceId = sequenceId; + } else { + mSequenceLastFrameNumberMap.insert(std::make_pair(sequenceId, lastFrameNumber)); + } + + if (mIdle) { + sp<AMessage> msg = new AMessage(kWhatSessionStateCb, mHandler); + msg->setPointer(kContextKey, session->mUserSessionCallback.context); + msg->setObject(kSessionSpKey, session); + msg->setPointer(kCallbackFpKey, (void*) session->mUserSessionCallback.onActive); + postSessionMsgAndCleanup(msg); + } + mIdle = false; + mBusySession = session; + + if (captureSequenceId) { + *captureSequenceId = sequenceId; + } + return ACAMERA_OK; +} + +} // namespace acam +} // namespace android
diff --git a/camera/ndk/impl/ACameraManager.cpp b/camera/ndk/impl/ACameraManager.cpp index c59d0e7..9d40fd7 100644 --- a/camera/ndk/impl/ACameraManager.cpp +++ b/camera/ndk/impl/ACameraManager.cpp
@@ -26,9 +26,10 @@ #include <stdlib.h> #include <camera/VendorTagDescriptor.h> -using namespace android; +using namespace android::acam; namespace android { +namespace acam { // Static member definitions const char* CameraManagerGlobal::kCameraIdKey = "CameraId"; const char* CameraManagerGlobal::kCallbackFpKey = "CallbackFp"; @@ -192,6 +193,20 @@ } } +void CameraManagerGlobal::registerExtendedAvailabilityCallback( + const ACameraManager_ExtendedAvailabilityCallbacks *callback) { + Mutex::Autolock _l(mLock); + Callback cb(callback); + mCallbacks.insert(cb); +} + +void CameraManagerGlobal::unregisterExtendedAvailabilityCallback( + const ACameraManager_ExtendedAvailabilityCallbacks *callback) { + Mutex::Autolock _l(mLock); + Callback cb(callback); + mCallbacks.erase(cb); +} + void CameraManagerGlobal::registerAvailabilityCallback( const ACameraManager_AvailabilityCallbacks *callback) { Mutex::Autolock _l(mLock); @@ -288,12 +303,40 @@ (*cb)(context, cameraId.c_str()); break; } + case kWhatSendSingleAccessCallback: + { + ACameraManager_AccessPrioritiesChangedCallback cb; + void* context; + AString cameraId; + bool found = msg->findPointer(kCallbackFpKey, (void**) &cb); + if (!found) { + ALOGE("%s: Cannot find camera callback fp!", __FUNCTION__); + return; + } + found = msg->findPointer(kContextKey, &context); + if (!found) { + ALOGE("%s: Cannot find callback context!", __FUNCTION__); + return; + } + (*cb)(context); + break; + } default: ALOGE("%s: unknown message type %d", __FUNCTION__, msg->what()); break; } } +binder::Status CameraManagerGlobal::CameraServiceListener::onCameraAccessPrioritiesChanged() { + sp<CameraManagerGlobal> cm = mCameraManager.promote(); + if (cm != nullptr) { + cm->onCameraAccessPrioritiesChanged(); + } else { + ALOGE("Cannot deliver camera access priority callback. Global camera manager died"); + } + return binder::Status::ok(); +} + binder::Status CameraManagerGlobal::CameraServiceListener::onStatusChanged( int32_t status, const String16& cameraId) { sp<CameraManagerGlobal> cm = mCameraManager.promote(); @@ -305,6 +348,19 @@ return binder::Status::ok(); } +void CameraManagerGlobal::onCameraAccessPrioritiesChanged() { + Mutex::Autolock _l(mLock); + for (auto cb : mCallbacks) { + sp<AMessage> msg = new AMessage(kWhatSendSingleAccessCallback, mHandler); + ACameraManager_AccessPrioritiesChangedCallback cbFp = cb.mAccessPriorityChanged; + if (cbFp != nullptr) { + msg->setPointer(kCallbackFpKey, (void *) cbFp); + msg->setPointer(kContextKey, cb.mContext); + msg->post(); + } + } +} + void CameraManagerGlobal::onStatusChanged( int32_t status, const String8& cameraId) { Mutex::Autolock _l(mLock); @@ -345,6 +401,7 @@ } } +} // namespace acam } // namespace android /** @@ -402,7 +459,7 @@ } camera_status_t ACameraManager::getCameraCharacteristics( - const char *cameraIdStr, ACameraMetadata **characteristics) { + const char* cameraIdStr, sp<ACameraMetadata>* characteristics) { Mutex::Autolock _l(mLock); sp<hardware::ICameraService> cs = CameraManagerGlobal::getInstance().getCameraService(); @@ -437,18 +494,16 @@ const char* cameraId, ACameraDevice_StateCallbacks* callback, /*out*/ACameraDevice** outDevice) { - ACameraMetadata* rawChars; - camera_status_t ret = getCameraCharacteristics(cameraId, &rawChars); + sp<ACameraMetadata> chars; + camera_status_t ret = getCameraCharacteristics(cameraId, &chars); Mutex::Autolock _l(mLock); if (ret != ACAMERA_OK) { ALOGE("%s: cannot get camera characteristics for camera %s. err %d", __FUNCTION__, cameraId, ret); return ACAMERA_ERROR_INVALID_PARAMETER; } - std::unique_ptr<ACameraMetadata> chars(rawChars); - rawChars = nullptr; - ACameraDevice* device = new ACameraDevice(cameraId, callback, std::move(chars)); + ACameraDevice* device = new ACameraDevice(cameraId, callback, chars); sp<hardware::ICameraService> cs = CameraManagerGlobal::getInstance().getCameraService(); if (cs == nullptr) {
diff --git a/camera/ndk/impl/ACameraManager.h b/camera/ndk/impl/ACameraManager.h index cc42f77..8c1da36 100644 --- a/camera/ndk/impl/ACameraManager.h +++ b/camera/ndk/impl/ACameraManager.h
@@ -35,6 +35,7 @@ #include <map> namespace android { +namespace acam { /** * Per-process singleton instance of CameraManger. Shared by all ACameraManager @@ -53,6 +54,11 @@ void unregisterAvailabilityCallback( const ACameraManager_AvailabilityCallbacks *callback); + void registerExtendedAvailabilityCallback( + const ACameraManager_ExtendedAvailabilityCallbacks* callback); + void unregisterExtendedAvailabilityCallback( + const ACameraManager_ExtendedAvailabilityCallbacks* callback); + /** * Return camera IDs that support camera2 */ @@ -85,6 +91,8 @@ return binder::Status::ok(); } + virtual binder::Status onCameraAccessPrioritiesChanged(); + private: const wp<CameraManagerGlobal> mCameraManager; }; @@ -95,11 +103,19 @@ explicit Callback(const ACameraManager_AvailabilityCallbacks *callback) : mAvailable(callback->onCameraAvailable), mUnavailable(callback->onCameraUnavailable), + mAccessPriorityChanged(nullptr), mContext(callback->context) {} + explicit Callback(const ACameraManager_ExtendedAvailabilityCallbacks *callback) : + mAvailable(callback->availabilityCallbacks.onCameraAvailable), + mUnavailable(callback->availabilityCallbacks.onCameraUnavailable), + mAccessPriorityChanged(callback->onCameraAccessPrioritiesChanged), + mContext(callback->availabilityCallbacks.context) {} + bool operator == (const Callback& other) const { return (mAvailable == other.mAvailable && mUnavailable == other.mUnavailable && + mAccessPriorityChanged == other.mAccessPriorityChanged && mContext == other.mContext); } bool operator != (const Callback& other) const { @@ -108,6 +124,9 @@ bool operator < (const Callback& other) const { if (*this == other) return false; if (mContext != other.mContext) return mContext < other.mContext; + if (mAccessPriorityChanged != other.mAccessPriorityChanged) { + return mAccessPriorityChanged < other.mAccessPriorityChanged; + } if (mAvailable != other.mAvailable) return mAvailable < other.mAvailable; return mUnavailable < other.mUnavailable; } @@ -116,13 +135,15 @@ } ACameraManager_AvailabilityCallback mAvailable; ACameraManager_AvailabilityCallback mUnavailable; + ACameraManager_AccessPrioritiesChangedCallback mAccessPriorityChanged; void* mContext; }; std::set<Callback> mCallbacks; // definition of handler and message enum { - kWhatSendSingleCallback + kWhatSendSingleCallback, + kWhatSendSingleAccessCallback, }; static const char* kCameraIdKey; static const char* kCallbackFpKey; @@ -135,6 +156,7 @@ sp<CallbackHandler> mHandler; sp<ALooper> mCbLooper; // Looper thread where callbacks actually happen on + void onCameraAccessPrioritiesChanged(); void onStatusChanged(int32_t status, const String8& cameraId); void onStatusChangedLocked(int32_t status, const String8& cameraId); // Utils for status @@ -172,6 +194,7 @@ ~CameraManagerGlobal(); }; +} // namespace acam; } // namespace android; /** @@ -180,13 +203,13 @@ */ struct ACameraManager { ACameraManager() : - mGlobalManager(&(android::CameraManagerGlobal::getInstance())) {} + mGlobalManager(&(android::acam::CameraManagerGlobal::getInstance())) {} ~ACameraManager(); camera_status_t getCameraIdList(ACameraIdList** cameraIdList); static void deleteCameraIdList(ACameraIdList* cameraIdList); camera_status_t getCameraCharacteristics( - const char *cameraId, ACameraMetadata **characteristics); + const char* cameraId, android::sp<ACameraMetadata>* characteristics); camera_status_t openCamera(const char* cameraId, ACameraDevice_StateCallbacks* callback, /*out*/ACameraDevice** device); @@ -196,7 +219,7 @@ kCameraIdListNotInit = -1 }; android::Mutex mLock; - android::sp<android::CameraManagerGlobal> mGlobalManager; + android::sp<android::acam::CameraManagerGlobal> mGlobalManager; }; #endif //_ACAMERA_MANAGER_H
diff --git a/camera/ndk/impl/ACameraMetadata.cpp b/camera/ndk/impl/ACameraMetadata.cpp index fc00a2d..77dcd48 100644 --- a/camera/ndk/impl/ACameraMetadata.cpp +++ b/camera/ndk/impl/ACameraMetadata.cpp
@@ -32,6 +32,14 @@ if (mType == ACM_CHARACTERISTICS) { filterUnsupportedFeatures(); filterStreamConfigurations(); + filterDurations(ANDROID_SCALER_AVAILABLE_MIN_FRAME_DURATIONS); + filterDurations(ANDROID_SCALER_AVAILABLE_STALL_DURATIONS); + filterDurations(ANDROID_DEPTH_AVAILABLE_DEPTH_MIN_FRAME_DURATIONS); + filterDurations(ANDROID_DEPTH_AVAILABLE_DEPTH_STALL_DURATIONS); + filterDurations(ANDROID_HEIC_AVAILABLE_HEIC_MIN_FRAME_DURATIONS); + filterDurations(ANDROID_HEIC_AVAILABLE_HEIC_STALL_DURATIONS); + filterDurations(ANDROID_DEPTH_AVAILABLE_DYNAMIC_DEPTH_MIN_FRAME_DURATIONS); + filterDurations(ANDROID_DEPTH_AVAILABLE_DYNAMIC_DEPTH_STALL_DURATIONS); } // TODO: filter request/result keys } @@ -39,23 +47,14 @@ bool ACameraMetadata::isNdkSupportedCapability(int32_t capability) { switch (capability) { - case ANDROID_REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE: - case ANDROID_REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR: - case ANDROID_REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING: - case ANDROID_REQUEST_AVAILABLE_CAPABILITIES_RAW: - case ANDROID_REQUEST_AVAILABLE_CAPABILITIES_READ_SENSOR_SETTINGS: - case ANDROID_REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE: - case ANDROID_REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT: - return true; case ANDROID_REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING: case ANDROID_REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING: case ANDROID_REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO: return false; default: - // Newly defined capabilities will be unsupported by default (blacklist) - // TODO: Should we do whitelist or blacklist here? - ALOGE("%s: Unknonwn capability %d", __FUNCTION__, capability); - return false; + // Assuming every capability passed to this function is actually a + // valid capability. + return true; } } @@ -75,11 +74,128 @@ uint8_t capability = entry.data.u8[i]; if (isNdkSupportedCapability(capability)) { capabilities.push(capability); + + if (capability == ANDROID_REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA) { + derivePhysicalCameraIds(); + } } } mData.update(ANDROID_REQUEST_AVAILABLE_CAPABILITIES, capabilities); } +void +ACameraMetadata::derivePhysicalCameraIds() { + ACameraMetadata_const_entry entry; + auto ret = getConstEntry(ACAMERA_LOGICAL_MULTI_CAMERA_PHYSICAL_IDS, &entry); + if (ret != ACAMERA_OK) { + ALOGE("%s: Get ACAMERA_LOGICAL_MULTI_CAMERA_PHYSICAL_IDS key failed. ret %d", + __FUNCTION__, ret); + return; + } + + const uint8_t* ids = entry.data.u8; + size_t start = 0; + for (size_t i = 0; i < entry.count; ++i) { + if (ids[i] == '\0') { + if (start != i) { + mStaticPhysicalCameraIdValues.push_back(String8((const char *)ids+start)); + mStaticPhysicalCameraIds.push_back(mStaticPhysicalCameraIdValues.back().string()); + } + start = i+1; + } + } + + if (mStaticPhysicalCameraIds.size() < 2) { + ALOGW("%s: Logical multi-camera device only has %zu physical cameras", + __FUNCTION__, mStaticPhysicalCameraIds.size()); + } +} + +void +ACameraMetadata::filterDurations(uint32_t tag) { + const int STREAM_CONFIGURATION_SIZE = 4; + const int STREAM_FORMAT_OFFSET = 0; + const int STREAM_WIDTH_OFFSET = 1; + const int STREAM_HEIGHT_OFFSET = 2; + const int STREAM_DURATION_OFFSET = 3; + camera_metadata_entry entry = mData.find(tag); + if (entry.count == 0 || entry.count % 4 || entry.type != TYPE_INT64) { + ALOGE("%s: malformed duration key %d! count %zu, type %d", + __FUNCTION__, tag, entry.count, entry.type); + return; + } + Vector<int64_t> filteredDurations; + filteredDurations.setCapacity(entry.count * 2); + + for (size_t i=0; i < entry.count; i += STREAM_CONFIGURATION_SIZE) { + int64_t format = entry.data.i64[i + STREAM_FORMAT_OFFSET]; + int64_t width = entry.data.i64[i + STREAM_WIDTH_OFFSET]; + int64_t height = entry.data.i64[i + STREAM_HEIGHT_OFFSET]; + int64_t duration = entry.data.i32[i + STREAM_DURATION_OFFSET]; + + // Leave the unfiltered format in so apps depending on previous wrong + // filter behavior continue to work + filteredDurations.push_back(format); + filteredDurations.push_back(width); + filteredDurations.push_back(height); + filteredDurations.push_back(duration); + + // Translate HAL formats to NDK format + switch (tag) { + case ANDROID_SCALER_AVAILABLE_MIN_FRAME_DURATIONS: + case ANDROID_SCALER_AVAILABLE_STALL_DURATIONS: + if (format == HAL_PIXEL_FORMAT_BLOB) { + format = AIMAGE_FORMAT_JPEG; + filteredDurations.push_back(format); + filteredDurations.push_back(width); + filteredDurations.push_back(height); + filteredDurations.push_back(duration); + } + break; + case ANDROID_DEPTH_AVAILABLE_DEPTH_MIN_FRAME_DURATIONS: + case ANDROID_DEPTH_AVAILABLE_DEPTH_STALL_DURATIONS: + if (format == HAL_PIXEL_FORMAT_BLOB) { + format = AIMAGE_FORMAT_DEPTH_POINT_CLOUD; + filteredDurations.push_back(format); + filteredDurations.push_back(width); + filteredDurations.push_back(height); + filteredDurations.push_back(duration); + } else if (format == HAL_PIXEL_FORMAT_Y16) { + format = AIMAGE_FORMAT_DEPTH16; + filteredDurations.push_back(format); + filteredDurations.push_back(width); + filteredDurations.push_back(height); + filteredDurations.push_back(duration); + } + break; + case ANDROID_HEIC_AVAILABLE_HEIC_MIN_FRAME_DURATIONS: + case ANDROID_HEIC_AVAILABLE_HEIC_STALL_DURATIONS: + if (format == HAL_PIXEL_FORMAT_BLOB) { + format = AIMAGE_FORMAT_HEIC; + filteredDurations.push_back(format); + filteredDurations.push_back(width); + filteredDurations.push_back(height); + filteredDurations.push_back(duration); + } + break; + case ANDROID_DEPTH_AVAILABLE_DYNAMIC_DEPTH_MIN_FRAME_DURATIONS: + case ANDROID_DEPTH_AVAILABLE_DYNAMIC_DEPTH_STALL_DURATIONS: + if (format == HAL_PIXEL_FORMAT_BLOB) { + format = AIMAGE_FORMAT_DEPTH_JPEG; + filteredDurations.push_back(format); + filteredDurations.push_back(width); + filteredDurations.push_back(height); + filteredDurations.push_back(duration); + } + break; + default: + // Should not reach here + ALOGE("%s: Unkown tag 0x%x", __FUNCTION__, tag); + } + } + + mData.update(tag, filteredDurations); +} void ACameraMetadata::filterStreamConfigurations() { @@ -89,7 +205,7 @@ const int STREAM_HEIGHT_OFFSET = 2; const int STREAM_IS_INPUT_OFFSET = 3; camera_metadata_entry entry = mData.find(ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS); - if (entry.count == 0 || entry.count % 4 || entry.type != TYPE_INT32) { + if (entry.count > 0 && (entry.count % 4 || entry.type != TYPE_INT32)) { ALOGE("%s: malformed available stream configuration key! count %zu, type %d", __FUNCTION__, entry.count, entry.type); return; @@ -117,9 +233,17 @@ filteredStreamConfigs.push_back(isInput); } - mData.update(ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS, filteredStreamConfigs); + if (filteredStreamConfigs.size() > 0) { + mData.update(ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS, filteredStreamConfigs); + } entry = mData.find(ANDROID_DEPTH_AVAILABLE_DEPTH_STREAM_CONFIGURATIONS); + if (entry.count > 0 && (entry.count % 4 || entry.type != TYPE_INT32)) { + ALOGE("%s: malformed available depth stream configuration key! count %zu, type %d", + __FUNCTION__, entry.count, entry.type); + return; + } + Vector<int32_t> filteredDepthStreamConfigs; filteredDepthStreamConfigs.setCapacity(entry.count); @@ -144,7 +268,62 @@ filteredDepthStreamConfigs.push_back(height); filteredDepthStreamConfigs.push_back(isInput); } - mData.update(ANDROID_DEPTH_AVAILABLE_DEPTH_STREAM_CONFIGURATIONS, filteredDepthStreamConfigs); + + if (filteredDepthStreamConfigs.size() > 0) { + mData.update(ANDROID_DEPTH_AVAILABLE_DEPTH_STREAM_CONFIGURATIONS, + filteredDepthStreamConfigs); + } + + entry = mData.find(ANDROID_HEIC_AVAILABLE_HEIC_STREAM_CONFIGURATIONS); + Vector<int32_t> filteredHeicStreamConfigs; + filteredHeicStreamConfigs.setCapacity(entry.count); + + for (size_t i=0; i < entry.count; i += STREAM_CONFIGURATION_SIZE) { + int32_t format = entry.data.i32[i + STREAM_FORMAT_OFFSET]; + int32_t width = entry.data.i32[i + STREAM_WIDTH_OFFSET]; + int32_t height = entry.data.i32[i + STREAM_HEIGHT_OFFSET]; + int32_t isInput = entry.data.i32[i + STREAM_IS_INPUT_OFFSET]; + if (isInput == ACAMERA_HEIC_AVAILABLE_HEIC_STREAM_CONFIGURATIONS_INPUT) { + // Hide input streams + continue; + } + // Translate HAL formats to NDK format + if (format == HAL_PIXEL_FORMAT_BLOB) { + format = AIMAGE_FORMAT_HEIC; + } + + filteredHeicStreamConfigs.push_back(format); + filteredHeicStreamConfigs.push_back(width); + filteredHeicStreamConfigs.push_back(height); + filteredHeicStreamConfigs.push_back(isInput); + } + mData.update(ANDROID_HEIC_AVAILABLE_HEIC_STREAM_CONFIGURATIONS, filteredHeicStreamConfigs); + + entry = mData.find(ANDROID_DEPTH_AVAILABLE_DYNAMIC_DEPTH_STREAM_CONFIGURATIONS); + Vector<int32_t> filteredDynamicDepthStreamConfigs; + filteredDynamicDepthStreamConfigs.setCapacity(entry.count); + + for (size_t i = 0; i < entry.count; i += STREAM_CONFIGURATION_SIZE) { + int32_t format = entry.data.i32[i + STREAM_FORMAT_OFFSET]; + int32_t width = entry.data.i32[i + STREAM_WIDTH_OFFSET]; + int32_t height = entry.data.i32[i + STREAM_HEIGHT_OFFSET]; + int32_t isInput = entry.data.i32[i + STREAM_IS_INPUT_OFFSET]; + if (isInput == ACAMERA_DEPTH_AVAILABLE_DYNAMIC_DEPTH_STREAM_CONFIGURATIONS_INPUT) { + // Hide input streams + continue; + } + // Translate HAL formats to NDK format + if (format == HAL_PIXEL_FORMAT_BLOB) { + format = AIMAGE_FORMAT_DEPTH_JPEG; + } + + filteredDynamicDepthStreamConfigs.push_back(format); + filteredDynamicDepthStreamConfigs.push_back(width); + filteredDynamicDepthStreamConfigs.push_back(height); + filteredDynamicDepthStreamConfigs.push_back(isInput); + } + mData.update(ACAMERA_DEPTH_AVAILABLE_DYNAMIC_DEPTH_STREAM_CONFIGURATIONS, + filteredDynamicDepthStreamConfigs); } bool @@ -239,6 +418,27 @@ return mData; } +bool +ACameraMetadata::isLogicalMultiCamera(size_t* count, const char*const** physicalCameraIds) const { + if (mType != ACM_CHARACTERISTICS) { + ALOGE("%s must be called for a static metadata!", __FUNCTION__); + return false; + } + if (count == nullptr || physicalCameraIds == nullptr) { + ALOGE("%s: Invalid input count: %p, physicalCameraIds: %p", __FUNCTION__, + count, physicalCameraIds); + return false; + } + + if (mStaticPhysicalCameraIds.size() >= 2) { + *count = mStaticPhysicalCameraIds.size(); + *physicalCameraIds = mStaticPhysicalCameraIds.data(); + return true; + } + + return false; +} + // TODO: some of key below should be hidden from user // ex: ACAMERA_REQUEST_ID and ACAMERA_REPROCESS_EFFECTIVE_EXPOSURE_FACTOR /*@O~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~ @@ -360,7 +560,10 @@ ANDROID_STATISTICS_INFO_MAX_HISTOGRAM_COUNT, ANDROID_STATISTICS_INFO_MAX_SHARPNESS_MAP_VALUE, ANDROID_STATISTICS_INFO_SHARPNESS_MAP_SIZE, + ANDROID_INFO_SUPPORTED_BUFFER_MANAGEMENT_VERSION, ANDROID_DEPTH_MAX_DEPTH_SAMPLES, + ANDROID_HEIC_INFO_SUPPORTED, + ANDROID_HEIC_INFO_MAX_JPEG_APP_SEGMENTS_COUNT, }); /*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~
diff --git a/camera/ndk/impl/ACameraMetadata.h b/camera/ndk/impl/ACameraMetadata.h index 0fd7efa..97f7f48 100644 --- a/camera/ndk/impl/ACameraMetadata.h +++ b/camera/ndk/impl/ACameraMetadata.h
@@ -17,12 +17,19 @@ #define _ACAMERA_METADATA_H #include <unordered_set> +#include <vector> #include <sys/types.h> #include <utils/Mutex.h> #include <utils/RefBase.h> #include <utils/Vector.h> + +#ifdef __ANDROID_VNDK__ +#include <CameraMetadata.h> +using CameraMetadata = android::hardware::camera::common::V1_0::helper::CameraMetadata; +#else #include <camera/CameraMetadata.h> +#endif #include <camera/NdkCameraMetadata.h> @@ -58,13 +65,20 @@ camera_status_t getTags(/*out*/int32_t* numTags, /*out*/const uint32_t** tags) const; + const CameraMetadata& getInternalData() const; + bool isLogicalMultiCamera(size_t* count, const char* const** physicalCameraIds) const; + + private: + + // This function does not check whether the capability passed to it is valid. + // The caller must make sure that it is. bool isNdkSupportedCapability(const int32_t capability); static inline bool isVendorTag(const uint32_t tag); static bool isCaptureRequestTag(const uint32_t tag); void filterUnsupportedFeatures(); // Hide features not yet supported by NDK void filterStreamConfigurations(); // Hide input streams, translate hal format to NDK formats - - const CameraMetadata& getInternalData() const; + void filterDurations(uint32_t tag); // translate hal format to NDK formats + void derivePhysicalCameraIds(); // Derive array of physical ids. template<typename INTERNAL_T, typename NDK_T> camera_status_t updateImpl(uint32_t tag, uint32_t count, const NDK_T* data) { @@ -96,7 +110,6 @@ } } - private: // guard access of public APIs: get/update/getTags mutable Mutex mLock; CameraMetadata mData; @@ -104,6 +117,9 @@ const ACAMERA_METADATA_TYPE mType; static std::unordered_set<uint32_t> sSystemTags; + + std::vector<const char*> mStaticPhysicalCameraIds; + std::vector<String8> mStaticPhysicalCameraIdValues; }; #endif // _ACAMERA_METADATA_H
diff --git a/camera/ndk/impl/ACaptureRequest.h b/camera/ndk/impl/ACaptureRequest.h index 06b2cc3..2ffcafe 100644 --- a/camera/ndk/impl/ACaptureRequest.h +++ b/camera/ndk/impl/ACaptureRequest.h
@@ -18,11 +18,15 @@ #include <camera/NdkCaptureRequest.h> #include <set> +#include <unordered_map> using namespace android; +#ifdef __ANDROID_VNDK__ +#include "ndk_vendor/impl/ACaptureRequestVendor.h" +#else struct ACameraOutputTarget { - explicit ACameraOutputTarget(ANativeWindow* window) : mWindow(window) {}; + explicit ACameraOutputTarget(ACameraWindowType* window) : mWindow(window) {}; bool operator == (const ACameraOutputTarget& other) const { return mWindow == other.mWindow; @@ -37,8 +41,9 @@ return mWindow > other.mWindow; } - ANativeWindow* mWindow; + ACameraWindowType* mWindow; }; +#endif struct ACameraOutputTargets { std::set<ACameraOutputTarget> mOutputs; @@ -55,7 +60,8 @@ return ACAMERA_OK; } - ACameraMetadata* settings; + sp<ACameraMetadata> settings; + std::unordered_map<std::string, sp<ACameraMetadata>> physicalSettings; ACameraOutputTargets* targets; void* context; };
diff --git a/camera/ndk/include/camera/NdkCameraCaptureSession.h b/camera/ndk/include/camera/NdkCameraCaptureSession.h index 51cef8c..07176cf 100644 --- a/camera/ndk/include/camera/NdkCameraCaptureSession.h +++ b/camera/ndk/include/camera/NdkCameraCaptureSession.h
@@ -35,9 +35,10 @@ #include <sys/cdefs.h> #include <stdbool.h> -#include <android/native_window.h> #include "NdkCameraError.h" #include "NdkCameraMetadata.h" +#include "NdkCaptureRequest.h" +#include "NdkCameraWindowType.h" #ifndef _NDK_CAMERA_CAPTURE_SESSION_H #define _NDK_CAMERA_CAPTURE_SESSION_H @@ -245,7 +246,7 @@ */ typedef void (*ACameraCaptureSession_captureCallback_bufferLost)( void* context, ACameraCaptureSession* session, - ACaptureRequest* request, ANativeWindow* window, int64_t frameNumber); + ACaptureRequest* request, ACameraWindowType* window, int64_t frameNumber); typedef struct ACameraCaptureSession_captureCallbacks { /// optional application context. @@ -433,7 +434,7 @@ * */ camera_status_t ACameraCaptureSession_getDevice( - ACameraCaptureSession* session, /*out*/ACameraDevice** device); + ACameraCaptureSession* session, /*out*/ACameraDevice** device) __INTRODUCED_IN(24); /** * Submit an array of requests to be captured in sequence as a burst in the minimum of time possible. @@ -471,7 +472,7 @@ ACameraCaptureSession* session, /*optional*/ACameraCaptureSession_captureCallbacks* callbacks, int numRequests, ACaptureRequest** requests, - /*optional*/int* captureSequenceId); + /*optional*/int* captureSequenceId) __INTRODUCED_IN(24); /** * Request endlessly repeating capture of a sequence of images by this capture session. @@ -525,7 +526,7 @@ ACameraCaptureSession* session, /*optional*/ACameraCaptureSession_captureCallbacks* callbacks, int numRequests, ACaptureRequest** requests, - /*optional*/int* captureSequenceId); + /*optional*/int* captureSequenceId) __INTRODUCED_IN(24); /** * Cancel any ongoing repeating capture set by {@link ACameraCaptureSession_setRepeatingRequest}. @@ -548,7 +549,8 @@ * <li>{@link ACAMERA_ERROR_CAMERA_SERVICE} if the camera service encounters fatal error</li> * <li>{@link ACAMERA_ERROR_UNKNOWN} if the method fails for some other reasons</li></ul> */ -camera_status_t ACameraCaptureSession_stopRepeating(ACameraCaptureSession* session); +camera_status_t ACameraCaptureSession_stopRepeating(ACameraCaptureSession* session) + __INTRODUCED_IN(24); /** * Discard all captures currently pending and in-progress as fast as possible. @@ -588,7 +590,8 @@ * <li>{@link ACAMERA_ERROR_CAMERA_SERVICE} if the camera service encounters fatal error</li> * <li>{@link ACAMERA_ERROR_UNKNOWN} if the method fails for some other reasons</li></ul> */ -camera_status_t ACameraCaptureSession_abortCaptures(ACameraCaptureSession* session); +camera_status_t ACameraCaptureSession_abortCaptures(ACameraCaptureSession* session) + __INTRODUCED_IN(24); #endif /* __ANDROID_API__ >= 24 */ @@ -637,9 +640,156 @@ * <li>{@link ACAMERA_ERROR_UNKNOWN} if the method fails for some other reasons</li></ul> */ camera_status_t ACameraCaptureSession_updateSharedOutput(ACameraCaptureSession* session, - ACaptureSessionOutput* output); + ACaptureSessionOutput* output) __INTRODUCED_IN(28); #endif /* __ANDROID_API__ >= 28 */ +#if __ANDROID_API__ >= 29 +/** + * The definition of final capture result callback with logical multi-camera support. + * + * This has the same functionality as final ACameraCaptureSession_captureCallback_result, with + * added ability to return physical camera result metadata within a logical multi-camera. + * + * For a logical multi-camera, this function will be called with the Id and result metadata + * of the underlying physical cameras, which the corresponding capture request contains targets for. + * If the capture request doesn't contain targets specific to any physical camera, or the current + * camera device isn't a logical multi-camera, physicalResultCount will be 0. + * + * @param context The optional application context provided by user in + * {@link ACameraCaptureSession_captureCallbacks}. + * @param session The camera capture session of interest. + * @param request The capture request of interest. Note that this pointer points to a copy of + * capture request sent by application, so the address is different to what + * application sent but the content will match. This request will be freed by + * framework immediately after this callback returns. + * @param result The capture result metadata reported by camera device. The memory is managed by + * camera framework. Do not access this pointer after this callback returns. + * @param physicalResultCount The number of physical camera result metadata + * @param physicalCameraIds The array of physical camera IDs on which the + * physical result metadata are reported. + * @param physicalResults The array of capture result metadata reported by the + * physical camera devices. + */ +typedef void (*ACameraCaptureSession_logicalCamera_captureCallback_result)( + void* context, ACameraCaptureSession* session, + ACaptureRequest* request, const ACameraMetadata* result, + size_t physicalResultCount, const char** physicalCameraIds, + const ACameraMetadata** physicalResults); + +/// Struct to describe a logical camera capture failure +typedef struct ALogicalCameraCaptureFailure { + /** + * The {@link ACameraCaptureFailure} contains information about regular logical device capture + * failure. + */ + struct ACameraCaptureFailure captureFailure; + + /** + * The physical camera device ID in case the capture failure comes from a capture request + * with configured physical camera streams for a logical camera. physicalCameraId will be set + * to NULL in case the capture request has no associated physical camera device. + * + */ + const char* physicalCameraId; +} ALogicalCameraCaptureFailure; + +/** + * The definition of logical camera capture failure callback. + * + * @param context The optional application context provided by user in + * {@link ACameraCaptureSession_captureCallbacks}. + * @param session The camera capture session of interest. + * @param request The capture request of interest. Note that this pointer points to a copy of + * capture request sent by application, so the address is different to what + * application sent but the content will match. This request will be freed by + * framework immediately after this callback returns. + * @param failure The {@link ALogicalCameraCaptureFailure} desribes the capture failure. The memory + * is managed by camera framework. Do not access this pointer after this callback + * returns. + */ +typedef void (*ACameraCaptureSession_logicalCamera_captureCallback_failed)( + void* context, ACameraCaptureSession* session, + ACaptureRequest* request, ALogicalCameraCaptureFailure* failure); + +/** + * This has the same functionality as ACameraCaptureSession_captureCallbacks, + * with the exception that an onLogicalCameraCaptureCompleted callback is + * used, instead of onCaptureCompleted, to support logical multi-camera. + */ +typedef struct ACameraCaptureSession_logicalCamera_captureCallbacks { + /** + * Same as ACameraCaptureSession_captureCallbacks + */ + void* context; + ACameraCaptureSession_captureCallback_start onCaptureStarted; + ACameraCaptureSession_captureCallback_result onCaptureProgressed; + + /** + * This callback is called when an image capture has fully completed and all the + * result metadata is available. For a logical multi-camera, this callback + * also returns the result metadata for all physical cameras being + * explicitly requested on. + * + * <p>This callback will always fire after the last {@link onCaptureProgressed}; + * in other words, no more partial results will be delivered once the completed result + * is available.</p> + * + * <p>For performance-intensive use-cases where latency is a factor, consider + * using {@link onCaptureProgressed} instead.</p> + * + * <p>Note that the ACaptureRequest pointer in the callback will not match what application has + * submitted, but the contents the ACaptureRequest will match what application submitted.</p> + */ + ACameraCaptureSession_logicalCamera_captureCallback_result onLogicalCameraCaptureCompleted; + + /** + * This callback is called instead of {@link onLogicalCameraCaptureCompleted} when the + * camera device failed to produce a capture result for the + * request. + * + * <p>Other requests are unaffected, and some or all image buffers from + * the capture may have been pushed to their respective output + * streams.</p> + * + * <p>Note that the ACaptureRequest pointer in the callback will not match what application has + * submitted, but the contents the ACaptureRequest will match what application submitted.</p> + * + * @see ALogicalCameraCaptureFailure + */ + ACameraCaptureSession_logicalCamera_captureCallback_failed onLogicalCameraCaptureFailed; + + /** + * Same as ACameraCaptureSession_captureCallbacks + */ + ACameraCaptureSession_captureCallback_sequenceEnd onCaptureSequenceCompleted; + ACameraCaptureSession_captureCallback_sequenceAbort onCaptureSequenceAborted; + ACameraCaptureSession_captureCallback_bufferLost onCaptureBufferLost; +} ACameraCaptureSession_logicalCamera_captureCallbacks; + +/** + * This has the same functionality as ACameraCaptureSession_capture, with added + * support for logical multi-camera where the capture callbacks supports result metadata for + * physical cameras. + */ +camera_status_t ACameraCaptureSession_logicalCamera_capture( + ACameraCaptureSession* session, + /*optional*/ACameraCaptureSession_logicalCamera_captureCallbacks* callbacks, + int numRequests, ACaptureRequest** requests, + /*optional*/int* captureSequenceId) __INTRODUCED_IN(29); + +/** + * This has the same functionality as ACameraCaptureSession_setRepeatingRequest, with added + * support for logical multi-camera where the capture callbacks supports result metadata for + * physical cameras. + */ +camera_status_t ACameraCaptureSession_logicalCamera_setRepeatingRequest( + ACameraCaptureSession* session, + /*optional*/ACameraCaptureSession_logicalCamera_captureCallbacks* callbacks, + int numRequests, ACaptureRequest** requests, + /*optional*/int* captureSequenceId) __INTRODUCED_IN(29); + +#endif /* __ANDROID_API__ >= 29 */ + __END_DECLS #endif /* _NDK_CAMERA_CAPTURE_SESSION_H */
diff --git a/camera/ndk/include/camera/NdkCameraDevice.h b/camera/ndk/include/camera/NdkCameraDevice.h index 92dad1c..1537bde 100644 --- a/camera/ndk/include/camera/NdkCameraDevice.h +++ b/camera/ndk/include/camera/NdkCameraDevice.h
@@ -34,10 +34,10 @@ */ #include <sys/cdefs.h> -#include <android/native_window.h> #include "NdkCameraError.h" #include "NdkCaptureRequest.h" #include "NdkCameraCaptureSession.h" +#include "NdkCameraWindowType.h" #ifndef _NDK_CAMERA_DEVICE_H #define _NDK_CAMERA_DEVICE_H @@ -53,6 +53,17 @@ */ typedef struct ACameraDevice ACameraDevice; +/** + * Struct to hold list of camera device Ids. This can refer to either the Ids + * of connected camera devices returned from {@link ACameraManager_getCameraIdList}, + * or the physical camera Ids passed into + * {@link ACameraDevice_createCaptureRequest_withPhysicalIds}. + */ +typedef struct ACameraIdList { + int numCameras; ///< Number of camera device Ids + const char** cameraIds; ///< list of camera device Ids +} ACameraIdList; + /// Enum for ACameraDevice_ErrorStateCallback error code enum { /** @@ -176,7 +187,7 @@ * <li>{@link ACAMERA_OK} if the method call succeeds.</li> * <li>{@link ACAMERA_ERROR_INVALID_PARAMETER} if device is NULL.</li></ul> */ -camera_status_t ACameraDevice_close(ACameraDevice* device); +camera_status_t ACameraDevice_close(ACameraDevice* device) __INTRODUCED_IN(24); /** * Return the camera id associated with this camera device. @@ -187,7 +198,7 @@ * delete/free by the application. Also the returned string must not be used after the device * has been closed. */ -const char* ACameraDevice_getId(const ACameraDevice* device); +const char* ACameraDevice_getId(const ACameraDevice* device) __INTRODUCED_IN(24); typedef enum { /** @@ -290,7 +301,7 @@ */ camera_status_t ACameraDevice_createCaptureRequest( const ACameraDevice* device, ACameraDevice_request_template templateId, - /*out*/ACaptureRequest** request); + /*out*/ACaptureRequest** request) __INTRODUCED_IN(24); typedef struct ACaptureSessionOutputContainer ACaptureSessionOutputContainer; @@ -313,7 +324,7 @@ * <li>{@link ACAMERA_ERROR_INVALID_PARAMETER} if container is NULL.</li></ul> */ camera_status_t ACaptureSessionOutputContainer_create( - /*out*/ACaptureSessionOutputContainer** container); + /*out*/ACaptureSessionOutputContainer** container) __INTRODUCED_IN(24); /** * Free a capture session output container. @@ -322,7 +333,8 @@ * * @see ACaptureSessionOutputContainer_create */ -void ACaptureSessionOutputContainer_free(ACaptureSessionOutputContainer* container); +void ACaptureSessionOutputContainer_free(ACaptureSessionOutputContainer* container) + __INTRODUCED_IN(24); /** * Create a ACaptureSessionOutput object. @@ -344,7 +356,7 @@ * @see ACaptureSessionOutputContainer_add */ camera_status_t ACaptureSessionOutput_create( - ANativeWindow* anw, /*out*/ACaptureSessionOutput** output); + ACameraWindowType* anw, /*out*/ACaptureSessionOutput** output) __INTRODUCED_IN(24); /** * Free a ACaptureSessionOutput object. @@ -353,7 +365,7 @@ * * @see ACaptureSessionOutput_create */ -void ACaptureSessionOutput_free(ACaptureSessionOutput* output); +void ACaptureSessionOutput_free(ACaptureSessionOutput* output) __INTRODUCED_IN(24); /** * Add an {@link ACaptureSessionOutput} object to {@link ACaptureSessionOutputContainer}. @@ -366,7 +378,8 @@ * <li>{@link ACAMERA_ERROR_INVALID_PARAMETER} if container or output is NULL.</li></ul> */ camera_status_t ACaptureSessionOutputContainer_add( - ACaptureSessionOutputContainer* container, const ACaptureSessionOutput* output); + ACaptureSessionOutputContainer* container, const ACaptureSessionOutput* output) + __INTRODUCED_IN(24); /** * Remove an {@link ACaptureSessionOutput} object from {@link ACaptureSessionOutputContainer}. @@ -382,7 +395,8 @@ * <li>{@link ACAMERA_ERROR_INVALID_PARAMETER} if container or output is NULL.</li></ul> */ camera_status_t ACaptureSessionOutputContainer_remove( - ACaptureSessionOutputContainer* container, const ACaptureSessionOutput* output); + ACaptureSessionOutputContainer* container, const ACaptureSessionOutput* output) + __INTRODUCED_IN(24); /** * Create a new camera capture session by providing the target output set of {@link ANativeWindow} @@ -671,7 +685,7 @@ ACameraDevice* device, const ACaptureSessionOutputContainer* outputs, const ACameraCaptureSession_stateCallbacks* callbacks, - /*out*/ACameraCaptureSession** session); + /*out*/ACameraCaptureSession** session) __INTRODUCED_IN(24); #endif /* __ANDROID_API__ >= 24 */ @@ -699,7 +713,7 @@ * @see ACaptureSessionOutputContainer_add */ camera_status_t ACaptureSessionSharedOutput_create( - ANativeWindow* anw, /*out*/ACaptureSessionOutput** output); + ACameraWindowType* anw, /*out*/ACaptureSessionOutput** output) __INTRODUCED_IN(28); /** * Add a native window to shared ACaptureSessionOutput. @@ -716,7 +730,8 @@ * window associated with ACaptureSessionOutput; or anw is already present inside * ACaptureSessionOutput.</li></ul> */ -camera_status_t ACaptureSessionSharedOutput_add(ACaptureSessionOutput *output, ANativeWindow *anw); +camera_status_t ACaptureSessionSharedOutput_add(ACaptureSessionOutput *output, + ACameraWindowType *anw) __INTRODUCED_IN(28); /** * Remove a native window from shared ACaptureSessionOutput. @@ -732,7 +747,7 @@ * ACaptureSessionOutput.</li></ul> */ camera_status_t ACaptureSessionSharedOutput_remove(ACaptureSessionOutput *output, - ANativeWindow* anw); + ACameraWindowType* anw) __INTRODUCED_IN(28); /** * Create a new camera capture session similar to {@link ACameraDevice_createCaptureSession}. This @@ -765,10 +780,118 @@ const ACaptureSessionOutputContainer* outputs, const ACaptureRequest* sessionParameters, const ACameraCaptureSession_stateCallbacks* callbacks, - /*out*/ACameraCaptureSession** session); + /*out*/ACameraCaptureSession** session) __INTRODUCED_IN(28); #endif /* __ANDROID_API__ >= 28 */ +#if __ANDROID_API__ >= 29 + +/** + * Create a ACaptureSessionOutput object used for streaming from a physical + * camera as part of a logical camera device. + * + * <p>The ACaptureSessionOutput is used in {@link ACaptureSessionOutputContainer_add} method to add + * an output {@link ANativeWindow} to ACaptureSessionOutputContainer. Use + * {@link ACaptureSessionOutput_free} to free the object and its memory after application no longer + * needs the {@link ACaptureSessionOutput}.</p> + * + * @param anw the {@link ANativeWindow} to be associated with the {@link ACaptureSessionOutput} + * @param physicalId the Id of the physical camera this output is associated + * with. + * @param output the output {@link ACaptureSessionOutput} will be stored here if the + * method call succeeds. + * + * @return <ul> + * <li>{@link ACAMERA_OK} if the method call succeeds. The created container will be + * filled in the output argument.</li> + * <li>{@link ACAMERA_ERROR_INVALID_PARAMETER} if anw, physicalId or output is NULL.</li></ul> + * + * @see ACaptureSessionOutputContainer_add + */ +camera_status_t ACaptureSessionPhysicalOutput_create( + ACameraWindowType* anw, const char* physicalId, + /*out*/ACaptureSessionOutput** output) __INTRODUCED_IN(29); + +/** + * Create a logical multi-camera ACaptureRequest for capturing images, initialized with template + * for a target use case, with the ability to specify physical camera settings. + * + * <p>The settings are chosen to be the best options for this camera device, + * so it is not recommended to reuse the same request for a different camera device.</p> + * + * <p>Note that for all keys in physical camera settings, only the keys + * advertised in ACAMERA_REQUEST_AVAILABLE_PHYSICAL_CAMERA_REQUEST_KEYS are + * applicable. All other keys are ignored by the camera device.</p> + * + * @param device the camera device of interest + * @param templateId the type of capture request to be created. + * See {@link ACameraDevice_request_template}. + * @param physicalIdList The list of physical camera Ids that can be used to + * customize the request for a specific physical camera. + * @param request the output request will be stored here if the method call succeeds. + * + * @return <ul> + * <li>{@link ACAMERA_OK} if the method call succeeds. The created capture request will be + * filled in request argument.</li> + * <li>{@link ACAMERA_ERROR_INVALID_PARAMETER} if device, physicalIdList, or request is + * NULL, templateId is undefined or camera device does not support + * requested template, or if some Ids in physicalIdList isn't a + * valid physical camera backing the current camera device.</li> + * <li>{@link ACAMERA_ERROR_CAMERA_DISCONNECTED} if the camera device is closed.</li> + * <li>{@link ACAMERA_ERROR_CAMERA_DEVICE} if the camera device encounters fatal error.</li> + * <li>{@link ACAMERA_ERROR_CAMERA_SERVICE} if the camera service encounters fatal error.</li> + * <li>{@link ACAMERA_ERROR_UNKNOWN} if the method fails for some other reasons.</li></ul> + * + * @see TEMPLATE_PREVIEW + * @see TEMPLATE_RECORD + * @see TEMPLATE_STILL_CAPTURE + * @see TEMPLATE_VIDEO_SNAPSHOT + * @see TEMPLATE_MANUAL + */ +camera_status_t ACameraDevice_createCaptureRequest_withPhysicalIds( + const ACameraDevice* device, ACameraDevice_request_template templateId, + const ACameraIdList* physicalIdList, + /*out*/ACaptureRequest** request) __INTRODUCED_IN(29); + +/** + * Check whether a particular {@ACaptureSessionOutputContainer} is supported by + * the camera device. + * + * <p>This method performs a runtime check of a given {@link + * ACaptureSessionOutputContainer}. The result confirms whether or not the + * passed CaptureSession outputs can be successfully used to create a camera + * capture session using {@link ACameraDevice_createCaptureSession}.</p> + * + * <p>This method can be called at any point before, during and after active + * capture session. It must not impact normal camera behavior in any way and + * must complete significantly faster than creating a capture session.</p> + * + * <p>Although this method is faster than creating a new capture session, it is not intended + * to be used for exploring the entire space of supported stream combinations.</p> + * + * @param device the camera device of interest + * @param sessionOutputContainer the {@link ACaptureSessionOutputContainer} of + * interest. + * + * @return <ul> + * <li>{@link ACAMERA_OK} if the given {@link ACaptureSessionOutputContainer} + * is supported by the camera device.</li> + * <li>{@link ACAMERA_ERROR_INVALID_PARAMETER} if device, or sessionOutputContainer + * is NULL.</li> + * <li>{@link ACAMERA_ERROR_STREAM_CONFIGURE_FAIL} if the given + * {@link ACaptureSessionOutputContainer} + * is not supported by + * the camera + * device.</li> + * <li>{@link ACAMERA_ERROR_UNSUPPORTED_OPERATION} if the query operation is not + * supported by the camera device.</li> + */ +camera_status_t ACameraDevice_isSessionConfigurationSupported( + const ACameraDevice* device, + const ACaptureSessionOutputContainer* sessionOutputContainer) __INTRODUCED_IN(29); + +#endif /* __ANDROID_API__ >= 29 */ + __END_DECLS #endif /* _NDK_CAMERA_DEVICE_H */
diff --git a/camera/ndk/include/camera/NdkCameraError.h b/camera/ndk/include/camera/NdkCameraError.h index 6b58155..fc618ee 100644 --- a/camera/ndk/include/camera/NdkCameraError.h +++ b/camera/ndk/include/camera/NdkCameraError.h
@@ -106,7 +106,8 @@ /** * Camera device does not support the stream configuration provided by application in - * {@link ACameraDevice_createCaptureSession}. + * {@link ACameraDevice_createCaptureSession} or {@link + * ACameraDevice_isSessionConfigurationSupported}. */ ACAMERA_ERROR_STREAM_CONFIGURE_FAIL = ACAMERA_ERROR_BASE - 9, @@ -130,6 +131,11 @@ * The application does not have permission to open camera. */ ACAMERA_ERROR_PERMISSION_DENIED = ACAMERA_ERROR_BASE - 13, + + /** + * The operation is not supported by the camera device. + */ + ACAMERA_ERROR_UNSUPPORTED_OPERATION = ACAMERA_ERROR_BASE - 14, } camera_status_t; #endif /* __ANDROID_API__ >= 24 */
diff --git a/camera/ndk/include/camera/NdkCameraManager.h b/camera/ndk/include/camera/NdkCameraManager.h index e5b3ad8..2cc8a97 100644 --- a/camera/ndk/include/camera/NdkCameraManager.h +++ b/camera/ndk/include/camera/NdkCameraManager.h
@@ -65,20 +65,14 @@ * @return a {@link ACameraManager} instance. * */ -ACameraManager* ACameraManager_create(); +ACameraManager* ACameraManager_create() __INTRODUCED_IN(24); /** * <p>Delete the {@link ACameraManager} instance and free its resources. </p> * * @param manager the {@link ACameraManager} instance to be deleted. */ -void ACameraManager_delete(ACameraManager* manager); - -/// Struct to hold list of camera devices -typedef struct ACameraIdList { - int numCameras; ///< Number of connected camera devices - const char** cameraIds; ///< list of identifier of connected camera devices -} ACameraIdList; +void ACameraManager_delete(ACameraManager* manager) __INTRODUCED_IN(24); /** * Create a list of currently connected camera devices, including @@ -91,6 +85,11 @@ * <p>ACameraManager_getCameraIdList will allocate and return an {@link ACameraIdList}. * The caller must call {@link ACameraManager_deleteCameraIdList} to free the memory</p> * + * <p>Note: the returned camera list might be a subset to the output of <a href= + * "https://developer.android.com/reference/android/hardware/camera2/CameraManager.html#getCameraIdList()"> + * SDK CameraManager#getCameraIdList API</a> as the NDK API does not support some legacy camera + * hardware.</p> + * * @param manager the {@link ACameraManager} of interest * @param cameraIdList the output {@link ACameraIdList} will be filled in here if the method call * succeeds. @@ -102,14 +101,14 @@ * <li>{@link ACAMERA_ERROR_NOT_ENOUGH_MEMORY} if allocating memory fails.</li></ul> */ camera_status_t ACameraManager_getCameraIdList(ACameraManager* manager, - /*out*/ACameraIdList** cameraIdList); + /*out*/ACameraIdList** cameraIdList) __INTRODUCED_IN(24); /** * Delete a list of camera devices allocated via {@link ACameraManager_getCameraIdList}. * * @param cameraIdList the {@link ACameraIdList} to be deleted. */ -void ACameraManager_deleteCameraIdList(ACameraIdList* cameraIdList); +void ACameraManager_deleteCameraIdList(ACameraIdList* cameraIdList) __INTRODUCED_IN(24); /** * Definition of camera availability callbacks. @@ -120,7 +119,8 @@ * argument is owned by camera framework and will become invalid immediately after * this callback returns. */ -typedef void (*ACameraManager_AvailabilityCallback)(void* context, const char* cameraId); +typedef void (*ACameraManager_AvailabilityCallback)(void* context, + const char* cameraId); /** * A listener for camera devices becoming available or unavailable to open. @@ -168,7 +168,8 @@ * {ACameraManager_AvailabilityCallbacks#onCameraUnavailable} is NULL.</li></ul> */ camera_status_t ACameraManager_registerAvailabilityCallback( - ACameraManager* manager, const ACameraManager_AvailabilityCallbacks* callback); + ACameraManager* manager, + const ACameraManager_AvailabilityCallbacks* callback) __INTRODUCED_IN(24); /** * Unregister camera availability callbacks. @@ -185,7 +186,8 @@ * {ACameraManager_AvailabilityCallbacks#onCameraUnavailable} is NULL.</li></ul> */ camera_status_t ACameraManager_unregisterAvailabilityCallback( - ACameraManager* manager, const ACameraManager_AvailabilityCallbacks* callback); + ACameraManager* manager, + const ACameraManager_AvailabilityCallbacks* callback) __INTRODUCED_IN(24); /** * Query the capabilities of a camera device. These capabilities are @@ -211,7 +213,7 @@ */ camera_status_t ACameraManager_getCameraCharacteristics( ACameraManager* manager, const char* cameraId, - /*out*/ACameraMetadata** characteristics); + /*out*/ACameraMetadata** characteristics) __INTRODUCED_IN(24); /** * Open a connection to a camera with the given ID. The opened camera device will be @@ -271,10 +273,126 @@ camera_status_t ACameraManager_openCamera( ACameraManager* manager, const char* cameraId, ACameraDevice_StateCallbacks* callback, - /*out*/ACameraDevice** device); + /*out*/ACameraDevice** device) __INTRODUCED_IN(24); #endif /* __ANDROID_API__ >= 24 */ +#if __ANDROID_API__ >= 29 + +/** + * Definition of camera access permission change callback. + * + * <p>Notification that camera access priorities have changed and the camera may + * now be openable. An application that was previously denied camera access due to + * a higher-priority user already using the camera, or that was disconnected from an + * active camera session due to a higher-priority user trying to open the camera, + * should try to open the camera again if it still wants to use it. Note that + * multiple applications may receive this callback at the same time, and only one of + * them will succeed in opening the camera in practice, depending on exact access + * priority levels and timing. This method is useful in cases where multiple + * applications may be in the resumed state at the same time, and the user switches + * focus between them, or if the current camera-using application moves between + * full-screen and Picture-in-Picture (PiP) states. In such cases, the camera + * available/unavailable callbacks will not be invoked, but another application may + * now have higher priority for camera access than the current camera-using + * application.</p> + + * @param context The optional application context provided by user in + * {@link ACameraManager_AvailabilityListener}. + */ +typedef void (*ACameraManager_AccessPrioritiesChangedCallback)(void* context); + +/** + * A listener for camera devices becoming available/unavailable to open or when + * the camera access permissions change. + * + * <p>Cameras become available when they are no longer in use, or when a new + * removable camera is connected. They become unavailable when some + * application or service starts using a camera, or when a removable camera + * is disconnected.</p> + * + * @see ACameraManager_registerExtendedAvailabilityCallback + */ +typedef struct ACameraManager_ExtendedAvailabilityListener { + /// + ACameraManager_AvailabilityCallbacks availabilityCallbacks; + + /// Called when there is camera access permission change + ACameraManager_AccessPrioritiesChangedCallback onCameraAccessPrioritiesChanged; + + /// Reserved for future use, please ensure that all entries are set to NULL + void *reserved[6]; +} ACameraManager_ExtendedAvailabilityCallbacks; + +/** + * Register camera extended availability callbacks. + * + * <p>onCameraUnavailable will be called whenever a camera device is opened by any camera API + * client. Other camera API clients may still be able to open such a camera device, evicting the + * existing client if they have higher priority than the existing client of a camera device. + * See {@link ACameraManager_openCamera} for more details.</p> + * + * <p>The callbacks will be called on a dedicated thread shared among all ACameraManager + * instances.</p> + * + * <p>Since this callback will be registered with the camera service, remember to unregister it + * once it is no longer needed; otherwise the callback will continue to receive events + * indefinitely and it may prevent other resources from being released. Specifically, the + * callbacks will be invoked independently of the general activity lifecycle and independently + * of the state of individual ACameraManager instances.</p> + * + * @param manager the {@link ACameraManager} of interest. + * @param callback the {@link ACameraManager_ExtendedAvailabilityCallbacks} to be registered. + * + * @return <ul> + * <li>{@link ACAMERA_OK} if the method call succeeds.</li> + * <li>{@link ACAMERA_ERROR_INVALID_PARAMETER} if manager or callback is NULL, or + * {ACameraManager_ExtendedAvailabilityCallbacks#onCameraAccessPrioritiesChanged} + * or {ACameraManager_AvailabilityCallbacks#onCameraAvailable} or + * {ACameraManager_AvailabilityCallbacks#onCameraUnavailable} is NULL.</li></ul> + */ +camera_status_t ACameraManager_registerExtendedAvailabilityCallback( + ACameraManager* manager, + const ACameraManager_ExtendedAvailabilityCallbacks* callback) __INTRODUCED_IN(29); + +/** + * Unregister camera extended availability callbacks. + * + * <p>Removing a callback that isn't registered has no effect.</p> + * + * @param manager the {@link ACameraManager} of interest. + * @param callback the {@link ACameraManager_ExtendedAvailabilityCallbacks} to be unregistered. + * + * @return <ul> + * <li>{@link ACAMERA_OK} if the method call succeeds.</li> + * <li>{@link ACAMERA_ERROR_INVALID_PARAMETER} if callback, + * {ACameraManager_ExtendedAvailabilityCallbacks#onCameraAccessPrioritiesChanged} + * or {ACameraManager_AvailabilityCallbacks#onCameraAvailable} or + * {ACameraManager_AvailabilityCallbacks#onCameraUnavailable} is NULL.</li></ul> + */ +camera_status_t ACameraManager_unregisterExtendedAvailabilityCallback( + ACameraManager* manager, + const ACameraManager_ExtendedAvailabilityCallbacks* callback) __INTRODUCED_IN(29); + +#ifdef __ANDROID_VNDK__ +/** + * Retrieve the tag value, given the tag name and camera id. + * This method is device specific since some metadata might be defined by device manufacturers + * and might only be accessible for specific cameras. + * @param manager The {@link ACameraManager} of interest. + * @param cameraId The cameraId, which is used to query camera characteristics. + * @param name The name of the tag being queried. + * @param tag The output tag assigned by this method. + * + * @return ACAMERA_OK only if the function call was successful. + */ +camera_status_t ACameraManager_getTagFromName(ACameraManager *manager, const char* cameraId, + const char *name, /*out*/uint32_t *tag) + __INTRODUCED_IN(29); +#endif + +#endif /* __ANDROID_API__ >= 29 */ + __END_DECLS #endif /* _NDK_CAMERA_MANAGER_H */
diff --git a/camera/ndk/include/camera/NdkCameraMetadata.h b/camera/ndk/include/camera/NdkCameraMetadata.h index f2aec98..9bbfb83 100644 --- a/camera/ndk/include/camera/NdkCameraMetadata.h +++ b/camera/ndk/include/camera/NdkCameraMetadata.h
@@ -36,6 +36,7 @@ #ifndef _NDK_CAMERA_METADATA_H #define _NDK_CAMERA_METADATA_H +#include <stdint.h> #include <sys/cdefs.h> #include "NdkCameraError.h" @@ -190,7 +191,8 @@ * of input tag value.</li></ul> */ camera_status_t ACameraMetadata_getConstEntry( - const ACameraMetadata* metadata, uint32_t tag, /*out*/ACameraMetadata_const_entry* entry); + const ACameraMetadata* metadata, + uint32_t tag, /*out*/ACameraMetadata_const_entry* entry) __INTRODUCED_IN(24); /** * List all the entry tags in input {@link ACameraMetadata}. @@ -207,7 +209,8 @@ * <li>{@link ACAMERA_ERROR_UNKNOWN} if the method fails for some other reasons.</li></ul> */ camera_status_t ACameraMetadata_getAllTags( - const ACameraMetadata* metadata, /*out*/int32_t* numEntries, /*out*/const uint32_t** tags); + const ACameraMetadata* metadata, + /*out*/int32_t* numEntries, /*out*/const uint32_t** tags) __INTRODUCED_IN(24); /** * Create a copy of input {@link ACameraMetadata}. @@ -219,17 +222,39 @@ * * @return a valid ACameraMetadata pointer or NULL if the input metadata cannot be copied. */ -ACameraMetadata* ACameraMetadata_copy(const ACameraMetadata* src); +ACameraMetadata* ACameraMetadata_copy(const ACameraMetadata* src) __INTRODUCED_IN(24); /** * Free a {@link ACameraMetadata} structure. * * @param metadata the {@link ACameraMetadata} to be freed. */ -void ACameraMetadata_free(ACameraMetadata* metadata); +void ACameraMetadata_free(ACameraMetadata* metadata) __INTRODUCED_IN(24); #endif /* __ANDROID_API__ >= 24 */ +#if __ANDROID_API__ >= 29 + +/** + * Helper function to check if a camera is logical multi-camera. + * + * <p> Check whether a camera device is a logical multi-camera based on its + * static metadata. If it is, also returns its physical sub camera Ids.</p> + * + * @param staticMetadata the static metadata of the camera being checked. + * @param numPhysicalCameras returns the number of physical cameras. + * @param physicalCameraIds returns the array of physical camera Ids backing this logical + * camera device. Note that this pointer is only valid + * during the lifetime of the staticMetadata object. + * + * @return true if this is a logical multi-camera, false otherwise. + */ +bool ACameraMetadata_isLogicalMultiCamera(const ACameraMetadata* staticMetadata, + /*out*/size_t* numPhysicalCameras, /*out*/const char* const** physicalCameraIds) + __INTRODUCED_IN(29); + +#endif /* __ANDROID_API__ >= 29 */ + __END_DECLS #endif /* _NDK_CAMERA_METADATA_H */
diff --git a/camera/ndk/include/camera/NdkCameraMetadataTags.h b/camera/ndk/include/camera/NdkCameraMetadataTags.h index bee1a46..8dd6e00 100644 --- a/camera/ndk/include/camera/NdkCameraMetadataTags.h +++ b/camera/ndk/include/camera/NdkCameraMetadataTags.h
@@ -71,6 +71,8 @@ ACAMERA_DEPTH, ACAMERA_LOGICAL_MULTI_CAMERA, ACAMERA_DISTORTION_CORRECTION, + ACAMERA_HEIC, + ACAMERA_HEIC_INFO, ACAMERA_SECTION_COUNT, ACAMERA_VENDOR = 0x8000 @@ -112,6 +114,8 @@ ACAMERA_DISTORTION_CORRECTION_START = ACAMERA_DISTORTION_CORRECTION << 16, + ACAMERA_HEIC_START = ACAMERA_HEIC << 16, + ACAMERA_HEIC_INFO_START = ACAMERA_HEIC_INFO << 16, ACAMERA_VENDOR_START = ACAMERA_VENDOR << 16 } acamera_metadata_section_start_t; @@ -1912,6 +1916,7 @@ * <li>ACaptureRequest</li> * </ul></p> * + * <p>This tag is also used for HEIC image capture.</p> */ ACAMERA_JPEG_GPS_COORDINATES = // double[3] ACAMERA_JPEG_START, @@ -1927,6 +1932,7 @@ * <li>ACaptureRequest</li> * </ul></p> * + * <p>This tag is also used for HEIC image capture.</p> */ ACAMERA_JPEG_GPS_PROCESSING_METHOD = // byte ACAMERA_JPEG_START + 1, @@ -1942,6 +1948,7 @@ * <li>ACaptureRequest</li> * </ul></p> * + * <p>This tag is also used for HEIC image capture.</p> */ ACAMERA_JPEG_GPS_TIMESTAMP = // int64 ACAMERA_JPEG_START + 2, @@ -1986,6 +1993,10 @@ * </code></pre> * <p>For EXTERNAL cameras the sensor orientation will always be set to 0 and the facing will * also be set to EXTERNAL. The above code is not relevant in such case.</p> + * <p>This tag is also used to describe the orientation of the HEIC image capture, in which + * case the rotation is reflected by + * <a href="https://developer.android.com/reference/android/media/ExifInterface.html#TAG_ORIENTATION">EXIF orientation flag</a>, and not by + * rotating the image data itself.</p> * * @see ACAMERA_SENSOR_ORIENTATION */ @@ -2003,7 +2014,8 @@ * <li>ACaptureRequest</li> * </ul></p> * - * <p>85-95 is typical usage range.</p> + * <p>85-95 is typical usage range. This tag is also used to describe the quality + * of the HEIC image capture.</p> */ ACAMERA_JPEG_QUALITY = // byte ACAMERA_JPEG_START + 4, @@ -2019,6 +2031,7 @@ * <li>ACaptureRequest</li> * </ul></p> * + * <p>This tag is also used to describe the quality of the HEIC image capture.</p> */ ACAMERA_JPEG_THUMBNAIL_QUALITY = // byte ACAMERA_JPEG_START + 5, @@ -2055,6 +2068,10 @@ * orientation is requested. LEGACY device will always report unrotated thumbnail * size.</li> * </ul> + * <p>The tag is also used as thumbnail size for HEIC image format capture, in which case the + * the thumbnail rotation is reflected by + * <a href="https://developer.android.com/reference/android/media/ExifInterface.html#TAG_ORIENTATION">EXIF orientation flag</a>, and not by + * rotating the thumbnail data itself.</p> * * @see ACAMERA_JPEG_ORIENTATION */ @@ -2088,6 +2105,7 @@ * and vice versa.</li> * <li>All non-<code>(0, 0)</code> sizes will have non-zero widths and heights.</li> * </ul> + * <p>This list is also used as supported thumbnail sizes for HEIC image format capture.</p> * * @see ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS */ @@ -2783,7 +2801,7 @@ * {@link AIMAGE_FORMAT_RAW12 RAW12}.</li> * <li>Processed (but not-stalling): any non-RAW format without a stall duration. Typically * {@link AIMAGE_FORMAT_YUV_420_888 YUV_420_888}, - * <a href="https://developer.android.com/reference/android/graphics/ImageFormat.html#NV21">NV21</a>, or <a href="https://developer.android.com/reference/android/graphics/ImageFormat.html#YV12">YV12</a>.</li> + * <a href="https://developer.android.com/reference/android/graphics/ImageFormat.html#NV21">NV21</a>, <a href="https://developer.android.com/reference/android/graphics/ImageFormat.html#YV12">YV12</a>, or {@link AIMAGE_FORMAT_Y8 Y8} .</li> * </ul> * * @see ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS @@ -3028,6 +3046,28 @@ */ ACAMERA_REQUEST_AVAILABLE_SESSION_KEYS = // int32[n] ACAMERA_REQUEST_START + 16, + /** + * <p>A subset of the available request keys that can be overridden for + * physical devices backing a logical multi-camera.</p> + * + * <p>Type: int32[n]</p> + * + * <p>This tag may appear in: + * <ul> + * <li>ACameraMetadata from ACameraManager_getCameraCharacteristics</li> + * </ul></p> + * + * <p>This is a subset of ACAMERA_REQUEST_AVAILABLE_REQUEST_KEYS which contains a list + * of keys that can be overridden using <a href="https://developer.android.com/reference/CaptureRequest/Builder.html#setPhysicalCameraKey">Builder#setPhysicalCameraKey</a>. + * The respective value of such request key can be obtained by calling + * <a href="https://developer.android.com/reference/CaptureRequest/Builder.html#getPhysicalCameraKey">Builder#getPhysicalCameraKey</a>. Capture requests that contain + * individual physical device requests must be built via + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice.html#createCaptureRequest(int,">Set)</a>.</p> + * + * @see ACAMERA_REQUEST_AVAILABLE_REQUEST_KEYS + */ + ACAMERA_REQUEST_AVAILABLE_PHYSICAL_CAMERA_REQUEST_KEYS = // int32[n] + ACAMERA_REQUEST_START + 17, ACAMERA_REQUEST_END, /** @@ -3257,6 +3297,7 @@ * <li>{@link AIMAGE_FORMAT_YUV_420_888 }</li> * <li>{@link AIMAGE_FORMAT_RAW10 }</li> * <li>{@link AIMAGE_FORMAT_RAW12 }</li> + * <li>{@link AIMAGE_FORMAT_Y8 }</li> * </ul> * <p>All other formats may or may not have an allowed stall duration on * a per-capability basis; refer to ACAMERA_REQUEST_AVAILABLE_CAPABILITIES @@ -3294,6 +3335,81 @@ */ ACAMERA_SCALER_CROPPING_TYPE = // byte (acamera_metadata_enum_android_scaler_cropping_type_t) ACAMERA_SCALER_START + 13, + /** + * <p>Recommended stream configurations for common client use cases.</p> + * + * <p>Type: int32[n*5] (acamera_metadata_enum_android_scaler_available_recommended_stream_configurations_t)</p> + * + * <p>This tag may appear in: + * <ul> + * <li>ACameraMetadata from ACameraManager_getCameraCharacteristics</li> + * </ul></p> + * + * <p>Optional subset of the ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS that contains + * similar tuples listed as + * (i.e. width, height, format, output/input stream, usecase bit field). + * Camera devices will be able to suggest particular stream configurations which are + * power and performance efficient for specific use cases. For more information about + * retrieving the suggestions see + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraCharacteristics.html#getRecommendedStreamConfigurationMap">CameraCharacteristics#getRecommendedStreamConfigurationMap</a>.</p> + * <p>The data representation is int[5], which maps to + * (width, height, format, output/input stream, usecase bit field). The array can be + * parsed using the following pseudo code:</p> + * <p>struct StreamConfiguration { + * int32_t format; + * int32_t width; + * int32_t height; + * int32_t isInput; };</p> + * <p>void getPreferredStreamConfigurations( + * int32_t *array, size_t count, int32_t usecaseId, + * Vector < StreamConfiguration > * scs) { + * const size_t STREAM_CONFIGURATION_SIZE = 5; + * const size_t STREAM_WIDTH_OFFSET = 0; + * const size_t STREAM_HEIGHT_OFFSET = 1; + * const size_t STREAM_FORMAT_OFFSET = 2; + * const size_t STREAM_IS_INPUT_OFFSET = 3; + * const size_t STREAM_USECASE_BITMAP_OFFSET = 4;</p> + * <pre><code>for (size_t i = 0; i < count; i+= STREAM_CONFIGURATION_SIZE) { + * int32_t width = array[i + STREAM_WIDTH_OFFSET]; + * int32_t height = array[i + STREAM_HEIGHT_OFFSET]; + * int32_t format = array[i + STREAM_FORMAT_OFFSET]; + * int32_t isInput = array[i + STREAM_IS_INPUT_OFFSET]; + * int32_t supportedUsecases = array[i + STREAM_USECASE_BITMAP_OFFSET]; + * if (supportedUsecases & (1 << usecaseId)) { + * StreamConfiguration sc = {format, width, height, isInput}; + * scs->add(sc); + * } + * } + * </code></pre> + * <p>}</p> + * + * @see ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS + */ + ACAMERA_SCALER_AVAILABLE_RECOMMENDED_STREAM_CONFIGURATIONS = + // int32[n*5] (acamera_metadata_enum_android_scaler_available_recommended_stream_configurations_t) + ACAMERA_SCALER_START + 14, + /** + * <p>Recommended mappings of image formats that are supported by this + * camera device for input streams, to their corresponding output formats.</p> + * + * <p>Type: int32</p> + * + * <p>This tag may appear in: + * <ul> + * <li>ACameraMetadata from ACameraManager_getCameraCharacteristics</li> + * </ul></p> + * + * <p>This is a recommended subset of the complete list of mappings found in + * android.scaler.availableInputOutputFormatsMap. The same requirements apply here as well. + * The list however doesn't need to contain all available and supported mappings. Instead of + * this developers must list only recommended and efficient entries. + * If set, the information will be available in the ZERO_SHUTTER_LAG recommended stream + * configuration see + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraCharacteristics.html#getRecommendedStreamConfigurationMap">CameraCharacteristics#getRecommendedStreamConfigurationMap</a>.</p> + */ + ACAMERA_SCALER_AVAILABLE_RECOMMENDED_INPUT_OUTPUT_FORMATS_MAP = + // int32 + ACAMERA_SCALER_START + 15, ACAMERA_SCALER_END, /** @@ -3454,6 +3570,8 @@ * <p>Some devices may choose to provide a second set of calibration * information for improved quality, including * ACAMERA_SENSOR_REFERENCE_ILLUMINANT2 and its corresponding matrices.</p> + * <p>Starting from Android Q, this key will not be present for a MONOCHROME camera, even if + * the camera device has RAW capability.</p> * * @see ACAMERA_SENSOR_CALIBRATION_TRANSFORM1 * @see ACAMERA_SENSOR_COLOR_TRANSFORM1 @@ -3483,6 +3601,8 @@ * <p>If this key is present, then ACAMERA_SENSOR_COLOR_TRANSFORM2, * ACAMERA_SENSOR_CALIBRATION_TRANSFORM2, and * ACAMERA_SENSOR_FORWARD_MATRIX2 will also be present.</p> + * <p>Starting from Android Q, this key will not be present for a MONOCHROME camera, even if + * the camera device has RAW capability.</p> * * @see ACAMERA_SENSOR_CALIBRATION_TRANSFORM2 * @see ACAMERA_SENSOR_COLOR_TRANSFORM2 @@ -3510,6 +3630,8 @@ * colorspace) into this camera device's native sensor color * space under the first reference illuminant * (ACAMERA_SENSOR_REFERENCE_ILLUMINANT1).</p> + * <p>Starting from Android Q, this key will not be present for a MONOCHROME camera, even if + * the camera device has RAW capability.</p> * * @see ACAMERA_SENSOR_REFERENCE_ILLUMINANT1 */ @@ -3537,6 +3659,8 @@ * (ACAMERA_SENSOR_REFERENCE_ILLUMINANT2).</p> * <p>This matrix will only be present if the second reference * illuminant is present.</p> + * <p>Starting from Android Q, this key will not be present for a MONOCHROME camera, even if + * the camera device has RAW capability.</p> * * @see ACAMERA_SENSOR_REFERENCE_ILLUMINANT2 */ @@ -3565,6 +3689,8 @@ * and the CIE XYZ colorspace when calculating this transform will * match the standard white point for the first reference illuminant * (i.e. no chromatic adaptation will be applied by this transform).</p> + * <p>Starting from Android Q, this key will not be present for a MONOCHROME camera, even if + * the camera device has RAW capability.</p> * * @see ACAMERA_SENSOR_REFERENCE_ILLUMINANT1 */ @@ -3595,6 +3721,8 @@ * (i.e. no chromatic adaptation will be applied by this transform).</p> * <p>This matrix will only be present if the second reference * illuminant is present.</p> + * <p>Starting from Android Q, this key will not be present for a MONOCHROME camera, even if + * the camera device has RAW capability.</p> * * @see ACAMERA_SENSOR_REFERENCE_ILLUMINANT2 */ @@ -3621,6 +3749,8 @@ * this matrix is chosen so that the standard white point for this reference * illuminant in the reference sensor colorspace is mapped to D50 in the * CIE XYZ colorspace.</p> + * <p>Starting from Android Q, this key will not be present for a MONOCHROME camera, even if + * the camera device has RAW capability.</p> * * @see ACAMERA_SENSOR_REFERENCE_ILLUMINANT1 */ @@ -3649,6 +3779,8 @@ * CIE XYZ colorspace.</p> * <p>This matrix will only be present if the second reference * illuminant is present.</p> + * <p>Starting from Android Q, this key will not be present for a MONOCHROME camera, even if + * the camera device has RAW capability.</p> * * @see ACAMERA_SENSOR_REFERENCE_ILLUMINANT2 */ @@ -3681,6 +3813,7 @@ * level values. For raw capture in particular, it is recommended to use * pixels from ACAMERA_SENSOR_OPTICAL_BLACK_REGIONS to calculate black * level values for each frame.</p> + * <p>For a MONOCHROME camera device, all of the 2x2 channels must have the same values.</p> * * @see ACAMERA_SENSOR_DYNAMIC_BLACK_LEVEL * @see ACAMERA_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT @@ -3775,6 +3908,8 @@ * used to interpolate between the provided color transforms when * processing raw sensor data.</p> * <p>The order of the values is R, G, B; where R is in the lowest index.</p> + * <p>Starting from Android Q, this key will not be present for a MONOCHROME camera, even if + * the camera device has RAW capability.</p> */ ACAMERA_SENSOR_NEUTRAL_COLOR_POINT = // rational[3] ACAMERA_SENSOR_START + 18, @@ -3805,6 +3940,8 @@ * that channel.</p> * <p>A more detailed description of the noise model can be found in the * Adobe DNG specification for the NoiseProfile tag.</p> + * <p>For a MONOCHROME camera, there is only one color channel. So the noise model coefficients + * will only contain one S and one O.</p> * * @see ACAMERA_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT */ @@ -3850,6 +3987,8 @@ * <li>R > 1.20 will require strong software correction to produce * a usuable image (>20% divergence).</li> * </ul> + * <p>Starting from Android Q, this key will not be present for a MONOCHROME camera, even if + * the camera device has RAW capability.</p> */ ACAMERA_SENSOR_GREEN_SPLIT = // float ACAMERA_SENSOR_START + 22, @@ -4002,6 +4141,7 @@ * layout key (see ACAMERA_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT), i.e. the * nth value given corresponds to the black level offset for the nth * color channel listed in the CFA.</p> + * <p>For a MONOCHROME camera, all of the 2x2 channels must have the same values.</p> * <p>This key will be available if ACAMERA_SENSOR_OPTICAL_BLACK_REGIONS is available or the * camera device advertises this key via {@link ACAMERA_REQUEST_AVAILABLE_RESULT_KEYS }.</p> * @@ -4104,7 +4244,8 @@ /** * <p>The arrangement of color filters on sensor; * represents the colors in the top-left 2x2 section of - * the sensor, in reading order.</p> + * the sensor, in reading order, for a Bayer camera, or the + * light spectrum it captures for MONOCHROME camera.</p> * * <p>Type: byte (acamera_metadata_enum_android_sensor_info_color_filter_arrangement_t)</p> * @@ -4573,13 +4714,13 @@ * (x,y) ϵ (0 ... N-1, 0 ... M-1) is the value of the shading map at * pixel ( ((W-1)/(N-1)) * x, ((H-1)/(M-1)) * y) for the four color channels. * The map is assumed to be bilinearly interpolated between the sample points.</p> - * <p>The channel order is [R, Geven, Godd, B], where Geven is the green - * channel for the even rows of a Bayer pattern, and Godd is the odd rows. + * <p>For a Bayer camera, the channel order is [R, Geven, Godd, B], where Geven is + * the green channel for the even rows of a Bayer pattern, and Godd is the odd rows. * The shading map is stored in a fully interleaved format, and its size * is provided in the camera static metadata by ACAMERA_LENS_INFO_SHADING_MAP_SIZE.</p> * <p>The shading map will generally have on the order of 30-40 rows and columns, * and will be smaller than 64x64.</p> - * <p>As an example, given a very small map defined as:</p> + * <p>As an example, given a very small map for a Bayer camera defined as:</p> * <pre><code>ACAMERA_LENS_INFO_SHADING_MAP_SIZE = [ 4, 3 ] * ACAMERA_STATISTICS_LENS_SHADING_MAP = * [ 1.3, 1.2, 1.15, 1.2, 1.2, 1.2, 1.15, 1.2, @@ -4599,6 +4740,17 @@ * image of a gray wall (using bicubic interpolation for visual quality) * as captured by the sensor gives:</p> * <p><img alt="Image of a uniform white wall (inverse shading map)" src="../images/camera2/metadata/android.statistics.lensShadingMap/inv_shading.png" /></p> + * <p>For a MONOCHROME camera, all of the 2x2 channels must have the same values. An example + * shading map for such a camera is defined as:</p> + * <pre><code>ACAMERA_LENS_INFO_SHADING_MAP_SIZE = [ 4, 3 ] + * ACAMERA_STATISTICS_LENS_SHADING_MAP = + * [ 1.3, 1.3, 1.3, 1.3, 1.2, 1.2, 1.2, 1.2, + * 1.1, 1.1, 1.1, 1.1, 1.3, 1.3, 1.3, 1.3, + * 1.2, 1.2, 1.2, 1.2, 1.1, 1.1, 1.1, 1.1, + * 1.0, 1.0, 1.0, 1.0, 1.2, 1.2, 1.2, 1.2, + * 1.3, 1.3, 1.3, 1.3, 1.2, 1.2, 1.2, 1.2, + * 1.2, 1.2, 1.2, 1.2, 1.3, 1.3, 1.3, 1.3 ] + * </code></pre> * <p>Note that the RAW image data might be subject to lens shading * correction not reported on this map. Query * ACAMERA_SENSOR_INFO_LENS_SHADING_APPLIED to see if RAW image data has subject @@ -4942,8 +5094,8 @@ * of points can be less than max (that is, the request doesn't have to * always provide a curve with number of points equivalent to * ACAMERA_TONEMAP_MAX_CURVE_POINTS).</p> - * <p>For devices with MONOCHROME capability, only red channel is used. Green and blue channels - * are ignored.</p> + * <p>For devices with MONOCHROME capability, all three channels must have the same set of + * control points.</p> * <p>A few examples, and their corresponding graphical mappings; these * only specify the red channel and the precision is limited to 4 * digits, for conciseness.</p> @@ -5417,9 +5569,121 @@ */ ACAMERA_DEPTH_DEPTH_IS_EXCLUSIVE = // byte (acamera_metadata_enum_android_depth_depth_is_exclusive_t) ACAMERA_DEPTH_START + 4, + /** + * <p>Recommended depth stream configurations for common client use cases.</p> + * + * <p>Type: int32[n*5]</p> + * + * <p>This tag may appear in: + * <ul> + * <li>ACameraMetadata from ACameraManager_getCameraCharacteristics</li> + * </ul></p> + * + * <p>Optional subset of the ACAMERA_DEPTH_AVAILABLE_DEPTH_STREAM_CONFIGURATIONS that + * contains similar tuples listed as + * (i.e. width, height, format, output/input stream, usecase bit field). + * Camera devices will be able to suggest particular depth stream configurations which are + * power and performance efficient for specific use cases. For more information about + * retrieving the suggestions see + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraCharacteristics.html#getRecommendedStreamConfigurationMap">CameraCharacteristics#getRecommendedStreamConfigurationMap</a>.</p> + * <p>For data representation please refer to + * ACAMERA_SCALER_AVAILABLE_RECOMMENDED_STREAM_CONFIGURATIONS</p> + * + * @see ACAMERA_DEPTH_AVAILABLE_DEPTH_STREAM_CONFIGURATIONS + * @see ACAMERA_SCALER_AVAILABLE_RECOMMENDED_STREAM_CONFIGURATIONS + */ + ACAMERA_DEPTH_AVAILABLE_RECOMMENDED_DEPTH_STREAM_CONFIGURATIONS = + // int32[n*5] + ACAMERA_DEPTH_START + 5, + /** + * <p>The available dynamic depth dataspace stream + * configurations that this camera device supports + * (i.e. format, width, height, output/input stream).</p> + * + * <p>Type: int32[n*4] (acamera_metadata_enum_android_depth_available_dynamic_depth_stream_configurations_t)</p> + * + * <p>This tag may appear in: + * <ul> + * <li>ACameraMetadata from ACameraManager_getCameraCharacteristics</li> + * </ul></p> + * + * <p>These are output stream configurations for use with + * dataSpace DYNAMIC_DEPTH. The configurations are + * listed as <code>(format, width, height, input?)</code> tuples.</p> + * <p>Only devices that support depth output for at least + * the HAL_PIXEL_FORMAT_Y16 dense depth map along with + * HAL_PIXEL_FORMAT_BLOB with the same size or size with + * the same aspect ratio can have dynamic depth dataspace + * stream configuration. ACAMERA_DEPTH_DEPTH_IS_EXCLUSIVE also + * needs to be set to FALSE.</p> + * + * @see ACAMERA_DEPTH_DEPTH_IS_EXCLUSIVE + */ + ACAMERA_DEPTH_AVAILABLE_DYNAMIC_DEPTH_STREAM_CONFIGURATIONS = + // int32[n*4] (acamera_metadata_enum_android_depth_available_dynamic_depth_stream_configurations_t) + ACAMERA_DEPTH_START + 6, + /** + * <p>This lists the minimum frame duration for each + * format/size combination for dynamic depth output streams.</p> + * + * <p>Type: int64[4*n]</p> + * + * <p>This tag may appear in: + * <ul> + * <li>ACameraMetadata from ACameraManager_getCameraCharacteristics</li> + * </ul></p> + * + * <p>This should correspond to the frame duration when only that + * stream is active, with all processing (typically in android.*.mode) + * set to either OFF or FAST.</p> + * <p>When multiple streams are used in a request, the minimum frame + * duration will be max(individual stream min durations).</p> + * <p>The minimum frame duration of a stream (of a particular format, size) + * is the same regardless of whether the stream is input or output.</p> + */ + ACAMERA_DEPTH_AVAILABLE_DYNAMIC_DEPTH_MIN_FRAME_DURATIONS = // int64[4*n] + ACAMERA_DEPTH_START + 7, + /** + * <p>This lists the maximum stall duration for each + * output format/size combination for dynamic depth streams.</p> + * + * <p>Type: int64[4*n]</p> + * + * <p>This tag may appear in: + * <ul> + * <li>ACameraMetadata from ACameraManager_getCameraCharacteristics</li> + * </ul></p> + * + * <p>A stall duration is how much extra time would get added + * to the normal minimum frame duration for a repeating request + * that has streams with non-zero stall.</p> + * <p>All dynamic depth output streams may have a nonzero stall + * duration.</p> + */ + ACAMERA_DEPTH_AVAILABLE_DYNAMIC_DEPTH_STALL_DURATIONS = // int64[4*n] + ACAMERA_DEPTH_START + 8, ACAMERA_DEPTH_END, /** + * <p>String containing the ids of the underlying physical cameras.</p> + * + * <p>Type: byte[n]</p> + * + * <p>This tag may appear in: + * <ul> + * <li>ACameraMetadata from ACameraManager_getCameraCharacteristics</li> + * </ul></p> + * + * <p>For a logical camera, this is concatenation of all underlying physical camera IDs. + * The null terminator for physical camera ID must be preserved so that the whole string + * can be tokenized using '\0' to generate list of physical camera IDs.</p> + * <p>For example, if the physical camera IDs of the logical camera are "2" and "3", the + * value of this tag will be ['2', '\0', '3', '\0'].</p> + * <p>The number of physical camera IDs must be no less than 2.</p> + */ + ACAMERA_LOGICAL_MULTI_CAMERA_PHYSICAL_IDS = // byte[n] + ACAMERA_LOGICAL_MULTI_CAMERA_START, + /** * <p>The accuracy of frame timestamp synchronization between physical cameras</p> * * <p>Type: byte (acamera_metadata_enum_android_logical_multi_camera_sensor_sync_type_t)</p> @@ -5437,9 +5701,37 @@ * <p>In both cases, all images generated for a particular capture request still carry the same * timestamps, so that they can be used to look up the matching frame number and * onCaptureStarted callback.</p> + * <p>This tag is only applicable if the logical camera device supports concurrent physical + * streams from different physical cameras.</p> */ ACAMERA_LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE = // byte (acamera_metadata_enum_android_logical_multi_camera_sensor_sync_type_t) ACAMERA_LOGICAL_MULTI_CAMERA_START + 1, + /** + * <p>String containing the ID of the underlying active physical camera.</p> + * + * <p>Type: byte</p> + * + * <p>This tag may appear in: + * <ul> + * <li>ACameraMetadata from ACameraCaptureSession_captureCallback_result callbacks</li> + * </ul></p> + * + * <p>The ID of the active physical camera that's backing the logical camera. All camera + * streams and metadata that are not physical camera specific will be originating from this + * physical camera.</p> + * <p>For a logical camera made up of physical cameras where each camera's lenses have + * different characteristics, the camera device may choose to switch between the physical + * cameras when application changes FOCAL_LENGTH or SCALER_CROP_REGION. + * At the time of lens switch, this result metadata reflects the new active physical camera + * ID.</p> + * <p>This key will be available if the camera device advertises this key via {@link ACAMERA_REQUEST_AVAILABLE_RESULT_KEYS }. + * When available, this must be one of valid physical IDs backing this logical multi-camera. + * If this key is not available for a logical multi-camera, the camera device implementation + * may still switch between different active physical cameras based on use case, but the + * current active physical camera information won't be available to the application.</p> + */ + ACAMERA_LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_ID = // byte + ACAMERA_LOGICAL_MULTI_CAMERA_START + 2, ACAMERA_LOGICAL_MULTI_CAMERA_END, /** @@ -5463,8 +5755,8 @@ * will not slow down capture rate when applying correction. FAST may be the same as OFF if * any correction at all would slow down capture rate. Every output stream will have a * similar amount of enhancement applied.</p> - * <p>The correction only applies to processed outputs such as YUV, JPEG, or DEPTH16; it is not - * applied to any RAW output.</p> + * <p>The correction only applies to processed outputs such as YUV, Y8, JPEG, or DEPTH16; it is + * not applied to any RAW output.</p> * <p>This control will be on by default on devices that support this control. Applications * disabling distortion correction need to pay extra attention with the coordinate system of * metering regions, crop region, and face rectangles. When distortion correction is OFF, @@ -5517,6 +5809,80 @@ ACAMERA_DISTORTION_CORRECTION_START + 1, ACAMERA_DISTORTION_CORRECTION_END, + /** + * <p>The available HEIC (ISO/IEC 23008-12) stream + * configurations that this camera device supports + * (i.e. format, width, height, output/input stream).</p> + * + * <p>Type: int32[n*4] (acamera_metadata_enum_android_heic_available_heic_stream_configurations_t)</p> + * + * <p>This tag may appear in: + * <ul> + * <li>ACameraMetadata from ACameraManager_getCameraCharacteristics</li> + * </ul></p> + * + * <p>The configurations are listed as <code>(format, width, height, input?)</code> tuples.</p> + * <p>If the camera device supports HEIC image format, it will support identical set of stream + * combinations involving HEIC image format, compared to the combinations involving JPEG + * image format as required by the device's hardware level and capabilities.</p> + * <p>All the static, control, and dynamic metadata tags related to JPEG apply to HEIC formats. + * Configuring JPEG and HEIC streams at the same time is not supported.</p> + * <p>All the configuration tuples <code>(format, width, height, input?)</code> will contain + * AIMAGE_FORMAT_HEIC format as OUTPUT only.</p> + */ + ACAMERA_HEIC_AVAILABLE_HEIC_STREAM_CONFIGURATIONS = // int32[n*4] (acamera_metadata_enum_android_heic_available_heic_stream_configurations_t) + ACAMERA_HEIC_START, + /** + * <p>This lists the minimum frame duration for each + * format/size combination for HEIC output formats.</p> + * + * <p>Type: int64[4*n]</p> + * + * <p>This tag may appear in: + * <ul> + * <li>ACameraMetadata from ACameraManager_getCameraCharacteristics</li> + * </ul></p> + * + * <p>This should correspond to the frame duration when only that + * stream is active, with all processing (typically in android.*.mode) + * set to either OFF or FAST.</p> + * <p>When multiple streams are used in a request, the minimum frame + * duration will be max(individual stream min durations).</p> + * <p>See ACAMERA_SENSOR_FRAME_DURATION and + * ACAMERA_SCALER_AVAILABLE_STALL_DURATIONS for more details about + * calculating the max frame rate.</p> + * + * @see ACAMERA_SCALER_AVAILABLE_STALL_DURATIONS + * @see ACAMERA_SENSOR_FRAME_DURATION + */ + ACAMERA_HEIC_AVAILABLE_HEIC_MIN_FRAME_DURATIONS = // int64[4*n] + ACAMERA_HEIC_START + 1, + /** + * <p>This lists the maximum stall duration for each + * output format/size combination for HEIC streams.</p> + * + * <p>Type: int64[4*n]</p> + * + * <p>This tag may appear in: + * <ul> + * <li>ACameraMetadata from ACameraManager_getCameraCharacteristics</li> + * </ul></p> + * + * <p>A stall duration is how much extra time would get added + * to the normal minimum frame duration for a repeating request + * that has streams with non-zero stall.</p> + * <p>This functions similarly to + * ACAMERA_SCALER_AVAILABLE_STALL_DURATIONS for HEIC + * streams.</p> + * <p>All HEIC output stream formats may have a nonzero stall + * duration.</p> + * + * @see ACAMERA_SCALER_AVAILABLE_STALL_DURATIONS + */ + ACAMERA_HEIC_AVAILABLE_HEIC_STALL_DURATIONS = // int64[4*n] + ACAMERA_HEIC_START + 2, + ACAMERA_HEIC_END, + } acamera_metadata_tag_t; /** @@ -6333,7 +6699,7 @@ /** * <p>Optimized for dim settings where the main light source - * is a flame.</p> + * is a candle.</p> */ ACAMERA_CONTROL_SCENE_MODE_CANDLELIGHT = 15, @@ -7011,6 +7377,10 @@ * <p>If this is supported, android.scaler.streamConfigurationMap will * additionally return a min frame duration that is greater than * zero for each supported size-format combination.</p> + * <p>For camera devices with LOGICAL_MULTI_CAMERA capability, when the underlying active + * physical camera switches, exposureTime, sensitivity, and lens properties may change + * even if AE/AF is locked. However, the overall auto exposure and auto focus experience + * for users will be consistent. Refer to LOGICAL_MULTI_CAMERA capability for details.</p> * * @see ACAMERA_BLACK_LEVEL_LOCK * @see ACAMERA_CONTROL_AE_LOCK @@ -7066,6 +7436,10 @@ * will accurately report the values applied by AWB in the result.</p> * <p>A given camera device may also support additional post-processing * controls, but this capability only covers the above list of controls.</p> + * <p>For camera devices with LOGICAL_MULTI_CAMERA capability, when underlying active + * physical camera switches, tonemap, white balance, and shading map may change even if + * awb is locked. However, the overall post-processing experience for users will be + * consistent. Refer to LOGICAL_MULTI_CAMERA capability for details.</p> * * @see ACAMERA_COLOR_CORRECTION_ABERRATION_MODE * @see ACAMERA_COLOR_CORRECTION_AVAILABLE_ABERRATION_MODES @@ -7137,19 +7511,20 @@ /** * <p>The camera device supports capturing high-resolution images at >= 20 frames per - * second, in at least the uncompressed YUV format, when post-processing settings are set - * to FAST. Additionally, maximum-resolution images can be captured at >= 10 frames - * per second. Here, 'high resolution' means at least 8 megapixels, or the maximum - * resolution of the device, whichever is smaller.</p> + * second, in at least the uncompressed YUV format, when post-processing settings are + * set to FAST. Additionally, all image resolutions less than 24 megapixels can be + * captured at >= 10 frames per second. Here, 'high resolution' means at least 8 + * megapixels, or the maximum resolution of the device, whichever is smaller.</p> * <p>More specifically, this means that at least one output {@link AIMAGE_FORMAT_YUV_420_888 } size listed in * {@link ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS } * is larger or equal to the 'high resolution' defined above, and can be captured at at * least 20 fps. For the largest {@link AIMAGE_FORMAT_YUV_420_888 } size listed in * {@link ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS }, - * camera device can capture this size for at least 10 frames per second. Also the - * ACAMERA_CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES entry lists at least one FPS range where - * the minimum FPS is >= 1 / minimumFrameDuration for the largest YUV_420_888 size.</p> - * <p>If the device supports the {@link AIMAGE_FORMAT_RAW10 }, {@link AIMAGE_FORMAT_RAW12 }, then those can also be + * camera device can capture this size for at least 10 frames per second if the size is + * less than 24 megapixels. Also the ACAMERA_CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES entry + * lists at least one FPS range where the minimum FPS is >= 1 / minimumFrameDuration + * for the largest YUV_420_888 size.</p> + * <p>If the device supports the {@link AIMAGE_FORMAT_RAW10 }, {@link AIMAGE_FORMAT_RAW12 }, {@link AIMAGE_FORMAT_Y8 }, then those can also be * captured at the same rate as the maximum-size YUV_420_888 resolution is.</p> * <p>In addition, the ACAMERA_SYNC_MAX_LATENCY field is guaranted to have a value between 0 * and 4, inclusive. ACAMERA_CONTROL_AE_LOCK_AVAILABLE and ACAMERA_CONTROL_AWB_LOCK_AVAILABLE @@ -7183,8 +7558,8 @@ * <li>The ACAMERA_DEPTH_DEPTH_IS_EXCLUSIVE entry is listed by this device.</li> * <li>As of Android P, the ACAMERA_LENS_POSE_REFERENCE entry is listed by this device.</li> * <li>A LIMITED camera with only the DEPTH_OUTPUT capability does not have to support - * normal YUV_420_888, JPEG, and PRIV-format outputs. It only has to support the DEPTH16 - * format.</li> + * normal YUV_420_888, Y8, JPEG, and PRIV-format outputs. It only has to support the + * DEPTH16 format.</li> * </ul> * <p>Generally, depth output operates at a slower frame rate than standard color capture, * so the DEPTH16 and DEPTH_POINT_CLOUD formats will commonly have a stall duration that @@ -7215,8 +7590,23 @@ ACAMERA_REQUEST_AVAILABLE_CAPABILITIES_MOTION_TRACKING = 10, /** - * <p>The camera device is a logical camera backed by two or more physical cameras that are - * also exposed to the application.</p> + * <p>The camera device is a logical camera backed by two or more physical cameras.</p> + * <p>In API level 28, the physical cameras must also be exposed to the application via + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraManager.html#getCameraIdList">CameraManager#getCameraIdList</a>.</p> + * <p>Starting from API level 29, some or all physical cameras may not be independently + * exposed to the application, in which case the physical camera IDs will not be + * available in <a href="https://developer.android.com/reference/android/hardware/camera2/CameraManager.html#getCameraIdList">CameraManager#getCameraIdList</a>. But the + * application can still query the physical cameras' characteristics by calling + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraManager.html#getCameraCharacteristics">CameraManager#getCameraCharacteristics</a>. Additionally, + * if a physical camera is hidden from camera ID list, the mandatory stream combinations + * for that physical camera must be supported through the logical camera using physical + * streams.</p> + * <p>Combinations of logical and physical streams, or physical streams from different + * physical cameras are not guaranteed. However, if the camera device supports + * {@link ACameraDevice_isSessionConfigurationSupported }, + * application must be able to query whether a stream combination involving physical + * streams is supported by calling + * {@link ACameraDevice_isSessionConfigurationSupported }.</p> * <p>Camera application shouldn't assume that there are at most 1 rear camera and 1 front * camera in the system. For an application that switches between front and back cameras, * the recommendation is to switch between the first rear camera and the first front @@ -7239,42 +7629,128 @@ * </li> * <li>The SENSOR_INFO_TIMESTAMP_SOURCE of the logical device and physical devices must be * the same.</li> - * <li>The logical camera device must be LIMITED or higher device.</li> + * <li>The logical camera must be LIMITED or higher device.</li> * </ul> - * <p>Both the logical camera device and its underlying physical devices support the - * mandatory stream combinations required for their device levels.</p> - * <p>Additionally, for each guaranteed stream combination, the logical camera supports:</p> + * <p>A logical camera device's dynamic metadata may contain + * ACAMERA_LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_ID to notify the application of the current + * active physical camera Id. An active physical camera is the physical camera from which + * the logical camera's main image data outputs (YUV or RAW) and metadata come from. + * In addition, this serves as an indication which physical camera is used to output to + * a RAW stream, or in case only physical cameras support RAW, which physical RAW stream + * the application should request.</p> + * <p>Logical camera's static metadata tags below describe the default active physical + * camera. An active physical camera is default if it's used when application directly + * uses requests built from a template. All templates will default to the same active + * physical camera.</p> * <ul> - * <li>For each guaranteed stream combination, the logical camera supports replacing one - * logical {@link AIMAGE_FORMAT_YUV_420_888 YUV_420_888} - * or raw stream with two physical streams of the same size and format, each from a - * separate physical camera, given that the size and format are supported by both - * physical cameras.</li> - * <li>If the logical camera doesn't advertise RAW capability, but the underlying physical - * cameras do, the logical camera will support guaranteed stream combinations for RAW - * capability, except that the RAW streams will be physical streams, each from a separate - * physical camera. This is usually the case when the physical cameras have different - * sensor sizes.</li> + * <li>ACAMERA_SENSOR_INFO_SENSITIVITY_RANGE</li> + * <li>ACAMERA_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT</li> + * <li>ACAMERA_SENSOR_INFO_EXPOSURE_TIME_RANGE</li> + * <li>ACAMERA_SENSOR_INFO_MAX_FRAME_DURATION</li> + * <li>ACAMERA_SENSOR_INFO_PHYSICAL_SIZE</li> + * <li>ACAMERA_SENSOR_INFO_WHITE_LEVEL</li> + * <li>ACAMERA_SENSOR_INFO_LENS_SHADING_APPLIED</li> + * <li>ACAMERA_SENSOR_REFERENCE_ILLUMINANT1</li> + * <li>ACAMERA_SENSOR_REFERENCE_ILLUMINANT2</li> + * <li>ACAMERA_SENSOR_CALIBRATION_TRANSFORM1</li> + * <li>ACAMERA_SENSOR_CALIBRATION_TRANSFORM2</li> + * <li>ACAMERA_SENSOR_COLOR_TRANSFORM1</li> + * <li>ACAMERA_SENSOR_COLOR_TRANSFORM2</li> + * <li>ACAMERA_SENSOR_FORWARD_MATRIX1</li> + * <li>ACAMERA_SENSOR_FORWARD_MATRIX2</li> + * <li>ACAMERA_SENSOR_BLACK_LEVEL_PATTERN</li> + * <li>ACAMERA_SENSOR_MAX_ANALOG_SENSITIVITY</li> + * <li>ACAMERA_SENSOR_OPTICAL_BLACK_REGIONS</li> + * <li>ACAMERA_SENSOR_AVAILABLE_TEST_PATTERN_MODES</li> + * <li>ACAMERA_LENS_INFO_HYPERFOCAL_DISTANCE</li> + * <li>ACAMERA_LENS_INFO_MINIMUM_FOCUS_DISTANCE</li> + * <li>ACAMERA_LENS_INFO_FOCUS_DISTANCE_CALIBRATION</li> + * <li>ACAMERA_LENS_POSE_ROTATION</li> + * <li>ACAMERA_LENS_POSE_TRANSLATION</li> + * <li>ACAMERA_LENS_INTRINSIC_CALIBRATION</li> + * <li>ACAMERA_LENS_POSE_REFERENCE</li> + * <li>ACAMERA_LENS_DISTORTION</li> * </ul> - * <p>Using physical streams in place of a logical stream of the same size and format will - * not slow down the frame rate of the capture, as long as the minimum frame duration - * of the physical and logical streams are the same.</p> + * <p>The field of view of all non-RAW physical streams must be the same or as close as + * possible to that of non-RAW logical streams. If the requested FOV is outside of the + * range supported by the physical camera, the physical stream for that physical camera + * will use either the maximum or minimum scaler crop region, depending on which one is + * closer to the requested FOV. For example, for a logical camera with wide-tele lens + * configuration where the wide lens is the default, if the logical camera's crop region + * is set to maximum, the physical stream for the tele lens will be configured to its + * maximum crop region. On the other hand, if the logical camera has a normal-wide lens + * configuration where the normal lens is the default, when the logical camera's crop + * region is set to maximum, the FOV of the logical streams will be that of the normal + * lens. The FOV of the physical streams for the wide lens will be the same as the + * logical stream, by making the crop region smaller than its active array size to + * compensate for the smaller focal length.</p> + * <p>Even if the underlying physical cameras have different RAW characteristics (such as + * size or CFA pattern), a logical camera can still advertise RAW capability. In this + * case, when the application configures a RAW stream, the camera device will make sure + * the active physical camera will remain active to ensure consistent RAW output + * behavior, and not switch to other physical cameras.</p> + * <p>The capture request and result metadata tags required for backward compatible camera + * functionalities will be solely based on the logical camera capabiltity. On the other + * hand, the use of manual capture controls (sensor or post-processing) with a + * logical camera may result in unexpected behavior when the HAL decides to switch + * between physical cameras with different characteristics under the hood. For example, + * when the application manually sets exposure time and sensitivity while zooming in, + * the brightness of the camera images may suddenly change because HAL switches from one + * physical camera to the other.</p> * * @see ACAMERA_LENS_DISTORTION + * @see ACAMERA_LENS_INFO_FOCUS_DISTANCE_CALIBRATION + * @see ACAMERA_LENS_INFO_HYPERFOCAL_DISTANCE + * @see ACAMERA_LENS_INFO_MINIMUM_FOCUS_DISTANCE * @see ACAMERA_LENS_INTRINSIC_CALIBRATION * @see ACAMERA_LENS_POSE_REFERENCE * @see ACAMERA_LENS_POSE_ROTATION * @see ACAMERA_LENS_POSE_TRANSLATION + * @see ACAMERA_LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_ID * @see ACAMERA_LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE + * @see ACAMERA_SENSOR_AVAILABLE_TEST_PATTERN_MODES + * @see ACAMERA_SENSOR_BLACK_LEVEL_PATTERN + * @see ACAMERA_SENSOR_CALIBRATION_TRANSFORM1 + * @see ACAMERA_SENSOR_CALIBRATION_TRANSFORM2 + * @see ACAMERA_SENSOR_COLOR_TRANSFORM1 + * @see ACAMERA_SENSOR_COLOR_TRANSFORM2 + * @see ACAMERA_SENSOR_FORWARD_MATRIX1 + * @see ACAMERA_SENSOR_FORWARD_MATRIX2 + * @see ACAMERA_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT + * @see ACAMERA_SENSOR_INFO_EXPOSURE_TIME_RANGE + * @see ACAMERA_SENSOR_INFO_LENS_SHADING_APPLIED + * @see ACAMERA_SENSOR_INFO_MAX_FRAME_DURATION + * @see ACAMERA_SENSOR_INFO_PHYSICAL_SIZE + * @see ACAMERA_SENSOR_INFO_SENSITIVITY_RANGE + * @see ACAMERA_SENSOR_INFO_WHITE_LEVEL + * @see ACAMERA_SENSOR_MAX_ANALOG_SENSITIVITY + * @see ACAMERA_SENSOR_OPTICAL_BLACK_REGIONS + * @see ACAMERA_SENSOR_REFERENCE_ILLUMINANT1 + * @see ACAMERA_SENSOR_REFERENCE_ILLUMINANT2 */ ACAMERA_REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA = 11, /** * <p>The camera device is a monochrome camera that doesn't contain a color filter array, - * and the pixel values on U and V planes are all 128.</p> + * and for YUV_420_888 stream, the pixel values on U and V planes are all 128.</p> + * <p>A MONOCHROME camera must support the guaranteed stream combinations required for + * its device level and capabilities. Additionally, if the monochrome camera device + * supports Y8 format, all mandatory stream combination requirements related to {@link AIMAGE_FORMAT_YUV_420_888 YUV_420_888} apply + * to {@link AIMAGE_FORMAT_Y8 Y8} as well. There are no + * mandatory stream combination requirements with regard to + * {@link AIMAGE_FORMAT_Y8 Y8} for Bayer camera devices.</p> + * <p>Starting from Android Q, the SENSOR_INFO_COLOR_FILTER_ARRANGEMENT of a MONOCHROME + * camera will be either MONO or NIR.</p> */ ACAMERA_REQUEST_AVAILABLE_CAPABILITIES_MONOCHROME = 12, + /** + * <p>The camera device is capable of writing image data into a region of memory + * inaccessible to Android userspace or the Android kernel, and only accessible to + * trusted execution environments (TEE).</p> + */ + ACAMERA_REQUEST_AVAILABLE_CAPABILITIES_SECURE_IMAGE_DATA = 13, + } acamera_metadata_enum_android_request_available_capabilities_t; @@ -7300,6 +7776,81 @@ } acamera_metadata_enum_android_scaler_cropping_type_t; +// ACAMERA_SCALER_AVAILABLE_RECOMMENDED_STREAM_CONFIGURATIONS +typedef enum acamera_metadata_enum_acamera_scaler_available_recommended_stream_configurations { + /** + * <p>Preview must only include non-stalling processed stream configurations with + * output formats like + * {@link AIMAGE_FORMAT_YUV_420_888 }, + * {@link AIMAGE_FORMAT_PRIVATE }, etc.</p> + */ + ACAMERA_SCALER_AVAILABLE_RECOMMENDED_STREAM_CONFIGURATIONS_PREVIEW + = 0x0, + + /** + * <p>Video record must include stream configurations that match the advertised + * supported media profiles <a href="https://developer.android.com/reference/android/media/CamcorderProfile.html">CamcorderProfile</a> with + * IMPLEMENTATION_DEFINED format.</p> + */ + ACAMERA_SCALER_AVAILABLE_RECOMMENDED_STREAM_CONFIGURATIONS_RECORD + = 0x1, + + /** + * <p>Video snapshot must include stream configurations at least as big as + * the maximum RECORD resolutions and only with + * {@link AIMAGE_FORMAT_JPEG JPEG output format}. + * Additionally the configurations shouldn't cause preview glitches and also be able to + * run at 30 fps.</p> + */ + ACAMERA_SCALER_AVAILABLE_RECOMMENDED_STREAM_CONFIGURATIONS_VIDEO_SNAPSHOT + = 0x2, + + /** + * <p>Recommended snapshot stream configurations must include at least one with + * size close to ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE and + * {@link AIMAGE_FORMAT_JPEG JPEG output format}. + * Taking into account restrictions on aspect ratio, alignment etc. the area of the + * maximum suggested size shouldn’t be less than 97% of the sensor array size area.</p> + * + * @see ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE + */ + ACAMERA_SCALER_AVAILABLE_RECOMMENDED_STREAM_CONFIGURATIONS_SNAPSHOT + = 0x3, + + /** + * <p>If supported, recommended input stream configurations must only be advertised with + * ZSL along with other processed and/or stalling output formats.</p> + */ + ACAMERA_SCALER_AVAILABLE_RECOMMENDED_STREAM_CONFIGURATIONS_ZSL = 0x4, + + /** + * <p>If supported, recommended raw stream configurations must only include RAW based + * output formats.</p> + */ + ACAMERA_SCALER_AVAILABLE_RECOMMENDED_STREAM_CONFIGURATIONS_RAW = 0x5, + + /** + * <p>If supported, the recommended low latency stream configurations must have + * end-to-end latency that does not exceed 200 ms. under standard operating conditions + * (reasonable light levels, not loaded system) and using template + * TEMPLATE_STILL_CAPTURE. This is primarily for listing configurations for the + * {@link AIMAGE_FORMAT_JPEG JPEG output format} + * however other supported output formats can be added as well.</p> + */ + ACAMERA_SCALER_AVAILABLE_RECOMMENDED_STREAM_CONFIGURATIONS_LOW_LATENCY_SNAPSHOT + = 0x6, + + ACAMERA_SCALER_AVAILABLE_RECOMMENDED_STREAM_CONFIGURATIONS_PUBLIC_END + = 0x7, + + /** + * <p>Vendor defined use cases. These depend on the vendor implementation.</p> + */ + ACAMERA_SCALER_AVAILABLE_RECOMMENDED_STREAM_CONFIGURATIONS_VENDOR_START + = 0x18, + +} acamera_metadata_enum_android_scaler_available_recommended_stream_configurations_t; + // ACAMERA_SENSOR_REFERENCE_ILLUMINANT1 typedef enum acamera_metadata_enum_acamera_sensor_reference_illuminant1 { @@ -7476,6 +8027,21 @@ */ ACAMERA_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_RGB = 4, + /** + * <p>Sensor doesn't have any Bayer color filter. + * Such sensor captures visible light in monochrome. The exact weighting and + * wavelengths captured is not specified, but generally only includes the visible + * frequencies. This value implies a MONOCHROME camera.</p> + */ + ACAMERA_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_MONO = 5, + + /** + * <p>Sensor has a near infrared filter capturing light with wavelength between + * roughly 750nm and 1400nm, and the same filter covers the whole sensor array. This + * value implies a MONOCHROME camera.</p> + */ + ACAMERA_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_NIR = 6, + } acamera_metadata_enum_android_sensor_info_color_filter_arrangement_t; // ACAMERA_SENSOR_INFO_TIMESTAMP_SOURCE @@ -7764,6 +8330,7 @@ * fire the flash for flash power metering during precapture, and then fire the flash * for the final capture, if a flash is available on the device and the AE mode is set to * enable the flash.</p> + * <p>Devices that initially shipped with Android version <a href="https://developer.android.com/reference/android/os/Build/VERSION_CODES.html#Q">Q</a> or newer will not include any LEGACY-level devices.</p> * * @see ACAMERA_CONTROL_AE_PRECAPTURE_TRIGGER * @see ACAMERA_REQUEST_AVAILABLE_CAPABILITIES @@ -7904,6 +8471,16 @@ } acamera_metadata_enum_android_depth_depth_is_exclusive_t; +// ACAMERA_DEPTH_AVAILABLE_DYNAMIC_DEPTH_STREAM_CONFIGURATIONS +typedef enum acamera_metadata_enum_acamera_depth_available_dynamic_depth_stream_configurations { + ACAMERA_DEPTH_AVAILABLE_DYNAMIC_DEPTH_STREAM_CONFIGURATIONS_OUTPUT + = 0, + + ACAMERA_DEPTH_AVAILABLE_DYNAMIC_DEPTH_STREAM_CONFIGURATIONS_INPUT + = 1, + +} acamera_metadata_enum_android_depth_available_dynamic_depth_stream_configurations_t; + // ACAMERA_LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE typedef enum acamera_metadata_enum_acamera_logical_multi_camera_sensor_sync_type { @@ -7947,6 +8524,16 @@ } acamera_metadata_enum_android_distortion_correction_mode_t; +// ACAMERA_HEIC_AVAILABLE_HEIC_STREAM_CONFIGURATIONS +typedef enum acamera_metadata_enum_acamera_heic_available_heic_stream_configurations { + ACAMERA_HEIC_AVAILABLE_HEIC_STREAM_CONFIGURATIONS_OUTPUT = 0, + + ACAMERA_HEIC_AVAILABLE_HEIC_STREAM_CONFIGURATIONS_INPUT = 1, + +} acamera_metadata_enum_android_heic_available_heic_stream_configurations_t; + + + #endif /* __ANDROID_API__ >= 24 */ __END_DECLS
diff --git a/camera/ndk/include/camera/NdkCameraWindowType.h b/camera/ndk/include/camera/NdkCameraWindowType.h new file mode 100644 index 0000000..99f67e9 --- /dev/null +++ b/camera/ndk/include/camera/NdkCameraWindowType.h
@@ -0,0 +1,53 @@ +/* + * 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 _NDK_CAMERA_WINDOW_TYPE_H +#define _NDK_CAMERA_WINDOW_TYPE_H + +/** + * @addtogroup Camera + * @{ + */ + +/** + * @file NdkCameraWindowType.h + */ + +/* + * This file defines an NDK API. + * Do not remove methods. + * Do not change method signatures. + * Do not change the value of constants. + * Do not change the size of any of the classes defined in here. + * Do not reference types that are not part of the NDK. + * Do not #include files that aren't part of the NDK. + */ + +/** + * This file defines the window type used by NDK and the VNDK variants of the + * camera2 NDK. This enables us to share the api definition headers and avoid + * code duplication (since the VNDK variant doesn't use ANativeWindow unlike the + * NDK variant). + */ +#ifdef __ANDROID_VNDK__ +#include <cutils/native_handle.h> +typedef native_handle_t ACameraWindowType; +#else +#include <android/native_window.h> +typedef ANativeWindow ACameraWindowType; +#endif + +#endif //_NDK_CAMERA_WINDOW_TYPE_H
diff --git a/camera/ndk/include/camera/NdkCaptureRequest.h b/camera/ndk/include/camera/NdkCaptureRequest.h index 4961ce3..d3f8826 100644 --- a/camera/ndk/include/camera/NdkCaptureRequest.h +++ b/camera/ndk/include/camera/NdkCaptureRequest.h
@@ -35,9 +35,9 @@ #include <sys/cdefs.h> -#include <android/native_window.h> #include "NdkCameraError.h" #include "NdkCameraMetadata.h" +#include "NdkCameraWindowType.h" #ifndef _NDK_CAPTURE_REQUEST_H #define _NDK_CAPTURE_REQUEST_H @@ -101,7 +101,8 @@ * * @see ACaptureRequest_addTarget */ -camera_status_t ACameraOutputTarget_create(ANativeWindow* window, ACameraOutputTarget** output); +camera_status_t ACameraOutputTarget_create(ACameraWindowType* window, + ACameraOutputTarget** output) __INTRODUCED_IN(24); /** * Free a ACameraOutputTarget object. @@ -110,7 +111,7 @@ * * @see ACameraOutputTarget_create */ -void ACameraOutputTarget_free(ACameraOutputTarget* output); +void ACameraOutputTarget_free(ACameraOutputTarget* output) __INTRODUCED_IN(24); /** * Add an {@link ACameraOutputTarget} object to {@link ACaptureRequest}. @@ -123,7 +124,7 @@ * <li>{@link ACAMERA_ERROR_INVALID_PARAMETER} if request or output is NULL.</li></ul> */ camera_status_t ACaptureRequest_addTarget(ACaptureRequest* request, - const ACameraOutputTarget* output); + const ACameraOutputTarget* output) __INTRODUCED_IN(24); /** * Remove an {@link ACameraOutputTarget} object from {@link ACaptureRequest}. @@ -138,7 +139,7 @@ * <li>{@link ACAMERA_ERROR_INVALID_PARAMETER} if request or output is NULL.</li></ul> */ camera_status_t ACaptureRequest_removeTarget(ACaptureRequest* request, - const ACameraOutputTarget* output); + const ACameraOutputTarget* output) __INTRODUCED_IN(24); /** * Get a metadata entry from input {@link ACaptureRequest}. @@ -158,7 +159,7 @@ * entry of input tag value.</li></ul> */ camera_status_t ACaptureRequest_getConstEntry( - const ACaptureRequest* request, uint32_t tag, ACameraMetadata_const_entry* entry); + const ACaptureRequest* request, uint32_t tag, ACameraMetadata_const_entry* entry) __INTRODUCED_IN(24); /* * List all the entry tags in input {@link ACaptureRequest}. @@ -179,7 +180,7 @@ * <li>{@link ACAMERA_ERROR_UNKNOWN} if the method fails for some other reasons.</li></ul> */ camera_status_t ACaptureRequest_getAllTags( - const ACaptureRequest* request, /*out*/int32_t* numTags, /*out*/const uint32_t** tags); + const ACaptureRequest* request, /*out*/int32_t* numTags, /*out*/const uint32_t** tags) __INTRODUCED_IN(24); /** * Set/change a camera capture control entry with unsigned 8 bits data type. @@ -198,7 +199,7 @@ * the tag is not controllable by application.</li></ul> */ camera_status_t ACaptureRequest_setEntry_u8( - ACaptureRequest* request, uint32_t tag, uint32_t count, const uint8_t* data); + ACaptureRequest* request, uint32_t tag, uint32_t count, const uint8_t* data) __INTRODUCED_IN(24); /** * Set/change a camera capture control entry with signed 32 bits data type. @@ -217,7 +218,7 @@ * the tag is not controllable by application.</li></ul> */ camera_status_t ACaptureRequest_setEntry_i32( - ACaptureRequest* request, uint32_t tag, uint32_t count, const int32_t* data); + ACaptureRequest* request, uint32_t tag, uint32_t count, const int32_t* data) __INTRODUCED_IN(24); /** * Set/change a camera capture control entry with float data type. @@ -236,7 +237,7 @@ * the tag is not controllable by application.</li></ul> */ camera_status_t ACaptureRequest_setEntry_float( - ACaptureRequest* request, uint32_t tag, uint32_t count, const float* data); + ACaptureRequest* request, uint32_t tag, uint32_t count, const float* data) __INTRODUCED_IN(24); /** * Set/change a camera capture control entry with signed 64 bits data type. @@ -255,7 +256,7 @@ * the tag is not controllable by application.</li></ul> */ camera_status_t ACaptureRequest_setEntry_i64( - ACaptureRequest* request, uint32_t tag, uint32_t count, const int64_t* data); + ACaptureRequest* request, uint32_t tag, uint32_t count, const int64_t* data) __INTRODUCED_IN(24); /** * Set/change a camera capture control entry with double data type. @@ -274,7 +275,7 @@ * the tag is not controllable by application.</li></ul> */ camera_status_t ACaptureRequest_setEntry_double( - ACaptureRequest* request, uint32_t tag, uint32_t count, const double* data); + ACaptureRequest* request, uint32_t tag, uint32_t count, const double* data) __INTRODUCED_IN(24); /** * Set/change a camera capture control entry with rational data type. @@ -294,14 +295,14 @@ */ camera_status_t ACaptureRequest_setEntry_rational( ACaptureRequest* request, uint32_t tag, uint32_t count, - const ACameraMetadata_rational* data); + const ACameraMetadata_rational* data) __INTRODUCED_IN(24); /** * Free a {@link ACaptureRequest} structure. * * @param request the {@link ACaptureRequest} to be freed. */ -void ACaptureRequest_free(ACaptureRequest* request); +void ACaptureRequest_free(ACaptureRequest* request) __INTRODUCED_IN(24); #endif /* __ANDROID_API__ >= 24 */ @@ -325,7 +326,7 @@ * <li>{@link ACAMERA_ERROR_INVALID_PARAMETER} if request is NULL.</li></ul> */ camera_status_t ACaptureRequest_setUserContext( - ACaptureRequest* request, void* context); + ACaptureRequest* request, void* context) __INTRODUCED_IN(28); /** * Get the user context pointer of the {@link ACaptureRequest} @@ -341,7 +342,7 @@ * <li>{@link ACAMERA_ERROR_INVALID_PARAMETER} if request is NULL.</li></ul> */ camera_status_t ACaptureRequest_getUserContext( - const ACaptureRequest* request, /*out*/void** context); + const ACaptureRequest* request, /*out*/void** context) __INTRODUCED_IN(28); /** * Create a copy of input {@link ACaptureRequest}. @@ -353,10 +354,223 @@ * * @return a valid ACaptureRequest pointer or NULL if the input request cannot be copied. */ -ACaptureRequest* ACaptureRequest_copy(const ACaptureRequest* src); +ACaptureRequest* ACaptureRequest_copy(const ACaptureRequest* src) __INTRODUCED_IN(28); #endif /* __ANDROID_API__ >= 28 */ +#if __ANDROID_API__ >= 29 + +/** + * Get a metadata entry from input {@link ACaptureRequest} for + * a physical camera backing a logical multi-camera device. + * + * <p>Same as ACaptureRequest_getConstEntry, except that if the key is contained + * in {@link ACAMERA_REQUEST_AVAILABLE_PHYSICAL_CAMERA_REQUEST_KEYS}, this function + * returns the entry set by ACaptureRequest_setEntry_physicalCamera_* class of + * functions on the particular physical camera.</p> + * + * @param request the {@link ACaptureRequest} of interest created by + * {@link ACameraDevice_createCaptureRequest_withPhysicalIds}. + * @param physicalId one of the physical Ids used when request is created with + * {@link ACameraDevice_createCaptureRequest_withPhysicalIds}. + * @param tag the capture request metadata tag in + * {@link ACAMERA_REQUEST_AVAILABLE_PHYSICAL_CAMERA_REQUEST_KEYS} + * that is set by ACaptureRequest_setEntry_physicalCamera_* class of functions. + * + * @return <ul> + * <li>{@link ACAMERA_OK} if the method call succeeds.</li> + * <li>{@link ACAMERA_ERROR_INVALID_PARAMETER} if metadata, physicalId, or entry is NULL, + * physicalId is not one of the Ids used in creating the request, or if the capture + * request is a regular request with no physical Ids at all.</li> + * <li>{@link ACAMERA_ERROR_METADATA_NOT_FOUND} if the capture request does not contain an + * entry of input tag value.</li></ul> + */ +camera_status_t ACaptureRequest_getConstEntry_physicalCamera( + const ACaptureRequest* request, const char* physicalId, uint32_t tag, + ACameraMetadata_const_entry* entry) __INTRODUCED_IN(29); + +/** + * Set/change a camera capture control entry with unsigned 8 bits data type for + * a physical camera backing a logical multi-camera device. + * + * <p>Same as ACaptureRequest_setEntry_u8, except that if {@link tag} is contained + * in {@link ACAMERA_REQUEST_AVAILABLE_PHYSICAL_CAMERA_REQUEST_KEYS}, this function + * sets the entry for a particular physical sub-camera backing the logical multi-camera. + * If {@link tag} is not contained in + * {@link ACAMERA_REQUEST_AVAILABLE_PHYSICAL_CAMERA_REQUEST_KEYS}, the key will be ignored + * by the camera device.</p> + * + * @param request the {@link ACaptureRequest} of interest created by + * {@link ACameraDevice_createCaptureRequest_withPhysicalIds}. + * @param physicalId one of the physical Ids used when request is created with + * {@link ACameraDevice_createCaptureRequest_withPhysicalIds}. + * @param tag one of the capture request metadata tags in + * {@link ACAMERA_REQUEST_AVAILABLE_PHYSICAL_CAMERA_REQUEST_KEYS}. + * + * @return <ul> + * <li>{@link ACAMERA_OK} if the method call succeeds.</li> + * <li>{@link ACAMERA_ERROR_INVALID_PARAMETER} if request or physicalId is NULL, count is + * larger than zero while data is NULL, the data type of the tag is not unsigned 8 bits, + * the tag is not controllable by application, physicalId is not one of the Ids used + * in creating the request, or if the capture request is a regular request with no + * physical Ids at all.</li></ul> + */ +camera_status_t ACaptureRequest_setEntry_physicalCamera_u8( + ACaptureRequest* request, const char* physicalId, uint32_t tag, + uint32_t count, const uint8_t* data) __INTRODUCED_IN(29); + +/** + * Set/change a camera capture control entry with signed 32 bits data type for + * a physical camera of a logical multi-camera device. + * + * <p>Same as ACaptureRequest_setEntry_i32, except that if {@link tag} is contained + * in {@link ACAMERA_REQUEST_AVAILABLE_PHYSICAL_CAMERA_REQUEST_KEYS}, this function + * sets the entry for a particular physical sub-camera backing the logical multi-camera. + * If {@link tag} is not contained in + * {@link ACAMERA_REQUEST_AVAILABLE_PHYSICAL_CAMERA_REQUEST_KEYS}, the key will be ignored + * by the camera device.</p> + * + * @param request the {@link ACaptureRequest} of interest created by + * {@link ACameraDevice_createCaptureRequest_withPhysicalIds}. + * @param physicalId one of the physical Ids used when request is created with + * {@link ACameraDevice_createCaptureRequest_withPhysicalIds}. + * @param tag one of the capture request metadata tags in + * {@link ACAMERA_REQUEST_AVAILABLE_PHYSICAL_CAMERA_REQUEST_KEYS}. + * + * @return <ul> + * <li>{@link ACAMERA_OK} if the method call succeeds.</li> + * <li>{@link ACAMERA_ERROR_INVALID_PARAMETER} if request or physicalId is NULL, count is + * larger than zero while data is NULL, the data type of the tag is not signed 32 bits, + * the tag is not controllable by application, physicalId is not one of the Ids used + * in creating the request, or if the capture request is a regular request with no + * physical Ids at all.</li></ul> + */ +camera_status_t ACaptureRequest_setEntry_physicalCamera_i32( + ACaptureRequest* request, const char* physicalId, uint32_t tag, + uint32_t count, const int32_t* data) __INTRODUCED_IN(29); + +/** + * Set/change a camera capture control entry with float data type for + * a physical camera of a logical multi-camera device. + * + * <p>Same as ACaptureRequest_setEntry_float, except that if {@link tag} is contained + * in {@link ACAMERA_REQUEST_AVAILABLE_PHYSICAL_CAMERA_REQUEST_KEYS}, this function + * sets the entry for a particular physical sub-camera backing the logical multi-camera. + * If {@link tag} is not contained in + * {@link ACAMERA_REQUEST_AVAILABLE_PHYSICAL_CAMERA_REQUEST_KEYS}, the key will be ignored + * by the camera device.</p> + * + * @param request the {@link ACaptureRequest} of interest created by + * {@link ACameraDevice_createCaptureRequest_withPhysicalIds}. + * @param physicalId one of the physical Ids used when request is created with + * {@link ACameraDevice_createCaptureRequest_withPhysicalIds}. + * @param tag one of the capture request metadata tags in + * {@link ACAMERA_REQUEST_AVAILABLE_PHYSICAL_CAMERA_REQUEST_KEYS}. + * + * @return <ul> + * <li>{@link ACAMERA_OK} if the method call succeeds.</li> + * <li>{@link ACAMERA_ERROR_INVALID_PARAMETER} if request or physicalId is NULL, count is + * larger than zero while data is NULL, the data type of the tag is not float, + * the tag is not controllable by application, physicalId is not one of the Ids used + * in creating the request, or if the capture request is a regular request with no + * physical Ids at all.</li></ul> + */ +camera_status_t ACaptureRequest_setEntry_physicalCamera_float( + ACaptureRequest* request, const char* physicalId, uint32_t tag, + uint32_t count, const float* data) __INTRODUCED_IN(29); + +/** + * Set/change a camera capture control entry with signed 64 bits data type for + * a physical camera of a logical multi-camera device. + * + * <p>Same as ACaptureRequest_setEntry_i64, except that if {@link tag} is contained + * in {@link ACAMERA_REQUEST_AVAILABLE_PHYSICAL_CAMERA_REQUEST_KEYS}, this function + * sets the entry for a particular physical sub-camera backing the logical multi-camera. + * If {@link tag} is not contained in + * {@link ACAMERA_REQUEST_AVAILABLE_PHYSICAL_CAMERA_REQUEST_KEYS}, the key will be ignored + * by the camera device.</p> + * + * @param request the {@link ACaptureRequest} of interest created by + * {@link ACameraDevice_createCaptureRequest_withPhysicalIds}. + * @param physicalId one of the physical Ids used when request is created with + * {@link ACameraDevice_createCaptureRequest_withPhysicalIds}. + * @param tag one of the capture request metadata tags in + * {@link ACAMERA_REQUEST_AVAILABLE_PHYSICAL_CAMERA_REQUEST_KEYS}. + * + * @return <ul> + * <li>{@link ACAMERA_OK} if the method call succeeds.</li> + * <li>{@link ACAMERA_ERROR_INVALID_PARAMETER} if request or physicalId is NULL, count is + * larger than zero while data is NULL, the data type of the tag is not signed 64 bits, + * the tag is not controllable by application, physicalId is not one of the Ids used + * in creating the request, or if the capture request is a regular request with no + * physical Ids at all.</li></ul> + */ +camera_status_t ACaptureRequest_setEntry_physicalCamera_i64( + ACaptureRequest* request, const char* physicalId, uint32_t tag, + uint32_t count, const int64_t* data) __INTRODUCED_IN(29); + +/** + * Set/change a camera capture control entry with double data type for + * a physical camera of a logical multi-camera device. + * + * <p>Same as ACaptureRequest_setEntry_double, except that if {@link tag} is contained + * in {@link ACAMERA_REQUEST_AVAILABLE_PHYSICAL_CAMERA_REQUEST_KEYS}, this function + * sets the entry for a particular physical sub-camera backing the logical multi-camera. + * If {@link tag} is not contained in + * {@link ACAMERA_REQUEST_AVAILABLE_PHYSICAL_CAMERA_REQUEST_KEYS}, the key will be ignored + * by the camera device.</p> + * + * @param request the {@link ACaptureRequest} of interest created by + * {@link ACameraDevice_createCaptureRequest_withPhysicalIds}. + * @param physicalId one of the physical Ids used when request is created with + * {@link ACameraDevice_createCaptureRequest_withPhysicalIds}. + * @param tag one of the capture request metadata tags in + * {@link ACAMERA_REQUEST_AVAILABLE_PHYSICAL_CAMERA_REQUEST_KEYS}. + * + * @return <ul> + * <li>{@link ACAMERA_OK} if the method call succeeds.</li> + * <li>{@link ACAMERA_ERROR_INVALID_PARAMETER} if request or physicalId is NULL, count is + * larger than zero while data is NULL, the data type of the tag is not double, + * the tag is not controllable by application, physicalId is not one of the Ids used + * in creating the request, or if the capture request is a regular request with no + * physical Ids at all.</li></ul> + */ +camera_status_t ACaptureRequest_setEntry_physicalCamera_double( + ACaptureRequest* request, const char* physicalId, uint32_t tag, + uint32_t count, const double* data) __INTRODUCED_IN(29); + +/** + * Set/change a camera capture control entry with rational data type for + * a physical camera of a logical multi-camera device. + * + * <p>Same as ACaptureRequest_setEntry_rational, except that if {@link tag} is contained + * in {@link ACAMERA_REQUEST_AVAILABLE_PHYSICAL_CAMERA_REQUEST_KEYS}, this function + * sets the entry for a particular physical sub-camera backing the logical multi-camera. + * If {@link tag} is not contained in + * {@link ACAMERA_REQUEST_AVAILABLE_PHYSICAL_CAMERA_REQUEST_KEYS}, the key will be ignored + * by the camera device.</p> + * + * @param request the {@link ACaptureRequest} of interest created by + * {@link ACameraDevice_createCaptureRequest_withPhysicalIds}. + * @param physicalId one of the physical Ids used when request is created with + * {@link ACameraDevice_createCaptureRequest_withPhysicalIds}. + * @param tag one of the capture request metadata tags in + * {@link ACAMERA_REQUEST_AVAILABLE_PHYSICAL_CAMERA_REQUEST_KEYS}. + * + * @return <ul> + * <li>{@link ACAMERA_OK} if the method call succeeds.</li> + * <li>{@link ACAMERA_ERROR_INVALID_PARAMETER} if request or physicalId is NULL, count is + * larger than zero while data is NULL, the data type of the tag is not rational, + * the tag is not controllable by application, physicalId is not one of the Ids used + * in creating the request, or if the capture request is a regular request with no + * physical Ids at all.</li></ul> + */ +camera_status_t ACaptureRequest_setEntry_physicalCamera_rational( + ACaptureRequest* request, const char* physicalId, uint32_t tag, + uint32_t count, const ACameraMetadata_rational* data) __INTRODUCED_IN(29); + +#endif /* __ANDROID_API__ >= 29 */ + __END_DECLS #endif /* _NDK_CAPTURE_REQUEST_H */
diff --git a/camera/ndk/libcamera2ndk.map.txt b/camera/ndk/libcamera2ndk.map.txt index d179aa0..b6f1553 100644 --- a/camera/ndk/libcamera2ndk.map.txt +++ b/camera/ndk/libcamera2ndk.map.txt
@@ -2,15 +2,19 @@ global: ACameraCaptureSession_abortCaptures; ACameraCaptureSession_capture; + ACameraCaptureSession_logicalCamera_capture; # introduced=29 ACameraCaptureSession_close; ACameraCaptureSession_getDevice; ACameraCaptureSession_setRepeatingRequest; + ACameraCaptureSession_logicalCamera_setRepeatingRequest; # introduced=29 ACameraCaptureSession_stopRepeating; - ACameraCaptureSession_updateSharedOutput; + ACameraCaptureSession_updateSharedOutput; # introduced=28 ACameraDevice_close; ACameraDevice_createCaptureRequest; + ACameraDevice_createCaptureRequest_withPhysicalIds; # introduced=29 ACameraDevice_createCaptureSession; - ACameraDevice_createCaptureSessionWithSessionParameters; + ACameraDevice_createCaptureSessionWithSessionParameters; # introduced=28 + ACameraDevice_isSessionConfigurationSupported; # introduced=29 ACameraDevice_getId; ACameraManager_create; ACameraManager_delete; @@ -20,34 +24,45 @@ ACameraManager_openCamera; ACameraManager_registerAvailabilityCallback; ACameraManager_unregisterAvailabilityCallback; + ACameraManager_registerExtendedAvailabilityCallback; # introduced=29 + ACameraManager_unregisterExtendedAvailabilityCallback; # introduced=29 ACameraMetadata_copy; ACameraMetadata_free; ACameraMetadata_getAllTags; ACameraMetadata_getConstEntry; + ACameraMetadata_isLogicalMultiCamera; # introduced=29 ACameraOutputTarget_create; ACameraOutputTarget_free; ACaptureRequest_addTarget; - ACaptureRequest_copy; + ACaptureRequest_copy; # introduced=28 ACaptureRequest_free; ACaptureRequest_getAllTags; ACaptureRequest_getConstEntry; - ACaptureRequest_getUserContext; + ACaptureRequest_getConstEntry_physicalCamera; # introduced=29 + ACaptureRequest_getUserContext; # introduced=28 ACaptureRequest_removeTarget; ACaptureRequest_setEntry_double; + ACaptureRequest_setEntry_physicalCamera_double; # introduced=29 ACaptureRequest_setEntry_float; + ACaptureRequest_setEntry_physicalCamera_float; # introduced=29 ACaptureRequest_setEntry_i32; + ACaptureRequest_setEntry_physicalCamera_i32; # introduced=29 ACaptureRequest_setEntry_i64; + ACaptureRequest_setEntry_physicalCamera_i64; # introduced=29 ACaptureRequest_setEntry_rational; + ACaptureRequest_setEntry_physicalCamera_rational; # introduced=29 ACaptureRequest_setEntry_u8; - ACaptureRequest_setUserContext; + ACaptureRequest_setEntry_physicalCamera_u8; # introduced=29 + ACaptureRequest_setUserContext; # introduced=28 ACaptureSessionOutputContainer_add; ACaptureSessionOutputContainer_create; ACaptureSessionOutputContainer_free; ACaptureSessionOutputContainer_remove; ACaptureSessionOutput_create; - ACaptureSessionSharedOutput_create; - ACaptureSessionSharedOutput_add; - ACaptureSessionSharedOutput_remove; + ACaptureSessionSharedOutput_create; # introduced=28 + ACaptureSessionSharedOutput_add; # introduced=28 + ACaptureSessionSharedOutput_remove; # introduced=28 + ACaptureSessionPhysicalOutput_create; # introduced=29 ACaptureSessionOutput_free; local: *;
diff --git a/camera/ndk/ndk_vendor/impl/ACameraCaptureSessionVendor.h b/camera/ndk/ndk_vendor/impl/ACameraCaptureSessionVendor.h new file mode 100644 index 0000000..e1af8c1 --- /dev/null +++ b/camera/ndk/ndk_vendor/impl/ACameraCaptureSessionVendor.h
@@ -0,0 +1,48 @@ +/* + * 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 <string> +#include "utils.h" + +struct ACaptureSessionOutput { + explicit ACaptureSessionOutput(native_handle_t* window, bool isShared = false, + const char* physicalCameraId = "") : + mWindow(window), mIsShared(isShared), mPhysicalCameraId(physicalCameraId) {}; + + bool operator == (const ACaptureSessionOutput& other) const { + return (mWindow == other.mWindow); + } + + bool operator != (const ACaptureSessionOutput& other) const { + return mWindow != other.mWindow; + } + + bool operator < (const ACaptureSessionOutput& other) const { + return mWindow < other.mWindow; + } + + bool operator > (const ACaptureSessionOutput& other) const { + return mWindow > other.mWindow; + } + + android::acam::utils::native_handle_ptr_wrapper mWindow; + std::set<android::acam::utils::native_handle_ptr_wrapper> mSharedWindows; + bool mIsShared; + int mRotation = CAMERA3_STREAM_ROTATION_0; + std::string mPhysicalCameraId; +}; + +
diff --git a/camera/ndk/ndk_vendor/impl/ACameraDevice.cpp b/camera/ndk/ndk_vendor/impl/ACameraDevice.cpp new file mode 100644 index 0000000..35c8355 --- /dev/null +++ b/camera/ndk/ndk_vendor/impl/ACameraDevice.cpp
@@ -0,0 +1,1719 @@ +/* + * 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 "ACameraDeviceVendor" + +#include <vector> +#include <inttypes.h> +#include <android/frameworks/cameraservice/service/2.0/ICameraService.h> +#include <android/frameworks/cameraservice/device/2.0/types.h> +#include <CameraMetadata.h> + +#include "ndk_vendor/impl/ACameraDevice.h" +#include "ACameraCaptureSession.h" +#include "ACameraMetadata.h" +#include "ACaptureRequest.h" +#include "utils.h" + +#include "ACameraCaptureSession.inc" + +#define CHECK_TRANSACTION_AND_RET(remoteRet, status, callName) \ + if (!remoteRet.isOk()) { \ + ALOGE("%s: Transaction error during %s call %s", __FUNCTION__, callName, \ + remoteRet.description().c_str()); \ + return ACAMERA_ERROR_UNKNOWN; \ + } \ + if (status != Status::NO_ERROR) { \ + ALOGE("%s: %s call failed", __FUNCTION__, callName); \ + return utils::convertFromHidl(status); \ + } + +using namespace android; + +ACameraDevice::~ACameraDevice() { + mDevice->stopLooper(); +} + +namespace android { +namespace acam { + +using HCameraMetadata = frameworks::cameraservice::device::V2_0::CameraMetadata; +using OutputConfiguration = frameworks::cameraservice::device::V2_0::OutputConfiguration; +using SessionConfiguration = frameworks::cameraservice::device::V2_0::SessionConfiguration; +using hardware::Void; + +// Static member definitions +const char* CameraDevice::kContextKey = "Context"; +const char* CameraDevice::kDeviceKey = "Device"; +const char* CameraDevice::kErrorCodeKey = "ErrorCode"; +const char* CameraDevice::kCallbackFpKey = "Callback"; +const char* CameraDevice::kSessionSpKey = "SessionSp"; +const char* CameraDevice::kCaptureRequestKey = "CaptureRequest"; +const char* CameraDevice::kTimeStampKey = "TimeStamp"; +const char* CameraDevice::kCaptureResultKey = "CaptureResult"; +const char* CameraDevice::kPhysicalCaptureResultKey = "PhysicalCaptureResult"; +const char* CameraDevice::kCaptureFailureKey = "CaptureFailure"; +const char* CameraDevice::kSequenceIdKey = "SequenceId"; +const char* CameraDevice::kFrameNumberKey = "FrameNumber"; +const char* CameraDevice::kAnwKey = "Anw"; +const char* CameraDevice::kFailingPhysicalCameraId= "FailingPhysicalCameraId"; + +/** + * CameraDevice Implementation + */ +CameraDevice::CameraDevice( + const char* id, + ACameraDevice_StateCallbacks* cb, + sp<ACameraMetadata> chars, + ACameraDevice* wrapper) : + mCameraId(id), + mAppCallbacks(*cb), + mChars(std::move(chars)), + mServiceCallback(new ServiceCallback(this)), + mWrapper(wrapper), + mInError(false), + mError(ACAMERA_OK), + mIdle(true), + mCurrentSession(nullptr) { + mClosing = false; + // Setup looper thread to perfrom device callbacks to app + mCbLooper = new ALooper; + mCbLooper->setName("C2N-dev-looper"); + status_t err = mCbLooper->start( + /*runOnCallingThread*/false, + /*canCallJava*/ true, + PRIORITY_DEFAULT); + if (err != OK) { + ALOGE("%s: Unable to start camera device callback looper: %s (%d)", + __FUNCTION__, strerror(-err), err); + setCameraDeviceErrorLocked(ACAMERA_ERROR_CAMERA_DEVICE); + } + mHandler = new CallbackHandler(id); + mCbLooper->registerHandler(mHandler); + + const CameraMetadata& metadata = mChars->getInternalData(); + camera_metadata_ro_entry entry = metadata.find(ANDROID_REQUEST_PARTIAL_RESULT_COUNT); + if (entry.count != 1) { + ALOGW("%s: bad count %zu for partial result count", __FUNCTION__, entry.count); + mPartialResultCount = 1; + } else { + mPartialResultCount = entry.data.i32[0]; + } + + entry = metadata.find(ANDROID_LENS_INFO_SHADING_MAP_SIZE); + if (entry.count != 2) { + ALOGW("%s: bad count %zu for shading map size", __FUNCTION__, entry.count); + mShadingMapSize[0] = 0; + mShadingMapSize[1] = 0; + } else { + mShadingMapSize[0] = entry.data.i32[0]; + mShadingMapSize[1] = entry.data.i32[1]; + } +} + +// Device close implementaiton +CameraDevice::~CameraDevice() { + sp<ACameraCaptureSession> session = mCurrentSession.promote(); + { + Mutex::Autolock _l(mDeviceLock); + if (!isClosed()) { + disconnectLocked(session); + } + mCurrentSession = nullptr; + LOG_ALWAYS_FATAL_IF(mCbLooper != nullptr, + "CameraDevice looper should've been stopped before ~CameraDevice"); + } +} + +void +CameraDevice::postSessionMsgAndCleanup(sp<AMessage>& msg) { + msg->post(); + msg.clear(); + sp<AMessage> cleanupMsg = new AMessage(kWhatCleanUpSessions, mHandler); + cleanupMsg->post(); +} + +// TODO: cached created request? +camera_status_t +CameraDevice::createCaptureRequest( + ACameraDevice_request_template templateId, + const ACameraIdList* physicalCameraIdList, + ACaptureRequest** request) const { + Mutex::Autolock _l(mDeviceLock); + camera_status_t ret = checkCameraClosedOrErrorLocked(); + if (ret != ACAMERA_OK) { + return ret; + } + if (mRemote == nullptr) { + return ACAMERA_ERROR_CAMERA_DISCONNECTED; + } + CameraMetadata rawRequest; + Status status = Status::UNKNOWN_ERROR; + auto remoteRet = mRemote->createDefaultRequest( + utils::convertToHidl(templateId), + [&status, &rawRequest](auto s, const hidl_vec<uint8_t> &metadata) { + status = s; + if (status == Status::NO_ERROR && utils::convertFromHidlCloned(metadata, &rawRequest)) { + } else { + ALOGE("%s: Couldn't create default request", __FUNCTION__); + } + }); + CHECK_TRANSACTION_AND_RET(remoteRet, status, "createDefaultRequest()") + ACaptureRequest* outReq = new ACaptureRequest(); + outReq->settings = new ACameraMetadata(rawRequest.release(), ACameraMetadata::ACM_REQUEST); + if (physicalCameraIdList != nullptr) { + for (auto i = 0; i < physicalCameraIdList->numCameras; i++) { + outReq->physicalSettings.emplace(physicalCameraIdList->cameraIds[i], + new ACameraMetadata(*(outReq->settings))); + } + } + outReq->targets = new ACameraOutputTargets(); + *request = outReq; + return ACAMERA_OK; +} + +camera_status_t +CameraDevice::createCaptureSession( + const ACaptureSessionOutputContainer* outputs, + const ACaptureRequest* sessionParameters, + const ACameraCaptureSession_stateCallbacks* callbacks, + /*out*/ACameraCaptureSession** session) { + sp<ACameraCaptureSession> currentSession = mCurrentSession.promote(); + Mutex::Autolock _l(mDeviceLock); + camera_status_t ret = checkCameraClosedOrErrorLocked(); + if (ret != ACAMERA_OK) { + return ret; + } + + if (currentSession != nullptr) { + currentSession->closeByDevice(); + stopRepeatingLocked(); + } + + // Create new session + ret = configureStreamsLocked(outputs, sessionParameters); + if (ret != ACAMERA_OK) { + ALOGE("Fail to create new session. cannot configure streams"); + return ret; + } + + ACameraCaptureSession* newSession = new ACameraCaptureSession( + mNextSessionId++, outputs, callbacks, this); + + // set new session as current session + newSession->incStrong((void *) ACameraDevice_createCaptureSession); + mCurrentSession = newSession; + mFlushing = false; + *session = newSession; + return ACAMERA_OK; +} + +camera_status_t CameraDevice::isSessionConfigurationSupported( + const ACaptureSessionOutputContainer* sessionOutputContainer) const { + Mutex::Autolock _l(mDeviceLock); + camera_status_t ret = checkCameraClosedOrErrorLocked(); + if (ret != ACAMERA_OK) { + return ret; + } + + SessionConfiguration sessionConfig; + sessionConfig.inputWidth = 0; + sessionConfig.inputHeight = 0; + sessionConfig.inputFormat = -1; + sessionConfig.operationMode = StreamConfigurationMode::NORMAL_MODE; + sessionConfig.outputStreams.resize(sessionOutputContainer->mOutputs.size()); + size_t index = 0; + for (const auto& output : sessionOutputContainer->mOutputs) { + sessionConfig.outputStreams[index].rotation = utils::convertToHidl(output.mRotation); + sessionConfig.outputStreams[index].windowGroupId = -1; + sessionConfig.outputStreams[index].windowHandles.resize(output.mSharedWindows.size() + 1); + sessionConfig.outputStreams[index].windowHandles[0] = output.mWindow; + sessionConfig.outputStreams[index].physicalCameraId = output.mPhysicalCameraId; + index++; + } + + bool configSupported = false; + Status status = Status::UNKNOWN_ERROR; + auto remoteRet = mRemote->isSessionConfigurationSupported(sessionConfig, + [&status, &configSupported](auto s, auto supported) { + status = s; + configSupported = supported; + }); + + CHECK_TRANSACTION_AND_RET(remoteRet, status, "isSessionConfigurationSupported()"); + return configSupported ? ACAMERA_OK : ACAMERA_ERROR_STREAM_CONFIGURE_FAIL; +} + +static void addMetadataToPhysicalCameraSettings(const CameraMetadata *metadata, + const std::string &cameraId, PhysicalCameraSettings *physicalCameraSettings) { + CameraMetadata metadataCopy = *metadata; + camera_metadata_t *camera_metadata = metadataCopy.release(); + HCameraMetadata hCameraMetadata; + utils::convertToHidl(camera_metadata, &hCameraMetadata, /*shouldOwn*/ true); + physicalCameraSettings->settings.metadata(std::move(hCameraMetadata)); + physicalCameraSettings->id = cameraId; +} + +void CameraDevice::addRequestSettingsMetadata(ACaptureRequest *aCaptureRequest, + sp<CaptureRequest> &req) { + req->mPhysicalCameraSettings.resize(1 + aCaptureRequest->physicalSettings.size()); + addMetadataToPhysicalCameraSettings(&(aCaptureRequest->settings->getInternalData()), getId(), + &(req->mPhysicalCameraSettings[0])); + size_t i = 1; + for (auto &physicalSetting : aCaptureRequest->physicalSettings) { + addMetadataToPhysicalCameraSettings(&(physicalSetting.second->getInternalData()), + physicalSetting.first, &(req->mPhysicalCameraSettings[i])); + i++; + } +} + +camera_status_t CameraDevice::updateOutputConfigurationLocked(ACaptureSessionOutput *output) { + camera_status_t ret = checkCameraClosedOrErrorLocked(); + if (ret != ACAMERA_OK) { + return ret; + } + + if (output == nullptr) { + return ACAMERA_ERROR_INVALID_PARAMETER; + } + + if (!output->mIsShared) { + ALOGE("Error output configuration is not shared"); + return ACAMERA_ERROR_INVALID_OPERATION; + } + + int32_t streamId = -1; + for (auto& kvPair : mConfiguredOutputs) { + if (utils::isWindowNativeHandleEqual(kvPair.second.first, output->mWindow)) { + streamId = kvPair.first; + break; + } + } + if (streamId < 0) { + ALOGE("Error: Invalid output configuration"); + return ACAMERA_ERROR_INVALID_PARAMETER; + } + + OutputConfigurationWrapper outConfigW; + OutputConfiguration &outConfig = outConfigW.mOutputConfiguration; + outConfig.rotation = utils::convertToHidl(output->mRotation); + outConfig.windowHandles.resize(output->mSharedWindows.size() + 1); + outConfig.windowHandles[0] = output->mWindow; + outConfig.physicalCameraId = output->mPhysicalCameraId; + int i = 1; + for (auto& anw : output->mSharedWindows) { + outConfig.windowHandles[i++] = anw; + } + + auto remoteRet = mRemote->updateOutputConfiguration(streamId, outConfig); + if (!remoteRet.isOk()) { + ALOGE("%s: Transaction error in updating OutputConfiguration: %s", __FUNCTION__, + remoteRet.description().c_str()); + return ACAMERA_ERROR_UNKNOWN; + } + + switch (remoteRet) { + case Status::NO_ERROR: + break; + case Status::INVALID_OPERATION: + ALOGE("Camera device %s invalid operation", getId()); + return ACAMERA_ERROR_INVALID_OPERATION; + case Status::ALREADY_EXISTS: + ALOGE("Camera device %s output surface already exists", getId()); + return ACAMERA_ERROR_INVALID_PARAMETER; + case Status::ILLEGAL_ARGUMENT: + ALOGE("Camera device %s invalid input argument", getId()); + return ACAMERA_ERROR_INVALID_PARAMETER; + default: + ALOGE("Camera device %s failed to add shared output", getId()); + return ACAMERA_ERROR_UNKNOWN; + } + + mConfiguredOutputs[streamId] = std::make_pair(output->mWindow, outConfigW); + + return ACAMERA_OK; +} + +camera_status_t +CameraDevice::allocateCaptureRequestLocked( + const ACaptureRequest* request, /*out*/sp<CaptureRequest> &outReq) { + sp<CaptureRequest> req(new CaptureRequest()); + req->mCaptureRequest.physicalCameraSettings.resize(1 + request->physicalSettings.size()); + + size_t index = 0; + allocateOneCaptureRequestMetadata( + req->mCaptureRequest.physicalCameraSettings[index++], mCameraId, request->settings); + + for (auto& physicalEntry : request->physicalSettings) { + allocateOneCaptureRequestMetadata( + req->mCaptureRequest.physicalCameraSettings[index++], + physicalEntry.first, physicalEntry.second); + } + + std::vector<int32_t> requestStreamIdxList; + std::vector<int32_t> requestSurfaceIdxList; + for (auto outputTarget : request->targets->mOutputs) { + native_handle_t* anw = outputTarget.mWindow; + bool found = false; + req->mSurfaceList.push_back(anw); + // lookup stream/surface ID + for (const auto& kvPair : mConfiguredOutputs) { + int streamId = kvPair.first; + const OutputConfigurationWrapper& outConfig = kvPair.second.second; + const auto& windowHandles = outConfig.mOutputConfiguration.windowHandles; + for (int surfaceId = 0; surfaceId < (int) windowHandles.size(); surfaceId++) { + // If two native handles are equivalent, so are their surfaces. + if (utils::isWindowNativeHandleEqual(windowHandles[surfaceId].getNativeHandle(), + anw)) { + found = true; + requestStreamIdxList.push_back(streamId); + requestSurfaceIdxList.push_back(surfaceId); + break; + } + } + if (found) { + break; + } + } + if (!found) { + ALOGE("Unconfigured output target %p in capture request!", anw); + return ACAMERA_ERROR_INVALID_PARAMETER; + } + } + req->mCaptureRequest.streamAndWindowIds.resize(requestStreamIdxList.size()); + for (int i = 0; i < requestStreamIdxList.size(); i++) { + req->mCaptureRequest.streamAndWindowIds[i].streamId = requestStreamIdxList[i]; + req->mCaptureRequest.streamAndWindowIds[i].windowId = requestSurfaceIdxList[i]; + } + outReq = req; + return ACAMERA_OK; +} + +void CameraDevice::allocateOneCaptureRequestMetadata( + PhysicalCameraSettings& cameraSettings, + const std::string& id, const sp<ACameraMetadata>& metadata) { + cameraSettings.id = id; + // TODO: Do we really need to copy the metadata here ? + CameraMetadata metadataCopy = metadata->getInternalData(); + camera_metadata_t *cameraMetadata = metadataCopy.release(); + HCameraMetadata hCameraMetadata; + utils::convertToHidl(cameraMetadata, &hCameraMetadata, true); + if (metadata != nullptr) { + if (hCameraMetadata.data() != nullptr && + mCaptureRequestMetadataQueue != nullptr && + mCaptureRequestMetadataQueue->write( + reinterpret_cast<const uint8_t *>(hCameraMetadata.data()), + hCameraMetadata.size())) { + // The metadata field of the union would've been destructued, so no need + // to re-size it. + cameraSettings.settings.fmqMetadataSize(hCameraMetadata.size()); + } else { + ALOGE("Fmq write capture result failed, falling back to hwbinder"); + cameraSettings.settings.metadata(std::move(hCameraMetadata)); + } + } +} + + +ACaptureRequest* +CameraDevice::allocateACaptureRequest(sp<CaptureRequest>& req, const char* deviceId) { + ACaptureRequest* pRequest = new ACaptureRequest(); + for (size_t i = 0; i < req->mPhysicalCameraSettings.size(); i++) { + const std::string& id = req->mPhysicalCameraSettings[i].id; + CameraMetadata clone; + utils::convertFromHidlCloned(req->mPhysicalCameraSettings[i].settings.metadata(), &clone); + camera_metadata_t *clonep = clone.release(); + if (id == deviceId) { + pRequest->settings = new ACameraMetadata(clonep, ACameraMetadata::ACM_REQUEST); + } else { + pRequest->physicalSettings[req->mPhysicalCameraSettings[i].id] = + new ACameraMetadata(clonep, ACameraMetadata::ACM_REQUEST); + } + } + pRequest->targets = new ACameraOutputTargets(); + for (size_t i = 0; i < req->mSurfaceList.size(); i++) { + native_handle_t* anw = req->mSurfaceList[i]; + ACameraOutputTarget outputTarget(anw); + pRequest->targets->mOutputs.insert(outputTarget); + } + return pRequest; +} + +void +CameraDevice::freeACaptureRequest(ACaptureRequest* req) { + if (req == nullptr) { + return; + } + req->settings.clear(); + delete req->targets; + delete req; +} + +void +CameraDevice::notifySessionEndOfLifeLocked(ACameraCaptureSession* session) { + if (isClosed()) { + // Device is closing already. do nothing + return; + } + + if (mCurrentSession != session) { + // Session has been replaced by other seesion or device is closed + return; + } + mCurrentSession = nullptr; + + // Should not happen + if (!session->mIsClosed) { + ALOGE("Error: unclosed session %p reaches end of life!", session); + setCameraDeviceErrorLocked(ACAMERA_ERROR_CAMERA_DEVICE); + return; + } + + // No new session, unconfigure now + camera_status_t ret = configureStreamsLocked(nullptr, nullptr); + if (ret != ACAMERA_OK) { + ALOGE("Unconfigure stream failed. Device might still be configured! ret %d", ret); + } +} + +void +CameraDevice::disconnectLocked(sp<ACameraCaptureSession>& session) { + if (mClosing.exchange(true)) { + // Already closing, just return + ALOGW("Camera device %s is already closing.", getId()); + return; + } + + if (mRemote != nullptr) { + auto ret = mRemote->disconnect(); + if (!ret.isOk()) { + ALOGE("%s: Transaction error while disconnecting device %s", __FUNCTION__, + ret.description().c_str()); + } + } + mRemote = nullptr; + + if (session != nullptr) { + session->closeByDevice(); + } +} + +camera_status_t +CameraDevice::stopRepeatingLocked() { + camera_status_t ret = checkCameraClosedOrErrorLocked(); + if (ret != ACAMERA_OK) { + ALOGE("Camera %s stop repeating failed! ret %d", getId(), ret); + return ret; + } + if (mRepeatingSequenceId != REQUEST_ID_NONE) { + int repeatingSequenceId = mRepeatingSequenceId; + mRepeatingSequenceId = REQUEST_ID_NONE; + + int64_t lastFrameNumber; + Status status = Status::UNKNOWN_ERROR; + auto remoteRet = mRemote->cancelRepeatingRequest( + [&status, &lastFrameNumber](Status s, auto frameNumber) { + status = s; + lastFrameNumber = frameNumber; + }); + CHECK_TRANSACTION_AND_RET(remoteRet, status, "cancelRepeatingRequest()"); + checkRepeatingSequenceCompleteLocked(repeatingSequenceId, lastFrameNumber); + } + return ACAMERA_OK; +} + +camera_status_t +CameraDevice::flushLocked(ACameraCaptureSession* session) { + camera_status_t ret = checkCameraClosedOrErrorLocked(); + if (ret != ACAMERA_OK) { + ALOGE("Camera %s abort captures failed! ret %d", getId(), ret); + return ret; + } + + // This should never happen because creating a new session will close + // previous one and thus reject any API call from previous session. + // But still good to check here in case something unexpected happen. + if (mCurrentSession != session) { + ALOGE("Camera %s session %p is not current active session!", getId(), session); + return ACAMERA_ERROR_INVALID_OPERATION; + } + + if (mFlushing) { + ALOGW("Camera %s is already aborting captures", getId()); + return ACAMERA_OK; + } + + mFlushing = true; + + // Send onActive callback to guarantee there is always active->ready transition + sp<AMessage> msg = new AMessage(kWhatSessionStateCb, mHandler); + msg->setPointer(kContextKey, session->mUserSessionCallback.context); + msg->setObject(kSessionSpKey, session); + msg->setPointer(kCallbackFpKey, (void*) session->mUserSessionCallback.onActive); + postSessionMsgAndCleanup(msg); + + // If device is already idling, send callback and exit early + if (mIdle) { + sp<AMessage> msg = new AMessage(kWhatSessionStateCb, mHandler); + msg->setPointer(kContextKey, session->mUserSessionCallback.context); + msg->setObject(kSessionSpKey, session); + msg->setPointer(kCallbackFpKey, (void*) session->mUserSessionCallback.onReady); + postSessionMsgAndCleanup(msg); + mFlushing = false; + return ACAMERA_OK; + } + + int64_t lastFrameNumber; + Status status = Status::UNKNOWN_ERROR; + auto remoteRet = mRemote->flush([&status, &lastFrameNumber](auto s, auto frameNumber) { + status = s; + lastFrameNumber = frameNumber; + }); + CHECK_TRANSACTION_AND_RET(remoteRet, status, "flush()") + if (mRepeatingSequenceId != REQUEST_ID_NONE) { + checkRepeatingSequenceCompleteLocked(mRepeatingSequenceId, lastFrameNumber); + } + return ACAMERA_OK; +} + +camera_status_t +CameraDevice::waitUntilIdleLocked() { + camera_status_t ret = checkCameraClosedOrErrorLocked(); + if (ret != ACAMERA_OK) { + ALOGE("Wait until camera %s idle failed! ret %d", getId(), ret); + return ret; + } + + if (mRepeatingSequenceId != REQUEST_ID_NONE) { + ALOGE("Camera device %s won't go to idle when there is repeating request!", getId()); + return ACAMERA_ERROR_INVALID_OPERATION; + } + + auto remoteRet = mRemote->waitUntilIdle(); + CHECK_TRANSACTION_AND_RET(remoteRet, remoteRet, "waitUntilIdle()") + return ACAMERA_OK; +} + +camera_status_t +CameraDevice::configureStreamsLocked(const ACaptureSessionOutputContainer* outputs, + const ACaptureRequest* sessionParameters) { + ACaptureSessionOutputContainer emptyOutput; + if (outputs == nullptr) { + outputs = &emptyOutput; + } + + camera_status_t ret = checkCameraClosedOrErrorLocked(); + if (ret != ACAMERA_OK) { + return ret; + } + + std::set<std::pair<native_handle_ptr_wrapper, OutputConfigurationWrapper>> outputSet; + for (auto outConfig : outputs->mOutputs) { + native_handle_t* anw = outConfig.mWindow; + OutputConfigurationWrapper outConfigInsertW; + OutputConfiguration &outConfigInsert = outConfigInsertW.mOutputConfiguration; + outConfigInsert.rotation = utils::convertToHidl(outConfig.mRotation); + outConfigInsert.windowGroupId = -1; + outConfigInsert.windowHandles.resize(outConfig.mSharedWindows.size() + 1); + outConfigInsert.windowHandles[0] = anw; + outConfigInsert.physicalCameraId = outConfig.mPhysicalCameraId; + native_handle_ptr_wrapper wrap(anw); + outputSet.insert(std::make_pair(anw, outConfigInsertW)); + } + std::set<std::pair<native_handle_ptr_wrapper, OutputConfigurationWrapper>> addSet = outputSet; + std::vector<int32_t> deleteList; + + // Determine which streams need to be created, which to be deleted + for (auto& kvPair : mConfiguredOutputs) { + int32_t streamId = kvPair.first; + auto& outputPair = kvPair.second; + if (outputSet.count(outputPair)) { + deleteList.push_back(streamId); // Need to delete a no longer needed stream + } else { + addSet.erase(outputPair); // No need to add already existing stream + } + } + + ret = stopRepeatingLocked(); + if (ret != ACAMERA_OK) { + ALOGE("Camera device %s stop repeating failed, ret %d", getId(), ret); + return ret; + } + + ret = waitUntilIdleLocked(); + if (ret != ACAMERA_OK) { + ALOGE("Camera device %s wait until idle failed, ret %d", getId(), ret); + return ret; + } + + // Send onReady to previous session + // CurrentSession will be updated after configureStreamLocked, so here + // mCurrentSession is the session to be replaced by a new session + if (!mIdle && mCurrentSession != nullptr) { + if (mBusySession != mCurrentSession) { + ALOGE("Current session != busy session"); + setCameraDeviceErrorLocked(ACAMERA_ERROR_CAMERA_DEVICE); + return ACAMERA_ERROR_CAMERA_DEVICE; + } + sp<AMessage> msg = new AMessage(kWhatSessionStateCb, mHandler); + msg->setPointer(kContextKey, mBusySession->mUserSessionCallback.context); + msg->setObject(kSessionSpKey, mBusySession); + msg->setPointer(kCallbackFpKey, (void*) mBusySession->mUserSessionCallback.onReady); + mBusySession.clear(); + postSessionMsgAndCleanup(msg); + } + mIdle = true; + + auto remoteRet = mRemote->beginConfigure(); + CHECK_TRANSACTION_AND_RET(remoteRet, remoteRet, "beginConfigure()") + + // delete to-be-deleted streams + for (auto streamId : deleteList) { + remoteRet = mRemote->deleteStream(streamId); + CHECK_TRANSACTION_AND_RET(remoteRet, remoteRet, "deleteStream()") + mConfiguredOutputs.erase(streamId); + } + + // add new streams + for (auto outputPair : addSet) { + int streamId; + Status status = Status::UNKNOWN_ERROR; + auto ret = mRemote->createStream(outputPair.second, + [&status, &streamId](Status s, auto stream_id) { + status = s; + streamId = stream_id; + }); + CHECK_TRANSACTION_AND_RET(ret, status, "createStream()") + mConfiguredOutputs.insert(std::make_pair(streamId, outputPair)); + } + + CameraMetadata params; + HCameraMetadata hidlParams; + if ((sessionParameters != nullptr) && (sessionParameters->settings != nullptr)) { + params.append(sessionParameters->settings->getInternalData()); + const camera_metadata_t *params_metadata = params.getAndLock(); + utils::convertToHidl(params_metadata, &hidlParams); + params.unlock(params_metadata); + } + remoteRet = mRemote->endConfigure(StreamConfigurationMode::NORMAL_MODE, hidlParams); + CHECK_TRANSACTION_AND_RET(remoteRet, remoteRet, "endConfigure()") + return ACAMERA_OK; +} + +void +CameraDevice::setRemoteDevice(sp<ICameraDeviceUser> remote) { + Mutex::Autolock _l(mDeviceLock); + mRemote = remote; +} + +bool +CameraDevice::setDeviceMetadataQueues() { + if (mRemote == nullptr) { + ALOGE("mRemote must not be null while trying to fetch metadata queues"); + return false; + } + std::shared_ptr<RequestMetadataQueue> &reqQueue = mCaptureRequestMetadataQueue; + auto ret = + mRemote->getCaptureRequestMetadataQueue( + [&reqQueue](const auto &mqDescriptor) { + reqQueue = std::make_shared<RequestMetadataQueue>(mqDescriptor); + if (!reqQueue->isValid() || reqQueue->availableToWrite() <=0) { + ALOGE("Empty fmq from cameraserver"); + reqQueue = nullptr; + } + }); + if (!ret.isOk()) { + ALOGE("Transaction error trying to get capture request metadata queue"); + return false; + } + std::shared_ptr<ResultMetadataQueue> &resQueue = mCaptureResultMetadataQueue; + ret = + mRemote->getCaptureResultMetadataQueue( + [&resQueue](const auto &mqDescriptor) { + resQueue = std::make_shared<ResultMetadataQueue>(mqDescriptor); + if (!resQueue->isValid() || resQueue->availableToWrite() <=0) { + ALOGE("Empty fmq from cameraserver"); + } + }); + if (!ret.isOk()) { + ALOGE("Transaction error trying to get capture result metadata queue"); + return false; + } + return true; +} + +camera_status_t +CameraDevice::checkCameraClosedOrErrorLocked() const { + if (mRemote == nullptr) { + ALOGE("%s: camera device already closed", __FUNCTION__); + return ACAMERA_ERROR_CAMERA_DISCONNECTED; + } + if (mInError) {// triggered by onDeviceError + ALOGE("%s: camera device has encountered a serious error", __FUNCTION__); + return mError; + } + return ACAMERA_OK; +} + +void +CameraDevice::setCameraDeviceErrorLocked(camera_status_t error) { + mInError = true; + mError = error; + return; +} + +void +CameraDevice::FrameNumberTracker::updateTracker(int64_t frameNumber, bool isError) { + ALOGV("updateTracker frame %" PRId64 " isError %d", frameNumber, isError); + if (isError) { + mFutureErrorSet.insert(frameNumber); + } else if (frameNumber <= mCompletedFrameNumber) { + ALOGE("Frame number %" PRId64 " decreased! current fn %" PRId64, + frameNumber, mCompletedFrameNumber); + return; + } else { + if (frameNumber != mCompletedFrameNumber + 1) { + ALOGE("Frame number out of order. Expect %" PRId64 " but get %" PRId64, + mCompletedFrameNumber + 1, frameNumber); + // Do not assert as in java implementation + } + mCompletedFrameNumber = frameNumber; + } + update(); +} + +void +CameraDevice::FrameNumberTracker::update() { + for (auto it = mFutureErrorSet.begin(); it != mFutureErrorSet.end();) { + int64_t errorFrameNumber = *it; + if (errorFrameNumber == mCompletedFrameNumber + 1) { + mCompletedFrameNumber++; + it = mFutureErrorSet.erase(it); + } else if (errorFrameNumber <= mCompletedFrameNumber) { + // This should not happen, but deal with it anyway + ALOGE("Completd frame number passed through current frame number!"); + // erase the old error since it's no longer useful + it = mFutureErrorSet.erase(it); + } else { + // Normal requests hasn't catched up error frames, just break + break; + } + } + ALOGV("Update complete frame %" PRId64, mCompletedFrameNumber); +} + +void +CameraDevice::onCaptureErrorLocked( + ErrorCode errorCode, + const CaptureResultExtras& resultExtras) { + int sequenceId = resultExtras.requestId; + int64_t frameNumber = resultExtras.frameNumber; + int32_t burstId = resultExtras.burstId; + auto it = mSequenceCallbackMap.find(sequenceId); + if (it == mSequenceCallbackMap.end()) { + ALOGE("%s: Error: capture sequence index %d not found!", + __FUNCTION__, sequenceId); + setCameraDeviceErrorLocked(ACAMERA_ERROR_CAMERA_SERVICE); + return; + } + + CallbackHolder cbh = (*it).second; + sp<ACameraCaptureSession> session = cbh.mSession; + if ((size_t) burstId >= cbh.mRequests.size()) { + ALOGE("%s: Error: request index %d out of bound (size %zu)", + __FUNCTION__, burstId, cbh.mRequests.size()); + setCameraDeviceErrorLocked(ACAMERA_ERROR_CAMERA_SERVICE); + return; + } + sp<CaptureRequest> request = cbh.mRequests[burstId]; + + // Handle buffer error + if (errorCode == ErrorCode::CAMERA_BUFFER) { + int32_t streamId = resultExtras.errorStreamId; + ACameraCaptureSession_captureCallback_bufferLost onBufferLost = + cbh.mOnCaptureBufferLost; + auto outputPairIt = mConfiguredOutputs.find(streamId); + if (outputPairIt == mConfiguredOutputs.end()) { + ALOGE("%s: Error: stream id %d does not exist", __FUNCTION__, streamId); + setCameraDeviceErrorLocked(ACAMERA_ERROR_CAMERA_SERVICE); + return; + } + + const auto& windowHandles = outputPairIt->second.second.mOutputConfiguration.windowHandles; + for (const auto& outHandle : windowHandles) { + for (auto streamAndWindowId : request->mCaptureRequest.streamAndWindowIds) { + int32_t windowId = streamAndWindowId.windowId; + if (utils::isWindowNativeHandleEqual(windowHandles[windowId],outHandle)) { + native_handle_t* anw = + const_cast<native_handle_t *>(windowHandles[windowId].getNativeHandle()); + ALOGV("Camera %s Lost output buffer for ANW %p frame %" PRId64, + getId(), anw, frameNumber); + + sp<AMessage> msg = new AMessage(kWhatCaptureBufferLost, mHandler); + msg->setPointer(kContextKey, cbh.mContext); + msg->setObject(kSessionSpKey, session); + msg->setPointer(kCallbackFpKey, (void*) onBufferLost); + msg->setObject(kCaptureRequestKey, request); + msg->setPointer(kAnwKey, (void*) anw); + msg->setInt64(kFrameNumberKey, frameNumber); + postSessionMsgAndCleanup(msg); + } + } + } + } else { // Handle other capture failures + // Fire capture failure callback if there is one registered + ACameraCaptureSession_captureCallback_failed onError = cbh.mOnCaptureFailed; + sp<CameraCaptureFailure> failure(new CameraCaptureFailure()); + failure->frameNumber = frameNumber; + // TODO: refine this when implementing flush + failure->reason = CAPTURE_FAILURE_REASON_ERROR; + failure->sequenceId = sequenceId; + failure->wasImageCaptured = (errorCode == ErrorCode::CAMERA_RESULT); + + sp<AMessage> msg = new AMessage(cbh.mIsLogicalCameraCallback ? kWhatLogicalCaptureFail : + kWhatCaptureFail, mHandler); + msg->setPointer(kContextKey, cbh.mContext); + msg->setObject(kSessionSpKey, session); + if (cbh.mIsLogicalCameraCallback) { + if (resultExtras.errorPhysicalCameraId.size() > 0) { + msg->setString(kFailingPhysicalCameraId, resultExtras.errorPhysicalCameraId.c_str(), + resultExtras.errorPhysicalCameraId.size()); + } + msg->setPointer(kCallbackFpKey, (void*) cbh.mOnLogicalCameraCaptureFailed); + } else { + msg->setPointer(kCallbackFpKey, (void*) onError); + } + msg->setObject(kCaptureRequestKey, request); + msg->setObject(kCaptureFailureKey, failure); + postSessionMsgAndCleanup(msg); + + // Update tracker + mFrameNumberTracker.updateTracker(frameNumber, /*isError*/true); + checkAndFireSequenceCompleteLocked(); + } + return; +} + +CameraDevice::CallbackHandler::CallbackHandler(const char *id) : mId(id) { } + +void CameraDevice::CallbackHandler::onMessageReceived( + const sp<AMessage> &msg) { + switch (msg->what()) { + case kWhatOnDisconnected: + case kWhatOnError: + case kWhatSessionStateCb: + case kWhatCaptureStart: + case kWhatCaptureResult: + case kWhatLogicalCaptureResult: + case kWhatCaptureFail: + case kWhatLogicalCaptureFail: + case kWhatCaptureSeqEnd: + case kWhatCaptureSeqAbort: + case kWhatCaptureBufferLost: + ALOGV("%s: Received msg %d", __FUNCTION__, msg->what()); + break; + case kWhatCleanUpSessions: + mCachedSessions.clear(); + return; + default: + ALOGE("%s:Error: unknown device callback %d", __FUNCTION__, msg->what()); + return; + } + // Check the common part of all message + void* context; + bool found = msg->findPointer(kContextKey, &context); + if (!found) { + ALOGE("%s: Cannot find callback context!", __FUNCTION__); + return; + } + switch (msg->what()) { + case kWhatOnDisconnected: + { + ACameraDevice* dev; + found = msg->findPointer(kDeviceKey, (void**) &dev); + if (!found || dev == nullptr) { + ALOGE("%s: Cannot find device pointer!", __FUNCTION__); + return; + } + ACameraDevice_StateCallback onDisconnected; + found = msg->findPointer(kCallbackFpKey, (void**) &onDisconnected); + if (!found) { + ALOGE("%s: Cannot find onDisconnected!", __FUNCTION__); + return; + } + if (onDisconnected == nullptr) { + return; + } + (*onDisconnected)(context, dev); + break; + } + case kWhatOnError: + { + ACameraDevice* dev; + found = msg->findPointer(kDeviceKey, (void**) &dev); + if (!found || dev == nullptr) { + ALOGE("%s: Cannot find device pointer!", __FUNCTION__); + return; + } + ACameraDevice_ErrorStateCallback onError; + found = msg->findPointer(kCallbackFpKey, (void**) &onError); + if (!found) { + ALOGE("%s: Cannot find onError!", __FUNCTION__); + return; + } + int errorCode; + found = msg->findInt32(kErrorCodeKey, &errorCode); + if (!found) { + ALOGE("%s: Cannot find error code!", __FUNCTION__); + return; + } + if (onError == nullptr) { + return; + } + (*onError)(context, dev, errorCode); + break; + } + case kWhatSessionStateCb: + case kWhatCaptureStart: + case kWhatCaptureResult: + case kWhatLogicalCaptureResult: + case kWhatCaptureFail: + case kWhatLogicalCaptureFail: + case kWhatCaptureSeqEnd: + case kWhatCaptureSeqAbort: + case kWhatCaptureBufferLost: + { + sp<RefBase> obj; + found = msg->findObject(kSessionSpKey, &obj); + if (!found || obj == nullptr) { + ALOGE("%s: Cannot find session pointer!", __FUNCTION__); + return; + } + sp<ACameraCaptureSession> session(static_cast<ACameraCaptureSession*>(obj.get())); + mCachedSessions.push(session); + sp<CaptureRequest> requestSp = nullptr; + const char *id_cstr = mId.c_str(); + switch (msg->what()) { + case kWhatCaptureStart: + case kWhatCaptureResult: + case kWhatLogicalCaptureResult: + case kWhatCaptureFail: + case kWhatLogicalCaptureFail: + case kWhatCaptureBufferLost: + found = msg->findObject(kCaptureRequestKey, &obj); + if (!found) { + ALOGE("%s: Cannot find capture request!", __FUNCTION__); + return; + } + requestSp = static_cast<CaptureRequest*>(obj.get()); + break; + } + + switch (msg->what()) { + case kWhatSessionStateCb: + { + ACameraCaptureSession_stateCallback onState; + found = msg->findPointer(kCallbackFpKey, (void**) &onState); + if (!found) { + ALOGE("%s: Cannot find state callback!", __FUNCTION__); + return; + } + if (onState == nullptr) { + return; + } + (*onState)(context, session.get()); + break; + } + case kWhatCaptureStart: + { + ACameraCaptureSession_captureCallback_start onStart; + found = msg->findPointer(kCallbackFpKey, (void**) &onStart); + if (!found) { + ALOGE("%s: Cannot find capture start callback!", __FUNCTION__); + return; + } + if (onStart == nullptr) { + return; + } + int64_t timestamp; + found = msg->findInt64(kTimeStampKey, ×tamp); + if (!found) { + ALOGE("%s: Cannot find timestamp!", __FUNCTION__); + return; + } + ACaptureRequest* request = allocateACaptureRequest(requestSp, id_cstr); + (*onStart)(context, session.get(), request, timestamp); + freeACaptureRequest(request); + break; + } + case kWhatCaptureResult: + { + ACameraCaptureSession_captureCallback_result onResult; + found = msg->findPointer(kCallbackFpKey, (void**) &onResult); + if (!found) { + ALOGE("%s: Cannot find capture result callback!", __FUNCTION__); + return; + } + if (onResult == nullptr) { + return; + } + + found = msg->findObject(kCaptureResultKey, &obj); + if (!found) { + ALOGE("%s: Cannot find capture result!", __FUNCTION__); + return; + } + sp<ACameraMetadata> result(static_cast<ACameraMetadata*>(obj.get())); + ACaptureRequest* request = allocateACaptureRequest(requestSp, id_cstr); + (*onResult)(context, session.get(), request, result.get()); + freeACaptureRequest(request); + break; + } + case kWhatLogicalCaptureResult: + { + ACameraCaptureSession_logicalCamera_captureCallback_result onResult; + found = msg->findPointer(kCallbackFpKey, (void**) &onResult); + if (!found) { + ALOGE("%s: Cannot find capture result callback!", __FUNCTION__); + return; + } + if (onResult == nullptr) { + return; + } + + found = msg->findObject(kCaptureResultKey, &obj); + if (!found) { + ALOGE("%s: Cannot find capture result!", __FUNCTION__); + return; + } + sp<ACameraMetadata> result(static_cast<ACameraMetadata*>(obj.get())); + + found = msg->findObject(kPhysicalCaptureResultKey, &obj); + if (!found) { + ALOGE("%s: Cannot find physical capture result!", __FUNCTION__); + return; + } + sp<ACameraPhysicalCaptureResultInfo> physicalResult( + static_cast<ACameraPhysicalCaptureResultInfo*>(obj.get())); + std::vector<PhysicalCaptureResultInfoLocal>& physicalResultInfo = + physicalResult->mPhysicalResultInfo; + + std::vector<std::string> physicalCameraIds; + std::vector<sp<ACameraMetadata>> physicalMetadataCopy; + for (size_t i = 0; i < physicalResultInfo.size(); i++) { + physicalCameraIds.push_back(physicalResultInfo[i].physicalCameraId); + + CameraMetadata clone = physicalResultInfo[i].physicalMetadata; + clone.update(ANDROID_SYNC_FRAME_NUMBER, + &physicalResult->mFrameNumber, /*data_count*/1); + sp<ACameraMetadata> metadata = + new ACameraMetadata(clone.release(), ACameraMetadata::ACM_RESULT); + physicalMetadataCopy.push_back(metadata); + } + std::vector<const char*> physicalCameraIdPtrs; + std::vector<const ACameraMetadata*> physicalMetadataCopyPtrs; + for (size_t i = 0; i < physicalResultInfo.size(); i++) { + physicalCameraIdPtrs.push_back(physicalCameraIds[i].c_str()); + physicalMetadataCopyPtrs.push_back(physicalMetadataCopy[i].get()); + } + + ACaptureRequest* request = allocateACaptureRequest(requestSp, id_cstr); + (*onResult)(context, session.get(), request, result.get(), + physicalResultInfo.size(), physicalCameraIdPtrs.data(), + physicalMetadataCopyPtrs.data()); + freeACaptureRequest(request); + break; + } + + case kWhatCaptureFail: + { + ACameraCaptureSession_captureCallback_failed onFail; + found = msg->findPointer(kCallbackFpKey, (void**) &onFail); + if (!found) { + ALOGE("%s: Cannot find capture fail callback!", __FUNCTION__); + return; + } + if (onFail == nullptr) { + return; + } + + found = msg->findObject(kCaptureFailureKey, &obj); + if (!found) { + ALOGE("%s: Cannot find capture failure!", __FUNCTION__); + return; + } + sp<CameraCaptureFailure> failureSp( + static_cast<CameraCaptureFailure*>(obj.get())); + ACameraCaptureFailure* failure = + static_cast<ACameraCaptureFailure*>(failureSp.get()); + ACaptureRequest* request = allocateACaptureRequest(requestSp, id_cstr); + (*onFail)(context, session.get(), request, failure); + freeACaptureRequest(request); + break; + } + case kWhatLogicalCaptureFail: + { + ACameraCaptureSession_logicalCamera_captureCallback_failed onFail; + found = msg->findPointer(kCallbackFpKey, (void**) &onFail); + if (!found) { + ALOGE("%s: Cannot find capture fail callback!", __FUNCTION__); + return; + } + if (onFail == nullptr) { + return; + } + + found = msg->findObject(kCaptureFailureKey, &obj); + if (!found) { + ALOGE("%s: Cannot find capture failure!", __FUNCTION__); + return; + } + sp<CameraCaptureFailure> failureSp( + static_cast<CameraCaptureFailure*>(obj.get())); + ALogicalCameraCaptureFailure failure; + AString physicalCameraId; + found = msg->findString(kFailingPhysicalCameraId, &physicalCameraId); + if (found && !physicalCameraId.empty()) { + failure.physicalCameraId = physicalCameraId.c_str(); + } else { + failure.physicalCameraId = nullptr; + } + failure.captureFailure = *failureSp; + ACaptureRequest* request = allocateACaptureRequest(requestSp, id_cstr); + (*onFail)(context, session.get(), request, &failure); + freeACaptureRequest(request); + break; + } + case kWhatCaptureSeqEnd: + { + ACameraCaptureSession_captureCallback_sequenceEnd onSeqEnd; + found = msg->findPointer(kCallbackFpKey, (void**) &onSeqEnd); + if (!found) { + ALOGE("%s: Cannot find sequence end callback!", __FUNCTION__); + return; + } + if (onSeqEnd == nullptr) { + return; + } + int seqId; + found = msg->findInt32(kSequenceIdKey, &seqId); + if (!found) { + ALOGE("%s: Cannot find frame number!", __FUNCTION__); + return; + } + int64_t frameNumber; + found = msg->findInt64(kFrameNumberKey, &frameNumber); + if (!found) { + ALOGE("%s: Cannot find frame number!", __FUNCTION__); + return; + } + (*onSeqEnd)(context, session.get(), seqId, frameNumber); + break; + } + case kWhatCaptureSeqAbort: + { + ACameraCaptureSession_captureCallback_sequenceAbort onSeqAbort; + found = msg->findPointer(kCallbackFpKey, (void**) &onSeqAbort); + if (!found) { + ALOGE("%s: Cannot find sequence end callback!", __FUNCTION__); + return; + } + if (onSeqAbort == nullptr) { + return; + } + int seqId; + found = msg->findInt32(kSequenceIdKey, &seqId); + if (!found) { + ALOGE("%s: Cannot find frame number!", __FUNCTION__); + return; + } + (*onSeqAbort)(context, session.get(), seqId); + break; + } + case kWhatCaptureBufferLost: + { + ACameraCaptureSession_captureCallback_bufferLost onBufferLost; + found = msg->findPointer(kCallbackFpKey, (void**) &onBufferLost); + if (!found) { + ALOGE("%s: Cannot find buffer lost callback!", __FUNCTION__); + return; + } + if (onBufferLost == nullptr) { + return; + } + + native_handle_t* anw; + found = msg->findPointer(kAnwKey, (void**) &anw); + if (!found) { + ALOGE("%s: Cannot find native_handle_t!", __FUNCTION__); + return; + } + + int64_t frameNumber; + found = msg->findInt64(kFrameNumberKey, &frameNumber); + if (!found) { + ALOGE("%s: Cannot find frame number!", __FUNCTION__); + return; + } + + ACaptureRequest* request = allocateACaptureRequest(requestSp, id_cstr); + (*onBufferLost)(context, session.get(), request, anw, frameNumber); + freeACaptureRequest(request); + break; + } + } + break; + } + } +} + +CameraDevice::CallbackHolder::CallbackHolder( + sp<ACameraCaptureSession> session, + const Vector<sp<CaptureRequest> >& requests, + bool isRepeating, + ACameraCaptureSession_captureCallbacks* cbs) : + mSession(session), mRequests(requests), + mIsRepeating(isRepeating), + mIsLogicalCameraCallback(false) { + initCaptureCallbacks(cbs); + + if (cbs != nullptr) { + mOnCaptureCompleted = cbs->onCaptureCompleted; + mOnCaptureFailed = cbs->onCaptureFailed; + } +} + +CameraDevice::CallbackHolder::CallbackHolder( + sp<ACameraCaptureSession> session, + const Vector<sp<CaptureRequest> >& requests, + bool isRepeating, + ACameraCaptureSession_logicalCamera_captureCallbacks* lcbs) : + mSession(session), mRequests(requests), + mIsRepeating(isRepeating), + mIsLogicalCameraCallback(true) { + initCaptureCallbacks(lcbs); + + if (lcbs != nullptr) { + mOnLogicalCameraCaptureCompleted = lcbs->onLogicalCameraCaptureCompleted; + mOnLogicalCameraCaptureFailed = lcbs->onLogicalCameraCaptureFailed; + } +} + +void +CameraDevice::checkRepeatingSequenceCompleteLocked( + const int sequenceId, const int64_t lastFrameNumber) { + ALOGV("Repeating seqId %d lastFrameNumer %" PRId64, sequenceId, lastFrameNumber); + if (lastFrameNumber == NO_FRAMES_CAPTURED) { + if (mSequenceCallbackMap.count(sequenceId) == 0) { + ALOGW("No callback found for sequenceId %d", sequenceId); + return; + } + // remove callback holder from callback map + auto cbIt = mSequenceCallbackMap.find(sequenceId); + CallbackHolder cbh = cbIt->second; + mSequenceCallbackMap.erase(cbIt); + // send seq aborted callback + sp<AMessage> msg = new AMessage(kWhatCaptureSeqAbort, mHandler); + msg->setPointer(kContextKey, cbh.mContext); + msg->setObject(kSessionSpKey, cbh.mSession); + msg->setPointer(kCallbackFpKey, (void*) cbh.mOnCaptureSequenceAborted); + msg->setInt32(kSequenceIdKey, sequenceId); + postSessionMsgAndCleanup(msg); + } else { + // Use mSequenceLastFrameNumberMap to track + mSequenceLastFrameNumberMap.insert(std::make_pair(sequenceId, lastFrameNumber)); + + // Last frame might have arrived. Check now + checkAndFireSequenceCompleteLocked(); + } +} + +void +CameraDevice::checkAndFireSequenceCompleteLocked() { + int64_t completedFrameNumber = mFrameNumberTracker.getCompletedFrameNumber(); + auto it = mSequenceLastFrameNumberMap.begin(); + while (it != mSequenceLastFrameNumberMap.end()) { + int sequenceId = it->first; + int64_t lastFrameNumber = it->second; + bool seqCompleted = false; + bool hasCallback = true; + + if (mRemote == nullptr) { + ALOGW("Camera %s closed while checking sequence complete", getId()); + return; + } + + // Check if there is callback for this sequence + // This should not happen because we always register callback (with nullptr inside) + if (mSequenceCallbackMap.count(sequenceId) == 0) { + ALOGW("No callback found for sequenceId %d", sequenceId); + hasCallback = false; + } + + if (lastFrameNumber <= completedFrameNumber) { + ALOGV("seq %d reached last frame %" PRId64 ", completed %" PRId64, + sequenceId, lastFrameNumber, completedFrameNumber); + seqCompleted = true; + } + + if (seqCompleted && hasCallback) { + // remove callback holder from callback map + auto cbIt = mSequenceCallbackMap.find(sequenceId); + CallbackHolder cbh = cbIt->second; + mSequenceCallbackMap.erase(cbIt); + // send seq complete callback + sp<AMessage> msg = new AMessage(kWhatCaptureSeqEnd, mHandler); + msg->setPointer(kContextKey, cbh.mContext); + msg->setObject(kSessionSpKey, cbh.mSession); + msg->setPointer(kCallbackFpKey, (void*) cbh.mOnCaptureSequenceCompleted); + msg->setInt32(kSequenceIdKey, sequenceId); + msg->setInt64(kFrameNumberKey, lastFrameNumber); + + // Clear the session sp before we send out the message + // This will guarantee the rare case where the message is processed + // before cbh goes out of scope and causing we call the session + // destructor while holding device lock + cbh.mSession.clear(); + postSessionMsgAndCleanup(msg); + } + + // No need to track sequence complete if there is no callback registered + if (seqCompleted || !hasCallback) { + it = mSequenceLastFrameNumberMap.erase(it); + } else { + ++it; + } + } +} + +void CameraDevice::stopLooper() { + Mutex::Autolock _l(mDeviceLock); + if (mCbLooper != nullptr) { + mCbLooper->unregisterHandler(mHandler->id()); + mCbLooper->stop(); + } + mCbLooper.clear(); + mHandler.clear(); +} + +/** + * Camera service callback implementation + */ +android::hardware::Return<void> +CameraDevice::ServiceCallback::onDeviceError( + ErrorCode errorCode, + const CaptureResultExtras& resultExtras) { + ALOGD("Device error received, code %d, frame number %" PRId64 ", request ID %d, subseq ID %d" + " physical camera ID %s", errorCode, resultExtras.frameNumber, resultExtras.requestId, + resultExtras.burstId, resultExtras.errorPhysicalCameraId.c_str()); + auto ret = Void(); + sp<CameraDevice> dev = mDevice.promote(); + if (dev == nullptr) { + return ret; // device has been closed + } + + sp<ACameraCaptureSession> session = dev->mCurrentSession.promote(); + Mutex::Autolock _l(dev->mDeviceLock); + if (dev->mRemote == nullptr) { + return ret; // device has been closed + } + switch (errorCode) { + case ErrorCode::CAMERA_DISCONNECTED: + { + // Camera is disconnected, close the session and expect no more callbacks + if (session != nullptr) { + session->closeByDevice(); + } + dev->mCurrentSession = nullptr; + sp<AMessage> msg = new AMessage(kWhatOnDisconnected, dev->mHandler); + msg->setPointer(kContextKey, dev->mAppCallbacks.context); + msg->setPointer(kDeviceKey, (void*) dev->getWrapper()); + msg->setPointer(kCallbackFpKey, (void*) dev->mAppCallbacks.onDisconnected); + msg->post(); + break; + } + default: + ALOGE("Unknown error from camera device: %d", errorCode); + [[fallthrough]]; + case ErrorCode::CAMERA_DEVICE: + case ErrorCode::CAMERA_SERVICE: + { + int32_t errorVal = ::ERROR_CAMERA_DEVICE; + // We keep this switch since this block might be encountered with + // more than just 2 states. The default fallthrough could have us + // handling more unmatched error cases. + switch (errorCode) { + case ErrorCode::CAMERA_DEVICE: + dev->setCameraDeviceErrorLocked(ACAMERA_ERROR_CAMERA_DEVICE); + break; + case ErrorCode::CAMERA_SERVICE: + dev->setCameraDeviceErrorLocked(ACAMERA_ERROR_CAMERA_SERVICE); + errorVal = ::ERROR_CAMERA_SERVICE; + break; + default: + dev->setCameraDeviceErrorLocked(ACAMERA_ERROR_UNKNOWN); + break; + } + sp<AMessage> msg = new AMessage(kWhatOnError, dev->mHandler); + msg->setPointer(kContextKey, dev->mAppCallbacks.context); + msg->setPointer(kDeviceKey, (void*) dev->getWrapper()); + msg->setPointer(kCallbackFpKey, (void*) dev->mAppCallbacks.onError); + msg->setInt32(kErrorCodeKey, errorVal); + msg->post(); + break; + } + case ErrorCode::CAMERA_REQUEST: + case ErrorCode::CAMERA_RESULT: + case ErrorCode::CAMERA_BUFFER: + dev->onCaptureErrorLocked(errorCode, resultExtras); + break; + } + return ret; +} + +android::hardware::Return<void> +CameraDevice::ServiceCallback::onDeviceIdle() { + ALOGV("Camera is now idle"); + auto ret = Void(); + sp<CameraDevice> dev = mDevice.promote(); + if (dev == nullptr) { + return ret; // device has been closed + } + + Mutex::Autolock _l(dev->mDeviceLock); + if (dev->isClosed() || dev->mRemote == nullptr) { + return ret; + } + + if (dev->mIdle) { + // Already in idle state. Possibly other thread did waitUntilIdle + return ret; + } + + if (dev->mCurrentSession != nullptr) { + ALOGE("onDeviceIdle sending state cb"); + if (dev->mBusySession != dev->mCurrentSession) { + ALOGE("Current session != busy session"); + dev->setCameraDeviceErrorLocked(ACAMERA_ERROR_CAMERA_DEVICE); + return ret; + } + + sp<AMessage> msg = new AMessage(kWhatSessionStateCb, dev->mHandler); + msg->setPointer(kContextKey, dev->mBusySession->mUserSessionCallback.context); + msg->setObject(kSessionSpKey, dev->mBusySession); + msg->setPointer(kCallbackFpKey, (void*) dev->mBusySession->mUserSessionCallback.onReady); + // Make sure we clear the sp first so the session destructor can + // only happen on handler thread (where we don't hold device/session lock) + dev->mBusySession.clear(); + dev->postSessionMsgAndCleanup(msg); + } + dev->mIdle = true; + dev->mFlushing = false; + return ret; +} + +android::hardware::Return<void> +CameraDevice::ServiceCallback::onCaptureStarted( + const CaptureResultExtras& resultExtras, + uint64_t timestamp) { + auto ret = Void(); + + sp<CameraDevice> dev = mDevice.promote(); + if (dev == nullptr) { + return ret; // device has been closed + } + Mutex::Autolock _l(dev->mDeviceLock); + if (dev->isClosed() || dev->mRemote == nullptr) { + return ret; + } + + int32_t sequenceId = resultExtras.requestId; + int32_t burstId = resultExtras.burstId; + + auto it = dev->mSequenceCallbackMap.find(sequenceId); + if (it != dev->mSequenceCallbackMap.end()) { + CallbackHolder cbh = (*it).second; + ACameraCaptureSession_captureCallback_start onStart = cbh.mOnCaptureStarted; + sp<ACameraCaptureSession> session = cbh.mSession; + if ((size_t) burstId >= cbh.mRequests.size()) { + ALOGE("%s: Error: request index %d out of bound (size %zu)", + __FUNCTION__, burstId, cbh.mRequests.size()); + dev->setCameraDeviceErrorLocked(ACAMERA_ERROR_CAMERA_SERVICE); + } + sp<CaptureRequest> request = cbh.mRequests[burstId]; + sp<AMessage> msg = new AMessage(kWhatCaptureStart, dev->mHandler); + msg->setPointer(kContextKey, cbh.mContext); + msg->setObject(kSessionSpKey, session); + msg->setPointer(kCallbackFpKey, (void*) onStart); + msg->setObject(kCaptureRequestKey, request); + msg->setInt64(kTimeStampKey, timestamp); + dev->postSessionMsgAndCleanup(msg); + } + return ret; +} + +android::hardware::Return<void> +CameraDevice::ServiceCallback::onResultReceived( + const FmqSizeOrMetadata& resultMetadata, + const CaptureResultExtras& resultExtras, + const hidl_vec<PhysicalCaptureResultInfo>& physicalResultInfos) { + auto ret = Void(); + + sp<CameraDevice> dev = mDevice.promote(); + if (dev == nullptr) { + return ret; // device has been closed + } + int32_t sequenceId = resultExtras.requestId; + int64_t frameNumber = resultExtras.frameNumber; + int32_t burstId = resultExtras.burstId; + bool isPartialResult = (resultExtras.partialResultCount < dev->mPartialResultCount); + + if (!isPartialResult) { + ALOGV("SeqId %d frame %" PRId64 " result arrive.", sequenceId, frameNumber); + } + + Mutex::Autolock _l(dev->mDeviceLock); + if (dev->mRemote == nullptr) { + return ret; // device has been disconnected + } + + if (dev->isClosed()) { + if (!isPartialResult) { + dev->mFrameNumberTracker.updateTracker(frameNumber, /*isError*/false); + } + // early return to avoid callback sent to closed devices + return ret; + } + + CameraMetadata metadataCopy; + camera_status_t status = readOneResultMetadata(resultMetadata, + dev->mCaptureResultMetadataQueue.get(), &metadataCopy); + if (status != ACAMERA_OK) { + ALOGE("%s: result metadata couldn't be converted", __FUNCTION__); + return ret; + } + + metadataCopy.update(ANDROID_LENS_INFO_SHADING_MAP_SIZE, dev->mShadingMapSize, /*data_count*/2); + metadataCopy.update(ANDROID_SYNC_FRAME_NUMBER, &frameNumber, /*data_count*/1); + + auto it = dev->mSequenceCallbackMap.find(sequenceId); + if (it != dev->mSequenceCallbackMap.end()) { + CallbackHolder cbh = (*it).second; + sp<ACameraCaptureSession> session = cbh.mSession; + if ((size_t) burstId >= cbh.mRequests.size()) { + ALOGE("%s: Error: request index %d out of bound (size %zu)", + __FUNCTION__, burstId, cbh.mRequests.size()); + dev->setCameraDeviceErrorLocked(ACAMERA_ERROR_CAMERA_SERVICE); + } + sp<CaptureRequest> request = cbh.mRequests[burstId]; + sp<ACameraMetadata> result(new ACameraMetadata( + metadataCopy.release(), ACameraMetadata::ACM_RESULT)); + + std::vector<PhysicalCaptureResultInfoLocal> localPhysicalResult; + localPhysicalResult.resize(physicalResultInfos.size()); + for (size_t i = 0; i < physicalResultInfos.size(); i++) { + localPhysicalResult[i].physicalCameraId = physicalResultInfos[i].physicalCameraId; + status = readOneResultMetadata(physicalResultInfos[i].physicalCameraMetadata, + dev->mCaptureResultMetadataQueue.get(), + &localPhysicalResult[i].physicalMetadata); + if (status != ACAMERA_OK) { + ALOGE("%s: physical camera result metadata couldn't be converted", __FUNCTION__); + return ret; + } + } + sp<ACameraPhysicalCaptureResultInfo> physicalResult( + new ACameraPhysicalCaptureResultInfo(localPhysicalResult, frameNumber)); + + sp<AMessage> msg = new AMessage( + cbh.mIsLogicalCameraCallback ? kWhatLogicalCaptureResult : kWhatCaptureResult, + dev->mHandler); + msg->setPointer(kContextKey, cbh.mContext); + msg->setObject(kSessionSpKey, session); + msg->setObject(kCaptureRequestKey, request); + msg->setObject(kCaptureResultKey, result); + if (isPartialResult) { + msg->setPointer(kCallbackFpKey, + (void *)cbh.mOnCaptureProgressed); + } else if (cbh.mIsLogicalCameraCallback) { + msg->setPointer(kCallbackFpKey, + (void *)cbh.mOnLogicalCameraCaptureCompleted); + msg->setObject(kPhysicalCaptureResultKey, physicalResult); + } else { + msg->setPointer(kCallbackFpKey, + (void *)cbh.mOnCaptureCompleted); + } + dev->postSessionMsgAndCleanup(msg); + } + + if (!isPartialResult) { + dev->mFrameNumberTracker.updateTracker(frameNumber, /*isError*/false); + dev->checkAndFireSequenceCompleteLocked(); + } + + return ret; +} + +android::hardware::Return<void> +CameraDevice::ServiceCallback::onRepeatingRequestError( + uint64_t lastFrameNumber, int32_t stoppedSequenceId) { + auto ret = Void(); + + sp<CameraDevice> dev = mDevice.promote(); + if (dev == nullptr) { + return ret; // device has been closed + } + + Mutex::Autolock _l(dev->mDeviceLock); + + int repeatingSequenceId = dev->mRepeatingSequenceId; + if (stoppedSequenceId == repeatingSequenceId) { + dev->mRepeatingSequenceId = REQUEST_ID_NONE; + } + + dev->checkRepeatingSequenceCompleteLocked(repeatingSequenceId, lastFrameNumber); + + return ret; +} + +camera_status_t CameraDevice::ServiceCallback::readOneResultMetadata( + const FmqSizeOrMetadata& fmqSizeOrMetadata, ResultMetadataQueue* metadataQueue, + CameraMetadata* metadata) { + if (metadataQueue == nullptr || metadata == nullptr) { + return ACAMERA_ERROR_INVALID_PARAMETER; + } + bool converted; + HCameraMetadata hCameraMetadata; + if (fmqSizeOrMetadata.getDiscriminator() == + FmqSizeOrMetadata::hidl_discriminator::fmqMetadataSize) { + hCameraMetadata.resize(fmqSizeOrMetadata.fmqMetadataSize()); + bool read = metadataQueue->read( + hCameraMetadata.data(), fmqSizeOrMetadata.fmqMetadataSize()); + if (!read) { + ALOGE("%s capture request settings could't be read from fmq", __FUNCTION__); + return ACAMERA_ERROR_UNKNOWN; + } + // TODO: Do we actually need to clone here ? + converted = utils::convertFromHidlCloned(hCameraMetadata, metadata); + } else { + converted = utils::convertFromHidlCloned(fmqSizeOrMetadata.metadata(), metadata); + } + + return converted ? ACAMERA_OK : ACAMERA_ERROR_UNKNOWN; +} + +} // namespace acam +} // namespace android
diff --git a/camera/ndk/ndk_vendor/impl/ACameraDevice.h b/camera/ndk/ndk_vendor/impl/ACameraDevice.h new file mode 100644 index 0000000..3328a85 --- /dev/null +++ b/camera/ndk/ndk_vendor/impl/ACameraDevice.h
@@ -0,0 +1,434 @@ +/* + * 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 _ACAMERA_DEVICE_H +#define _ACAMERA_DEVICE_H + +#include <memory> +#include <map> +#include <set> +#include <atomic> +#include <utility> +#include <vector> +#include <utils/StrongPointer.h> +#include <utils/Mutex.h> +#include <utils/List.h> +#include <utils/Vector.h> +#include <android/frameworks/cameraservice/device/2.0/ICameraDeviceUser.h> +#include <android/frameworks/cameraservice/device/2.0/ICameraDeviceCallback.h> +#include <android/frameworks/cameraservice/device/2.0/types.h> +#include <fmq/MessageQueue.h> +#include <media/stagefright/foundation/ALooper.h> +#include <media/stagefright/foundation/AHandler.h> +#include <media/stagefright/foundation/AMessage.h> + +#include <camera/NdkCameraManager.h> +#include <camera/NdkCameraCaptureSession.h> +#include "ACameraMetadata.h" +#include "utils.h" + +namespace android { +namespace acam { + +using ICameraDeviceCallback = frameworks::cameraservice::device::V2_0::ICameraDeviceCallback; +using ICameraDeviceUser = frameworks::cameraservice::device::V2_0::ICameraDeviceUser; +using CaptureResultExtras = frameworks::cameraservice::device::V2_0::CaptureResultExtras; +using PhysicalCaptureResultInfo = frameworks::cameraservice::device::V2_0::PhysicalCaptureResultInfo; +using PhysicalCameraSettings = frameworks::cameraservice::device::V2_0::PhysicalCameraSettings; +using SubmitInfo = frameworks::cameraservice::device::V2_0::SubmitInfo; +using CaptureResultExtras = frameworks::cameraservice::device::V2_0::CaptureResultExtras; +using ErrorCode = frameworks::cameraservice::device::V2_0::ErrorCode; +using FmqSizeOrMetadata = frameworks::cameraservice::device::V2_0::FmqSizeOrMetadata; +using StreamConfigurationMode = frameworks::cameraservice::device::V2_0::StreamConfigurationMode; +using Status = frameworks::cameraservice::common::V2_0::Status; +using ResultMetadataQueue = hardware::MessageQueue<uint8_t, hardware::kSynchronizedReadWrite>; +using RequestMetadataQueue = hardware::MessageQueue<uint8_t, hardware::kSynchronizedReadWrite>; +using CameraStatusAndId = frameworks::cameraservice::service::V2_0::CameraStatusAndId; + +using hardware::hidl_vec; +using hardware::hidl_string; +using utils::native_handle_ptr_wrapper; +using utils::CaptureRequest; +using utils::OutputConfigurationWrapper; + +// Wrap ACameraCaptureFailure so it can be ref-counted +struct CameraCaptureFailure : public RefBase, public ACameraCaptureFailure { }; + +// Wrap PhysicalCaptureResultInfo so that it can be ref-counted +struct PhysicalCaptureResultInfoLocal { + std::string physicalCameraId; + CameraMetadata physicalMetadata; +}; + +struct ACameraPhysicalCaptureResultInfo: public RefBase { + ACameraPhysicalCaptureResultInfo(const std::vector<PhysicalCaptureResultInfoLocal>& info, + int64_t frameNumber) : + mPhysicalResultInfo(info), mFrameNumber(frameNumber) {} + + std::vector<PhysicalCaptureResultInfoLocal> mPhysicalResultInfo; + int64_t mFrameNumber; +}; + +class CameraDevice final : public RefBase { + public: + CameraDevice(const char* id, ACameraDevice_StateCallbacks* cb, + sp<ACameraMetadata> chars, + ACameraDevice* wrapper); + ~CameraDevice(); + + inline const char* getId() const { return mCameraId.c_str(); } + + camera_status_t createCaptureRequest( + ACameraDevice_request_template templateId, + const ACameraIdList* physicalCameraIdList, + ACaptureRequest** request) const; + + camera_status_t createCaptureSession( + const ACaptureSessionOutputContainer* outputs, + const ACaptureRequest* sessionParameters, + const ACameraCaptureSession_stateCallbacks* callbacks, + /*out*/ACameraCaptureSession** session); + + camera_status_t isSessionConfigurationSupported( + const ACaptureSessionOutputContainer* sessionOutputContainer) const; + + // Callbacks from camera service + class ServiceCallback : public ICameraDeviceCallback { + public: + explicit ServiceCallback(CameraDevice* device) : mDevice(device) {} + android::hardware::Return<void> onDeviceError(ErrorCode errorCode, + const CaptureResultExtras& resultExtras) override; + android::hardware::Return<void> onDeviceIdle() override; + android::hardware::Return<void> onCaptureStarted(const CaptureResultExtras& resultExtras, + uint64_t timestamp) override; + android::hardware::Return<void> onResultReceived(const FmqSizeOrMetadata& result, + const CaptureResultExtras& resultExtras, + const hidl_vec<PhysicalCaptureResultInfo>& physicalResultInfos) override; + android::hardware::Return<void> onRepeatingRequestError(uint64_t lastFrameNumber, + int32_t stoppedSequenceId) override; + private: + camera_status_t readOneResultMetadata(const FmqSizeOrMetadata& fmqSizeOrMetadata, + ResultMetadataQueue* metadataQueue, CameraMetadata* metadata); + const wp<CameraDevice> mDevice; + }; + inline sp<ICameraDeviceCallback> getServiceCallback() { + return mServiceCallback; + }; + + // Camera device is only functional after remote being set + void setRemoteDevice(sp<ICameraDeviceUser> remote); + + bool setDeviceMetadataQueues(); + inline ACameraDevice* getWrapper() const { return mWrapper; }; + + // Stop the looper thread and unregister the handler + void stopLooper(); + + private: + friend ACameraCaptureSession; + + camera_status_t checkCameraClosedOrErrorLocked() const; + + // device goes into fatal error state after this + void setCameraDeviceErrorLocked(camera_status_t error); + + void disconnectLocked(sp<ACameraCaptureSession>& session); // disconnect from camera service + + camera_status_t stopRepeatingLocked(); + + camera_status_t flushLocked(ACameraCaptureSession*); + + camera_status_t waitUntilIdleLocked(); + + template<class T> + camera_status_t captureLocked(sp<ACameraCaptureSession> session, + /*optional*/T* cbs, + int numRequests, ACaptureRequest** requests, + /*optional*/int* captureSequenceId); + + template<class T> + camera_status_t setRepeatingRequestsLocked(sp<ACameraCaptureSession> session, + /*optional*/T* cbs, + int numRequests, ACaptureRequest** requests, + /*optional*/int* captureSequenceId); + + template<class T> + camera_status_t submitRequestsLocked( + sp<ACameraCaptureSession> session, + /*optional*/T* cbs, + int numRequests, ACaptureRequest** requests, + /*out*/int* captureSequenceId, + bool isRepeating); + + void addRequestSettingsMetadata(ACaptureRequest *aCaptureRequest, sp<CaptureRequest> &req); + + camera_status_t updateOutputConfigurationLocked(ACaptureSessionOutput *output); + + // Since this writes to ICameraDeviceUser's fmq, clients must take care that: + // a) This function is called serially. + // b) This function is called in accordance with ICameraDeviceUser.submitRequestList, + // otherwise, the wrong capture request might have the wrong settings + // metadata associated with it. + camera_status_t allocateCaptureRequestLocked( + const ACaptureRequest* request, sp<CaptureRequest>& outReq); + void allocateOneCaptureRequestMetadata( + PhysicalCameraSettings& cameraSettings, + const std::string& id, const sp<ACameraMetadata>& metadata); + + static ACaptureRequest* allocateACaptureRequest(sp<CaptureRequest>& req, const char* deviceId); + static void freeACaptureRequest(ACaptureRequest*); + + // only For session to hold device lock + // Always grab device lock before grabbing session lock + void lockDeviceForSessionOps() const { mDeviceLock.lock(); }; + void unlockDevice() const { mDeviceLock.unlock(); }; + + // For capture session to notify its end of life + void notifySessionEndOfLifeLocked(ACameraCaptureSession* session); + + camera_status_t configureStreamsLocked(const ACaptureSessionOutputContainer* outputs, + const ACaptureRequest* sessionParameters); + + // Input message will be posted and cleared after this returns + void postSessionMsgAndCleanup(sp<AMessage>& msg); + + mutable Mutex mDeviceLock; + const hidl_string mCameraId; // Camera ID + const ACameraDevice_StateCallbacks mAppCallbacks; // Callback to app + const sp<ACameraMetadata> mChars; // Camera characteristics + const sp<ServiceCallback> mServiceCallback; + ACameraDevice* mWrapper; + + // stream id -> pair of (ACameraWindowType* from application, OutputConfiguration used for + // camera service) + std::map<int, std::pair<native_handle_ptr_wrapper, OutputConfigurationWrapper>> mConfiguredOutputs; + + // TODO: maybe a bool will suffice for synchronous implementation? + std::atomic_bool mClosing; + inline bool isClosed() { return mClosing; } + + bool mInError = false; + camera_status_t mError = ACAMERA_OK; + void onCaptureErrorLocked( + ErrorCode errorCode, + const CaptureResultExtras& resultExtras); + + bool mIdle = true; + // This will avoid a busy session being deleted before it's back to idle state + sp<ACameraCaptureSession> mBusySession; + + sp<ICameraDeviceUser> mRemote; + + // Looper thread to handle callback to app + sp<ALooper> mCbLooper; + // definition of handler and message + enum { + // Device state callbacks + kWhatOnDisconnected, // onDisconnected + kWhatOnError, // onError + // Session state callbacks + kWhatSessionStateCb, // onReady, onActive + // Capture callbacks + kWhatCaptureStart, // onCaptureStarted + kWhatCaptureResult, // onCaptureProgressed, onCaptureCompleted + kWhatLogicalCaptureResult, // onLogicalCameraCaptureCompleted + kWhatCaptureFail, // onCaptureFailed + kWhatLogicalCaptureFail, // onLogicalCameraCaptureFailed + kWhatCaptureSeqEnd, // onCaptureSequenceCompleted + kWhatCaptureSeqAbort, // onCaptureSequenceAborted + kWhatCaptureBufferLost,// onCaptureBufferLost + // Internal cleanup + kWhatCleanUpSessions // Cleanup cached sp<ACameraCaptureSession> + }; + static const char* kContextKey; + static const char* kDeviceKey; + static const char* kErrorCodeKey; + static const char* kCallbackFpKey; + static const char* kSessionSpKey; + static const char* kCaptureRequestKey; + static const char* kTimeStampKey; + static const char* kCaptureResultKey; + static const char* kPhysicalCaptureResultKey; + static const char* kCaptureFailureKey; + static const char* kSequenceIdKey; + static const char* kFrameNumberKey; + static const char* kAnwKey; + static const char* kFailingPhysicalCameraId; + + class CallbackHandler : public AHandler { + public: + explicit CallbackHandler(const char *id); + void onMessageReceived(const sp<AMessage> &msg) override; + + private: + std::string mId; + // This handler will cache all capture session sp until kWhatCleanUpSessions + // is processed. This is used to guarantee the last session reference is always + // being removed in callback thread without holding camera device lock + Vector<sp<ACameraCaptureSession>> mCachedSessions; + }; + sp<CallbackHandler> mHandler; + + /*********************************** + * Capture session related members * + ***********************************/ + // The current active session + wp<ACameraCaptureSession> mCurrentSession; + bool mFlushing = false; + + int mNextSessionId = 0; + // TODO: might need another looper/handler to handle callbacks from service + + static const int REQUEST_ID_NONE = -1; + int mRepeatingSequenceId = REQUEST_ID_NONE; + + // sequence id -> last frame number map + std::map<int32_t, int64_t> mSequenceLastFrameNumberMap; + + struct CallbackHolder { + CallbackHolder(sp<ACameraCaptureSession> session, + const Vector<sp<CaptureRequest>>& requests, + bool isRepeating, + ACameraCaptureSession_captureCallbacks* cbs); + CallbackHolder(sp<ACameraCaptureSession> session, + const Vector<sp<CaptureRequest>>& requests, + bool isRepeating, + ACameraCaptureSession_logicalCamera_captureCallbacks* lcbs); + + template <class T> + void initCaptureCallbacks(T* cbs) { + mContext = nullptr; + mOnCaptureStarted = nullptr; + mOnCaptureProgressed = nullptr; + mOnCaptureCompleted = nullptr; + mOnLogicalCameraCaptureCompleted = nullptr; + mOnLogicalCameraCaptureFailed = nullptr; + mOnCaptureFailed = nullptr; + mOnCaptureSequenceCompleted = nullptr; + mOnCaptureSequenceAborted = nullptr; + mOnCaptureBufferLost = nullptr; + if (cbs != nullptr) { + mContext = cbs->context; + mOnCaptureStarted = cbs->onCaptureStarted; + mOnCaptureProgressed = cbs->onCaptureProgressed; + mOnCaptureSequenceCompleted = cbs->onCaptureSequenceCompleted; + mOnCaptureSequenceAborted = cbs->onCaptureSequenceAborted; + mOnCaptureBufferLost = cbs->onCaptureBufferLost; + } + } + + sp<ACameraCaptureSession> mSession; + Vector<sp<CaptureRequest>> mRequests; + const bool mIsRepeating; + const bool mIsLogicalCameraCallback; + + void* mContext; + ACameraCaptureSession_captureCallback_start mOnCaptureStarted; + ACameraCaptureSession_captureCallback_result mOnCaptureProgressed; + ACameraCaptureSession_captureCallback_result mOnCaptureCompleted; + ACameraCaptureSession_logicalCamera_captureCallback_result mOnLogicalCameraCaptureCompleted; + ACameraCaptureSession_logicalCamera_captureCallback_failed mOnLogicalCameraCaptureFailed; + ACameraCaptureSession_captureCallback_failed mOnCaptureFailed; + ACameraCaptureSession_captureCallback_sequenceEnd mOnCaptureSequenceCompleted; + ACameraCaptureSession_captureCallback_sequenceAbort mOnCaptureSequenceAborted; + ACameraCaptureSession_captureCallback_bufferLost mOnCaptureBufferLost; + }; + // sequence id -> callbacks map + std::map<int, CallbackHolder> mSequenceCallbackMap; + + static const int64_t NO_FRAMES_CAPTURED = -1; + class FrameNumberTracker { + public: + // TODO: Called in onResultReceived and onCaptureErrorLocked + void updateTracker(int64_t frameNumber, bool isError); + inline int64_t getCompletedFrameNumber() { return mCompletedFrameNumber; } + private: + void update(); + void updateCompletedFrameNumber(int64_t frameNumber); + + int64_t mCompletedFrameNumber = NO_FRAMES_CAPTURED; + List<int64_t> mSkippedFrameNumbers; + std::set<int64_t> mFutureErrorSet; + }; + FrameNumberTracker mFrameNumberTracker; + + void checkRepeatingSequenceCompleteLocked(const int sequenceId, const int64_t lastFrameNumber); + void checkAndFireSequenceCompleteLocked(); + + // Misc variables + int32_t mShadingMapSize[2]; // const after constructor + int32_t mPartialResultCount; // const after constructor + std::shared_ptr<ResultMetadataQueue> mCaptureRequestMetadataQueue = nullptr; + std::shared_ptr<ResultMetadataQueue> mCaptureResultMetadataQueue = nullptr; +}; + +} // namespace acam; +} // namespace android; + +/** + * ACameraDevice opaque struct definition + * Leave outside of android namespace because it's NDK struct + */ +struct ACameraDevice { + ACameraDevice(const char* id, ACameraDevice_StateCallbacks* cb, + sp<ACameraMetadata> chars) : + mDevice(new android::acam::CameraDevice(id, cb, std::move(chars), this)) {} + + ~ACameraDevice(); + + /******************* + * NDK public APIs * + *******************/ + inline const char* getId() const { return mDevice->getId(); } + + camera_status_t createCaptureRequest( + ACameraDevice_request_template templateId, + const ACameraIdList* physicalCameraIdList, + ACaptureRequest** request) const { + return mDevice->createCaptureRequest(templateId, physicalCameraIdList, request); + } + + camera_status_t createCaptureSession( + const ACaptureSessionOutputContainer* outputs, + const ACaptureRequest* sessionParameters, + const ACameraCaptureSession_stateCallbacks* callbacks, + /*out*/ACameraCaptureSession** session) { + return mDevice->createCaptureSession(outputs, sessionParameters, callbacks, session); + } + + camera_status_t isSessionConfigurationSupported( + const ACaptureSessionOutputContainer* sessionOutputContainer) const { + return mDevice->isSessionConfigurationSupported(sessionOutputContainer); + } + + /*********************** + * Device interal APIs * + ***********************/ + inline android::sp<android::acam::ICameraDeviceCallback> getServiceCallback() { + return mDevice->getServiceCallback(); + }; + + // Camera device is only functional after remote being set + inline void setRemoteDevice(android::sp<android::acam::ICameraDeviceUser> remote) { + mDevice->setRemoteDevice(remote); + } + inline bool setDeviceMetadataQueues() { + return mDevice->setDeviceMetadataQueues(); + } + private: + android::sp<android::acam::CameraDevice> mDevice; +}; + +#endif // _ACAMERA_DEVICE_H
diff --git a/camera/ndk/ndk_vendor/impl/ACameraDeviceVendor.inc b/camera/ndk/ndk_vendor/impl/ACameraDeviceVendor.inc new file mode 100644 index 0000000..8bd5a52 --- /dev/null +++ b/camera/ndk/ndk_vendor/impl/ACameraDeviceVendor.inc
@@ -0,0 +1,152 @@ +/* + * 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 <vector> +#include <inttypes.h> +#include <android/frameworks/cameraservice/service/2.0/ICameraService.h> +#include <android/frameworks/cameraservice/device/2.0/types.h> +#include <CameraMetadata.h> + +#include "ndk_vendor/impl/ACameraDevice.h" +#include "ACameraCaptureSession.h" +#include "ACameraMetadata.h" +#include "ACaptureRequest.h" +#include "utils.h" + +using namespace android; + +namespace android { +namespace acam { + +template<class T> +camera_status_t +CameraDevice::captureLocked( + sp<ACameraCaptureSession> session, + /*optional*/T* cbs, + int numRequests, ACaptureRequest** requests, + /*optional*/int* captureSequenceId) { + return submitRequestsLocked( + session, cbs, numRequests, requests, captureSequenceId, /*isRepeating*/false); +} + +template<class T> +camera_status_t +CameraDevice::setRepeatingRequestsLocked( + sp<ACameraCaptureSession> session, + /*optional*/T* cbs, + int numRequests, ACaptureRequest** requests, + /*optional*/int* captureSequenceId) { + return submitRequestsLocked( + session, cbs, numRequests, requests, captureSequenceId, /*isRepeating*/true); +} + +template<class T> +camera_status_t CameraDevice::submitRequestsLocked( + sp<ACameraCaptureSession> session, + /*optional*/T* cbs, + int numRequests, ACaptureRequest** requests, + /*out*/int* captureSequenceId, + bool isRepeating) +{ + camera_status_t ret = checkCameraClosedOrErrorLocked(); + if (ret != ACAMERA_OK) { + ALOGE("Camera %s submit capture request failed! ret %d", getId(), ret); + return ret; + } + + // Form two vectors of capture request, one for internal tracking + std::vector<frameworks::cameraservice::device::V2_0::CaptureRequest> requestList; + Vector<sp<CaptureRequest>> requestsV; + requestsV.setCapacity(numRequests); + for (int i = 0; i < numRequests; i++) { + sp<CaptureRequest> req; + ret = allocateCaptureRequestLocked(requests[i], req); + // We need to call this method since after submitRequestList is called, + // the request metadata queue might have removed the capture request + // metadata. Therefore we simply add the metadata to its wrapper class, + // so that it can be retrieved later. + addRequestSettingsMetadata(requests[i], req); + if (ret != ACAMERA_OK) { + ALOGE("Convert capture request to internal format failure! ret %d", ret); + return ret; + } + if (req->mCaptureRequest.streamAndWindowIds.size() == 0) { + ALOGE("Capture request without output target cannot be submitted!"); + return ACAMERA_ERROR_INVALID_PARAMETER; + } + requestList.push_back(utils::convertToHidl(req.get())); + requestsV.push_back(req); + } + if (isRepeating) { + ret = stopRepeatingLocked(); + if (ret != ACAMERA_OK) { + ALOGE("Camera %s stop repeating failed! ret %d", getId(), ret); + return ret; + } + } + + SubmitInfo info; + Status status; + auto remoteRet = mRemote->submitRequestList(requestList, isRepeating, + [&status, &info](auto s, auto &submitInfo) { + status = s; + info = submitInfo; + }); + if (!remoteRet.isOk()) { + ALOGE("%s: Transaction error for submitRequestList call: %s", __FUNCTION__, + remoteRet.description().c_str()); + } + if (status != Status::NO_ERROR) { + return utils::convertFromHidl(status); + } + int32_t sequenceId = info.requestId; + int64_t lastFrameNumber = info.lastFrameNumber; + if (sequenceId < 0) { + ALOGE("Camera %s submit request remote failure: ret %d", getId(), sequenceId); + return ACAMERA_ERROR_UNKNOWN; + } + + CallbackHolder cbHolder(session, requestsV, isRepeating, cbs); + mSequenceCallbackMap.insert(std::make_pair(sequenceId, cbHolder)); + if (isRepeating) { + // stopRepeating above should have cleanup repeating sequence id + if (mRepeatingSequenceId != REQUEST_ID_NONE) { + setCameraDeviceErrorLocked(ACAMERA_ERROR_CAMERA_DEVICE); + return ACAMERA_ERROR_CAMERA_DEVICE; + } + mRepeatingSequenceId = sequenceId; + } else { + mSequenceLastFrameNumberMap.insert(std::make_pair(sequenceId, lastFrameNumber)); + } + + if (mIdle) { + sp<AMessage> msg = new AMessage(kWhatSessionStateCb, mHandler); + msg->setPointer(kContextKey, session->mUserSessionCallback.context); + msg->setObject(kSessionSpKey, session); + msg->setPointer(kCallbackFpKey, (void*) session->mUserSessionCallback.onActive); + postSessionMsgAndCleanup(msg); + } + mIdle = false; + mBusySession = session; + + if (captureSequenceId) { + *captureSequenceId = sequenceId; + } + return ACAMERA_OK; +} + +} // namespace acam +} // namespace android
diff --git a/camera/ndk/ndk_vendor/impl/ACameraManager.cpp b/camera/ndk/ndk_vendor/impl/ACameraManager.cpp new file mode 100644 index 0000000..70c887a --- /dev/null +++ b/camera/ndk/ndk_vendor/impl/ACameraManager.cpp
@@ -0,0 +1,614 @@ +/* + * 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 "ACameraManagerVendor" + +#include <memory> +#include "ndk_vendor/impl/ACameraManager.h" +#include "ACameraMetadata.h" +#include "ndk_vendor/impl/ACameraDevice.h" +#include "utils.h" +#include <CameraMetadata.h> +#include <camera_metadata_hidden.h> + +#include <utils/Vector.h> +#include <cutils/properties.h> +#include <stdlib.h> + +#include <VendorTagDescriptor.h> + +using namespace android::acam; + +namespace android { +namespace acam { + +using frameworks::cameraservice::service::V2_0::CameraStatusAndId; +using frameworks::cameraservice::common::V2_0::ProviderIdAndVendorTagSections; +using android::hardware::camera::common::V1_0::helper::VendorTagDescriptor; +using android::hardware::camera::common::V1_0::helper::VendorTagDescriptorCache; + +// Static member definitions +const char* CameraManagerGlobal::kCameraIdKey = "CameraId"; +const char* CameraManagerGlobal::kCallbackFpKey = "CallbackFp"; +const char* CameraManagerGlobal::kContextKey = "CallbackContext"; +Mutex CameraManagerGlobal::sLock; +CameraManagerGlobal* CameraManagerGlobal::sInstance = nullptr; + +/** + * The vendor tag descriptor class that takes HIDL vendor tag information as + * input. Not part of vendor available VendorTagDescriptor class because that class is used by + * default HAL implementation code as well. + */ +class HidlVendorTagDescriptor : public VendorTagDescriptor { +public: + /** + * Create a VendorTagDescriptor object from the HIDL VendorTagSection + * vector. + * + * Returns OK on success, or a negative error code. + */ + static status_t createDescriptorFromHidl(const hidl_vec<VendorTagSection>& vts, + /*out*/ sp<VendorTagDescriptor> *descriptor); +}; + +status_t HidlVendorTagDescriptor::createDescriptorFromHidl(const hidl_vec<VendorTagSection> &vts, + sp<VendorTagDescriptor> *descriptor) { + int tagCount = 0; + + for (size_t s = 0; s < vts.size(); s++) { + tagCount += vts[s].tags.size(); + } + + if (tagCount < 0 || tagCount > INT32_MAX) { + ALOGE("%s: tag count %d from vendor tag sections is invalid.", __FUNCTION__, tagCount); + return BAD_VALUE; + } + + Vector<uint32_t> tagArray; + LOG_ALWAYS_FATAL_IF(tagArray.resize(tagCount) != tagCount, + "%s: too many (%u) vendor tags defined.", __FUNCTION__, tagCount); + + sp<HidlVendorTagDescriptor> desc = new HidlVendorTagDescriptor(); + desc->mTagCount = tagCount; + + KeyedVector<uint32_t, String8> tagToSectionMap; + + int idx = 0; + for (size_t s = 0; s < vts.size(); s++) { + const VendorTagSection& section = vts[s]; + const char *sectionName = section.sectionName.c_str(); + if (sectionName == NULL) { + ALOGE("%s: no section name defined for vendor tag section %zu.", __FUNCTION__, s); + return BAD_VALUE; + } + String8 sectionString(sectionName); + desc->mSections.add(sectionString); + + for (size_t j = 0; j < section.tags.size(); j++) { + uint32_t tag = section.tags[j].tagId; + if (tag < CAMERA_METADATA_VENDOR_TAG_BOUNDARY) { + ALOGE("%s: vendor tag %d not in vendor tag section.", __FUNCTION__, tag); + return BAD_VALUE; + } + + tagArray.editItemAt(idx++) = section.tags[j].tagId; + + const char *tagName = section.tags[j].tagName.c_str(); + if (tagName == NULL) { + ALOGE("%s: no tag name defined for vendor tag %d.", __FUNCTION__, tag); + return BAD_VALUE; + } + desc->mTagToNameMap.add(tag, String8(tagName)); + tagToSectionMap.add(tag, sectionString); + + int tagType = (int) section.tags[j].tagType; + if (tagType < 0 || tagType >= NUM_TYPES) { + ALOGE("%s: tag type %d from vendor ops does not exist.", __FUNCTION__, tagType); + return BAD_VALUE; + } + desc->mTagToTypeMap.emplace(tag, tagType); + } + } + + for (size_t i = 0; i < tagArray.size(); ++i) { + uint32_t tag = tagArray[i]; + String8 sectionString = tagToSectionMap.valueFor(tag); + + // Set up tag to section index map + ssize_t index = desc->mSections.indexOf(sectionString); + LOG_ALWAYS_FATAL_IF(index < 0, "index %zd must be non-negative", index); + desc->mTagToSectionMap.add(tag, static_cast<uint32_t>(index)); + + // Set up reverse mapping + ssize_t reverseIndex = -1; + if ((reverseIndex = desc->mReverseMapping.indexOfKey(sectionString)) < 0) { + KeyedVector<String8, uint32_t>* nameMapper = new KeyedVector<String8, uint32_t>(); + reverseIndex = desc->mReverseMapping.add(sectionString, nameMapper); + } + desc->mReverseMapping[reverseIndex]->add(desc->mTagToNameMap.valueFor(tag), tag); + } + + *descriptor = std::move(desc); + return OK; +} + +CameraManagerGlobal& +CameraManagerGlobal::getInstance() { + Mutex::Autolock _l(sLock); + CameraManagerGlobal* instance = sInstance; + if (instance == nullptr) { + instance = new CameraManagerGlobal(); + sInstance = instance; + } + return *instance; +} + +CameraManagerGlobal::~CameraManagerGlobal() { + // clear sInstance so next getInstance call knows to create a new one + Mutex::Autolock _sl(sLock); + sInstance = nullptr; + Mutex::Autolock _l(mLock); + if (mCameraService != nullptr) { + mCameraService->unlinkToDeath(mDeathNotifier); + mCameraService->removeListener(mCameraServiceListener); + } + mDeathNotifier.clear(); + if (mCbLooper != nullptr) { + mCbLooper->unregisterHandler(mHandler->id()); + mCbLooper->stop(); + } + mCbLooper.clear(); + mHandler.clear(); + mCameraServiceListener.clear(); + mCameraService.clear(); +} + +static bool isCameraServiceDisabled() { + char value[PROPERTY_VALUE_MAX]; + property_get("config.disable_cameraservice", value, "0"); + return (strncmp(value, "0", 2) != 0 && strncasecmp(value, "false", 6) != 0); +} + +bool CameraManagerGlobal::setupVendorTags() { + sp<VendorTagDescriptorCache> tagCache = new VendorTagDescriptorCache(); + Status status = Status::NO_ERROR; + std::vector<ProviderIdAndVendorTagSections> providerIdsAndVts; + auto remoteRet = mCameraService->getCameraVendorTagSections([&status, &providerIdsAndVts] + (Status s, + auto &IdsAndVts) { + status = s; + providerIdsAndVts = IdsAndVts; }); + + if (!remoteRet.isOk() || status != Status::NO_ERROR) { + ALOGE("Failed to retrieve VendorTagSections %s", remoteRet.description().c_str()); + return false; + } + // Convert each providers VendorTagSections into a VendorTagDescriptor and + // add it to the cache + for (auto &providerIdAndVts : providerIdsAndVts) { + sp<VendorTagDescriptor> vendorTagDescriptor; + if (HidlVendorTagDescriptor::createDescriptorFromHidl(providerIdAndVts.vendorTagSections, + &vendorTagDescriptor) != OK) { + ALOGE("Failed to convert from Hidl: VendorTagDescriptor"); + return false; + } + tagCache->addVendorDescriptor(providerIdAndVts.providerId, vendorTagDescriptor); + } + VendorTagDescriptorCache::setAsGlobalVendorTagCache(tagCache); + return true; +} + +sp<ICameraService> CameraManagerGlobal::getCameraService() { + Mutex::Autolock _l(mLock); + if (mCameraService.get() == nullptr) { + if (isCameraServiceDisabled()) { + return mCameraService; + } + + sp<ICameraService> cameraServiceBinder; + do { + cameraServiceBinder = ICameraService::getService(); + if (cameraServiceBinder != nullptr) { + break; + } + ALOGW("CameraService not published, waiting..."); + usleep(kCameraServicePollDelay); + } while(true); + if (mDeathNotifier == nullptr) { + mDeathNotifier = new DeathNotifier(this); + } + cameraServiceBinder->linkToDeath(mDeathNotifier, 0); + mCameraService = cameraServiceBinder; + + // Setup looper thread to perfrom availiability callbacks + if (mCbLooper == nullptr) { + mCbLooper = new ALooper; + mCbLooper->setName("C2N-mgr-looper"); + status_t err = mCbLooper->start( + /*runOnCallingThread*/false, + /*canCallJava*/ true, + PRIORITY_DEFAULT); + if (err != OK) { + ALOGE("%s: Unable to start camera service listener looper: %s (%d)", + __FUNCTION__, strerror(-err), err); + mCbLooper.clear(); + return nullptr; + } + if (mHandler == nullptr) { + mHandler = new CallbackHandler(); + } + mCbLooper->registerHandler(mHandler); + } + + // register ICameraServiceListener + if (mCameraServiceListener == nullptr) { + mCameraServiceListener = new CameraServiceListener(this); + } + hidl_vec<CameraStatusAndId> cameraStatuses{}; + Status status = Status::NO_ERROR; + auto remoteRet = mCameraService->addListener(mCameraServiceListener, + [&status, &cameraStatuses](Status s, + auto &retStatuses) { + status = s; + cameraStatuses = retStatuses; + }); + if (!remoteRet.isOk() || status != Status::NO_ERROR) { + ALOGE("Failed to add listener to camera service %s", remoteRet.description().c_str()); + } + + // Setup vendor tags + if (!setupVendorTags()) { + ALOGE("Unable to set up vendor tags"); + return nullptr; + } + + for (auto& c : cameraStatuses) { + onStatusChangedLocked(c); + } + } + return mCameraService; +} + +void CameraManagerGlobal::DeathNotifier::serviceDied(uint64_t cookie, const wp<IBase> &who) { + (void) cookie; + (void) who; + ALOGE("Camera service binderDied!"); + sp<CameraManagerGlobal> cm = mCameraManager.promote(); + if (cm != nullptr) { + AutoMutex lock(cm->mLock); + for (auto& pair : cm->mDeviceStatusMap) { + CameraStatusAndId cameraStatusAndId; + cameraStatusAndId.cameraId = pair.first; + cameraStatusAndId.deviceStatus = pair.second; + cm->onStatusChangedLocked(cameraStatusAndId); + } + cm->mCameraService.clear(); + // TODO: consider adding re-connect call here? + } +} + +void CameraManagerGlobal::registerAvailabilityCallback( + const ACameraManager_AvailabilityCallbacks *callback) { + Mutex::Autolock _l(mLock); + Callback cb(callback); + auto pair = mCallbacks.insert(cb); + // Send initial callbacks if callback is newly registered + if (pair.second) { + for (auto& pair : mDeviceStatusMap) { + const hidl_string& cameraId = pair.first; + CameraDeviceStatus status = pair.second; + + sp<AMessage> msg = new AMessage(kWhatSendSingleCallback, mHandler); + ACameraManager_AvailabilityCallback cb = isStatusAvailable(status) ? + callback->onCameraAvailable : callback->onCameraUnavailable; + msg->setPointer(kCallbackFpKey, (void *) cb); + msg->setPointer(kContextKey, callback->context); + msg->setString(kCameraIdKey, AString(cameraId.c_str())); + msg->post(); + } + } +} + +void CameraManagerGlobal::unregisterAvailabilityCallback( + const ACameraManager_AvailabilityCallbacks *callback) { + Mutex::Autolock _l(mLock); + Callback cb(callback); + mCallbacks.erase(cb); +} + +void CameraManagerGlobal::getCameraIdList(std::vector<hidl_string>* cameraIds) { + // Ensure that we have initialized/refreshed the list of available devices + auto cs = getCameraService(); + Mutex::Autolock _l(mLock); + + for(auto& deviceStatus : mDeviceStatusMap) { + if (deviceStatus.second == CameraDeviceStatus::STATUS_NOT_PRESENT || + deviceStatus.second == CameraDeviceStatus::STATUS_ENUMERATING) { + continue; + } + cameraIds->push_back(deviceStatus.first); + } +} + +bool CameraManagerGlobal::validStatus(CameraDeviceStatus status) { + switch (status) { + case CameraDeviceStatus::STATUS_NOT_PRESENT: + case CameraDeviceStatus::STATUS_PRESENT: + case CameraDeviceStatus::STATUS_ENUMERATING: + case CameraDeviceStatus::STATUS_NOT_AVAILABLE: + return true; + default: + return false; + } +} + +bool CameraManagerGlobal::isStatusAvailable(CameraDeviceStatus status) { + switch (status) { + case CameraDeviceStatus::STATUS_PRESENT: + return true; + default: + return false; + } +} + +void CameraManagerGlobal::CallbackHandler::onMessageReceived( + const sp<AMessage> &msg) { + switch (msg->what()) { + case kWhatSendSingleCallback: + { + ACameraManager_AvailabilityCallback cb; + void* context; + AString cameraId; + bool found = msg->findPointer(kCallbackFpKey, (void**) &cb); + if (!found) { + ALOGE("%s: Cannot find camera callback fp!", __FUNCTION__); + return; + } + found = msg->findPointer(kContextKey, &context); + if (!found) { + ALOGE("%s: Cannot find callback context!", __FUNCTION__); + return; + } + found = msg->findString(kCameraIdKey, &cameraId); + if (!found) { + ALOGE("%s: Cannot find camera ID!", __FUNCTION__); + return; + } + (*cb)(context, cameraId.c_str()); + break; + } + default: + ALOGE("%s: unknown message type %d", __FUNCTION__, msg->what()); + break; + } +} + +hardware::Return<void> CameraManagerGlobal::CameraServiceListener::onStatusChanged( + const CameraStatusAndId &statusAndId) { + sp<CameraManagerGlobal> cm = mCameraManager.promote(); + if (cm != nullptr) { + cm->onStatusChanged(statusAndId); + } else { + ALOGE("Cannot deliver status change. Global camera manager died"); + } + return Void(); +} + +void CameraManagerGlobal::onStatusChanged( + const CameraStatusAndId &statusAndId) { + Mutex::Autolock _l(mLock); + onStatusChangedLocked(statusAndId); +} + +void CameraManagerGlobal::onStatusChangedLocked( + const CameraStatusAndId &statusAndId) { + hidl_string cameraId = statusAndId.cameraId; + CameraDeviceStatus status = statusAndId.deviceStatus; + if (!validStatus(status)) { + ALOGE("%s: Invalid status %d", __FUNCTION__, status); + return; + } + + bool firstStatus = (mDeviceStatusMap.count(cameraId) == 0); + CameraDeviceStatus oldStatus = firstStatus ? + status : // first status + mDeviceStatusMap[cameraId]; + + if (!firstStatus && + isStatusAvailable(status) == isStatusAvailable(oldStatus)) { + // No status update. No need to send callback + return; + } + + // Iterate through all registered callbacks + mDeviceStatusMap[cameraId] = status; + for (auto cb : mCallbacks) { + sp<AMessage> msg = new AMessage(kWhatSendSingleCallback, mHandler); + ACameraManager_AvailabilityCallback cbFp = isStatusAvailable(status) ? + cb.mAvailable : cb.mUnavailable; + msg->setPointer(kCallbackFpKey, (void *) cbFp); + msg->setPointer(kContextKey, cb.mContext); + msg->setString(kCameraIdKey, AString(cameraId.c_str())); + msg->post(); + } + if (status == CameraDeviceStatus::STATUS_NOT_PRESENT) { + mDeviceStatusMap.erase(cameraId); + } +} + +} // namespace acam +} // namespace android + +/** + * ACameraManger Implementation + */ +camera_status_t +ACameraManager::getCameraIdList(ACameraIdList** cameraIdList) { + Mutex::Autolock _l(mLock); + + std::vector<hidl_string> idList; + CameraManagerGlobal::getInstance().getCameraIdList(&idList); + + int numCameras = idList.size(); + ACameraIdList *out = new ACameraIdList; + if (!out) { + ALOGE("Allocate memory for ACameraIdList failed!"); + return ACAMERA_ERROR_NOT_ENOUGH_MEMORY; + } + out->numCameras = numCameras; + out->cameraIds = new const char*[numCameras]; + if (!out->cameraIds) { + ALOGE("Allocate memory for ACameraIdList failed!"); + deleteCameraIdList(out); + return ACAMERA_ERROR_NOT_ENOUGH_MEMORY; + } + for (int i = 0; i < numCameras; i++) { + const char* src = idList[i].c_str(); + size_t dstSize = strlen(src) + 1; + char* dst = new char[dstSize]; + if (!dst) { + ALOGE("Allocate memory for ACameraIdList failed!"); + deleteCameraIdList(out); + return ACAMERA_ERROR_NOT_ENOUGH_MEMORY; + } + strlcpy(dst, src, dstSize); + out->cameraIds[i] = dst; + } + *cameraIdList = out; + return ACAMERA_OK; +} + +void +ACameraManager::deleteCameraIdList(ACameraIdList* cameraIdList) { + if (cameraIdList != nullptr) { + if (cameraIdList->cameraIds != nullptr) { + for (int i = 0; i < cameraIdList->numCameras; i ++) { + if (cameraIdList->cameraIds[i] != nullptr) { + delete[] cameraIdList->cameraIds[i]; + } + } + delete[] cameraIdList->cameraIds; + } + delete cameraIdList; + } +} + +camera_status_t ACameraManager::getCameraCharacteristics( + const char *cameraIdStr, sp<ACameraMetadata> *characteristics) { + Mutex::Autolock _l(mLock); + + sp<ICameraService> cs = CameraManagerGlobal::getInstance().getCameraService(); + if (cs == nullptr) { + ALOGE("%s: Cannot reach camera service!", __FUNCTION__); + return ACAMERA_ERROR_CAMERA_DISCONNECTED; + } + CameraMetadata rawMetadata; + Status status = Status::NO_ERROR; + auto serviceRet = + cs->getCameraCharacteristics(cameraIdStr, + [&status, &rawMetadata] (auto s , + const hidl_vec<uint8_t> &metadata) { + status = s; + if (status == Status::NO_ERROR) { + utils::convertFromHidlCloned(metadata, &rawMetadata); + } + }); + if (!serviceRet.isOk() || status != Status::NO_ERROR) { + ALOGE("Get camera characteristics from camera service failed"); + return ACAMERA_ERROR_UNKNOWN; // should not reach here + } + + *characteristics = new ACameraMetadata( + rawMetadata.release(), ACameraMetadata::ACM_CHARACTERISTICS); + return ACAMERA_OK; +} + +camera_status_t +ACameraManager::openCamera( + const char* cameraId, + ACameraDevice_StateCallbacks* callback, + /*out*/ACameraDevice** outDevice) { + sp<ACameraMetadata> rawChars; + camera_status_t ret = getCameraCharacteristics(cameraId, &rawChars); + Mutex::Autolock _l(mLock); + if (ret != ACAMERA_OK) { + ALOGE("%s: cannot get camera characteristics for camera %s. err %d", + __FUNCTION__, cameraId, ret); + return ACAMERA_ERROR_INVALID_PARAMETER; + } + + ACameraDevice* device = new ACameraDevice(cameraId, callback, std::move(rawChars)); + + sp<ICameraService> cs = CameraManagerGlobal::getInstance().getCameraService(); + if (cs == nullptr) { + ALOGE("%s: Cannot reach camera service!", __FUNCTION__); + delete device; + return ACAMERA_ERROR_CAMERA_DISCONNECTED; + } + + sp<ICameraDeviceCallback> callbacks = device->getServiceCallback(); + sp<ICameraDeviceUser> deviceRemote; + + // No way to get package name from native. + // Send a zero length package name and let camera service figure it out from UID + Status status = Status::NO_ERROR; + auto serviceRet = cs->connectDevice( + callbacks, cameraId, [&status, &deviceRemote](auto s, auto &device) { + status = s; + deviceRemote = device; + }); + + if (!serviceRet.isOk() || status != Status::NO_ERROR) { + ALOGE("%s: connect camera device failed", __FUNCTION__); + // TODO: Convert serviceRet to camera_status_t + delete device; + return ACAMERA_ERROR_UNKNOWN; + } + if (deviceRemote == nullptr) { + ALOGE("%s: connect camera device failed! remote device is null", __FUNCTION__); + delete device; + return ACAMERA_ERROR_CAMERA_DISCONNECTED; + } + device->setRemoteDevice(deviceRemote); + device->setDeviceMetadataQueues(); + *outDevice = device; + return ACAMERA_OK; +} + +camera_status_t +ACameraManager::getTagFromName(const char *cameraId, const char *name, uint32_t *tag) { + sp<ACameraMetadata> rawChars; + camera_status_t ret = getCameraCharacteristics(cameraId, &rawChars); + if (ret != ACAMERA_OK) { + ALOGE("%s, Cannot retrieve camera characteristics for camera id %s", __FUNCTION__, + cameraId); + return ACAMERA_ERROR_METADATA_NOT_FOUND; + } + const CameraMetadata& metadata = rawChars->getInternalData(); + const camera_metadata_t *rawMetadata = metadata.getAndLock(); + metadata_vendor_id_t vendorTagId = get_camera_metadata_vendor_id(rawMetadata); + metadata.unlock(rawMetadata); + sp<VendorTagDescriptorCache> vtCache = VendorTagDescriptorCache::getGlobalVendorTagCache(); + sp<VendorTagDescriptor> vTags = nullptr; + vtCache->getVendorTagDescriptor(vendorTagId, &vTags); + status_t status= metadata.getTagFromName(name, vTags.get(), tag); + return status == OK ? ACAMERA_OK : ACAMERA_ERROR_METADATA_NOT_FOUND; +} + +ACameraManager::~ACameraManager() { + +}
diff --git a/camera/ndk/ndk_vendor/impl/ACameraManager.h b/camera/ndk/ndk_vendor/impl/ACameraManager.h new file mode 100644 index 0000000..2c62d44 --- /dev/null +++ b/camera/ndk/ndk_vendor/impl/ACameraManager.h
@@ -0,0 +1,217 @@ +/* + * 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 _ACAMERA_MANAGER_H +#define _ACAMERA_MANAGER_H + +#include <camera/NdkCameraManager.h> + +#include <android-base/parseint.h> +#include <android/frameworks/cameraservice/service/2.0/ICameraService.h> + +#include <CameraMetadata.h> +#include <utils/StrongPointer.h> +#include <utils/Mutex.h> + +#include <media/stagefright/foundation/ALooper.h> +#include <media/stagefright/foundation/AHandler.h> +#include <media/stagefright/foundation/AMessage.h> + +#include <set> +#include <map> + +namespace android { +namespace acam { + +using ICameraService = frameworks::cameraservice::service::V2_0::ICameraService; +using CameraDeviceStatus = frameworks::cameraservice::service::V2_0::CameraDeviceStatus; +using ICameraServiceListener = frameworks::cameraservice::service::V2_0::ICameraServiceListener; +using CameraStatusAndId = frameworks::cameraservice::service::V2_0::CameraStatusAndId; +using Status = frameworks::cameraservice::common::V2_0::Status; +using VendorTagSection = frameworks::cameraservice::common::V2_0::VendorTagSection; +using VendorTag = frameworks::cameraservice::common::V2_0::VendorTag; +using IBase = android::hidl::base::V1_0::IBase; +using android::hardware::hidl_string; +using hardware::Void; + +/** + * Per-process singleton instance of CameraManger. Shared by all ACameraManager + * instances. Created when first ACameraManager is created and destroyed when + * all ACameraManager instances are deleted. + * + * TODO: maybe CameraManagerGlobal is better suited in libcameraclient? + */ +class CameraManagerGlobal final : public RefBase { + public: + static CameraManagerGlobal& getInstance(); + sp<ICameraService> getCameraService(); + + void registerAvailabilityCallback( + const ACameraManager_AvailabilityCallbacks *callback); + void unregisterAvailabilityCallback( + const ACameraManager_AvailabilityCallbacks *callback); + + void registerExtendedAvailabilityCallback( + const ACameraManager_ExtendedAvailabilityCallbacks* /*callback*/) {} + void unregisterExtendedAvailabilityCallback( + const ACameraManager_ExtendedAvailabilityCallbacks* /*callback*/) {} + + /** + * Return camera IDs that support camera2 + */ + void getCameraIdList(std::vector<hidl_string> *cameraIds); + + private: + sp<ICameraService> mCameraService; + const int kCameraServicePollDelay = 500000; // 0.5s + Mutex mLock; + class DeathNotifier : public android::hardware::hidl_death_recipient { + public: + explicit DeathNotifier(CameraManagerGlobal* cm) : mCameraManager(cm) {} + protected: + // IBinder::DeathRecipient implementation + virtual void serviceDied(uint64_t cookie, const wp<IBase> &who); + private: + const wp<CameraManagerGlobal> mCameraManager; + }; + sp<DeathNotifier> mDeathNotifier; + + class CameraServiceListener final : public ICameraServiceListener { + public: + explicit CameraServiceListener(CameraManagerGlobal* cm) : mCameraManager(cm) {} + android::hardware::Return<void> onStatusChanged( + const CameraStatusAndId &statusAndId) override; + + private: + const wp<CameraManagerGlobal> mCameraManager; + }; + sp<CameraServiceListener> mCameraServiceListener; + + // Wrapper of ACameraManager_AvailabilityCallbacks so we can store it in std::set + struct Callback { + explicit Callback(const ACameraManager_AvailabilityCallbacks *callback) : + mAvailable(callback->onCameraAvailable), + mUnavailable(callback->onCameraUnavailable), + mContext(callback->context) {} + + bool operator == (const Callback& other) const { + return (mAvailable == other.mAvailable && + mUnavailable == other.mUnavailable && + mContext == other.mContext); + } + bool operator != (const Callback& other) const { + return !(*this == other); + } + bool operator < (const Callback& other) const { + if (*this == other) return false; + if (mContext != other.mContext) return mContext < other.mContext; + if (mAvailable != other.mAvailable) return mAvailable < other.mAvailable; + return mUnavailable < other.mUnavailable; + } + bool operator > (const Callback& other) const { + return (*this != other && !(*this < other)); + } + ACameraManager_AvailabilityCallback mAvailable; + ACameraManager_AvailabilityCallback mUnavailable; + void* mContext; + }; + std::set<Callback> mCallbacks; + + // definition of handler and message + enum { + kWhatSendSingleCallback + }; + static const char* kCameraIdKey; + static const char* kCallbackFpKey; + static const char* kContextKey; + class CallbackHandler : public AHandler { + public: + CallbackHandler() {} + void onMessageReceived(const sp<AMessage> &msg) override; + }; + sp<CallbackHandler> mHandler; + sp<ALooper> mCbLooper; // Looper thread where callbacks actually happen on + + void onStatusChanged(const CameraStatusAndId &statusAndId); + void onStatusChangedLocked(const CameraStatusAndId &statusAndId); + bool setupVendorTags(); + + // Utils for status + static bool validStatus(CameraDeviceStatus status); + static bool isStatusAvailable(CameraDeviceStatus status); + + // The sort logic must match the logic in + // libcameraservice/common/CameraProviderManager.cpp::getAPI1CompatibleCameraDeviceIds + struct CameraIdComparator { + bool operator()(const hidl_string& a, const hidl_string& b) const { + uint32_t aUint = 0, bUint = 0; + bool aIsUint = base::ParseUint(a.c_str(), &aUint); + bool bIsUint = base::ParseUint(b.c_str(), &bUint); + + // Uint device IDs first + if (aIsUint && bIsUint) { + return aUint < bUint; + } else if (aIsUint) { + return true; + } else if (bIsUint) { + return false; + } + // Simple string compare if both id are not uint + return a < b; + } + }; + + // Map camera_id -> status + std::map<hidl_string, CameraDeviceStatus, CameraIdComparator> mDeviceStatusMap; + + // For the singleton instance + static Mutex sLock; + static CameraManagerGlobal* sInstance; + CameraManagerGlobal() {}; + ~CameraManagerGlobal(); +}; + +} // namespace acam; +} // namespace android; + +/** + * ACameraManager opaque struct definition + * Leave outside of android namespace because it's NDK struct + */ +struct ACameraManager { + ACameraManager() : + mGlobalManager(&(android::acam::CameraManagerGlobal::getInstance())) {} + ~ACameraManager(); + camera_status_t getCameraIdList(ACameraIdList** cameraIdList); + static void deleteCameraIdList(ACameraIdList* cameraIdList); + + camera_status_t getCameraCharacteristics( + const char* cameraId, android::sp<ACameraMetadata>* characteristics); + + camera_status_t openCamera(const char* cameraId, + ACameraDevice_StateCallbacks* callback, + /*out*/ACameraDevice** device); + camera_status_t getTagFromName(const char *cameraId, const char *name, uint32_t *tag); + + private: + enum { + kCameraIdListNotInit = -1 + }; + android::Mutex mLock; + android::sp<android::acam::CameraManagerGlobal> mGlobalManager; +}; + +#endif //_ACAMERA_MANAGER_H
diff --git a/camera/ndk/ndk_vendor/impl/ACaptureRequestVendor.h b/camera/ndk/ndk_vendor/impl/ACaptureRequestVendor.h new file mode 100644 index 0000000..ed67615 --- /dev/null +++ b/camera/ndk/ndk_vendor/impl/ACaptureRequestVendor.h
@@ -0,0 +1,36 @@ +/* + * 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 "utils.h" + +struct ACameraOutputTarget { + explicit ACameraOutputTarget(native_handle_t* window) : mWindow(window) {}; + + bool operator == (const ACameraOutputTarget& other) const { + return mWindow == other.mWindow; + } + bool operator != (const ACameraOutputTarget& other) const { + return mWindow != other.mWindow; + } + bool operator < (const ACameraOutputTarget& other) const { + return mWindow < other.mWindow; + } + bool operator > (const ACameraOutputTarget& other) const { + return mWindow > other.mWindow; + } + + android::acam::utils::native_handle_ptr_wrapper mWindow; +};
diff --git a/camera/ndk/ndk_vendor/impl/utils.cpp b/camera/ndk/ndk_vendor/impl/utils.cpp new file mode 100644 index 0000000..e4fb204 --- /dev/null +++ b/camera/ndk/ndk_vendor/impl/utils.cpp
@@ -0,0 +1,197 @@ +/* + * 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 "ACameraVendorUtils" + +#include <utils/Log.h> + +#include "utils.h" + +namespace android { +namespace acam { +namespace utils { + +// Convert CaptureRequest wrappable by sp<> to hidl CaptureRequest. +frameworks::cameraservice::device::V2_0::CaptureRequest +convertToHidl(const CaptureRequest *captureRequest) { + frameworks::cameraservice::device::V2_0::CaptureRequest hCaptureRequest; + hCaptureRequest.physicalCameraSettings = captureRequest->mCaptureRequest.physicalCameraSettings; + hCaptureRequest.streamAndWindowIds = captureRequest->mCaptureRequest.streamAndWindowIds; + return hCaptureRequest; +} + +HRotation convertToHidl(int rotation) { + HRotation hRotation = HRotation::R0; + switch(rotation) { + case CAMERA3_STREAM_ROTATION_90: + hRotation = HRotation::R90; + break; + case CAMERA3_STREAM_ROTATION_180: + hRotation = HRotation::R180; + break; + case CAMERA3_STREAM_ROTATION_270: + hRotation = HRotation::R270; + break; + default: + break; + } + return hRotation; +} + +bool convertFromHidlCloned(const HCameraMetadata &metadata, CameraMetadata *rawMetadata) { + const camera_metadata *buffer = (camera_metadata_t*)(metadata.data()); + size_t expectedSize = metadata.size(); + int ret = validate_camera_metadata_structure(buffer, &expectedSize); + if (ret == OK || ret == CAMERA_METADATA_VALIDATION_SHIFTED) { + *rawMetadata = buffer; + } else { + ALOGE("%s: Malformed camera metadata received from caller", __FUNCTION__); + return false; + } + return true; +} + +// Note: existing data in dst will be gone. dst owns memory if shouldOwn is set +// to true. +void convertToHidl(const camera_metadata_t *src, HCameraMetadata* dst, bool shouldOwn) { + if (src == nullptr) { + return; + } + size_t size = get_camera_metadata_size(src); + dst->setToExternal((uint8_t *) src, size, shouldOwn); + return; +} + +TemplateId convertToHidl(ACameraDevice_request_template templateId) { + switch(templateId) { + case TEMPLATE_STILL_CAPTURE: + return TemplateId::STILL_CAPTURE; + case TEMPLATE_RECORD: + return TemplateId::RECORD; + case TEMPLATE_VIDEO_SNAPSHOT: + return TemplateId::VIDEO_SNAPSHOT; + case TEMPLATE_ZERO_SHUTTER_LAG: + return TemplateId::ZERO_SHUTTER_LAG; + case TEMPLATE_MANUAL: + return TemplateId::MANUAL; + default: + return TemplateId::PREVIEW; + } +} + +camera_status_t convertFromHidl(Status status) { + camera_status_t ret = ACAMERA_OK; + switch(status) { + case Status::NO_ERROR: + break; + case Status::DISCONNECTED: + ret = ACAMERA_ERROR_CAMERA_DISCONNECTED; + break; + case Status::CAMERA_IN_USE: + ret = ACAMERA_ERROR_CAMERA_IN_USE; + break; + case Status::MAX_CAMERAS_IN_USE: + ret = ACAMERA_ERROR_MAX_CAMERA_IN_USE; + break; + case Status::ILLEGAL_ARGUMENT: + ret = ACAMERA_ERROR_INVALID_PARAMETER; + break; + case Status::DEPRECATED_HAL: + // Should not reach here since we filtered legacy HALs earlier + ret = ACAMERA_ERROR_INVALID_PARAMETER; + break; + case Status::DISABLED: + ret = ACAMERA_ERROR_CAMERA_DISABLED; + break; + case Status::PERMISSION_DENIED: + ret = ACAMERA_ERROR_PERMISSION_DENIED; + break; + case Status::INVALID_OPERATION: + ret = ACAMERA_ERROR_INVALID_OPERATION; + break; + default: + ret = ACAMERA_ERROR_UNKNOWN; + break; + } + return ret; +} + +bool isWindowNativeHandleEqual(const native_handle_t *nh1, const native_handle_t *nh2) { + if (nh1->numFds !=0 || nh2->numFds !=0) { + ALOGE("Invalid window native handles being compared"); + return false; + } + if (nh1->version != nh2->version || nh1->numFds != nh2->numFds || + nh1->numInts != nh2->numInts) { + return false; + } + for (int i = 0; i < nh1->numInts; i++) { + if(nh1->data[i] != nh2->data[i]) { + return false; + } + } + return true; +} + +bool isWindowNativeHandleLessThan(const native_handle_t *nh1, const native_handle_t *nh2) { + if (isWindowNativeHandleEqual(nh1, nh2)) { + return false; + } + if (nh1->numInts != nh2->numInts) { + return nh1->numInts < nh2->numInts; + } + + for (int i = 0; i < nh1->numInts; i++) { + if (nh1->data[i] != nh2->data[i]) { + return nh1->data[i] < nh2->data[i]; + } + } + return false; +} + +bool isWindowNativeHandleGreaterThan(const native_handle_t *nh1, const native_handle_t *nh2) { + return !isWindowNativeHandleLessThan(nh1, nh2) && !isWindowNativeHandleEqual(nh1, nh2); +} + +bool areWindowNativeHandlesEqual(hidl_vec<hidl_handle> handles1, hidl_vec<hidl_handle> handles2) { + if (handles1.size() != handles2.size()) { + return false; + } + for (int i = 0; i < handles1.size(); i++) { + if (!isWindowNativeHandleEqual(handles1[i], handles2[i])) { + return false; + } + } + return true; +} + +bool areWindowNativeHandlesLessThan(hidl_vec<hidl_handle> handles1, hidl_vec<hidl_handle>handles2) { + if (handles1.size() != handles2.size()) { + return handles1.size() < handles2.size(); + } + for (int i = 0; i < handles1.size(); i++) { + const native_handle_t *handle1 = handles1[i].getNativeHandle(); + const native_handle_t *handle2 = handles2[i].getNativeHandle(); + if (!isWindowNativeHandleEqual(handle1, handle2)) { + return isWindowNativeHandleLessThan(handle1, handle2); + } + } + return false; +} + +} // namespace utils +} // namespace acam +} // namespace android
diff --git a/camera/ndk/ndk_vendor/impl/utils.h b/camera/ndk/ndk_vendor/impl/utils.h new file mode 100644 index 0000000..f389f03 --- /dev/null +++ b/camera/ndk/ndk_vendor/impl/utils.h
@@ -0,0 +1,182 @@ +/* + * 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 <android/frameworks/cameraservice/service/2.0/ICameraService.h> +#include <android/frameworks/cameraservice/device/2.0/ICameraDeviceUser.h> +#include <android/frameworks/cameraservice/device/2.0/types.h> +#include <camera/NdkCameraDevice.h> +#include <CameraMetadata.h> +#include <hardware/camera3.h> + +#ifndef CAMERA_NDK_VENDOR_H +#define CAMERA_NDK_VENDOR_H + +using android::hardware::hidl_vec; +using android::hardware::hidl_handle; + +namespace android { +namespace acam { +namespace utils { + +using CameraMetadata = hardware::camera::common::V1_0::helper::CameraMetadata; +using HCameraMetadata = frameworks::cameraservice::service::V2_0::CameraMetadata; +using Status = frameworks::cameraservice::common::V2_0::Status; +using TemplateId = frameworks::cameraservice::device::V2_0::TemplateId; +using PhysicalCameraSettings = frameworks::cameraservice::device::V2_0::PhysicalCameraSettings; +using HRotation = frameworks::cameraservice::device::V2_0::OutputConfiguration::Rotation; +using OutputConfiguration = frameworks::cameraservice::device::V2_0::OutputConfiguration; + +// Utility class so that CaptureRequest can be stored by sp<> +struct CaptureRequest : public RefBase { + frameworks::cameraservice::device::V2_0::CaptureRequest mCaptureRequest; + std::vector<native_handle_t *> mSurfaceList; + //Physical camera settings metadata is stored here, since the capture request + //might not contain it. That's since, fmq might have consumed it. + hidl_vec<PhysicalCameraSettings> mPhysicalCameraSettings; +}; + +bool areWindowNativeHandlesEqual(hidl_vec<hidl_handle> handles1, hidl_vec<hidl_handle>handles2); + +bool areWindowNativeHandlesLessThan(hidl_vec<hidl_handle> handles1, hidl_vec<hidl_handle>handles2); + +bool isWindowNativeHandleEqual(const native_handle_t *nh1, const native_handle_t *nh2); + +bool isWindowNativeHandleLessThan(const native_handle_t *nh1, const native_handle_t *nh2); + +// Convenience wrapper over isWindowNativeHandleLessThan and isWindowNativeHandleEqual +bool isWindowNativeHandleGreaterThan(const native_handle_t *nh1, const native_handle_t *nh2); + +// Utility class so the native_handle_t can be compared with its contents instead +// of just raw pointer comparisons. +struct native_handle_ptr_wrapper { + native_handle_t *mWindow = nullptr; + + native_handle_ptr_wrapper(native_handle_t *nh) : mWindow(nh) { } + + native_handle_ptr_wrapper() = default; + + operator native_handle_t *() const { return mWindow; } + + bool operator ==(const native_handle_ptr_wrapper other) const { + return isWindowNativeHandleEqual(mWindow, other.mWindow); + } + + bool operator != (const native_handle_ptr_wrapper& other) const { + return !isWindowNativeHandleEqual(mWindow, other.mWindow); + } + + bool operator < (const native_handle_ptr_wrapper& other) const { + return isWindowNativeHandleLessThan(mWindow, other.mWindow); + } + + bool operator > (const native_handle_ptr_wrapper& other) const { + return !isWindowNativeHandleGreaterThan(mWindow, other.mWindow); + } + +}; + +// Wrapper around OutputConfiguration. This is needed since HIDL +// OutputConfiguration is auto-generated and marked final. Therefore, operator +// overloads outside the class, will not get picked by clang while trying to +// store OutputConfiguration in maps/sets. +struct OutputConfigurationWrapper { + OutputConfiguration mOutputConfiguration; + + operator const OutputConfiguration &() const { + return mOutputConfiguration; + } + + OutputConfigurationWrapper() { + mOutputConfiguration.rotation = OutputConfiguration::Rotation::R0; + // The ndk currently doesn't support deferred surfaces + mOutputConfiguration.isDeferred = false; + mOutputConfiguration.width = 0; + mOutputConfiguration.height = 0; + // ndk doesn't support inter OutputConfiguration buffer sharing. + mOutputConfiguration.windowGroupId = -1; + }; + + OutputConfigurationWrapper(OutputConfiguration &outputConfiguration) + : mOutputConfiguration((outputConfiguration)) { } + + bool operator ==(const OutputConfiguration &other) const { + const OutputConfiguration &self = mOutputConfiguration; + return self.rotation == other.rotation && self.windowGroupId == other.windowGroupId && + self.physicalCameraId == other.physicalCameraId && self.width == other.width && + self.height == other.height && self.isDeferred == other.isDeferred && + areWindowNativeHandlesEqual(self.windowHandles, other.windowHandles); + } + + bool operator < (const OutputConfiguration &other) const { + if (*this == other) { + return false; + } + const OutputConfiguration &self = mOutputConfiguration; + if (self.windowGroupId != other.windowGroupId) { + return self.windowGroupId < other.windowGroupId; + } + + if (self.width != other.width) { + return self.width < other.width; + } + + if (self.height != other.height) { + return self.height < other.height; + } + + if (self.rotation != other.rotation) { + return static_cast<uint32_t>(self.rotation) < static_cast<uint32_t>(other.rotation); + } + + if (self.isDeferred != other.isDeferred) { + return self.isDeferred < other.isDeferred; + } + + if (self.physicalCameraId != other.physicalCameraId) { + return self.physicalCameraId < other.physicalCameraId; + } + return areWindowNativeHandlesLessThan(self.windowHandles, other.windowHandles); + } + + bool operator != (const OutputConfiguration &other) const { + return !(*this == other); + } + + bool operator > (const OutputConfiguration &other) const { + return (*this != other) && !(*this < other); + } +}; + +// Convert CaptureRequest wrappable by sp<> to hidl CaptureRequest. +frameworks::cameraservice::device::V2_0::CaptureRequest convertToHidl( + const CaptureRequest *captureRequest); + +HRotation convertToHidl(int rotation); + +bool convertFromHidlCloned(const HCameraMetadata &metadata, CameraMetadata *rawMetadata); + +// Note: existing data in dst will be gone. +void convertToHidl(const camera_metadata_t *src, HCameraMetadata* dst, bool shouldOwn = false); + +TemplateId convertToHidl(ACameraDevice_request_template templateId); + +camera_status_t convertFromHidl(Status status); + +} // namespace utils +} // namespace acam +} // namespace android + +#endif // CAMERA_NDK_VENDOR_H
diff --git a/camera/ndk/ndk_vendor/tests/AImageReaderVendorTest.cpp b/camera/ndk/ndk_vendor/tests/AImageReaderVendorTest.cpp new file mode 100644 index 0000000..37de30a --- /dev/null +++ b/camera/ndk/ndk_vendor/tests/AImageReaderVendorTest.cpp
@@ -0,0 +1,826 @@ +/* + * 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 "AImageReaderVendorTest" +//#define LOG_NDEBUG 0 + +#include <stdint.h> +#include <unistd.h> +#include <gtest/gtest.h> + +#include <algorithm> +#include <mutex> +#include <string> +#include <vector> +#include <stdio.h> +#include <stdio.h> +#include <stdio.h> + +#include <android/log.h> +#include <camera/NdkCameraError.h> +#include <camera/NdkCameraManager.h> +#include <camera/NdkCameraDevice.h> +#include <camera/NdkCameraCaptureSession.h> +#include <media/NdkImage.h> +#include <media/NdkImageReader.h> +#include <cutils/native_handle.h> +#include <VendorTagDescriptor.h> + +namespace { + +static constexpr int kDummyFenceFd = -1; +static constexpr int kCaptureWaitUs = 100 * 1000; +static constexpr int kCaptureWaitRetry = 10; +static constexpr int kTestImageWidth = 640; +static constexpr int kTestImageHeight = 480; +static constexpr int kTestImageFormat = AIMAGE_FORMAT_YUV_420_888; + +using android::hardware::camera::common::V1_0::helper::VendorTagDescriptorCache; + +class CameraHelper { + public: + CameraHelper(const char* id, ACameraManager *manager) : + mImgReaderAnw(nullptr), mCameraId(id), mCameraManager(manager) {} + ~CameraHelper() { closeCamera(); } + + struct PhysicalImgReaderInfo { + const char* physicalCameraId; + native_handle_t* anw; + }; + int initCamera(native_handle_t* imgReaderAnw, + const std::vector<PhysicalImgReaderInfo>& physicalImgReaders, + bool usePhysicalSettings) { + if (imgReaderAnw == nullptr) { + ALOGE("Cannot initialize camera before image reader get initialized."); + return -1; + } + if (mIsCameraReady) { + ALOGE("initCamera should only be called once."); + return -1; + } + + int ret; + mImgReaderAnw = imgReaderAnw; + + ret = ACameraManager_openCamera(mCameraManager, mCameraId, &mDeviceCb, &mDevice); + if (ret != AMEDIA_OK || mDevice == nullptr) { + ALOGE("Failed to open camera, ret=%d, mDevice=%p.", ret, mDevice); + return -1; + } + + // Create capture session + ret = ACaptureSessionOutputContainer_create(&mOutputs); + if (ret != AMEDIA_OK) { + ALOGE("ACaptureSessionOutputContainer_create failed, ret=%d", ret); + return ret; + } + ret = ACaptureSessionOutput_create(mImgReaderAnw, &mImgReaderOutput); + if (ret != AMEDIA_OK) { + ALOGE("ACaptureSessionOutput_create failed, ret=%d", ret); + return ret; + } + ret = ACaptureSessionOutputContainer_add(mOutputs, mImgReaderOutput); + if (ret != AMEDIA_OK) { + ALOGE("ACaptureSessionOutputContainer_add failed, ret=%d", ret); + return ret; + } + + std::vector<const char*> idPointerList; + for (auto& physicalStream : physicalImgReaders) { + ACaptureSessionOutput* sessionOutput = nullptr; + ret = ACaptureSessionPhysicalOutput_create(physicalStream.anw, + physicalStream.physicalCameraId, &sessionOutput); + if (ret != ACAMERA_OK) { + ALOGE("ACaptureSessionPhysicalOutput_create failed, ret=%d", ret); + return ret; + } + ret = ACaptureSessionOutputContainer_add(mOutputs, sessionOutput); + if (ret != AMEDIA_OK) { + ALOGE("ACaptureSessionOutputContainer_add failed, ret=%d", ret); + return ret; + } + mExtraOutputs.push_back(sessionOutput); + // Assume that at most one physical stream per physical camera. + mPhysicalCameraIds.push_back(physicalStream.physicalCameraId); + idPointerList.push_back(physicalStream.physicalCameraId); + } + ACameraIdList cameraIdList; + cameraIdList.numCameras = idPointerList.size(); + cameraIdList.cameraIds = idPointerList.data(); + + ret = ACameraDevice_isSessionConfigurationSupported(mDevice, mOutputs); + if (ret != ACAMERA_OK && ret != ACAMERA_ERROR_UNSUPPORTED_OPERATION) { + ALOGE("ACameraDevice_isSessionConfigurationSupported failed, ret=%d", ret); + return ret; + } + + ret = ACameraDevice_createCaptureSession(mDevice, mOutputs, &mSessionCb, &mSession); + if (ret != AMEDIA_OK) { + ALOGE("ACameraDevice_createCaptureSession failed, ret=%d", ret); + return ret; + } + + // Create capture request + if (usePhysicalSettings) { + ret = ACameraDevice_createCaptureRequest_withPhysicalIds(mDevice, + TEMPLATE_STILL_CAPTURE, &cameraIdList, &mStillRequest); + } else { + ret = ACameraDevice_createCaptureRequest(mDevice, + TEMPLATE_STILL_CAPTURE, &mStillRequest); + } + if (ret != AMEDIA_OK) { + ALOGE("ACameraDevice_createCaptureRequest failed, ret=%d", ret); + return ret; + } + ret = ACameraOutputTarget_create(mImgReaderAnw, &mReqImgReaderOutput); + if (ret != AMEDIA_OK) { + ALOGE("ACameraOutputTarget_create failed, ret=%d", ret); + return ret; + } + ret = ACaptureRequest_addTarget(mStillRequest, mReqImgReaderOutput); + if (ret != AMEDIA_OK) { + ALOGE("ACaptureRequest_addTarget failed, ret=%d", ret); + return ret; + } + + for (auto& physicalStream : physicalImgReaders) { + ACameraOutputTarget* outputTarget = nullptr; + ret = ACameraOutputTarget_create(physicalStream.anw, &outputTarget); + if (ret != AMEDIA_OK) { + ALOGE("ACameraOutputTarget_create failed, ret=%d", ret); + return ret; + } + ret = ACaptureRequest_addTarget(mStillRequest, outputTarget); + if (ret != AMEDIA_OK) { + ALOGE("ACaptureRequest_addTarget failed, ret=%d", ret); + return ret; + } + mReqExtraOutputs.push_back(outputTarget); + } + + mIsCameraReady = true; + return 0; + } + + + bool isCameraReady() { return mIsCameraReady; } + + void closeCamera() { + // Destroy capture request + if (mReqImgReaderOutput) { + ACameraOutputTarget_free(mReqImgReaderOutput); + mReqImgReaderOutput = nullptr; + } + for (auto& outputTarget : mReqExtraOutputs) { + ACameraOutputTarget_free(outputTarget); + } + mReqExtraOutputs.clear(); + if (mStillRequest) { + ACaptureRequest_free(mStillRequest); + mStillRequest = nullptr; + } + // Destroy capture session + if (mSession != nullptr) { + ACameraCaptureSession_close(mSession); + mSession = nullptr; + } + if (mImgReaderOutput) { + ACaptureSessionOutput_free(mImgReaderOutput); + mImgReaderOutput = nullptr; + } + for (auto& extraOutput : mExtraOutputs) { + ACaptureSessionOutput_free(extraOutput); + } + mExtraOutputs.clear(); + if (mOutputs) { + ACaptureSessionOutputContainer_free(mOutputs); + mOutputs = nullptr; + } + // Destroy camera device + if (mDevice) { + ACameraDevice_close(mDevice); + mDevice = nullptr; + } + mIsCameraReady = false; + } + + int takePicture() { + int seqId; + return ACameraCaptureSession_capture(mSession, &mCaptureCallbacks, 1, &mStillRequest, + &seqId); + } + + int takeLogicalCameraPicture() { + int seqId; + return ACameraCaptureSession_logicalCamera_capture(mSession, &mLogicalCaptureCallbacks, + 1, &mStillRequest, &seqId); + } + + bool checkCallbacks(int pictureCount) { + std::lock_guard<std::mutex> lock(mMutex); + if (mCompletedCaptureCallbackCount != pictureCount) { + ALOGE("Completed capture callaback count not as expected. expected %d actual %d", + pictureCount, mCompletedCaptureCallbackCount); + return false; + } + return true; + } + + static void onDeviceDisconnected(void* /*obj*/, ACameraDevice* /*device*/) {} + + static void onDeviceError(void* /*obj*/, ACameraDevice* /*device*/, int /*errorCode*/) {} + + static void onSessionClosed(void* /*obj*/, ACameraCaptureSession* /*session*/) {} + + static void onSessionReady(void* /*obj*/, ACameraCaptureSession* /*session*/) {} + + static void onSessionActive(void* /*obj*/, ACameraCaptureSession* /*session*/) {} + + private: + ACameraDevice_StateCallbacks mDeviceCb{this, onDeviceDisconnected, + onDeviceError}; + ACameraCaptureSession_stateCallbacks mSessionCb{ + this, onSessionClosed, onSessionReady, onSessionActive}; + + native_handle_t* mImgReaderAnw = nullptr; // not owned by us. + + // Camera device + ACameraDevice* mDevice = nullptr; + // Capture session + ACaptureSessionOutputContainer* mOutputs = nullptr; + ACaptureSessionOutput* mImgReaderOutput = nullptr; + std::vector<ACaptureSessionOutput*> mExtraOutputs; + + ACameraCaptureSession* mSession = nullptr; + // Capture request + ACaptureRequest* mStillRequest = nullptr; + ACameraOutputTarget* mReqImgReaderOutput = nullptr; + std::vector<ACameraOutputTarget*> mReqExtraOutputs; + + bool mIsCameraReady = false; + const char* mCameraId; + ACameraManager* mCameraManager; + int mCompletedCaptureCallbackCount = 0; + std::mutex mMutex; + ACameraCaptureSession_captureCallbacks mCaptureCallbacks = { + // TODO: Add tests for other callbacks + this, // context + nullptr, // onCaptureStarted + nullptr, // onCaptureProgressed + [](void* ctx , ACameraCaptureSession *, ACaptureRequest *, + const ACameraMetadata *) { + CameraHelper *ch = static_cast<CameraHelper *>(ctx); + std::lock_guard<std::mutex> lock(ch->mMutex); + ch->mCompletedCaptureCallbackCount++; + }, + nullptr, // onCaptureFailed + nullptr, // onCaptureSequenceCompleted + nullptr, // onCaptureSequenceAborted + nullptr, // onCaptureBufferLost + }; + + std::vector<std::string> mPhysicalCameraIds; + ACameraCaptureSession_logicalCamera_captureCallbacks mLogicalCaptureCallbacks = { + // TODO: Add tests for other callbacks + this, // context + nullptr, // onCaptureStarted + nullptr, // onCaptureProgressed + [](void* ctx , ACameraCaptureSession *, ACaptureRequest *, + const ACameraMetadata *, size_t physicalResultCount, + const char** physicalCameraIds, const ACameraMetadata** physicalResults) { + CameraHelper *ch = static_cast<CameraHelper *>(ctx); + std::lock_guard<std::mutex> lock(ch->mMutex); + ASSERT_EQ(physicalResultCount, ch->mPhysicalCameraIds.size()); + for (size_t i = 0; i < physicalResultCount; i++) { + ASSERT_TRUE(physicalCameraIds[i] != nullptr); + ASSERT_TRUE(physicalResults[i] != nullptr); + ASSERT_NE(std::find(ch->mPhysicalCameraIds.begin(), + ch->mPhysicalCameraIds.end(), physicalCameraIds[i]), + ch->mPhysicalCameraIds.end()); + + // Verify frameNumber and sensorTimestamp exist in physical + // result metadata + ACameraMetadata_const_entry entry; + ACameraMetadata_getConstEntry( + physicalResults[i], ACAMERA_SYNC_FRAME_NUMBER, &entry); + ASSERT_EQ(entry.count, 1); + ACameraMetadata_getConstEntry( + physicalResults[i], ACAMERA_SENSOR_TIMESTAMP, &entry); + ASSERT_EQ(entry.count, 1); + } + ch->mCompletedCaptureCallbackCount++; + }, + [] (void * /*ctx*/, ACameraCaptureSession* /*session*/, ACaptureRequest* /*request*/, + ALogicalCameraCaptureFailure* failure) { + if (failure->physicalCameraId) { + ALOGD("%s: Physical camera id: %s result failure", __FUNCTION__, + failure->physicalCameraId); + } + }, + nullptr, // onCaptureSequenceCompleted + nullptr, // onCaptureSequenceAborted + nullptr, // onCaptureBufferLost + }; +}; + +class ImageReaderTestCase { + public: + ImageReaderTestCase(int32_t width, + int32_t height, + int32_t format, + uint64_t usage, + int32_t maxImages, + bool async) + : mWidth(width), + mHeight(height), + mFormat(format), + mUsage(usage), + mMaxImages(maxImages), + mAsync(async) {} + + ~ImageReaderTestCase() { + if (mImgReaderAnw) { + AImageReader_delete(mImgReader); + // No need to call native_handle_t_release on imageReaderAnw + } + } + + int initImageReader() { + if (mImgReader != nullptr || mImgReaderAnw != nullptr) { + ALOGE("Cannot re-initalize image reader, mImgReader=%p, mImgReaderAnw=%p", mImgReader, + mImgReaderAnw); + return -1; + } + + media_status_t ret = AImageReader_newWithUsage( + mWidth, mHeight, mFormat, mUsage, mMaxImages, &mImgReader); + if (ret != AMEDIA_OK || mImgReader == nullptr) { + ALOGE("Failed to create new AImageReader, ret=%d, mImgReader=%p", ret, mImgReader); + return -1; + } + + ret = AImageReader_setImageListener(mImgReader, &mReaderAvailableCb); + if (ret != AMEDIA_OK) { + ALOGE("Failed to set image available listener, ret=%d.", ret); + return ret; + } + + ret = AImageReader_setBufferRemovedListener(mImgReader, &mReaderDetachedCb); + if (ret != AMEDIA_OK) { + ALOGE("Failed to set buffer detaching listener, ret=%d.", ret); + return ret; + } + + ret = AImageReader_getWindowNativeHandle(mImgReader, &mImgReaderAnw); + if (ret != AMEDIA_OK || mImgReaderAnw == nullptr) { + ALOGE("Failed to get native_handle_t from AImageReader, ret=%d, mImgReaderAnw=%p.", ret, + mImgReaderAnw); + return -1; + } + + return 0; + } + + native_handle_t* getNativeWindow() { return mImgReaderAnw; } + + int getAcquiredImageCount() { + std::lock_guard<std::mutex> lock(mMutex); + return mAcquiredImageCount; + } + + void HandleImageAvailable(AImageReader* reader) { + std::lock_guard<std::mutex> lock(mMutex); + + AImage* outImage = nullptr; + media_status_t ret; + + // Make sure AImage will be deleted automatically when it goes out of + // scope. + auto imageDeleter = [this](AImage* img) { + if (mAsync) { + AImage_deleteAsync(img, kDummyFenceFd); + } else { + AImage_delete(img); + } + }; + std::unique_ptr<AImage, decltype(imageDeleter)> img(nullptr, imageDeleter); + + if (mAsync) { + int outFenceFd = 0; + // Verity that outFenceFd's value will be changed by + // AImageReader_acquireNextImageAsync. + ret = AImageReader_acquireNextImageAsync(reader, &outImage, &outFenceFd); + if (ret != AMEDIA_OK || outImage == nullptr || outFenceFd == 0) { + ALOGE("Failed to acquire image, ret=%d, outIamge=%p, outFenceFd=%d.", ret, outImage, + outFenceFd); + return; + } + img.reset(outImage); + } else { + ret = AImageReader_acquireNextImage(reader, &outImage); + if (ret != AMEDIA_OK || outImage == nullptr) { + ALOGE("Failed to acquire image, ret=%d, outIamge=%p.", ret, outImage); + return; + } + img.reset(outImage); + } + + AHardwareBuffer* outBuffer = nullptr; + ret = AImage_getHardwareBuffer(img.get(), &outBuffer); + if (ret != AMEDIA_OK || outBuffer == nullptr) { + ALOGE("Faild to get hardware buffer, ret=%d, outBuffer=%p.", ret, outBuffer); + return; + } + + // No need to release AHardwareBuffer, since we don't acquire additional + // reference to it. + AHardwareBuffer_Desc outDesc; + AHardwareBuffer_describe(outBuffer, &outDesc); + int32_t imageWidth = 0; + int32_t imageHeight = 0; + int32_t bufferWidth = static_cast<int32_t>(outDesc.width); + int32_t bufferHeight = static_cast<int32_t>(outDesc.height); + + AImage_getWidth(outImage, &imageWidth); + AImage_getHeight(outImage, &imageHeight); + if (imageWidth != mWidth || imageHeight != mHeight) { + ALOGE("Mismatched output image dimension: expected=%dx%d, actual=%dx%d", mWidth, + mHeight, imageWidth, imageHeight); + return; + } + + if (mFormat == AIMAGE_FORMAT_RGBA_8888 || + mFormat == AIMAGE_FORMAT_RGBX_8888 || + mFormat == AIMAGE_FORMAT_RGB_888 || + mFormat == AIMAGE_FORMAT_RGB_565 || + mFormat == AIMAGE_FORMAT_RGBA_FP16 || + mFormat == AIMAGE_FORMAT_YUV_420_888 || + mFormat == AIMAGE_FORMAT_Y8) { + // Check output buffer dimension for certain formats. Don't do this for blob based + // formats. + if (bufferWidth != mWidth || bufferHeight != mHeight) { + ALOGE("Mismatched output buffer dimension: expected=%dx%d, actual=%dx%d", mWidth, + mHeight, bufferWidth, bufferHeight); + return; + } + } + + if ((outDesc.usage & mUsage) != mUsage) { + ALOGE("Mismatched output buffer usage: actual (%" PRIu64 "), expected (%" PRIu64 ")", + outDesc.usage, mUsage); + return; + } + + uint8_t* data = nullptr; + int dataLength = 0; + ret = AImage_getPlaneData(img.get(), 0, &data, &dataLength); + if (mUsage & AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN) { + // When we have CPU_READ_OFTEN usage bits, we can lock the image. + if (ret != AMEDIA_OK || data == nullptr || dataLength < 0) { + ALOGE("Failed to access CPU data, ret=%d, data=%p, dataLength=%d", ret, data, + dataLength); + return; + } + } else { + if (ret != AMEDIA_IMGREADER_CANNOT_LOCK_IMAGE || data != nullptr || dataLength != 0) { + ALOGE("Shouldn't be able to access CPU data, ret=%d, data=%p, dataLength=%d", ret, + data, dataLength); + return; + } + } + // Only increase mAcquiredImageCount if all checks pass. + mAcquiredImageCount++; + } + + static void onImageAvailable(void* obj, AImageReader* reader) { + ImageReaderTestCase* thiz = reinterpret_cast<ImageReaderTestCase*>(obj); + thiz->HandleImageAvailable(reader); + } + + static void + onBufferRemoved(void* /*obj*/, AImageReader* /*reader*/, AHardwareBuffer* /*buffer*/) { + // No-op, just to check the listener can be set properly. + } + + private: + int32_t mWidth; + int32_t mHeight; + int32_t mFormat; + uint64_t mUsage; + int32_t mMaxImages; + bool mAsync; + + std::mutex mMutex; + int mAcquiredImageCount{0}; + + AImageReader* mImgReader = nullptr; + native_handle_t* mImgReaderAnw = nullptr; + + AImageReader_ImageListener mReaderAvailableCb{this, onImageAvailable}; + AImageReader_BufferRemovedListener mReaderDetachedCb{this, onBufferRemoved}; +}; + + +class AImageReaderVendorTest : public ::testing::Test { + public: + void SetUp() override { + mCameraManager = ACameraManager_create(); + if (mCameraManager == nullptr) { + ALOGE("Failed to create ACameraManager."); + return; + } + + camera_status_t ret = ACameraManager_getCameraIdList(mCameraManager, &mCameraIdList); + if (ret != ACAMERA_OK) { + ALOGE("Failed to get cameraIdList: ret=%d", ret); + return; + } + // TODO: Add more rigorous tests for vendor tags + ASSERT_NE(VendorTagDescriptorCache::getGlobalVendorTagCache(), nullptr); + if (mCameraIdList->numCameras < 1) { + ALOGW("Device has no camera on board."); + return; + } + } + void TearDown() override { + // Destroy camera manager + if (mCameraIdList) { + ACameraManager_deleteCameraIdList(mCameraIdList); + mCameraIdList = nullptr; + } + if (mCameraManager) { + ACameraManager_delete(mCameraManager); + mCameraManager = nullptr; + } + } + + bool takePictures(const char* id, uint64_t readerUsage, int readerMaxImages, + bool readerAsync, int pictureCount) { + int ret = 0; + + ImageReaderTestCase testCase( + kTestImageWidth, kTestImageHeight, kTestImageFormat, readerUsage, readerMaxImages, + readerAsync); + ret = testCase.initImageReader(); + if (ret < 0) { + ALOGE("Unable to initialize ImageReader"); + return false; + } + + CameraHelper cameraHelper(id, mCameraManager); + ret = cameraHelper.initCamera(testCase.getNativeWindow(), + {}/*physicalImageReaders*/, false/*usePhysicalSettings*/); + if (ret < 0) { + ALOGE("Unable to initialize camera helper"); + return false; + } + + if (!cameraHelper.isCameraReady()) { + ALOGW("Camera is not ready after successful initialization. It's either due to camera " + "on board lacks BACKWARDS_COMPATIBLE capability or the device does not have " + "camera on board."); + return true; + } + + for (int i = 0; i < pictureCount; i++) { + ret = cameraHelper.takePicture(); + if (ret < 0) { + ALOGE("Unable to take picture"); + return false; + } + } + + // Sleep until all capture finished + for (int i = 0; i < kCaptureWaitRetry * pictureCount; i++) { + usleep(kCaptureWaitUs); + if (testCase.getAcquiredImageCount() == pictureCount) { + ALOGI("Session take ~%d ms to capture %d images", i * kCaptureWaitUs / 1000, + pictureCount); + break; + } + } + return testCase.getAcquiredImageCount() == pictureCount && + cameraHelper.checkCallbacks(pictureCount); + } + + bool testTakePicturesNative(const char* id) { + for (auto& readerUsage : + {AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN}) { + for (auto& readerMaxImages : {1, 4, 8}) { + for (auto& readerAsync : {true, false}) { + for (auto& pictureCount : {1, 4, 8}) { + if (!takePictures(id, readerUsage, readerMaxImages, + readerAsync, pictureCount)) { + ALOGE("Test takePictures failed for test case usage=%" PRIu64 + ", maxImages=%d, async=%d, pictureCount=%d", + readerUsage, readerMaxImages, readerAsync, pictureCount); + return false; + } + } + } + } + } + return true; + } + + // Camera manager + ACameraManager* mCameraManager = nullptr; + ACameraIdList* mCameraIdList = nullptr; + + bool isCapabilitySupported(ACameraMetadata* staticInfo, + acamera_metadata_enum_android_request_available_capabilities_t cap) { + ACameraMetadata_const_entry entry; + ACameraMetadata_getConstEntry( + staticInfo, ACAMERA_REQUEST_AVAILABLE_CAPABILITIES, &entry); + for (uint32_t i = 0; i < entry.count; i++) { + if (entry.data.u8[i] == cap) { + return true; + } + } + return false; + } + + bool isSizeSupportedForFormat(ACameraMetadata* staticInfo, + int32_t format, int32_t width, int32_t height) { + ACameraMetadata_const_entry entry; + ACameraMetadata_getConstEntry(staticInfo, + ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS, &entry); + for (uint32_t i = 0; i < entry.count; i += 4) { + if (entry.data.i32[i] == format && + entry.data.i32[i+3] == ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT && + entry.data.i32[i+1] == width && + entry.data.i32[i+2] == height) { + return true; + } + } + return false; + } + + void findCandidateLogicalCamera(const char **cameraId, + ACameraMetadata** staticMetadata, + std::vector<const char*>* candidatePhysicalIds) { + // Find first available logical camera + for (int i = 0; i < mCameraIdList->numCameras; i++) { + camera_status_t ret; + ret = ACameraManager_getCameraCharacteristics( + mCameraManager, mCameraIdList->cameraIds[i], staticMetadata); + ASSERT_EQ(ret, ACAMERA_OK); + ASSERT_NE(*staticMetadata, nullptr); + + if (!isCapabilitySupported(*staticMetadata, + ACAMERA_REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA)) { + ACameraMetadata_free(*staticMetadata); + *staticMetadata = nullptr; + continue; + } + + // Check returned physical camera Ids are valid + size_t physicalCameraIdCnt = 0; + const char*const* physicalCameraIds = nullptr; + bool isLogicalCamera = ACameraMetadata_isLogicalMultiCamera(*staticMetadata, + &physicalCameraIdCnt, &physicalCameraIds); + ASSERT_TRUE(isLogicalCamera); + ASSERT_GE(physicalCameraIdCnt, 2); + ACameraMetadata* physicalCameraMetadata = nullptr; + candidatePhysicalIds->clear(); + for (size_t j = 0; j < physicalCameraIdCnt && candidatePhysicalIds->size() < 2; j++) { + ASSERT_GT(strlen(physicalCameraIds[j]), 0); + ret = ACameraManager_getCameraCharacteristics( + mCameraManager, physicalCameraIds[j], &physicalCameraMetadata); + ASSERT_EQ(ret, ACAMERA_OK); + ASSERT_NE(physicalCameraMetadata, nullptr); + + if (isSizeSupportedForFormat(physicalCameraMetadata, kTestImageFormat, + kTestImageWidth, kTestImageHeight)) { + candidatePhysicalIds->push_back(physicalCameraIds[j]); + } + ACameraMetadata_free(physicalCameraMetadata); + } + if (candidatePhysicalIds->size() == 2) { + *cameraId = mCameraIdList->cameraIds[i]; + return; + } else { + ACameraMetadata_free(*staticMetadata); + *staticMetadata = nullptr; + } + } + *cameraId = nullptr; + return; + } + + void testLogicalCameraPhysicalStream(bool usePhysicalSettings) { + const char* cameraId = nullptr; + ACameraMetadata* staticMetadata = nullptr; + std::vector<const char*> physicalCameraIds; + + findCandidateLogicalCamera(&cameraId, &staticMetadata, &physicalCameraIds); + if (cameraId == nullptr) { + // Couldn't find logical camera to test + return; + } + + // Test streaming the logical multi-camera + uint64_t readerUsage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN; + int32_t readerMaxImages = 8; + bool readerAsync = false; + const int pictureCount = 6; + std::vector<ImageReaderTestCase*> testCases; + for (size_t i = 0; i < 3; i++) { + ImageReaderTestCase* testCase = new ImageReaderTestCase( + kTestImageWidth, kTestImageHeight, kTestImageFormat, readerUsage, + readerMaxImages, readerAsync); + ASSERT_EQ(testCase->initImageReader(), 0); + testCases.push_back(testCase); + } + + CameraHelper cameraHelper(cameraId, mCameraManager); + std::vector<CameraHelper::PhysicalImgReaderInfo> physicalImgReaderInfo; + physicalImgReaderInfo.push_back({physicalCameraIds[0], testCases[1]->getNativeWindow()}); + physicalImgReaderInfo.push_back({physicalCameraIds[1], testCases[2]->getNativeWindow()}); + + int ret = cameraHelper.initCamera(testCases[0]->getNativeWindow(), + physicalImgReaderInfo, usePhysicalSettings); + ASSERT_EQ(ret, 0); + + if (!cameraHelper.isCameraReady()) { + ALOGW("Camera is not ready after successful initialization. It's either due to camera " + "on board lacks BACKWARDS_COMPATIBLE capability or the device does not have " + "camera on board."); + return; + } + + for (int i = 0; i < pictureCount; i++) { + ret = cameraHelper.takeLogicalCameraPicture(); + ASSERT_EQ(ret, 0); + } + + // Sleep until all capture finished + for (int i = 0; i < kCaptureWaitRetry * pictureCount; i++) { + usleep(kCaptureWaitUs); + if (testCases[0]->getAcquiredImageCount() == pictureCount) { + ALOGI("Session take ~%d ms to capture %d images", i * kCaptureWaitUs / 1000, + pictureCount); + break; + } + } + ASSERT_EQ(testCases[0]->getAcquiredImageCount(), pictureCount); + ASSERT_EQ(testCases[1]->getAcquiredImageCount(), pictureCount); + ASSERT_EQ(testCases[2]->getAcquiredImageCount(), pictureCount); + ASSERT_TRUE(cameraHelper.checkCallbacks(pictureCount)); + + ACameraMetadata_free(staticMetadata); + } +}; + +TEST_F(AImageReaderVendorTest, CreateWindowNativeHandle) { + // We always use the first camera. + const char* cameraId = mCameraIdList->cameraIds[0]; + ASSERT_TRUE(cameraId != nullptr); + + ACameraMetadata* staticMetadata = nullptr; + camera_status_t ret = ACameraManager_getCameraCharacteristics( + mCameraManager, cameraId, &staticMetadata); + ASSERT_EQ(ret, ACAMERA_OK); + ASSERT_NE(staticMetadata, nullptr); + + bool isBC = isCapabilitySupported(staticMetadata, + ACAMERA_REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE); + + uint32_t namedTag = 0; + // Test that ACameraMetadata_getTagFromName works as expected for public tag + // names + camera_status_t status = ACameraManager_getTagFromName(mCameraManager, cameraId, + "android.control.aeMode", &namedTag); + + ASSERT_EQ(status, ACAMERA_OK); + ASSERT_EQ(namedTag, ACAMERA_CONTROL_AE_MODE); + + ACameraMetadata_free(staticMetadata); + + if (!isBC) { + ALOGW("Camera does not support BACKWARD_COMPATIBLE."); + return; + } + + EXPECT_TRUE(testTakePicturesNative(cameraId)); +} + +TEST_F(AImageReaderVendorTest, LogicalCameraPhysicalStream) { + testLogicalCameraPhysicalStream(false/*usePhysicalSettings*/); + testLogicalCameraPhysicalStream(true/*usePhysicalSettings*/); +} + +} // namespace
diff --git a/camera/tests/Android.mk b/camera/tests/Android.mk index 659484f..e5c1631 100644 --- a/camera/tests/Android.mk +++ b/camera/tests/Android.mk
@@ -19,7 +19,8 @@ LOCAL_SRC_FILES:= \ VendorTagDescriptorTests.cpp \ CameraBinderTests.cpp \ - CameraZSLTests.cpp + CameraZSLTests.cpp \ + CameraCharacteristicsPermission.cpp LOCAL_SHARED_LIBRARIES := \ liblog \
diff --git a/camera/tests/CameraBinderTests.cpp b/camera/tests/CameraBinderTests.cpp index 1de7013..8fe029a 100644 --- a/camera/tests/CameraBinderTests.cpp +++ b/camera/tests/CameraBinderTests.cpp
@@ -29,6 +29,7 @@ #include <utils/Condition.h> #include <utils/Mutex.h> #include <system/graphics.h> +#include <hardware/camera3.h> #include <hardware/gralloc.h> #include <camera/CameraMetadata.h> @@ -40,6 +41,7 @@ #include <android/hardware/camera2/BnCameraDeviceCallbacks.h> #include <camera/camera2/CaptureRequest.h> #include <camera/camera2/OutputConfiguration.h> +#include <camera/camera2/SessionConfiguration.h> #include <camera/camera2/SubmitInfo.h> #include <gui/BufferItemConsumer.h> @@ -55,6 +57,8 @@ #include <algorithm> using namespace android; +using ::android::hardware::ICameraServiceDefault; +using ::android::hardware::camera2::ICameraDeviceUser; #define ASSERT_NOT_NULL(x) \ ASSERT_TRUE((x) != nullptr) @@ -86,6 +90,11 @@ return binder::Status::ok(); }; + virtual binder::Status onCameraAccessPrioritiesChanged() { + // No op + return binder::Status::ok(); + } + bool waitForNumCameras(size_t num) const { Mutex::Autolock l(mLock); @@ -476,7 +485,8 @@ sp<Surface> surface(new Surface(gbProducer, /*controlledByApp*/false)); - OutputConfiguration output(gbProducer, /*rotation*/0); + String16 noPhysicalId; + OutputConfiguration output(gbProducer, /*rotation*/0, noPhysicalId); // Can we configure? res = device->beginConfigure(); @@ -490,6 +500,19 @@ EXPECT_TRUE(res.isOk()) << res; EXPECT_FALSE(callbacks->hadError()); + // Session configuration must also be supported in this case + SessionConfiguration sessionConfiguration = { /*inputWidth*/ 0, /*inputHeight*/0, + /*inputFormat*/ -1, CAMERA3_STREAM_CONFIGURATION_NORMAL_MODE}; + sessionConfiguration.addOutputConfiguration(output); + bool queryStatus; + res = device->isSessionConfigurationSupported(sessionConfiguration, &queryStatus); + EXPECT_TRUE(res.isOk() || + (res.serviceSpecificErrorCode() == ICameraServiceDefault::ERROR_INVALID_OPERATION)) + << res; + if (res.isOk()) { + EXPECT_TRUE(queryStatus); + } + // Can we make requests? CameraMetadata requestTemplate; res = device->createDefaultRequest(/*preview template*/1,
diff --git a/camera/tests/CameraCharacteristicsPermission.cpp b/camera/tests/CameraCharacteristicsPermission.cpp new file mode 100644 index 0000000..135d2a3 --- /dev/null +++ b/camera/tests/CameraCharacteristicsPermission.cpp
@@ -0,0 +1,96 @@ +/* + * 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 "CameraCharacteristicsPermission" + +#include <gtest/gtest.h> + +#include <binder/ProcessState.h> +#include <utils/Errors.h> +#include <utils/Log.h> +#include <camera/CameraMetadata.h> +#include <camera/Camera.h> +#include <android/hardware/ICameraService.h> + +using namespace android; +using namespace android::hardware; + +class CameraCharacteristicsPermission : public ::testing::Test { +protected: + + CameraCharacteristicsPermission() : numCameras(0){} + //Gtest interface + void SetUp() override; + void TearDown() override; + + int32_t numCameras; + sp<ICameraService> mCameraService; +}; + +void CameraCharacteristicsPermission::SetUp() { + ::android::binder::Status rc; + ProcessState::self()->startThreadPool(); + sp<IServiceManager> sm = defaultServiceManager(); + sp<IBinder> binder = sm->getService(String16("media.camera")); + mCameraService = interface_cast<ICameraService>(binder); + rc = mCameraService->getNumberOfCameras( + hardware::ICameraService::CAMERA_TYPE_ALL, &numCameras); + EXPECT_TRUE(rc.isOk()); +} + +void CameraCharacteristicsPermission::TearDown() { + mCameraService.clear(); +} + +// Revoking and acquiring permissions automatically might not be possible. +// Test the functionality for removal of camera characteristics needing +// a camera permission. +TEST_F(CameraCharacteristicsPermission, TestCameraPermission) { + for (int32_t cameraId = 0; cameraId < numCameras; cameraId++) { + + String16 cameraIdStr = String16(String8::format("%d", cameraId)); + bool isSupported = false; + auto rc = mCameraService->supportsCameraApi(cameraIdStr, + hardware::ICameraService::API_VERSION_2, &isSupported); + EXPECT_TRUE(rc.isOk()); + if (!isSupported) { + continue; + } + + CameraMetadata metadata; + std::vector<int32_t> tagsNeedingPermission; + rc = mCameraService->getCameraCharacteristics(cameraIdStr, &metadata); + ASSERT_TRUE(rc.isOk()); + EXPECT_FALSE(metadata.isEmpty()); + EXPECT_EQ(metadata.removePermissionEntries(CAMERA_METADATA_INVALID_VENDOR_ID, + &tagsNeedingPermission), NO_ERROR); + camera_metadata_entry_t availableCharacteristics = + metadata.find(ANDROID_REQUEST_AVAILABLE_CHARACTERISTICS_KEYS); + EXPECT_TRUE(0 < availableCharacteristics.count); + + std::vector<uint32_t> availableKeys; + availableKeys.reserve(availableCharacteristics.count); + availableKeys.insert(availableKeys.begin(), availableCharacteristics.data.i32, + availableCharacteristics.data.i32 + availableCharacteristics.count); + + for (const auto &key : tagsNeedingPermission) { + ASSERT_FALSE(metadata.exists(key)); + auto it = std::find(availableKeys.begin(), availableKeys.end(), key); + ASSERT_TRUE(it == availableKeys.end()); + } + } +}
diff --git a/cmds/screenrecord/Android.bp b/cmds/screenrecord/Android.bp new file mode 100644 index 0000000..86476cd --- /dev/null +++ b/cmds/screenrecord/Android.bp
@@ -0,0 +1,55 @@ +// Copyright 2013 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_binary { + name: "screenrecord", + + srcs: [ + "screenrecord.cpp", + "EglWindow.cpp", + "FrameOutput.cpp", + "TextRenderer.cpp", + "Overlay.cpp", + "Program.cpp", + ], + + shared_libs: [ + "libstagefright", + "libmedia", + "libmedia_omx", + "libutils", + "libbinder", + "libstagefright_foundation", + "libjpeg", + "libui", + "libgui", + "libcutils", + "liblog", + "libEGL", + "libGLESv2", + ], + + include_dirs: [ + "frameworks/av/media/libstagefright", + "frameworks/av/media/libstagefright/include", + "frameworks/native/include/media/openmax", + ], + + cflags: [ + "-Werror", + "-Wall", + "-Wno-multichar", + //"-UNDEBUG", + ] +}
diff --git a/cmds/screenrecord/Android.mk b/cmds/screenrecord/Android.mk deleted file mode 100644 index 5e83ed6..0000000 --- a/cmds/screenrecord/Android.mk +++ /dev/null
@@ -1,45 +0,0 @@ -# Copyright 2013 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. - -LOCAL_PATH:= $(call my-dir) - -include $(CLEAR_VARS) - -LOCAL_SRC_FILES := \ - screenrecord.cpp \ - EglWindow.cpp \ - FrameOutput.cpp \ - TextRenderer.cpp \ - Overlay.cpp \ - Program.cpp - -LOCAL_SHARED_LIBRARIES := \ - libstagefright libmedia libmedia_omx libutils libbinder libstagefright_foundation \ - libjpeg libui libgui libcutils liblog libEGL libGLESv2 - -LOCAL_C_INCLUDES := \ - frameworks/av/media/libstagefright \ - frameworks/av/media/libstagefright/include \ - frameworks/native/include/media/openmax \ - external/jpeg - -LOCAL_CFLAGS := -Werror -Wall -LOCAL_CFLAGS += -Wno-multichar -#LOCAL_CFLAGS += -UNDEBUG - -LOCAL_MODULE_TAGS := optional - -LOCAL_MODULE:= screenrecord - -include $(BUILD_EXECUTABLE)
diff --git a/cmds/screenrecord/screenrecord.cpp b/cmds/screenrecord/screenrecord.cpp index d1859d1..7aa655f 100644 --- a/cmds/screenrecord/screenrecord.cpp +++ b/cmds/screenrecord/screenrecord.cpp
@@ -48,6 +48,7 @@ #include <media/stagefright/foundation/ABuffer.h> #include <media/stagefright/foundation/AMessage.h> #include <media/stagefright/MediaCodec.h> +#include <media/stagefright/MediaCodecConstants.h> #include <media/stagefright/MediaErrors.h> #include <media/stagefright/MediaMuxer.h> #include <media/stagefright/PersistentSurface.h> @@ -58,7 +59,35 @@ #include "Overlay.h" #include "FrameOutput.h" -using namespace android; +using android::ABuffer; +using android::ALooper; +using android::AMessage; +using android::AString; +using android::DisplayInfo; +using android::FrameOutput; +using android::IBinder; +using android::IGraphicBufferProducer; +using android::ISurfaceComposer; +using android::MediaCodec; +using android::MediaCodecBuffer; +using android::MediaMuxer; +using android::Overlay; +using android::PersistentSurface; +using android::ProcessState; +using android::Rect; +using android::String8; +using android::SurfaceComposerClient; +using android::Vector; +using android::sp; +using android::status_t; + +using android::DISPLAY_ORIENTATION_0; +using android::DISPLAY_ORIENTATION_180; +using android::DISPLAY_ORIENTATION_90; +using android::INVALID_OPERATION; +using android::NAME_NOT_FOUND; +using android::NO_ERROR; +using android::UNKNOWN_ERROR; static const uint32_t kMinBitRate = 100000; // 0.1Mbps static const uint32_t kMaxBitRate = 200 * 1000000; // 200Mbps @@ -73,7 +102,7 @@ static bool gMonotonicTime = false; // use system monotonic time for timestamps static bool gPersistentSurface = false; // use persistent surface static enum { - FORMAT_MP4, FORMAT_H264, FORMAT_FRAMES, FORMAT_RAW_FRAMES + FORMAT_MP4, FORMAT_H264, FORMAT_WEBM, FORMAT_3GPP, FORMAT_FRAMES, FORMAT_RAW_FRAMES } gOutputFormat = FORMAT_MP4; // data format for output static AString gCodecName = ""; // codec name override static bool gSizeSpecified = false; // was size explicitly requested? @@ -83,6 +112,7 @@ static uint32_t gVideoHeight = 0; static uint32_t gBitRate = 20000000; // 20Mbps static uint32_t gTimeLimitSec = kMaxTimeLimitSec; +static uint32_t gBframes = 0; // Set by signal handler to stop recording. static volatile bool gStopRequested = false; @@ -154,15 +184,20 @@ } sp<AMessage> format = new AMessage; - format->setInt32("width", gVideoWidth); - format->setInt32("height", gVideoHeight); - format->setString("mime", kMimeTypeAvc); - format->setInt32("color-format", OMX_COLOR_FormatAndroidOpaque); - format->setInt32("bitrate", gBitRate); - format->setFloat("frame-rate", displayFps); - format->setInt32("i-frame-interval", 10); + format->setInt32(KEY_WIDTH, gVideoWidth); + format->setInt32(KEY_HEIGHT, gVideoHeight); + format->setString(KEY_MIME, kMimeTypeAvc); + format->setInt32(KEY_COLOR_FORMAT, OMX_COLOR_FormatAndroidOpaque); + format->setInt32(KEY_BIT_RATE, gBitRate); + format->setFloat(KEY_FRAME_RATE, displayFps); + format->setInt32(KEY_I_FRAME_INTERVAL, 10); + format->setInt32(KEY_MAX_B_FRAMES, gBframes); + if (gBframes > 0) { + format->setInt32(KEY_PROFILE, AVCProfileMain); + format->setInt32(KEY_LEVEL, AVCLevel41); + } - sp<ALooper> looper = new ALooper; + sp<android::ALooper> looper = new android::ALooper; looper->setName("screenrecord_looper"); looper->start(); ALOGV("Creating codec"); @@ -235,10 +270,10 @@ // Set the region of the layer stack we're interested in, which in our // case is "all of it". - Rect layerStackRect(mainDpyInfo.w, mainDpyInfo.h); + Rect layerStackRect(mainDpyInfo.viewportW, mainDpyInfo.viewportH); // We need to preserve the aspect ratio of the display. - float displayAspect = (float) mainDpyInfo.h / (float) mainDpyInfo.w; + float displayAspect = (float) mainDpyInfo.viewportH / (float) mainDpyInfo.viewportW; // Set the way we map the output onto the display surface (which will @@ -315,22 +350,6 @@ } /* - * Set the main display width and height to the actual width and height - */ -static status_t getActualDisplaySize(const sp<IBinder>& mainDpy, DisplayInfo* mainDpyInfo) { - Rect viewport; - status_t err = SurfaceComposerClient::getDisplayViewport(mainDpy, &viewport); - if (err != NO_ERROR) { - fprintf(stderr, "ERROR: unable to get display viewport\n"); - return err; - } - mainDpyInfo->w = viewport.width(); - mainDpyInfo->h = viewport.height(); - - return NO_ERROR; -} - -/* * Runs the MediaCodec encoder, sending the output to the MediaMuxer. The * input frames are coming from the virtual display as fast as SurfaceFlinger * wants to send them. @@ -400,22 +419,14 @@ // useful stuff is hard to get at without a Dalvik VM. err = SurfaceComposerClient::getDisplayInfo(mainDpy, &mainDpyInfo); - if (err == NO_ERROR) { - err = getActualDisplaySize(mainDpy, &mainDpyInfo); - if (err != NO_ERROR) { - fprintf(stderr, "ERROR: unable to set actual display size\n"); - return err; - } - - if (orientation != mainDpyInfo.orientation) { - ALOGD("orientation changed, now %d", mainDpyInfo.orientation); - SurfaceComposerClient::Transaction t; - setDisplayProjection(t, virtualDpy, mainDpyInfo); - t.apply(); - orientation = mainDpyInfo.orientation; - } - } else { + if (err != NO_ERROR) { ALOGW("getDisplayInfo(main) failed: %d", err); + } else if (orientation != mainDpyInfo.orientation) { + ALOGD("orientation changed, now %d", mainDpyInfo.orientation); + SurfaceComposerClient::Transaction t; + setDisplayProjection(t, virtualDpy, mainDpyInfo); + t.apply(); + orientation = mainDpyInfo.orientation; } } @@ -472,7 +483,7 @@ case -EAGAIN: // INFO_TRY_AGAIN_LATER ALOGV("Got -EAGAIN, looping"); break; - case INFO_FORMAT_CHANGED: // INFO_OUTPUT_FORMAT_CHANGED + case android::INFO_FORMAT_CHANGED: // INFO_OUTPUT_FORMAT_CHANGED { // Format includes CSD, which we must provide to muxer. ALOGV("Encoder format changed"); @@ -489,7 +500,7 @@ } } break; - case INFO_OUTPUT_BUFFERS_CHANGED: // INFO_OUTPUT_BUFFERS_CHANGED + case android::INFO_OUTPUT_BUFFERS_CHANGED: // INFO_OUTPUT_BUFFERS_CHANGED // Not expected for an encoder; handle it anyway. ALOGV("Encoder buffers changed"); err = encoder->getOutputBuffers(&buffers); @@ -580,8 +591,12 @@ self->startThreadPool(); // Get main display parameters. - sp<IBinder> mainDpy = SurfaceComposerClient::getBuiltInDisplay( - ISurfaceComposer::eDisplayIdMain); + const sp<IBinder> mainDpy = SurfaceComposerClient::getInternalDisplayToken(); + if (mainDpy == nullptr) { + fprintf(stderr, "ERROR: no display\n"); + return NAME_NOT_FOUND; + } + DisplayInfo mainDpyInfo; err = SurfaceComposerClient::getDisplayInfo(mainDpy, &mainDpyInfo); if (err != NO_ERROR) { @@ -589,25 +604,19 @@ return err; } - err = getActualDisplaySize(mainDpy, &mainDpyInfo); - if (err != NO_ERROR) { - fprintf(stderr, "ERROR: unable to set actual display size\n"); - return err; - } - if (gVerbose) { printf("Main display is %dx%d @%.2ffps (orientation=%u)\n", - mainDpyInfo.w, mainDpyInfo.h, mainDpyInfo.fps, + mainDpyInfo.viewportW, mainDpyInfo.viewportH, mainDpyInfo.fps, mainDpyInfo.orientation); fflush(stdout); } // Encoder can't take odd number as config if (gVideoWidth == 0) { - gVideoWidth = floorToEven(mainDpyInfo.w); + gVideoWidth = floorToEven(mainDpyInfo.viewportW); } if (gVideoHeight == 0) { - gVideoHeight = floorToEven(mainDpyInfo.h); + gVideoHeight = floorToEven(mainDpyInfo.viewportH); } // Configure and start the encoder. @@ -685,7 +694,9 @@ sp<MediaMuxer> muxer = NULL; FILE* rawFp = NULL; switch (gOutputFormat) { - case FORMAT_MP4: { + case FORMAT_MP4: + case FORMAT_WEBM: + case FORMAT_3GPP: { // Configure muxer. We have to wait for the CSD blob from the encoder // before we can start it. err = unlink(fileName); @@ -698,7 +709,13 @@ fprintf(stderr, "ERROR: couldn't open file\n"); abort(); } - muxer = new MediaMuxer(fd, MediaMuxer::OUTPUT_FORMAT_MPEG_4); + if (gOutputFormat == FORMAT_MP4) { + muxer = new MediaMuxer(fd, MediaMuxer::OUTPUT_FORMAT_MPEG_4); + } else if (gOutputFormat == FORMAT_WEBM) { + muxer = new MediaMuxer(fd, MediaMuxer::OUTPUT_FORMAT_WEBM); + } else { + muxer = new MediaMuxer(fd, MediaMuxer::OUTPUT_FORMAT_THREE_GPP); + } close(fd); if (gRotate) { muxer->setOrientationHint(90); // TODO: does this do anything? @@ -948,6 +965,7 @@ { "codec-name", required_argument, NULL, 'N' }, { "monotonic-time", no_argument, NULL, 'm' }, { "persistent-surface", no_argument, NULL, 'p' }, + { "bframes", required_argument, NULL, 'B' }, { NULL, 0, NULL, 0 } }; @@ -1018,6 +1036,10 @@ gOutputFormat = FORMAT_MP4; } else if (strcmp(optarg, "h264") == 0) { gOutputFormat = FORMAT_H264; + } else if (strcmp(optarg, "webm") == 0) { + gOutputFormat = FORMAT_WEBM; + } else if (strcmp(optarg, "3gpp") == 0) { + gOutputFormat = FORMAT_3GPP; } else if (strcmp(optarg, "frames") == 0) { gOutputFormat = FORMAT_FRAMES; } else if (strcmp(optarg, "raw-frames") == 0) { @@ -1036,6 +1058,11 @@ case 'p': gPersistentSurface = true; break; + case 'B': + if (parseValueWithUnit(optarg, &gBframes) != NO_ERROR) { + return 2; + } + break; default: if (ic != '?') { fprintf(stderr, "getopt_long returned unexpected value 0x%x\n", ic);
diff --git a/cmds/stagefright/Android.mk b/cmds/stagefright/Android.mk index c7619af..6eb2e9f 100644 --- a/cmds/stagefright/Android.mk +++ b/cmds/stagefright/Android.mk
@@ -8,7 +8,7 @@ SineSource.cpp LOCAL_SHARED_LIBRARIES := \ - libstagefright libmedia libmedia_omx libmediaextractor libutils libbinder \ + libstagefright libmedia libmedia_omx libutils libbinder \ libstagefright_foundation libjpeg libui libgui libcutils liblog \ libhidlbase \ android.hardware.media.omx@1.0 \ @@ -36,7 +36,7 @@ record.cpp LOCAL_SHARED_LIBRARIES := \ - libstagefright libmedia libmediaextractor liblog libutils libbinder \ + libstagefright libmedia liblog libutils libbinder \ libstagefright_foundation LOCAL_C_INCLUDES:= \ @@ -61,7 +61,7 @@ recordvideo.cpp LOCAL_SHARED_LIBRARIES := \ - libstagefright libmedia libmediaextractor liblog libutils libbinder \ + libstagefright libmedia liblog libutils libbinder \ libstagefright_foundation LOCAL_C_INCLUDES:= \ @@ -87,7 +87,7 @@ audioloop.cpp LOCAL_SHARED_LIBRARIES := \ - libstagefright libmedia libmediaextractor liblog libutils libbinder \ + libstagefright libmedia liblog libutils libbinder \ libstagefright_foundation LOCAL_C_INCLUDES:= \ @@ -111,7 +111,7 @@ LOCAL_SHARED_LIBRARIES := \ libstagefright liblog libutils libbinder libui libgui \ - libstagefright_foundation libmedia libcutils libmediaextractor + libstagefright_foundation libmedia libcutils LOCAL_C_INCLUDES:= \ frameworks/av/media/libstagefright \ @@ -191,7 +191,6 @@ LOCAL_MODULE:= mediafilter LOCAL_SANITIZE := cfi -LOCAL_SANITIZE_DIAG := cfi include $(BUILD_EXECUTABLE) @@ -204,7 +203,7 @@ LOCAL_SHARED_LIBRARIES := \ libstagefright liblog libutils libbinder libstagefright_foundation \ - libcutils libc libmediaextractor + libcutils libc LOCAL_C_INCLUDES:= \ frameworks/av/media/libstagefright \
diff --git a/cmds/stagefright/codec.cpp b/cmds/stagefright/codec.cpp index 6a58467..e5a4337 100644 --- a/cmds/stagefright/codec.cpp +++ b/cmds/stagefright/codec.cpp
@@ -138,7 +138,7 @@ CHECK(!stateByTrack.isEmpty()); - int64_t startTimeUs = ALooper::GetNowUs(); + int64_t startTimeUs = android::ALooper::GetNowUs(); int64_t startTimeRender = -1; for (size_t i = 0; i < stateByTrack.size(); ++i) { @@ -307,7 +307,7 @@ } } - int64_t elapsedTimeUs = ALooper::GetNowUs() - startTimeUs; + int64_t elapsedTimeUs = android::ALooper::GetNowUs() - startTimeUs; for (size_t i = 0; i < stateByTrack.size(); ++i) { CodecState *state = &stateByTrack.editValueAt(i); @@ -366,13 +366,13 @@ case 'T': { useTimestamp = true; + FALLTHROUGH_INTENDED; } - // fall through case 'R': { renderSurface = true; + FALLTHROUGH_INTENDED; } - // fall through case 'S': { useSurface = true; @@ -400,7 +400,7 @@ ProcessState::self()->startThreadPool(); - sp<ALooper> looper = new ALooper; + sp<android::ALooper> looper = new android::ALooper; looper->start(); sp<SurfaceComposerClient> composerClient; @@ -411,10 +411,12 @@ composerClient = new SurfaceComposerClient; CHECK_EQ(composerClient->initCheck(), (status_t)OK); - sp<IBinder> display(SurfaceComposerClient::getBuiltInDisplay( - ISurfaceComposer::eDisplayIdMain)); + const sp<IBinder> display = SurfaceComposerClient::getInternalDisplayToken(); + CHECK(display != nullptr); + DisplayInfo info; - SurfaceComposerClient::getDisplayInfo(display, &info); + CHECK_EQ(SurfaceComposerClient::getDisplayInfo(display, &info), NO_ERROR); + ssize_t displayWidth = info.w; ssize_t displayHeight = info.h;
diff --git a/cmds/stagefright/mediafilter.cpp b/cmds/stagefright/mediafilter.cpp index f24d2dd..2cf6955 100644 --- a/cmds/stagefright/mediafilter.cpp +++ b/cmds/stagefright/mediafilter.cpp
@@ -310,7 +310,7 @@ } static int decode( - const sp<ALooper> &looper, + const sp<android::ALooper> &looper, const char *path, const sp<Surface> &surface, bool renderSurface, @@ -465,7 +465,7 @@ filterState->mSignalledInputEOS = false; filterState->mSawOutputEOS = false; - int64_t startTimeUs = ALooper::GetNowUs(); + int64_t startTimeUs = android::ALooper::GetNowUs(); int64_t startTimeRender = -1; for (size_t i = 0; i < stateByTrack.size(); ++i) { @@ -643,7 +643,7 @@ useTimestamp, &startTimeRender); } - int64_t elapsedTimeUs = ALooper::GetNowUs() - startTimeUs; + int64_t elapsedTimeUs = android::ALooper::GetNowUs() - startTimeUs; for (size_t i = 0; i < stateByTrack.size(); ++i) { CodecState *state = &stateByTrack.editValueAt(i); @@ -706,13 +706,13 @@ case 'T': { useTimestamp = true; + FALLTHROUGH_INTENDED; } - // fall through case 'R': { renderSurface = true; + FALLTHROUGH_INTENDED; } - // fall through case 'S': { useSurface = true; @@ -737,7 +737,7 @@ ProcessState::self()->startThreadPool(); - android::sp<ALooper> looper = new ALooper; + android::sp<android::ALooper> looper = new android::ALooper; looper->start(); android::sp<SurfaceComposerClient> composerClient; @@ -748,10 +748,12 @@ composerClient = new SurfaceComposerClient; CHECK_EQ((status_t)OK, composerClient->initCheck()); - android::sp<IBinder> display(SurfaceComposerClient::getBuiltInDisplay( - ISurfaceComposer::eDisplayIdMain)); + const android::sp<IBinder> display = SurfaceComposerClient::getInternalDisplayToken(); + CHECK(display != nullptr); + DisplayInfo info; - SurfaceComposerClient::getDisplayInfo(display, &info); + CHECK_EQ(SurfaceComposerClient::getDisplayInfo(display, &info), NO_ERROR); + ssize_t displayWidth = info.w; ssize_t displayHeight = info.h;
diff --git a/cmds/stagefright/record.cpp b/cmds/stagefright/record.cpp index 44b0015..95a16f3 100644 --- a/cmds/stagefright/record.cpp +++ b/cmds/stagefright/record.cpp
@@ -17,7 +17,6 @@ #include "SineSource.h" #include <binder/ProcessState.h> -#include <media/MediaExtractor.h> #include <media/stagefright/foundation/ADebug.h> #include <media/stagefright/foundation/ALooper.h> #include <media/stagefright/foundation/AMessage.h> @@ -28,6 +27,7 @@ #include <media/stagefright/MediaDefs.h> #include <media/stagefright/MediaCodecSource.h> #include <media/stagefright/MetaData.h> +#include <media/stagefright/MediaExtractor.h> #include <media/stagefright/MediaExtractorFactory.h> #include <media/stagefright/MPEG4Writer.h> #include <media/stagefright/SimpleDecodingSource.h>
diff --git a/cmds/stagefright/stagefright.cpp b/cmds/stagefright/stagefright.cpp index 61fc897..bf36be0 100644 --- a/cmds/stagefright/stagefright.cpp +++ b/cmds/stagefright/stagefright.cpp
@@ -32,7 +32,6 @@ #include <binder/IServiceManager.h> #include <binder/ProcessState.h> #include <media/DataSource.h> -#include <media/MediaExtractor.h> #include <media/MediaSource.h> #include <media/ICrypto.h> #include <media/IMediaHTTPService.h> @@ -47,9 +46,11 @@ #include <media/stagefright/JPEGSource.h> #include <media/stagefright/InterfaceUtils.h> #include <media/stagefright/MediaCodec.h> +#include <media/stagefright/MediaCodecConstants.h> #include <media/stagefright/MediaCodecList.h> #include <media/stagefright/MediaDefs.h> #include <media/stagefright/MediaErrors.h> +#include <media/stagefright/MediaExtractor.h> #include <media/stagefright/MediaExtractorFactory.h> #include <media/stagefright/MetaData.h> #include <media/stagefright/SimpleDecodingSource.h> @@ -78,6 +79,7 @@ static bool gPlaybackAudio; static bool gWriteMP4; static bool gDisplayHistogram; +static bool gVerbose = false; static bool showProgress = true; static String8 gWriteMP4Filename; static String8 gComponentNameOverride; @@ -159,6 +161,11 @@ break; } + if (gVerbose) { + MetaDataBase &meta = mbuf->meta_data(); + fprintf(stdout, "sample format: %s\n", meta.toString().c_str()); + } + CHECK_EQ( fwrite((const uint8_t *)mbuf->data() + mbuf->range_offset(), 1, @@ -218,11 +225,15 @@ player->setSource(rawSource); rawSource.clear(); - player->start(true /* sourceAlreadyStarted */); + err = player->start(true /* sourceAlreadyStarted */); - status_t finalStatus; - while (!player->reachedEOS(&finalStatus)) { - usleep(100000ll); + if (err == OK) { + status_t finalStatus; + while (!player->reachedEOS(&finalStatus)) { + usleep(100000ll); + } + } else { + fprintf(stderr, "unable to start playback err=%d (0x%08x)\n", err, err); } delete player; @@ -348,7 +359,10 @@ decodeTimesUs.push(delayDecodeUs); } - if (showProgress && (n++ % 16) == 0) { + if (gVerbose) { + MetaDataBase &meta = buffer->meta_data(); + fprintf(stdout, "%ld sample format: %s\n", numFrames, meta.toString().c_str()); + } else if (showProgress && (n++ % 16) == 0) { printf("."); fflush(stdout); } @@ -579,12 +593,12 @@ break; } + CHECK(buffer != NULL); + if (buffer->range_length() > 0) { break; } - CHECK(buffer != NULL); - buffer->release(); buffer = NULL; } @@ -615,7 +629,7 @@ fprintf(stderr, " -l(ist) components\n"); fprintf(stderr, " -m max-number-of-frames-to-decode in each pass\n"); fprintf(stderr, " -b bug to reproduce\n"); - fprintf(stderr, " -p(rofiles) dump decoder profiles supported\n"); + fprintf(stderr, " -i(nfo) dump codec info (profiles and color formats supported, details)\n"); fprintf(stderr, " -t(humbnail) extract video thumbnail or album art\n"); fprintf(stderr, " -s(oftware) prefer software codec\n"); fprintf(stderr, " -r(hardware) force to use hardware codec\n"); @@ -630,55 +644,134 @@ fprintf(stderr, " -T allocate buffers from a surface texture\n"); fprintf(stderr, " -d(ump) output_filename (raw stream data to a file)\n"); fprintf(stderr, " -D(ump) output_filename (decoded PCM data to a file)\n"); + fprintf(stderr, " -v be more verbose\n"); } -static void dumpCodecProfiles(bool queryDecoders) { - const char *kMimeTypes[] = { - MEDIA_MIMETYPE_VIDEO_AVC, MEDIA_MIMETYPE_VIDEO_MPEG4, - MEDIA_MIMETYPE_VIDEO_H263, MEDIA_MIMETYPE_AUDIO_AAC, - MEDIA_MIMETYPE_AUDIO_AMR_NB, MEDIA_MIMETYPE_AUDIO_AMR_WB, - MEDIA_MIMETYPE_AUDIO_MPEG, MEDIA_MIMETYPE_AUDIO_G711_MLAW, - MEDIA_MIMETYPE_AUDIO_G711_ALAW, MEDIA_MIMETYPE_AUDIO_VORBIS, - MEDIA_MIMETYPE_VIDEO_VP8, MEDIA_MIMETYPE_VIDEO_VP9, - MEDIA_MIMETYPE_VIDEO_DOLBY_VISION - }; - - const char *codecType = queryDecoders? "decoder" : "encoder"; - printf("%s profiles:\n", codecType); +static void dumpCodecDetails(bool queryDecoders) { + const char *codecType = queryDecoders? "Decoder" : "Encoder"; + printf("\n%s infos by media types:\n" + "=============================\n", codecType); sp<IMediaCodecList> list = MediaCodecList::getInstance(); size_t numCodecs = list->countCodecs(); - for (size_t k = 0; k < sizeof(kMimeTypes) / sizeof(kMimeTypes[0]); ++k) { - printf("type '%s':\n", kMimeTypes[k]); + // gather all media types supported by codec class, and link to codecs that support them + KeyedVector<AString, Vector<sp<MediaCodecInfo>>> allMediaTypes; + for (size_t codec_ix = 0; codec_ix < numCodecs; ++codec_ix) { + sp<MediaCodecInfo> info = list->getCodecInfo(codec_ix); + if (info->isEncoder() == !queryDecoders) { + Vector<AString> supportedMediaTypes; + info->getSupportedMediaTypes(&supportedMediaTypes); + if (!supportedMediaTypes.size()) { + printf("warning: %s does not support any media types\n", + info->getCodecName()); + } else { + for (const AString &mediaType : supportedMediaTypes) { + if (allMediaTypes.indexOfKey(mediaType) < 0) { + allMediaTypes.add(mediaType, Vector<sp<MediaCodecInfo>>()); + } + allMediaTypes.editValueFor(mediaType).add(info); + } + } + } + } - for (size_t index = 0; index < numCodecs; ++index) { - sp<MediaCodecInfo> info = list->getCodecInfo(index); - if (info == NULL || info->isEncoder() != !queryDecoders) { - continue; - } - sp<MediaCodecInfo::Capabilities> caps = info->getCapabilitiesFor(kMimeTypes[k]); + KeyedVector<AString, bool> visitedCodecs; + for (size_t type_ix = 0; type_ix < allMediaTypes.size(); ++type_ix) { + const AString &mediaType = allMediaTypes.keyAt(type_ix); + printf("\nMedia type '%s':\n", mediaType.c_str()); + + for (const sp<MediaCodecInfo> &info : allMediaTypes.valueAt(type_ix)) { + sp<MediaCodecInfo::Capabilities> caps = info->getCapabilitiesFor(mediaType.c_str()); if (caps == NULL) { + printf("warning: %s does not have capabilities for type %s\n", + info->getCodecName(), mediaType.c_str()); continue; } - printf(" %s '%s' supports ", + printf(" %s \"%s\" supports\n", codecType, info->getCodecName()); - Vector<MediaCodecInfo::ProfileLevel> profileLevels; - caps->getSupportedProfileLevels(&profileLevels); - if (profileLevels.size() == 0) { - printf("NOTHING.\n"); - continue; + auto printList = [](const char *type, const Vector<AString> &values){ + printf(" %s: [", type); + for (size_t j = 0; j < values.size(); ++j) { + printf("\n %s%s", values[j].c_str(), + j == values.size() - 1 ? " " : ","); + } + printf("]\n"); + }; + + if (visitedCodecs.indexOfKey(info->getCodecName()) < 0) { + visitedCodecs.add(info->getCodecName(), true); + { + Vector<AString> aliases; + info->getAliases(&aliases); + // quote alias + for (AString &alias : aliases) { + alias.insert("\"", 1, 0); + alias.append('"'); + } + printList("aliases", aliases); + } + { + uint32_t attrs = info->getAttributes(); + Vector<AString> list; + list.add(AStringPrintf("encoder: %d", !!(attrs & MediaCodecInfo::kFlagIsEncoder))); + list.add(AStringPrintf("vendor: %d", !!(attrs & MediaCodecInfo::kFlagIsVendor))); + list.add(AStringPrintf("software-only: %d", !!(attrs & MediaCodecInfo::kFlagIsSoftwareOnly))); + list.add(AStringPrintf("hw-accelerated: %d", !!(attrs & MediaCodecInfo::kFlagIsHardwareAccelerated))); + printList(AStringPrintf("attributes: %#x", attrs).c_str(), list); + } + + printf(" owner: \"%s\"\n", info->getOwnerName()); + printf(" rank: %u\n", info->getRank()); + } else { + printf(" aliases, attributes, owner, rank: see above\n"); } - for (size_t j = 0; j < profileLevels.size(); ++j) { - const MediaCodecInfo::ProfileLevel &profileLevel = profileLevels[j]; + { + Vector<AString> list; + Vector<MediaCodecInfo::ProfileLevel> profileLevels; + caps->getSupportedProfileLevels(&profileLevels); + for (const MediaCodecInfo::ProfileLevel &pl : profileLevels) { + const char *niceProfile = + mediaType.equalsIgnoreCase(MIMETYPE_AUDIO_AAC) ? asString_AACObject(pl.mProfile) : + mediaType.equalsIgnoreCase(MIMETYPE_VIDEO_MPEG2) ? asString_MPEG2Profile(pl.mProfile) : + mediaType.equalsIgnoreCase(MIMETYPE_VIDEO_H263) ? asString_H263Profile(pl.mProfile) : + mediaType.equalsIgnoreCase(MIMETYPE_VIDEO_MPEG4) ? asString_MPEG4Profile(pl.mProfile) : + mediaType.equalsIgnoreCase(MIMETYPE_VIDEO_AVC) ? asString_AVCProfile(pl.mProfile) : + mediaType.equalsIgnoreCase(MIMETYPE_VIDEO_VP8) ? asString_VP8Profile(pl.mProfile) : + mediaType.equalsIgnoreCase(MIMETYPE_VIDEO_HEVC) ? asString_HEVCProfile(pl.mProfile) : + mediaType.equalsIgnoreCase(MIMETYPE_VIDEO_VP9) ? asString_VP9Profile(pl.mProfile) : + mediaType.equalsIgnoreCase(MIMETYPE_VIDEO_AV1) ? asString_AV1Profile(pl.mProfile) :"??"; + const char *niceLevel = + mediaType.equalsIgnoreCase(MIMETYPE_VIDEO_MPEG2) ? asString_MPEG2Level(pl.mLevel) : + mediaType.equalsIgnoreCase(MIMETYPE_VIDEO_H263) ? asString_H263Level(pl.mLevel) : + mediaType.equalsIgnoreCase(MIMETYPE_VIDEO_MPEG4) ? asString_MPEG4Level(pl.mLevel) : + mediaType.equalsIgnoreCase(MIMETYPE_VIDEO_AVC) ? asString_AVCLevel(pl.mLevel) : + mediaType.equalsIgnoreCase(MIMETYPE_VIDEO_VP8) ? asString_VP8Level(pl.mLevel) : + mediaType.equalsIgnoreCase(MIMETYPE_VIDEO_HEVC) ? asString_HEVCTierLevel(pl.mLevel) : + mediaType.equalsIgnoreCase(MIMETYPE_VIDEO_VP9) ? asString_VP9Level(pl.mLevel) : + mediaType.equalsIgnoreCase(MIMETYPE_VIDEO_AV1) ? asString_AV1Level(pl.mLevel) : + "??"; - printf("%s%u/%u", j > 0 ? ", " : "", - profileLevel.mProfile, profileLevel.mLevel); + list.add(AStringPrintf("% 5u/% 5u (%s/%s)", + pl.mProfile, pl.mLevel, niceProfile, niceLevel)); + } + printList("profile/levels", list); } - printf("\n"); + { + Vector<AString> list; + Vector<uint32_t> colors; + caps->getSupportedColorFormats(&colors); + for (uint32_t color : colors) { + list.add(AStringPrintf("%#x (%s)", color, + asString_ColorFormat((int32_t)color))); + } + printList("colors", list); + } + + printf(" details: %s\n", caps->getDetails()->debugString(6).c_str()); } } } @@ -688,7 +781,7 @@ bool audioOnly = false; bool listComponents = false; - bool dumpProfiles = false; + bool dumpCodecInfo = false; bool extractThumbnail = false; bool seekTest = false; bool useSurfaceAlloc = false; @@ -705,10 +798,10 @@ gWriteMP4 = false; gDisplayHistogram = false; - sp<ALooper> looper; + sp<android::ALooper> looper; int res; - while ((res = getopt(argc, argv, "haqn:lm:b:ptsrow:kN:xSTd:D:")) >= 0) { + while ((res = getopt(argc, argv, "vhaqn:lm:b:itsrow:kN:xSTd:D:")) >= 0) { switch (res) { case 'a': { @@ -778,9 +871,9 @@ break; } - case 'p': + case 'i': { - dumpProfiles = true; + dumpCodecInfo = true; break; } @@ -832,6 +925,12 @@ break; } + case 'v': + { + gVerbose = true; + break; + } + case '?': case 'h': default: @@ -915,9 +1014,9 @@ return 0; } - if (dumpProfiles) { - dumpCodecProfiles(true /* queryDecoders */); - dumpCodecProfiles(false /* queryDecoders */); + if (dumpCodecInfo) { + dumpCodecDetails(true /* queryDecoders */); + dumpCodecDetails(false /* queryDecoders */); } if (listComponents) {
diff --git a/cmds/stagefright/stream.cpp b/cmds/stagefright/stream.cpp index b0199d8..35bdbc0 100644 --- a/cmds/stagefright/stream.cpp +++ b/cmds/stagefright/stream.cpp
@@ -24,7 +24,6 @@ #include <media/DataSource.h> #include <media/IMediaHTTPService.h> #include <media/IStreamSource.h> -#include <media/MediaExtractor.h> #include <media/mediaplayer.h> #include <media/MediaSource.h> #include <media/stagefright/foundation/ADebug.h> @@ -32,6 +31,7 @@ #include <media/stagefright/DataSourceFactory.h> #include <media/stagefright/InterfaceUtils.h> #include <media/stagefright/MPEG2TSWriter.h> +#include <media/stagefright/MediaExtractor.h> #include <media/stagefright/MediaExtractorFactory.h> #include <media/stagefright/MetaData.h> @@ -318,10 +318,12 @@ sp<SurfaceComposerClient> composerClient = new SurfaceComposerClient; CHECK_EQ(composerClient->initCheck(), (status_t)OK); - sp<IBinder> display(SurfaceComposerClient::getBuiltInDisplay( - ISurfaceComposer::eDisplayIdMain)); + const sp<IBinder> display = SurfaceComposerClient::getInternalDisplayToken(); + CHECK(display != nullptr); + DisplayInfo info; - SurfaceComposerClient::getDisplayInfo(display, &info); + CHECK_EQ(SurfaceComposerClient::getDisplayInfo(display, &info), NO_ERROR); + ssize_t displayWidth = info.w; ssize_t displayHeight = info.h;
diff --git a/drm/common/Android.bp b/drm/common/Android.bp index 1552c3f..272684c 100644 --- a/drm/common/Android.bp +++ b/drm/common/Android.bp
@@ -35,7 +35,7 @@ cflags: ["-Wall", "-Werror"], - static_libs: ["libbinder"], + shared_libs: ["libbinder"], export_include_dirs: ["include"], }
diff --git a/drm/common/DrmEngineBase.cpp b/drm/common/DrmEngineBase.cpp index f734905..aec5959 100644 --- a/drm/common/DrmEngineBase.cpp +++ b/drm/common/DrmEngineBase.cpp
@@ -79,12 +79,12 @@ } status_t DrmEngineBase::consumeRights( - int uniqueId, DecryptHandle* decryptHandle, int action, bool reserve) { + int uniqueId, sp<DecryptHandle>& decryptHandle, int action, bool reserve) { return onConsumeRights(uniqueId, decryptHandle, action, reserve); } status_t DrmEngineBase::setPlaybackStatus( - int uniqueId, DecryptHandle* decryptHandle, int playbackStatus, int64_t position) { + int uniqueId, sp<DecryptHandle>& decryptHandle, int playbackStatus, int64_t position) { return onSetPlaybackStatus(uniqueId, decryptHandle, playbackStatus, position); } @@ -120,7 +120,7 @@ } status_t DrmEngineBase::openDecryptSession( - int uniqueId, DecryptHandle* decryptHandle, + int uniqueId, sp<DecryptHandle>& decryptHandle, int fd, off64_t offset, off64_t length, const char* mime) { if (!mime || mime[0] == '\0') { @@ -131,7 +131,7 @@ } status_t DrmEngineBase::openDecryptSession( - int uniqueId, DecryptHandle* decryptHandle, + int uniqueId, sp<DecryptHandle>& decryptHandle, const char* uri, const char* mime) { if (!mime || mime[0] == '\0') { return onOpenDecryptSession(uniqueId, decryptHandle, uri); @@ -139,33 +139,33 @@ return onOpenDecryptSession(uniqueId, decryptHandle, uri, mime); } -status_t DrmEngineBase::openDecryptSession(int uniqueId, DecryptHandle* decryptHandle, +status_t DrmEngineBase::openDecryptSession(int uniqueId, sp<DecryptHandle>& decryptHandle, const DrmBuffer& buf, const String8& mimeType) { return onOpenDecryptSession(uniqueId, decryptHandle, buf, mimeType); } -status_t DrmEngineBase::closeDecryptSession(int uniqueId, DecryptHandle* decryptHandle) { +status_t DrmEngineBase::closeDecryptSession(int uniqueId, sp<DecryptHandle>& decryptHandle) { return onCloseDecryptSession(uniqueId, decryptHandle); } status_t DrmEngineBase::initializeDecryptUnit( - int uniqueId, DecryptHandle* decryptHandle, int decryptUnitId, const DrmBuffer* headerInfo) { + int uniqueId, sp<DecryptHandle>& decryptHandle, int decryptUnitId, const DrmBuffer* headerInfo) { return onInitializeDecryptUnit(uniqueId, decryptHandle, decryptUnitId, headerInfo); } status_t DrmEngineBase::decrypt( - int uniqueId, DecryptHandle* decryptHandle, int decryptUnitId, + int uniqueId, sp<DecryptHandle>& decryptHandle, int decryptUnitId, const DrmBuffer* encBuffer, DrmBuffer** decBuffer, DrmBuffer* IV) { return onDecrypt(uniqueId, decryptHandle, decryptUnitId, encBuffer, decBuffer, IV); } status_t DrmEngineBase::finalizeDecryptUnit( - int uniqueId, DecryptHandle* decryptHandle, int decryptUnitId) { + int uniqueId, sp<DecryptHandle>& decryptHandle, int decryptUnitId) { return onFinalizeDecryptUnit(uniqueId, decryptHandle, decryptUnitId); } ssize_t DrmEngineBase::pread( - int uniqueId, DecryptHandle* decryptHandle, void* buffer, ssize_t numBytes, off64_t offset) { + int uniqueId, sp<DecryptHandle>& decryptHandle, void* buffer, ssize_t numBytes, off64_t offset) { return onPread(uniqueId, decryptHandle, buffer, numBytes, offset); }
diff --git a/drm/common/IDrmManagerService.cpp b/drm/common/IDrmManagerService.cpp index 44f98dd..a6d33b0 100644 --- a/drm/common/IDrmManagerService.cpp +++ b/drm/common/IDrmManagerService.cpp
@@ -39,7 +39,7 @@ using namespace android; static void writeDecryptHandleToParcelData( - const DecryptHandle* handle, Parcel* data) { + const sp<DecryptHandle>& handle, Parcel* data) { data->writeInt32(handle->decryptId); data->writeString8(handle->mimeType); data->writeInt32(handle->decryptApiType); @@ -67,7 +67,7 @@ } static void readDecryptHandleFromParcelData( - DecryptHandle* handle, const Parcel& data) { + sp<DecryptHandle>& handle, const Parcel& data) { if (0 == data.dataAvail()) { return; } @@ -99,7 +99,7 @@ } } -static void clearDecryptHandle(DecryptHandle* handle) { +static void clearDecryptHandle(sp<DecryptHandle> &handle) { if (handle == NULL) { return; } @@ -414,7 +414,7 @@ } status_t BpDrmManagerService::consumeRights( - int uniqueId, DecryptHandle* decryptHandle, int action, bool reserve) { + int uniqueId, sp<DecryptHandle>& decryptHandle, int action, bool reserve) { ALOGV("consumeRights"); Parcel data, reply; @@ -431,7 +431,7 @@ } status_t BpDrmManagerService::setPlaybackStatus( - int uniqueId, DecryptHandle* decryptHandle, int playbackStatus, int64_t position) { + int uniqueId, sp<DecryptHandle>& decryptHandle, int playbackStatus, int64_t position) { ALOGV("setPlaybackStatus"); Parcel data, reply; @@ -603,7 +603,7 @@ return reply.readInt32(); } -DecryptHandle* BpDrmManagerService::openDecryptSession( +sp<DecryptHandle> BpDrmManagerService::openDecryptSession( int uniqueId, int fd, off64_t offset, off64_t length, const char* mime) { ALOGV("Entering BpDrmManagerService::openDecryptSession"); Parcel data, reply; @@ -621,7 +621,7 @@ remote()->transact(OPEN_DECRYPT_SESSION, data, &reply); - DecryptHandle* handle = NULL; + sp<DecryptHandle> handle; if (0 != reply.dataAvail()) { handle = new DecryptHandle(); readDecryptHandleFromParcelData(handle, reply); @@ -629,7 +629,7 @@ return handle; } -DecryptHandle* BpDrmManagerService::openDecryptSession( +sp<DecryptHandle> BpDrmManagerService::openDecryptSession( int uniqueId, const char* uri, const char* mime) { ALOGV("Entering BpDrmManagerService::openDecryptSession: mime=%s", mime? mime: "NULL"); @@ -646,7 +646,7 @@ remote()->transact(OPEN_DECRYPT_SESSION_FROM_URI, data, &reply); - DecryptHandle* handle = NULL; + sp<DecryptHandle> handle; if (0 != reply.dataAvail()) { handle = new DecryptHandle(); readDecryptHandleFromParcelData(handle, reply); @@ -656,7 +656,7 @@ return handle; } -DecryptHandle* BpDrmManagerService::openDecryptSession( +sp<DecryptHandle> BpDrmManagerService::openDecryptSession( int uniqueId, const DrmBuffer& buf, const String8& mimeType) { ALOGV("Entering BpDrmManagerService::openDecryptSession"); Parcel data, reply; @@ -673,7 +673,7 @@ remote()->transact(OPEN_DECRYPT_SESSION_FOR_STREAMING, data, &reply); - DecryptHandle* handle = NULL; + sp<DecryptHandle> handle; if (0 != reply.dataAvail()) { handle = new DecryptHandle(); readDecryptHandleFromParcelData(handle, reply); @@ -683,7 +683,7 @@ return handle; } -status_t BpDrmManagerService::closeDecryptSession(int uniqueId, DecryptHandle* decryptHandle) { +status_t BpDrmManagerService::closeDecryptSession(int uniqueId, sp<DecryptHandle>& decryptHandle) { ALOGV("closeDecryptSession"); Parcel data, reply; @@ -698,7 +698,7 @@ } status_t BpDrmManagerService::initializeDecryptUnit( - int uniqueId, DecryptHandle* decryptHandle, + int uniqueId, sp<DecryptHandle>& decryptHandle, int decryptUnitId, const DrmBuffer* headerInfo) { ALOGV("initializeDecryptUnit"); Parcel data, reply; @@ -718,7 +718,7 @@ } status_t BpDrmManagerService::decrypt( - int uniqueId, DecryptHandle* decryptHandle, int decryptUnitId, + int uniqueId, sp<DecryptHandle>& decryptHandle, int decryptUnitId, const DrmBuffer* encBuffer, DrmBuffer** decBuffer, DrmBuffer* IV) { ALOGV("decrypt"); Parcel data, reply; @@ -754,7 +754,7 @@ } status_t BpDrmManagerService::finalizeDecryptUnit( - int uniqueId, DecryptHandle* decryptHandle, int decryptUnitId) { + int uniqueId, sp<DecryptHandle>& decryptHandle, int decryptUnitId) { ALOGV("finalizeDecryptUnit"); Parcel data, reply; @@ -770,7 +770,7 @@ } ssize_t BpDrmManagerService::pread( - int uniqueId, DecryptHandle* decryptHandle, void* buffer, + int uniqueId, sp<DecryptHandle>& decryptHandle, void* buffer, ssize_t numBytes, off64_t offset) { ALOGV("read"); Parcel data, reply; @@ -1128,16 +1128,16 @@ const int uniqueId = data.readInt32(); - DecryptHandle handle; - readDecryptHandleFromParcelData(&handle, data); + sp<DecryptHandle> handle = new DecryptHandle(); + readDecryptHandleFromParcelData(handle, data); const int action = data.readInt32(); const bool reserve = static_cast<bool>(data.readInt32()); const status_t status - = consumeRights(uniqueId, &handle, action, reserve); + = consumeRights(uniqueId, handle, action, reserve); reply->writeInt32(status); - clearDecryptHandle(&handle); + clearDecryptHandle(handle); return DRM_NO_ERROR; } @@ -1148,16 +1148,16 @@ const int uniqueId = data.readInt32(); - DecryptHandle handle; - readDecryptHandleFromParcelData(&handle, data); + sp<DecryptHandle> handle = new DecryptHandle(); + readDecryptHandleFromParcelData(handle, data); const int playbackStatus = data.readInt32(); const int64_t position = data.readInt64(); const status_t status - = setPlaybackStatus(uniqueId, &handle, playbackStatus, position); + = setPlaybackStatus(uniqueId, handle, playbackStatus, position); reply->writeInt32(status); - clearDecryptHandle(&handle); + clearDecryptHandle(handle); return DRM_NO_ERROR; } @@ -1329,13 +1329,13 @@ const off64_t length = data.readInt64(); const String8 mime = data.readString8(); - DecryptHandle* handle + sp<DecryptHandle> handle = openDecryptSession(uniqueId, fd, offset, length, mime.string()); - if (NULL != handle) { - writeDecryptHandleToParcelData(handle, reply); + if (NULL != handle.get()) { + writeDecryptHandleToParcelData(handle.get(), reply); clearDecryptHandle(handle); - delete handle; handle = NULL; + handle.clear(); } return DRM_NO_ERROR; } @@ -1349,13 +1349,13 @@ const String8 uri = data.readString8(); const String8 mime = data.readString8(); - DecryptHandle* handle = openDecryptSession(uniqueId, uri.string(), mime.string()); + sp<DecryptHandle> handle = openDecryptSession(uniqueId, uri.string(), mime.string()); - if (NULL != handle) { - writeDecryptHandleToParcelData(handle, reply); + if (NULL != handle.get()) { + writeDecryptHandleToParcelData(handle.get(), reply); clearDecryptHandle(handle); - delete handle; handle = NULL; + handle.clear(); } else { ALOGV("NULL decryptHandle is returned"); } @@ -1373,13 +1373,12 @@ bufferSize); const String8 mimeType(data.readString8()); - DecryptHandle* handle = openDecryptSession(uniqueId, buf, mimeType); + sp<DecryptHandle> handle = openDecryptSession(uniqueId, buf, mimeType); if (handle != NULL) { writeDecryptHandleToParcelData(handle, reply); clearDecryptHandle(handle); - delete handle; - handle = NULL; + handle.clear(); } else { ALOGV("NULL decryptHandle is returned"); } @@ -1393,7 +1392,7 @@ const int uniqueId = data.readInt32(); - DecryptHandle* handle = new DecryptHandle(); + sp<DecryptHandle> handle = new DecryptHandle(); readDecryptHandleFromParcelData(handle, data); const status_t status = closeDecryptSession(uniqueId, handle); @@ -1408,8 +1407,8 @@ const int uniqueId = data.readInt32(); - DecryptHandle handle; - readDecryptHandleFromParcelData(&handle, data); + sp<DecryptHandle> handle = new DecryptHandle(); + readDecryptHandleFromParcelData(handle, data); const int decryptUnitId = data.readInt32(); @@ -1417,17 +1416,17 @@ const uint32_t bufferSize = data.readInt32(); if (bufferSize > data.dataAvail()) { reply->writeInt32(BAD_VALUE); - clearDecryptHandle(&handle); + clearDecryptHandle(handle); return DRM_NO_ERROR; } DrmBuffer* headerInfo = NULL; headerInfo = new DrmBuffer((char *)data.readInplace(bufferSize), bufferSize); const status_t status - = initializeDecryptUnit(uniqueId, &handle, decryptUnitId, headerInfo); + = initializeDecryptUnit(uniqueId, handle, decryptUnitId, headerInfo); reply->writeInt32(status); - clearDecryptHandle(&handle); + clearDecryptHandle(handle); delete headerInfo; headerInfo = NULL; return DRM_NO_ERROR; } @@ -1439,8 +1438,8 @@ const int uniqueId = data.readInt32(); - DecryptHandle handle; - readDecryptHandleFromParcelData(&handle, data); + sp<DecryptHandle> handle = new DecryptHandle; + readDecryptHandleFromParcelData(handle, data); const int decryptUnitId = data.readInt32(); const uint32_t decBufferSize = data.readInt32(); @@ -1450,7 +1449,7 @@ decBufferSize > MAX_BINDER_TRANSACTION_SIZE) { reply->writeInt32(BAD_VALUE); reply->writeInt32(0); - clearDecryptHandle(&handle); + clearDecryptHandle(handle); return DRM_NO_ERROR; } @@ -1470,7 +1469,7 @@ } const status_t status - = decrypt(uniqueId, &handle, decryptUnitId, encBuffer, &decBuffer, IV); + = decrypt(uniqueId, handle, decryptUnitId, encBuffer, &decBuffer, IV); reply->writeInt32(status); @@ -1480,7 +1479,7 @@ reply->write(decBuffer->data, size); } - clearDecryptHandle(&handle); + clearDecryptHandle(handle); delete encBuffer; encBuffer = NULL; delete decBuffer; decBuffer = NULL; delete [] buffer; buffer = NULL; @@ -1495,13 +1494,13 @@ const int uniqueId = data.readInt32(); - DecryptHandle handle; - readDecryptHandleFromParcelData(&handle, data); + sp<DecryptHandle> handle = new DecryptHandle(); + readDecryptHandleFromParcelData(handle, data); - const status_t status = finalizeDecryptUnit(uniqueId, &handle, data.readInt32()); + const status_t status = finalizeDecryptUnit(uniqueId, handle, data.readInt32()); reply->writeInt32(status); - clearDecryptHandle(&handle); + clearDecryptHandle(handle); return DRM_NO_ERROR; } @@ -1512,8 +1511,8 @@ const int uniqueId = data.readInt32(); - DecryptHandle handle; - readDecryptHandleFromParcelData(&handle, data); + sp<DecryptHandle> handle = new DecryptHandle(); + readDecryptHandleFromParcelData(handle, data); const uint32_t numBytes = data.readInt32(); if (numBytes > MAX_BINDER_TRANSACTION_SIZE) { @@ -1524,13 +1523,13 @@ const off64_t offset = data.readInt64(); - ssize_t result = pread(uniqueId, &handle, buffer, numBytes, offset); + ssize_t result = pread(uniqueId, handle, buffer, numBytes, offset); reply->writeInt32(result); if (0 < result) { reply->write(buffer, result); } - clearDecryptHandle(&handle); + clearDecryptHandle(handle); delete [] buffer, buffer = NULL; return DRM_NO_ERROR; }
diff --git a/drm/common/include/DrmEngineBase.h b/drm/common/include/DrmEngineBase.h index 417107f..73f11a4 100644 --- a/drm/common/include/DrmEngineBase.h +++ b/drm/common/include/DrmEngineBase.h
@@ -59,10 +59,11 @@ int checkRightsStatus(int uniqueId, const String8& path, int action); - status_t consumeRights(int uniqueId, DecryptHandle* decryptHandle, int action, bool reserve); + status_t consumeRights(int uniqueId, sp<DecryptHandle>& decryptHandle, int action, + bool reserve); status_t setPlaybackStatus( - int uniqueId, DecryptHandle* decryptHandle, int playbackStatus, int64_t position); + int uniqueId, sp<DecryptHandle>& decryptHandle, int playbackStatus, int64_t position); bool validateAction( int uniqueId, const String8& path, int action, const ActionDescription& description); @@ -80,27 +81,28 @@ DrmSupportInfo* getSupportInfo(int uniqueId); status_t openDecryptSession( - int uniqueId, DecryptHandle* decryptHandle, + int uniqueId, sp<DecryptHandle>& decryptHandle, int fd, off64_t offset, off64_t length, const char* mime); status_t openDecryptSession( - int uniqueId, DecryptHandle* decryptHandle, + int uniqueId, sp<DecryptHandle>& decryptHandle, const char* uri, const char* mime); - status_t openDecryptSession(int uniqueId, DecryptHandle* decryptHandle, + status_t openDecryptSession(int uniqueId, sp<DecryptHandle>& decryptHandle, const DrmBuffer& buf, const String8& mimeType); - status_t closeDecryptSession(int uniqueId, DecryptHandle* decryptHandle); + status_t closeDecryptSession(int uniqueId, sp<DecryptHandle>& decryptHandle); - status_t initializeDecryptUnit(int uniqueId, DecryptHandle* decryptHandle, + status_t initializeDecryptUnit(int uniqueId, sp<DecryptHandle>& decryptHandle, int decryptUnitId, const DrmBuffer* headerInfo); - status_t decrypt(int uniqueId, DecryptHandle* decryptHandle, int decryptUnitId, + status_t decrypt(int uniqueId, sp<DecryptHandle>& decryptHandle, int decryptUnitId, const DrmBuffer* encBuffer, DrmBuffer** decBuffer, DrmBuffer* IV); - status_t finalizeDecryptUnit(int uniqueId, DecryptHandle* decryptHandle, int decryptUnitId); + status_t finalizeDecryptUnit(int uniqueId, sp<DecryptHandle>& decryptHandle, + int decryptUnitId); - ssize_t pread(int uniqueId, DecryptHandle* decryptHandle, + ssize_t pread(int uniqueId, sp<DecryptHandle>& decryptHandle, void* buffer, ssize_t numBytes, off64_t offset); protected: @@ -265,7 +267,7 @@ * @return status_t * Returns DRM_NO_ERROR for success, DRM_ERROR_UNKNOWN for failure */ - virtual status_t onConsumeRights(int uniqueId, DecryptHandle* decryptHandle, + virtual status_t onConsumeRights(int uniqueId, sp<DecryptHandle>& decryptHandle, int action, bool reserve) = 0; /** @@ -280,7 +282,8 @@ * Returns DRM_NO_ERROR for success, DRM_ERROR_UNKNOWN for failure */ virtual status_t onSetPlaybackStatus( - int uniqueId, DecryptHandle* decryptHandle, int playbackStatus, int64_t position) = 0; + int uniqueId, sp<DecryptHandle>& decryptHandle, int playbackStatus, + int64_t position) = 0; /** * Validates whether an action on the DRM content is allowed or not. @@ -381,7 +384,7 @@ * DRM_ERROR_CANNOT_HANDLE for failure and DRM_NO_ERROR for success */ virtual status_t onOpenDecryptSession( - int uniqueId, DecryptHandle* decryptHandle, + int uniqueId, sp<DecryptHandle>& decryptHandle, int fd, off64_t offset, off64_t length) = 0; /** @@ -398,7 +401,7 @@ * DRM_ERROR_CANNOT_HANDLE for failure and DRM_NO_ERROR for success */ virtual status_t onOpenDecryptSession( - int /* uniqueId */, DecryptHandle* /* decryptHandle */, + int /* uniqueId */, sp<DecryptHandle>& /* decryptHandle */, int /* fd */, off64_t /* offset */, off64_t /* length */, const char* /* mime */) { @@ -415,7 +418,7 @@ * DRM_ERROR_CANNOT_HANDLE for failure and DRM_NO_ERROR for success */ virtual status_t onOpenDecryptSession( - int uniqueId, DecryptHandle* decryptHandle, + int uniqueId, sp<DecryptHandle>& decryptHandle, const char* uri) = 0; /** @@ -430,7 +433,7 @@ * DRM_ERROR_CANNOT_HANDLE for failure and DRM_NO_ERROR for success */ virtual status_t onOpenDecryptSession( - int /* uniqueId */, DecryptHandle* /* decryptHandle */, + int /* uniqueId */, sp<DecryptHandle>& /* decryptHandle */, const char* /* uri */, const char* /* mime */) { return DRM_ERROR_CANNOT_HANDLE; @@ -447,7 +450,7 @@ * DRM_ERROR_CANNOT_HANDLE for failure and DRM_NO_ERROR for success */ virtual status_t onOpenDecryptSession(int /* uniqueId */, - DecryptHandle* /* decryptHandle */, + sp<DecryptHandle>& /* decryptHandle */, const DrmBuffer& /* buf */, const String8& /* mimeType */) { return DRM_ERROR_CANNOT_HANDLE; @@ -461,7 +464,7 @@ * @return status_t * Returns DRM_NO_ERROR for success, DRM_ERROR_UNKNOWN for failure */ - virtual status_t onCloseDecryptSession(int uniqueId, DecryptHandle* decryptHandle) = 0; + virtual status_t onCloseDecryptSession(int uniqueId, sp<DecryptHandle>& decryptHandle) = 0; /** * Initialize decryption for the given unit of the protected content @@ -473,7 +476,7 @@ * @return status_t * Returns DRM_NO_ERROR for success, DRM_ERROR_UNKNOWN for failure */ - virtual status_t onInitializeDecryptUnit(int uniqueId, DecryptHandle* decryptHandle, + virtual status_t onInitializeDecryptUnit(int uniqueId, sp<DecryptHandle>& decryptHandle, int decryptUnitId, const DrmBuffer* headerInfo) = 0; /** @@ -493,7 +496,7 @@ * DRM_ERROR_SESSION_NOT_OPENED, DRM_ERROR_DECRYPT_UNIT_NOT_INITIALIZED, * DRM_ERROR_DECRYPT for failure. */ - virtual status_t onDecrypt(int uniqueId, DecryptHandle* decryptHandle, int decryptUnitId, + virtual status_t onDecrypt(int uniqueId, sp<DecryptHandle>& decryptHandle, int decryptUnitId, const DrmBuffer* encBuffer, DrmBuffer** decBuffer, DrmBuffer* IV) = 0; /** @@ -506,7 +509,7 @@ * Returns DRM_NO_ERROR for success, DRM_ERROR_UNKNOWN for failure */ virtual status_t onFinalizeDecryptUnit( - int uniqueId, DecryptHandle* decryptHandle, int decryptUnitId) = 0; + int uniqueId, sp<DecryptHandle>& decryptHandle, int decryptUnitId) = 0; /** * Reads the specified number of bytes from an open DRM file. @@ -519,7 +522,7 @@ * * @return Number of bytes read. Returns -1 for Failure. */ - virtual ssize_t onPread(int uniqueId, DecryptHandle* decryptHandle, + virtual ssize_t onPread(int uniqueId, sp<DecryptHandle>& decryptHandle, void* buffer, ssize_t numBytes, off64_t offset) = 0; };
diff --git a/drm/common/include/IDrmEngine.h b/drm/common/include/IDrmEngine.h index acc8ed9..1837a11 100644 --- a/drm/common/include/IDrmEngine.h +++ b/drm/common/include/IDrmEngine.h
@@ -210,7 +210,7 @@ * Returns DRM_NO_ERROR for success, DRM_ERROR_UNKNOWN for failure */ virtual status_t consumeRights( - int uniqueId, DecryptHandle* decryptHandle, int action, bool reserve) = 0; + int uniqueId, sp<DecryptHandle>& decryptHandle, int action, bool reserve) = 0; /** * Informs the DRM Engine about the playback actions performed on the DRM files. @@ -223,7 +223,7 @@ * @return status_t * Returns DRM_NO_ERROR for success, DRM_ERROR_UNKNOWN for failure */ - virtual status_t setPlaybackStatus(int uniqueId, DecryptHandle* decryptHandle, + virtual status_t setPlaybackStatus(int uniqueId, sp<DecryptHandle>& decryptHandle, int playbackStatus, int64_t position) = 0; /** @@ -327,7 +327,7 @@ * DRM_ERROR_CANNOT_HANDLE for failure and DRM_NO_ERROR for success */ virtual status_t openDecryptSession( - int uniqueId, DecryptHandle* decryptHandle, + int uniqueId, sp<DecryptHandle>& decryptHandle, int fd, off64_t offset, off64_t length, const char* mime) = 0; /** @@ -342,7 +342,7 @@ * DRM_ERROR_CANNOT_HANDLE for failure and DRM_NO_ERROR for success */ virtual status_t openDecryptSession( - int uniqueId, DecryptHandle* decryptHandle, + int uniqueId, sp<DecryptHandle>& decryptHandle, const char* uri, const char* mime) = 0; /** @@ -355,7 +355,7 @@ * @return * DRM_ERROR_CANNOT_HANDLE for failure and DRM_NO_ERROR for success */ - virtual status_t openDecryptSession(int uniqueId, DecryptHandle* decryptHandle, + virtual status_t openDecryptSession(int uniqueId, sp<DecryptHandle>& decryptHandle, const DrmBuffer& buf, const String8& mimeType) = 0; /** @@ -366,7 +366,7 @@ * @return status_t * Returns DRM_NO_ERROR for success, DRM_ERROR_UNKNOWN for failure */ - virtual status_t closeDecryptSession(int uniqueId, DecryptHandle* decryptHandle) = 0; + virtual status_t closeDecryptSession(int uniqueId, sp<DecryptHandle>& decryptHandle) = 0; /** * Initialize decryption for the given unit of the protected content @@ -378,7 +378,7 @@ * @return status_t * Returns DRM_NO_ERROR for success, DRM_ERROR_UNKNOWN for failure */ - virtual status_t initializeDecryptUnit(int uniqueId, DecryptHandle* decryptHandle, + virtual status_t initializeDecryptUnit(int uniqueId, sp<DecryptHandle>& decryptHandle, int decryptUnitId, const DrmBuffer* headerInfo) = 0; /** @@ -398,7 +398,7 @@ * DRM_ERROR_SESSION_NOT_OPENED, DRM_ERROR_DECRYPT_UNIT_NOT_INITIALIZED, * DRM_ERROR_DECRYPT for failure. */ - virtual status_t decrypt(int uniqueId, DecryptHandle* decryptHandle, int decryptUnitId, + virtual status_t decrypt(int uniqueId, sp<DecryptHandle>& decryptHandle, int decryptUnitId, const DrmBuffer* encBuffer, DrmBuffer** decBuffer, DrmBuffer* IV) = 0; /** @@ -411,7 +411,7 @@ * Returns DRM_NO_ERROR for success, DRM_ERROR_UNKNOWN for failure */ virtual status_t finalizeDecryptUnit( - int uniqueId, DecryptHandle* decryptHandle, int decryptUnitId) = 0; + int uniqueId, sp<DecryptHandle>& decryptHandle, int decryptUnitId) = 0; /** * Reads the specified number of bytes from an open DRM file. @@ -424,7 +424,7 @@ * * @return Number of bytes read. Returns -1 for Failure. */ - virtual ssize_t pread(int uniqueId, DecryptHandle* decryptHandle, + virtual ssize_t pread(int uniqueId, sp<DecryptHandle>& decryptHandle, void* buffer, ssize_t numBytes, off64_t offset) = 0; };
diff --git a/drm/common/include/IDrmManagerService.h b/drm/common/include/IDrmManagerService.h index 0376b49..836ae0a 100644 --- a/drm/common/include/IDrmManagerService.h +++ b/drm/common/include/IDrmManagerService.h
@@ -115,10 +115,11 @@ virtual int checkRightsStatus(int uniqueId, const String8& path, int action) = 0; virtual status_t consumeRights( - int uniqueId, DecryptHandle* decryptHandle, int action, bool reserve) = 0; + int uniqueId, sp<DecryptHandle>& decryptHandle, int action, bool reserve) = 0; virtual status_t setPlaybackStatus( - int uniqueId, DecryptHandle* decryptHandle, int playbackStatus, int64_t position) = 0; + int uniqueId, sp<DecryptHandle>& decryptHandle, int playbackStatus, + int64_t position) = 0; virtual bool validateAction( int uniqueId, const String8& path, @@ -138,28 +139,28 @@ virtual status_t getAllSupportInfo( int uniqueId, int* length, DrmSupportInfo** drmSupportInfoArray) = 0; - virtual DecryptHandle* openDecryptSession( + virtual sp<DecryptHandle> openDecryptSession( int uniqueId, int fd, off64_t offset, off64_t length, const char* mime) = 0; - virtual DecryptHandle* openDecryptSession( + virtual sp<DecryptHandle> openDecryptSession( int uniqueId, const char* uri, const char* mime) = 0; - virtual DecryptHandle* openDecryptSession( + virtual sp<DecryptHandle> openDecryptSession( int uniqueId, const DrmBuffer& buf, const String8& mimeType) = 0; - virtual status_t closeDecryptSession(int uniqueId, DecryptHandle* decryptHandle) = 0; + virtual status_t closeDecryptSession(int uniqueId, sp<DecryptHandle>& decryptHandle) = 0; - virtual status_t initializeDecryptUnit(int uniqueId, DecryptHandle* decryptHandle, + virtual status_t initializeDecryptUnit(int uniqueId, sp<DecryptHandle>& decryptHandle, int decryptUnitId, const DrmBuffer* headerInfo) = 0; - virtual status_t decrypt(int uniqueId, DecryptHandle* decryptHandle, int decryptUnitId, + virtual status_t decrypt(int uniqueId, sp<DecryptHandle>& decryptHandle, int decryptUnitId, const DrmBuffer* encBuffer, DrmBuffer** decBuffer, DrmBuffer* IV) = 0; virtual status_t finalizeDecryptUnit( - int uniqueId, DecryptHandle* decryptHandle, int decryptUnitId) = 0; + int uniqueId, sp<DecryptHandle>& decryptHandle, int decryptUnitId) = 0; - virtual ssize_t pread(int uniqueId, DecryptHandle* decryptHandle, + virtual ssize_t pread(int uniqueId, sp<DecryptHandle>& decryptHandle, void* buffer, ssize_t numBytes,off64_t offset) = 0; }; @@ -203,10 +204,10 @@ virtual int checkRightsStatus(int uniqueId, const String8& path, int action); virtual status_t consumeRights( - int uniqueId, DecryptHandle* decryptHandle, int action, bool reserve); + int uniqueId, sp<DecryptHandle>& decryptHandle, int action, bool reserve); virtual status_t setPlaybackStatus( - int uniqueId, DecryptHandle* decryptHandle, int playbackStatus, int64_t position); + int uniqueId, sp<DecryptHandle>& decryptHandle, int playbackStatus, int64_t position); virtual bool validateAction( int uniqueId, const String8& path, int action, const ActionDescription& description); @@ -225,28 +226,28 @@ virtual status_t getAllSupportInfo( int uniqueId, int* length, DrmSupportInfo** drmSupportInfoArray); - virtual DecryptHandle* openDecryptSession( + virtual sp<DecryptHandle> openDecryptSession( int uniqueId, int fd, off64_t offset, off64_t length, const char* mime); - virtual DecryptHandle* openDecryptSession( + virtual sp<DecryptHandle> openDecryptSession( int uniqueId, const char* uri, const char* mime); - virtual DecryptHandle* openDecryptSession( + virtual sp<DecryptHandle> openDecryptSession( int uniqueId, const DrmBuffer& buf, const String8& mimeType); - virtual status_t closeDecryptSession(int uniqueId, DecryptHandle* decryptHandle); + virtual status_t closeDecryptSession(int uniqueId, sp<DecryptHandle>& decryptHandle); - virtual status_t initializeDecryptUnit(int uniqueId, DecryptHandle* decryptHandle, + virtual status_t initializeDecryptUnit(int uniqueId, sp<DecryptHandle>& decryptHandle, int decryptUnitId, const DrmBuffer* headerInfo); - virtual status_t decrypt(int uniqueId, DecryptHandle* decryptHandle, int decryptUnitId, + virtual status_t decrypt(int uniqueId, sp<DecryptHandle>& decryptHandle, int decryptUnitId, const DrmBuffer* encBuffer, DrmBuffer** decBuffer, DrmBuffer* IV); virtual status_t finalizeDecryptUnit( - int uniqueId, DecryptHandle* decryptHandle, int decryptUnitId); + int uniqueId, sp<DecryptHandle>& decryptHandle, int decryptUnitId); - virtual ssize_t pread(int uniqueId, DecryptHandle* decryptHandle, + virtual ssize_t pread(int uniqueId, sp<DecryptHandle>& decryptHandle, void* buffer, ssize_t numBytes, off64_t offset); };
diff --git a/drm/drmserver/DrmManager.cpp b/drm/drmserver/DrmManager.cpp index bf04a89..afbcb39 100644 --- a/drm/drmserver/DrmManager.cpp +++ b/drm/drmserver/DrmManager.cpp
@@ -267,7 +267,7 @@ } status_t DrmManager::consumeRights( - int uniqueId, DecryptHandle* decryptHandle, int action, bool reserve) { + int uniqueId, sp<DecryptHandle>& decryptHandle, int action, bool reserve) { status_t result = DRM_ERROR_UNKNOWN; Mutex::Autolock _l(mDecryptLock); if (mDecryptSessionMap.indexOfKey(decryptHandle->decryptId) != NAME_NOT_FOUND) { @@ -278,7 +278,7 @@ } status_t DrmManager::setPlaybackStatus( - int uniqueId, DecryptHandle* decryptHandle, int playbackStatus, int64_t position) { + int uniqueId, sp<DecryptHandle>& decryptHandle, int playbackStatus, int64_t position) { status_t result = DRM_ERROR_UNKNOWN; Mutex::Autolock _l(mDecryptLock); if (mDecryptSessionMap.indexOfKey(decryptHandle->decryptId) != NAME_NOT_FOUND) { @@ -396,15 +396,15 @@ return DRM_NO_ERROR; } -DecryptHandle* DrmManager::openDecryptSession( +sp<DecryptHandle> DrmManager::openDecryptSession( int uniqueId, int fd, off64_t offset, off64_t length, const char* mime) { Mutex::Autolock _l(mDecryptLock); status_t result = DRM_ERROR_CANNOT_HANDLE; Vector<String8> plugInIdList = mPlugInManager.getPlugInIdList(); - DecryptHandle* handle = new DecryptHandle(); - if (NULL != handle) { + sp<DecryptHandle> handle = new DecryptHandle(); + if (NULL != handle.get()) { handle->decryptId = mDecryptSessionId + 1; for (size_t index = 0; index < plugInIdList.size(); index++) { @@ -420,19 +420,19 @@ } } if (DRM_NO_ERROR != result) { - delete handle; handle = NULL; + handle.clear(); } return handle; } -DecryptHandle* DrmManager::openDecryptSession( +sp<DecryptHandle> DrmManager::openDecryptSession( int uniqueId, const char* uri, const char* mime) { Mutex::Autolock _l(mDecryptLock); status_t result = DRM_ERROR_CANNOT_HANDLE; Vector<String8> plugInIdList = mPlugInManager.getPlugInIdList(); - DecryptHandle* handle = new DecryptHandle(); - if (NULL != handle) { + sp<DecryptHandle> handle = new DecryptHandle(); + if (NULL != handle.get()) { handle->decryptId = mDecryptSessionId + 1; for (size_t index = 0; index < plugInIdList.size(); index++) { @@ -448,20 +448,20 @@ } } if (DRM_NO_ERROR != result) { - delete handle; handle = NULL; + handle.clear(); ALOGV("DrmManager::openDecryptSession: no capable plug-in found"); } return handle; } -DecryptHandle* DrmManager::openDecryptSession( +sp<DecryptHandle> DrmManager::openDecryptSession( int uniqueId, const DrmBuffer& buf, const String8& mimeType) { Mutex::Autolock _l(mDecryptLock); status_t result = DRM_ERROR_CANNOT_HANDLE; Vector<String8> plugInIdList = mPlugInManager.getPlugInIdList(); - DecryptHandle* handle = new DecryptHandle(); - if (NULL != handle) { + sp<DecryptHandle> handle = new DecryptHandle(); + if (NULL != handle.get()) { handle->decryptId = mDecryptSessionId + 1; for (size_t index = 0; index < plugInIdList.size(); index++) { @@ -477,20 +477,19 @@ } } if (DRM_NO_ERROR != result) { - delete handle; - handle = NULL; + handle.clear(); ALOGV("DrmManager::openDecryptSession: no capable plug-in found"); } return handle; } -status_t DrmManager::closeDecryptSession(int uniqueId, DecryptHandle* decryptHandle) { +status_t DrmManager::closeDecryptSession(int uniqueId, sp<DecryptHandle>& decryptHandle) { Mutex::Autolock _l(mDecryptLock); status_t result = DRM_ERROR_UNKNOWN; if (mDecryptSessionMap.indexOfKey(decryptHandle->decryptId) != NAME_NOT_FOUND) { IDrmEngine* drmEngine = mDecryptSessionMap.valueFor(decryptHandle->decryptId); result = drmEngine->closeDecryptSession(uniqueId, decryptHandle); - if (DRM_NO_ERROR == result) { + if (DRM_NO_ERROR == result && NULL != decryptHandle.get()) { mDecryptSessionMap.removeItem(decryptHandle->decryptId); } } @@ -498,7 +497,8 @@ } status_t DrmManager::initializeDecryptUnit( - int uniqueId, DecryptHandle* decryptHandle, int decryptUnitId, const DrmBuffer* headerInfo) { + int uniqueId, sp<DecryptHandle>& decryptHandle, int decryptUnitId, + const DrmBuffer* headerInfo) { status_t result = DRM_ERROR_UNKNOWN; Mutex::Autolock _l(mDecryptLock); if (mDecryptSessionMap.indexOfKey(decryptHandle->decryptId) != NAME_NOT_FOUND) { @@ -508,7 +508,7 @@ return result; } -status_t DrmManager::decrypt(int uniqueId, DecryptHandle* decryptHandle, int decryptUnitId, +status_t DrmManager::decrypt(int uniqueId, sp<DecryptHandle>& decryptHandle, int decryptUnitId, const DrmBuffer* encBuffer, DrmBuffer** decBuffer, DrmBuffer* IV) { status_t result = DRM_ERROR_UNKNOWN; @@ -522,7 +522,7 @@ } status_t DrmManager::finalizeDecryptUnit( - int uniqueId, DecryptHandle* decryptHandle, int decryptUnitId) { + int uniqueId, sp<DecryptHandle>& decryptHandle, int decryptUnitId) { status_t result = DRM_ERROR_UNKNOWN; Mutex::Autolock _l(mDecryptLock); if (mDecryptSessionMap.indexOfKey(decryptHandle->decryptId) != NAME_NOT_FOUND) { @@ -532,7 +532,7 @@ return result; } -ssize_t DrmManager::pread(int uniqueId, DecryptHandle* decryptHandle, +ssize_t DrmManager::pread(int uniqueId, sp<DecryptHandle>& decryptHandle, void* buffer, ssize_t numBytes, off64_t offset) { ssize_t result = DECRYPT_FILE_ERROR;
diff --git a/drm/drmserver/DrmManager.h b/drm/drmserver/DrmManager.h index e7cdd36..26222bc 100644 --- a/drm/drmserver/DrmManager.h +++ b/drm/drmserver/DrmManager.h
@@ -89,10 +89,11 @@ int checkRightsStatus(int uniqueId, const String8& path, int action); - status_t consumeRights(int uniqueId, DecryptHandle* decryptHandle, int action, bool reserve); + status_t consumeRights(int uniqueId, sp<DecryptHandle>& decryptHandle, int action, + bool reserve); status_t setPlaybackStatus( - int uniqueId, DecryptHandle* decryptHandle, int playbackStatus, int64_t position); + int uniqueId, sp<DecryptHandle>& decryptHandle, int playbackStatus, int64_t position); bool validateAction( int uniqueId, const String8& path, int action, const ActionDescription& description); @@ -109,25 +110,26 @@ status_t getAllSupportInfo(int uniqueId, int* length, DrmSupportInfo** drmSupportInfoArray); - DecryptHandle* openDecryptSession( + sp<DecryptHandle> openDecryptSession( int uniqueId, int fd, off64_t offset, off64_t length, const char* mime); - DecryptHandle* openDecryptSession(int uniqueId, const char* uri, const char* mime); + sp<DecryptHandle> openDecryptSession(int uniqueId, const char* uri, const char* mime); - DecryptHandle* openDecryptSession(int uniqueId, const DrmBuffer& buf, + sp<DecryptHandle> openDecryptSession(int uniqueId, const DrmBuffer& buf, const String8& mimeType); - status_t closeDecryptSession(int uniqueId, DecryptHandle* decryptHandle); + status_t closeDecryptSession(int uniqueId, sp<DecryptHandle>& decryptHandle); - status_t initializeDecryptUnit(int uniqueId, DecryptHandle* decryptHandle, + status_t initializeDecryptUnit(int uniqueId, sp<DecryptHandle>& decryptHandle, int decryptUnitId, const DrmBuffer* headerInfo); - status_t decrypt(int uniqueId, DecryptHandle* decryptHandle, int decryptUnitId, + status_t decrypt(int uniqueId, sp<DecryptHandle>& decryptHandle, int decryptUnitId, const DrmBuffer* encBuffer, DrmBuffer** decBuffer, DrmBuffer* IV); - status_t finalizeDecryptUnit(int uniqueId, DecryptHandle* decryptHandle, int decryptUnitId); + status_t finalizeDecryptUnit(int uniqueId, sp<DecryptHandle>& decryptHandle, + int decryptUnitId); - ssize_t pread(int uniqueId, DecryptHandle* decryptHandle, + ssize_t pread(int uniqueId, sp<DecryptHandle>& decryptHandle, void* buffer, ssize_t numBytes, off64_t offset); void onInfo(const DrmInfoEvent& event);
diff --git a/drm/drmserver/DrmManagerService.cpp b/drm/drmserver/DrmManagerService.cpp index dad599b..2600a2c 100644 --- a/drm/drmserver/DrmManagerService.cpp +++ b/drm/drmserver/DrmManagerService.cpp
@@ -58,22 +58,26 @@ return drm_perm_labels[index]; } -bool DrmManagerService::selinuxIsProtectedCallAllowed(pid_t spid, drm_perm_t perm) { +bool DrmManagerService::selinuxIsProtectedCallAllowed(pid_t spid, const char* ssid, drm_perm_t perm) { if (selinux_enabled <= 0) { return true; } - char *sctx; + char *sctx = NULL; const char *selinux_class = "drmservice"; const char *str_perm = get_perm_label(perm); - if (getpidcon(spid, &sctx) != 0) { - ALOGE("SELinux: getpidcon(pid=%d) failed.\n", spid); - return false; + if (ssid == NULL) { + android_errorWriteLog(0x534e4554, "121035042"); + + if (getpidcon(spid, &sctx) != 0) { + ALOGE("SELinux: getpidcon(pid=%d) failed.\n", spid); + return false; + } } - bool allowed = (selinux_check_access(sctx, drmserver_context, selinux_class, - str_perm, NULL) == 0); + bool allowed = (selinux_check_access(ssid ? ssid : sctx, drmserver_context, + selinux_class, str_perm, NULL) == 0); freecon(sctx); return allowed; @@ -86,10 +90,11 @@ IPCThreadState* ipcState = IPCThreadState::self(); uid_t uid = ipcState->getCallingUid(); pid_t spid = ipcState->getCallingPid(); + const char* ssid = ipcState->getCallingSid(); for (unsigned int i = 0; i < trustedUids.size(); ++i) { if (trustedUids[i] == uid) { - return selinuxIsProtectedCallAllowed(spid, perm); + return selinuxIsProtectedCallAllowed(spid, ssid, perm); } } return false; @@ -97,7 +102,9 @@ void DrmManagerService::instantiate() { ALOGV("instantiate"); - defaultServiceManager()->addService(String16("drm.drmManager"), new DrmManagerService()); + sp<DrmManagerService> service = new DrmManagerService(); + service->setRequestingSid(true); + defaultServiceManager()->addService(String16("drm.drmManager"), service); if (0 >= trustedUids.size()) { // TODO @@ -206,7 +213,7 @@ } status_t DrmManagerService::consumeRights( - int uniqueId, DecryptHandle* decryptHandle, int action, bool reserve) { + int uniqueId, sp<DecryptHandle>& decryptHandle, int action, bool reserve) { ALOGV("Entering consumeRights"); if (!isProtectedCallAllowed(CONSUME_RIGHTS)) { return DRM_ERROR_NO_PERMISSION; @@ -215,7 +222,7 @@ } status_t DrmManagerService::setPlaybackStatus( - int uniqueId, DecryptHandle* decryptHandle, int playbackStatus, int64_t position) { + int uniqueId, sp<DecryptHandle>& decryptHandle, int playbackStatus, int64_t position) { ALOGV("Entering setPlaybackStatus"); if (!isProtectedCallAllowed(SET_PLAYBACK_STATUS)) { return DRM_ERROR_NO_PERMISSION; @@ -262,7 +269,7 @@ return mDrmManager->getAllSupportInfo(uniqueId, length, drmSupportInfoArray); } -DecryptHandle* DrmManagerService::openDecryptSession( +sp<DecryptHandle> DrmManagerService::openDecryptSession( int uniqueId, int fd, off64_t offset, off64_t length, const char* mime) { ALOGV("Entering DrmManagerService::openDecryptSession"); if (isProtectedCallAllowed(OPEN_DECRYPT_SESSION)) { @@ -272,7 +279,7 @@ return NULL; } -DecryptHandle* DrmManagerService::openDecryptSession( +sp<DecryptHandle> DrmManagerService::openDecryptSession( int uniqueId, const char* uri, const char* mime) { ALOGV("Entering DrmManagerService::openDecryptSession with uri"); if (isProtectedCallAllowed(OPEN_DECRYPT_SESSION)) { @@ -282,7 +289,7 @@ return NULL; } -DecryptHandle* DrmManagerService::openDecryptSession( +sp<DecryptHandle> DrmManagerService::openDecryptSession( int uniqueId, const DrmBuffer& buf, const String8& mimeType) { ALOGV("Entering DrmManagerService::openDecryptSession for streaming"); if (isProtectedCallAllowed(OPEN_DECRYPT_SESSION)) { @@ -292,7 +299,7 @@ return NULL; } -status_t DrmManagerService::closeDecryptSession(int uniqueId, DecryptHandle* decryptHandle) { +status_t DrmManagerService::closeDecryptSession(int uniqueId, sp<DecryptHandle>& decryptHandle) { ALOGV("Entering closeDecryptSession"); if (!isProtectedCallAllowed(CLOSE_DECRYPT_SESSION)) { return DRM_ERROR_NO_PERMISSION; @@ -300,7 +307,7 @@ return mDrmManager->closeDecryptSession(uniqueId, decryptHandle); } -status_t DrmManagerService::initializeDecryptUnit(int uniqueId, DecryptHandle* decryptHandle, +status_t DrmManagerService::initializeDecryptUnit(int uniqueId, sp<DecryptHandle>& decryptHandle, int decryptUnitId, const DrmBuffer* headerInfo) { ALOGV("Entering initializeDecryptUnit"); if (!isProtectedCallAllowed(INITIALIZE_DECRYPT_UNIT)) { @@ -310,7 +317,7 @@ } status_t DrmManagerService::decrypt( - int uniqueId, DecryptHandle* decryptHandle, int decryptUnitId, + int uniqueId, sp<DecryptHandle>& decryptHandle, int decryptUnitId, const DrmBuffer* encBuffer, DrmBuffer** decBuffer, DrmBuffer* IV) { ALOGV("Entering decrypt"); if (!isProtectedCallAllowed(DECRYPT)) { @@ -320,7 +327,7 @@ } status_t DrmManagerService::finalizeDecryptUnit( - int uniqueId, DecryptHandle* decryptHandle, int decryptUnitId) { + int uniqueId, sp<DecryptHandle>& decryptHandle, int decryptUnitId) { ALOGV("Entering finalizeDecryptUnit"); if (!isProtectedCallAllowed(FINALIZE_DECRYPT_UNIT)) { return DRM_ERROR_NO_PERMISSION; @@ -328,7 +335,7 @@ return mDrmManager->finalizeDecryptUnit(uniqueId, decryptHandle, decryptUnitId); } -ssize_t DrmManagerService::pread(int uniqueId, DecryptHandle* decryptHandle, +ssize_t DrmManagerService::pread(int uniqueId, sp<DecryptHandle>& decryptHandle, void* buffer, ssize_t numBytes, off64_t offset) { ALOGV("Entering pread"); if (!isProtectedCallAllowed(PREAD)) {
diff --git a/drm/drmserver/DrmManagerService.h b/drm/drmserver/DrmManagerService.h index 45cee2e..2e27a3c 100644 --- a/drm/drmserver/DrmManagerService.h +++ b/drm/drmserver/DrmManagerService.h
@@ -60,7 +60,7 @@ static const char *get_perm_label(drm_perm_t perm); - static bool selinuxIsProtectedCallAllowed(pid_t spid, drm_perm_t perm); + static bool selinuxIsProtectedCallAllowed(pid_t spid, const char* ssid, drm_perm_t perm); static bool isProtectedCallAllowed(drm_perm_t perm); @@ -95,10 +95,11 @@ int checkRightsStatus(int uniqueId, const String8& path,int action); - status_t consumeRights(int uniqueId, DecryptHandle* decryptHandle, int action, bool reserve); + status_t consumeRights(int uniqueId, sp<DecryptHandle>& decryptHandle, int action, + bool reserve); status_t setPlaybackStatus( - int uniqueId, DecryptHandle* decryptHandle, int playbackStatus, int64_t position); + int uniqueId, sp<DecryptHandle>& decryptHandle, int playbackStatus, int64_t position); bool validateAction(int uniqueId, const String8& path, int action, const ActionDescription& description); @@ -115,26 +116,27 @@ status_t getAllSupportInfo(int uniqueId, int* length, DrmSupportInfo** drmSupportInfoArray); - DecryptHandle* openDecryptSession( + sp<DecryptHandle> openDecryptSession( int uniqueId, int fd, off64_t offset, off64_t length, const char *mime); - DecryptHandle* openDecryptSession( + sp<DecryptHandle> openDecryptSession( int uniqueId, const char* uri, const char* mime); - DecryptHandle* openDecryptSession(int uniqueId, const DrmBuffer& buf, + sp<DecryptHandle> openDecryptSession(int uniqueId, const DrmBuffer& buf, const String8& mimeType); - status_t closeDecryptSession(int uniqueId, DecryptHandle* decryptHandle); + status_t closeDecryptSession(int uniqueId, sp<DecryptHandle>& decryptHandle); - status_t initializeDecryptUnit(int uniqueId, DecryptHandle* decryptHandle, + status_t initializeDecryptUnit(int uniqueId, sp<DecryptHandle>& decryptHandle, int decryptUnitId, const DrmBuffer* headerInfo); - status_t decrypt(int uniqueId, DecryptHandle* decryptHandle, int decryptUnitId, + status_t decrypt(int uniqueId, sp<DecryptHandle>& decryptHandle, int decryptUnitId, const DrmBuffer* encBuffer, DrmBuffer** decBuffer, DrmBuffer* IV); - status_t finalizeDecryptUnit(int uniqueId, DecryptHandle* decryptHandle, int decryptUnitId); + status_t finalizeDecryptUnit(int uniqueId, sp<DecryptHandle>& decryptHandle, + int decryptUnitId); - ssize_t pread(int uniqueId, DecryptHandle* decryptHandle, + ssize_t pread(int uniqueId, sp<DecryptHandle>& decryptHandle, void* buffer, ssize_t numBytes, off64_t offset); virtual status_t dump(int fd, const Vector<String16>& args);
diff --git a/drm/libdrmframework/Android.bp b/drm/libdrmframework/Android.bp index 43ba72b..940c17d 100644 --- a/drm/libdrmframework/Android.bp +++ b/drm/libdrmframework/Android.bp
@@ -39,4 +39,3 @@ cflags: ["-Werror"], } -subdirs = ["plugins/*"]
diff --git a/drm/libdrmframework/DrmManagerClientImpl.cpp b/drm/libdrmframework/DrmManagerClientImpl.cpp index c047eb1..b0a441b 100644 --- a/drm/libdrmframework/DrmManagerClientImpl.cpp +++ b/drm/libdrmframework/DrmManagerClientImpl.cpp
@@ -183,7 +183,7 @@ status_t status = DRM_ERROR_UNKNOWN; if (NULL != decryptHandle.get()) { status = getDrmManagerService()->consumeRights( - uniqueId, decryptHandle.get(), action, reserve); + uniqueId, decryptHandle, action, reserve); } return status; } @@ -194,7 +194,7 @@ status_t status = DRM_ERROR_UNKNOWN; if (NULL != decryptHandle.get()) { status = getDrmManagerService()->setPlaybackStatus( - uniqueId, decryptHandle.get(), playbackStatus, position); + uniqueId, decryptHandle, playbackStatus, position); } return status; } @@ -267,7 +267,7 @@ sp<DecryptHandle> DrmManagerClientImpl::openDecryptSession( int uniqueId, const char* uri, const char* mime) { - DecryptHandle* handle = NULL; + sp<DecryptHandle> handle; if (NULL != uri) { handle = getDrmManagerService()->openDecryptSession(uniqueId, uri, mime); } @@ -284,7 +284,7 @@ status_t status = DRM_ERROR_UNKNOWN; if (NULL != decryptHandle.get()) { status = getDrmManagerService()->closeDecryptSession( - uniqueId, decryptHandle.get()); + uniqueId, decryptHandle); } return status; } @@ -295,7 +295,7 @@ status_t status = DRM_ERROR_UNKNOWN; if ((NULL != decryptHandle.get()) && (NULL != headerInfo)) { status = getDrmManagerService()->initializeDecryptUnit( - uniqueId, decryptHandle.get(), decryptUnitId, headerInfo); + uniqueId, decryptHandle, decryptUnitId, headerInfo); } return status; } @@ -308,7 +308,7 @@ if ((NULL != decryptHandle.get()) && (NULL != encBuffer) && (NULL != decBuffer) && (NULL != *decBuffer)) { status = getDrmManagerService()->decrypt( - uniqueId, decryptHandle.get(), decryptUnitId, + uniqueId, decryptHandle, decryptUnitId, encBuffer, decBuffer, IV); } return status; @@ -319,7 +319,7 @@ status_t status = DRM_ERROR_UNKNOWN; if (NULL != decryptHandle.get()) { status = getDrmManagerService()->finalizeDecryptUnit( - uniqueId, decryptHandle.get(), decryptUnitId); + uniqueId, decryptHandle, decryptUnitId); } return status; } @@ -329,7 +329,7 @@ ssize_t retCode = INVALID_VALUE; if ((NULL != decryptHandle.get()) && (NULL != buffer) && (0 < numBytes)) { retCode = getDrmManagerService()->pread( - uniqueId, decryptHandle.get(), buffer, numBytes, offset); + uniqueId, decryptHandle, buffer, numBytes, offset); } return retCode; }
diff --git a/drm/libdrmframework/plugins/common/Android.bp b/drm/libdrmframework/plugins/common/Android.bp deleted file mode 100644 index 213e57f..0000000 --- a/drm/libdrmframework/plugins/common/Android.bp +++ /dev/null
@@ -1 +0,0 @@ -subdirs = ["util"]
diff --git a/drm/libdrmframework/plugins/forward-lock/Android.bp b/drm/libdrmframework/plugins/forward-lock/Android.bp deleted file mode 100644 index f884c14..0000000 --- a/drm/libdrmframework/plugins/forward-lock/Android.bp +++ /dev/null
@@ -1,4 +0,0 @@ -subdirs = [ - "FwdLockEngine", - "internal-format", -]
diff --git a/drm/libdrmframework/plugins/forward-lock/FwdLockEngine/Android.bp b/drm/libdrmframework/plugins/forward-lock/FwdLockEngine/Android.bp index 28a78aa..bb9d7ec 100644 --- a/drm/libdrmframework/plugins/forward-lock/FwdLockEngine/Android.bp +++ b/drm/libdrmframework/plugins/forward-lock/FwdLockEngine/Android.bp
@@ -29,8 +29,7 @@ srcs: ["src/FwdLockEngine.cpp"], shared_libs: [ - "libicui18n", - "libicuuc", + "libandroidicu", "libutils", "liblog", "libdl",
diff --git a/drm/libdrmframework/plugins/forward-lock/FwdLockEngine/include/FwdLockEngine.h b/drm/libdrmframework/plugins/forward-lock/FwdLockEngine/include/FwdLockEngine.h index a571b3a..b62ddb9 100644 --- a/drm/libdrmframework/plugins/forward-lock/FwdLockEngine/include/FwdLockEngine.h +++ b/drm/libdrmframework/plugins/forward-lock/FwdLockEngine/include/FwdLockEngine.h
@@ -198,7 +198,7 @@ * Returns DRM_NO_ERROR for success, DRM_ERROR_UNKNOWN for failure */ status_t onConsumeRights(int uniqueId, - DecryptHandle* decryptHandle, + sp<DecryptHandle>& decryptHandle, int action, bool reserve); @@ -215,12 +215,12 @@ */ #ifdef USE_64BIT_DRM_API status_t onSetPlaybackStatus(int uniqueId, - DecryptHandle* decryptHandle, + sp<DecryptHandle>& decryptHandle, int playbackStatus, int64_t position); #else status_t onSetPlaybackStatus(int uniqueId, - DecryptHandle* decryptHandle, + sp<DecryptHandle>& decryptHandle, int playbackStatus, int position); #endif @@ -330,11 +330,11 @@ */ #ifdef USE_64BIT_DRM_API status_t onOpenDecryptSession(int uniqueId, - DecryptHandle* decryptHandle, + sp<DecryptHandle>& decryptHandle, int fd, off64_t offset, off64_t length); #else status_t onOpenDecryptSession(int uniqueId, - DecryptHandle* decryptHandle, + sp<DecryptHandle>& decryptHandle, int fd, int offset, int length); #endif @@ -348,7 +348,7 @@ * DRM_ERROR_CANNOT_HANDLE for failure and DRM_NO_ERROR for success */ status_t onOpenDecryptSession(int uniqueId, - DecryptHandle* decryptHandle, + sp<DecryptHandle>& decryptHandle, const char* uri); /** @@ -360,7 +360,7 @@ * Returns DRM_NO_ERROR for success, DRM_ERROR_UNKNOWN for failure */ status_t onCloseDecryptSession(int uniqueId, - DecryptHandle* decryptHandle); + sp<DecryptHandle>& decryptHandle); /** * Initialize decryption for the given unit of the protected content. @@ -373,7 +373,7 @@ * DRM_ERROR_CANNOT_HANDLE for failure and DRM_NO_ERROR for success */ status_t onInitializeDecryptUnit(int uniqueId, - DecryptHandle* decryptHandle, + sp<DecryptHandle>& decryptHandle, int decryptUnitId, const DrmBuffer* headerInfo); @@ -394,7 +394,7 @@ * DRM_ERROR_DECRYPT for failure. */ status_t onDecrypt(int uniqueId, - DecryptHandle* decryptHandle, + sp<DecryptHandle>& decryptHandle, int decryptUnitId, const DrmBuffer* encBuffer, DrmBuffer** decBuffer); @@ -416,7 +416,7 @@ * DRM_ERROR_SESSION_NOT_OPENED, DRM_ERROR_DECRYPT_UNIT_NOT_INITIALIZED, * DRM_ERROR_DECRYPT for failure. */ -status_t onDecrypt(int uniqueId, DecryptHandle* decryptHandle, +status_t onDecrypt(int uniqueId, sp<DecryptHandle>& decryptHandle, int decryptUnitId, const DrmBuffer* encBuffer, DrmBuffer** decBuffer, DrmBuffer* IV); @@ -430,7 +430,7 @@ * DRM_ERROR_CANNOT_HANDLE for failure and DRM_NO_ERROR for success */ status_t onFinalizeDecryptUnit(int uniqueId, - DecryptHandle* decryptHandle, + sp<DecryptHandle>& decryptHandle, int decryptUnitId); /** @@ -445,7 +445,7 @@ * @retval -1 Failure. */ ssize_t onRead(int uniqueId, - DecryptHandle* decryptHandle, + sp<DecryptHandle>& decryptHandle, void* pBuffer, int numBytes); @@ -463,12 +463,12 @@ */ #ifdef USE_64BIT_DRM_API off64_t onLseek(int uniqueId, - DecryptHandle* decryptHandle, + sp<DecryptHandle>& decryptHandle, off64_t offset, int whence); #else off_t onLseek(int uniqueId, - DecryptHandle* decryptHandle, + sp<DecryptHandle>& decryptHandle, off_t offset, int whence); #endif @@ -486,13 +486,13 @@ */ #ifdef USE_64BIT_DRM_API ssize_t onPread(int uniqueId, - DecryptHandle* decryptHandle, + sp<DecryptHandle>& decryptHandle, void* buffer, ssize_t numBytes, off64_t offset); #else ssize_t onPread(int uniqueId, - DecryptHandle* decryptHandle, + sp<DecryptHandle>& decryptHandle, void* buffer, ssize_t numBytes, off_t offset);
diff --git a/drm/libdrmframework/plugins/forward-lock/FwdLockEngine/src/FwdLockEngine.cpp b/drm/libdrmframework/plugins/forward-lock/FwdLockEngine/src/FwdLockEngine.cpp index 73eea89..769de0c 100644 --- a/drm/libdrmframework/plugins/forward-lock/FwdLockEngine/src/FwdLockEngine.cpp +++ b/drm/libdrmframework/plugins/forward-lock/FwdLockEngine/src/FwdLockEngine.cpp
@@ -294,7 +294,7 @@ } status_t FwdLockEngine::onConsumeRights(int /* uniqueId */, - DecryptHandle* /* decryptHandle */, + sp<DecryptHandle>& /* decryptHandle */, int /* action */, bool /* reserve */) { // No rights consumption @@ -372,11 +372,13 @@ } #ifdef USE_64BIT_DRM_API -status_t FwdLockEngine::onSetPlaybackStatus(int /* uniqueId */, DecryptHandle* /* decryptHandle */, - int /* playbackStatus */, int64_t /* position */) { +status_t FwdLockEngine::onSetPlaybackStatus(int /* uniqueId */, + sp<DecryptHandle>& /* decryptHandle */, int /* playbackStatus */, + int64_t /* position */) { #else -status_t FwdLockEngine::onSetPlaybackStatus(int /* uniqueId */, DecryptHandle* /* decryptHandle */, - int /* playbackStatus */, int /* position */) { +status_t FwdLockEngine::onSetPlaybackStatus(int /* uniqueId */, + sp<DecryptHandle>& /* decryptHandle */, + int /* playbackStatus */, int /* position */) { #endif // Not used LOG_VERBOSE("FwdLockEngine::onSetPlaybackStatus"); @@ -470,13 +472,13 @@ #ifdef USE_64BIT_DRM_API status_t FwdLockEngine::onOpenDecryptSession(int /* uniqueId */, - DecryptHandle* decryptHandle, + sp<DecryptHandle>& decryptHandle, int fd, off64_t offset, off64_t /* length */) { #else status_t FwdLockEngine::onOpenDecryptSession(int /* uniqueId */, - DecryptHandle* decryptHandle, + sp<DecryptHandle>& decryptHandle, int fd, int offset, int /* length */) { @@ -487,7 +489,7 @@ LOG_VERBOSE("FwdLockEngine::onOpenDecryptSession"); if ((-1 < fd) && - (NULL != decryptHandle) && + (NULL != decryptHandle.get()) && (!decodeSessionMap.isCreated(decryptHandle->decryptId))) { fileDesc = dup(fd); } else { @@ -533,12 +535,12 @@ } status_t FwdLockEngine::onOpenDecryptSession(int uniqueId, - DecryptHandle* decryptHandle, + sp<DecryptHandle>& decryptHandle, const char* uri) { status_t result = DRM_ERROR_CANNOT_HANDLE; const char fileTag [] = "file://"; - if (NULL != decryptHandle && NULL != uri && strlen(uri) > sizeof(fileTag)) { + if (NULL != decryptHandle.get() && NULL != uri && strlen(uri) > sizeof(fileTag)) { String8 uriTag = String8(uri); uriTag.toLower(); @@ -562,11 +564,11 @@ } status_t FwdLockEngine::onCloseDecryptSession(int /* uniqueId */, - DecryptHandle* decryptHandle) { + sp<DecryptHandle>& decryptHandle) { status_t result = DRM_ERROR_UNKNOWN; LOG_VERBOSE("FwdLockEngine::onCloseDecryptSession"); - if (NULL != decryptHandle && decodeSessionMap.isCreated(decryptHandle->decryptId)) { + if (NULL != decryptHandle.get() && decodeSessionMap.isCreated(decryptHandle->decryptId)) { DecodeSession* session = decodeSessionMap.getValue(decryptHandle->decryptId); if (NULL != session && session->fileDesc > -1) { FwdLockFile_detach(session->fileDesc); @@ -576,7 +578,7 @@ } } - if (NULL != decryptHandle) { + if (NULL != decryptHandle.get()) { if (NULL != decryptHandle->decryptInfo) { delete decryptHandle->decryptInfo; decryptHandle->decryptInfo = NULL; @@ -584,9 +586,7 @@ decryptHandle->copyControlVector.clear(); decryptHandle->extendedData.clear(); - - delete decryptHandle; - decryptHandle = NULL; + decryptHandle.clear(); } LOG_VERBOSE("FwdLockEngine::onCloseDecryptSession Exit"); @@ -594,7 +594,7 @@ } status_t FwdLockEngine::onInitializeDecryptUnit(int /* uniqueId */, - DecryptHandle* /* decryptHandle */, + sp<DecryptHandle>& /* decryptHandle */, int /* decryptUnitId */, const DrmBuffer* /* headerInfo */) { ALOGE("FwdLockEngine::onInitializeDecryptUnit is not supported for this DRM scheme"); @@ -603,7 +603,7 @@ status_t FwdLockEngine::onDecrypt( int /* uniqueId */, - DecryptHandle* /* decryptHandle */, + sp<DecryptHandle>& /* decryptHandle */, int /* decryptUnitId */, const DrmBuffer* /* encBuffer */, DrmBuffer** /* decBuffer */, @@ -613,7 +613,7 @@ } status_t FwdLockEngine::onDecrypt(int /* uniqueId */, - DecryptHandle* /* decryptHandle */, + sp<DecryptHandle>& /* decryptHandle */, int /* decryptUnitId */, const DrmBuffer* /* encBuffer */, DrmBuffer** /* decBuffer */) { @@ -622,19 +622,19 @@ } status_t FwdLockEngine::onFinalizeDecryptUnit(int /* uniqueId */, - DecryptHandle* /* decryptHandle */, + sp<DecryptHandle>& /* decryptHandle */, int /* decryptUnitId */) { ALOGE("FwdLockEngine::onFinalizeDecryptUnit is not supported for this DRM scheme"); return DRM_ERROR_UNKNOWN; } ssize_t FwdLockEngine::onRead(int /* uniqueId */, - DecryptHandle* decryptHandle, + sp<DecryptHandle>& decryptHandle, void* buffer, int numBytes) { ssize_t size = -1; - if (NULL != decryptHandle && + if (NULL != decryptHandle.get() && decodeSessionMap.isCreated(decryptHandle->decryptId) && NULL != buffer && numBytes > -1) { @@ -654,15 +654,15 @@ } #ifdef USE_64BIT_DRM_API -off64_t FwdLockEngine::onLseek(int /* uniqueId */, DecryptHandle* decryptHandle, +off64_t FwdLockEngine::onLseek(int /* uniqueId */, sp<DecryptHandle>& decryptHandle, off64_t offset, int whence) { #else -off_t FwdLockEngine::onLseek(int /* uniqueId */, DecryptHandle* decryptHandle, +off_t FwdLockEngine::onLseek(int /* uniqueId */, sp<DecryptHandle>& decryptHandle, off_t offset, int whence) { #endif off_t offval = -1; - if (NULL != decryptHandle && decodeSessionMap.isCreated(decryptHandle->decryptId)) { + if (NULL != decryptHandle.get() && decodeSessionMap.isCreated(decryptHandle->decryptId)) { DecodeSession* session = decodeSessionMap.getValue(decryptHandle->decryptId); if (NULL != session && session->fileDesc > -1) { offval = FwdLockFile_lseek(session->fileDesc, offset, whence); @@ -675,13 +675,13 @@ #ifdef USE_64BIT_DRM_API ssize_t FwdLockEngine::onPread(int uniqueId, - DecryptHandle* decryptHandle, + sp<DecryptHandle>& decryptHandle, void* buffer, ssize_t numBytes, off64_t offset) { #else ssize_t FwdLockEngine::onPread(int uniqueId, - DecryptHandle* decryptHandle, + sp<DecryptHandle>& decryptHandle, void* buffer, ssize_t numBytes, off_t offset) { @@ -690,7 +690,7 @@ DecodeSession* decoderSession = NULL; - if ((NULL != decryptHandle) && + if ((NULL != decryptHandle.get()) && (NULL != (decoderSession = decodeSessionMap.getValue(decryptHandle->decryptId))) && (NULL != buffer) && (numBytes > -1) &&
diff --git a/drm/libdrmframework/plugins/forward-lock/internal-format/Android.bp b/drm/libdrmframework/plugins/forward-lock/internal-format/Android.bp deleted file mode 100644 index 9f58e26..0000000 --- a/drm/libdrmframework/plugins/forward-lock/internal-format/Android.bp +++ /dev/null
@@ -1,5 +0,0 @@ -subdirs = [ - "common", - "converter", - "decoder", -]
diff --git a/drm/libdrmframework/plugins/passthru/include/DrmPassthruPlugIn.h b/drm/libdrmframework/plugins/passthru/include/DrmPassthruPlugIn.h index 7b66dc7..4ab5272 100644 --- a/drm/libdrmframework/plugins/passthru/include/DrmPassthruPlugIn.h +++ b/drm/libdrmframework/plugins/passthru/include/DrmPassthruPlugIn.h
@@ -53,10 +53,11 @@ int onCheckRightsStatus(int uniqueId, const String8& path, int action); - status_t onConsumeRights(int uniqueId, DecryptHandle* decryptHandle, int action, bool reserve); + status_t onConsumeRights(int uniqueId, sp<DecryptHandle>& decryptHandle, + int action, bool reserve); status_t onSetPlaybackStatus( - int uniqueId, DecryptHandle* decryptHandle, int playbackStatus, int64_t position); + int uniqueId, sp<DecryptHandle>& decryptHandle, int playbackStatus, int64_t position); bool onValidateAction( int uniqueId, const String8& path, int action, const ActionDescription& description); @@ -74,26 +75,25 @@ DrmSupportInfo* onGetSupportInfo(int uniqueId); status_t onOpenDecryptSession( - int uniqueId, DecryptHandle* decryptHandle, int fd, off64_t offset, off64_t length); + int uniqueId, sp<DecryptHandle>& decryptHandle, int fd, off64_t offset, + off64_t length); status_t onOpenDecryptSession( - int uniqueId, DecryptHandle* decryptHandle, const char* uri); + int uniqueId, sp<DecryptHandle>& decryptHandle, const char* uri); - status_t onCloseDecryptSession(int uniqueId, DecryptHandle* decryptHandle); + status_t onCloseDecryptSession(int uniqueId, sp<DecryptHandle>& decryptHandle); - status_t onInitializeDecryptUnit(int uniqueId, DecryptHandle* decryptHandle, + status_t onInitializeDecryptUnit(int uniqueId, sp<DecryptHandle>& decryptHandle, int decryptUnitId, const DrmBuffer* headerInfo); - status_t onDecrypt(int uniqueId, DecryptHandle* decryptHandle, int decryptUnitId, + status_t onDecrypt(int uniqueId, sp<DecryptHandle>& decryptHandle, int decryptUnitId, const DrmBuffer* encBuffer, DrmBuffer** decBuffer, DrmBuffer* IV); - status_t onFinalizeDecryptUnit(int uniqueId, DecryptHandle* decryptHandle, int decryptUnitId); + status_t onFinalizeDecryptUnit(int uniqueId, sp<DecryptHandle>& decryptHandle, + int decryptUnitId); - ssize_t onPread(int uniqueId, DecryptHandle* decryptHandle, + ssize_t onPread(int uniqueId, sp<DecryptHandle>& decryptHandle, void* buffer, ssize_t numBytes, off64_t offset); - -private: - DecryptHandle* openDecryptSessionImpl(); }; };
diff --git a/drm/libdrmframework/plugins/passthru/src/DrmPassthruPlugIn.cpp b/drm/libdrmframework/plugins/passthru/src/DrmPassthruPlugIn.cpp index d7f2d28..0fa3478 100644 --- a/drm/libdrmframework/plugins/passthru/src/DrmPassthruPlugIn.cpp +++ b/drm/libdrmframework/plugins/passthru/src/DrmPassthruPlugIn.cpp
@@ -183,13 +183,13 @@ } status_t DrmPassthruPlugIn::onConsumeRights(int uniqueId, - DecryptHandle* /*decryptHandle*/, int /*action*/, bool /*reserve*/) { + sp<DecryptHandle>& /*decryptHandle*/, int /*action*/, bool /*reserve*/) { ALOGV("DrmPassthruPlugIn::onConsumeRights() : %d", uniqueId); return DRM_NO_ERROR; } status_t DrmPassthruPlugIn::onSetPlaybackStatus(int uniqueId, - DecryptHandle* /*decryptHandle*/, int /*playbackStatus*/, int64_t /*position*/) { + sp<DecryptHandle>& /*decryptHandle*/, int /*playbackStatus*/, int64_t /*position*/) { ALOGV("DrmPassthruPlugIn::onSetPlaybackStatus() : %d", uniqueId); return DRM_NO_ERROR; } @@ -236,7 +236,8 @@ } status_t DrmPassthruPlugIn::onOpenDecryptSession( - int uniqueId, DecryptHandle* decryptHandle, int /*fd*/, off64_t /*offset*/, off64_t /*length*/) { + int uniqueId, sp<DecryptHandle>& decryptHandle, int /*fd*/, off64_t /*offset*/, + off64_t /*length*/) { ALOGV("DrmPassthruPlugIn::onOpenDecryptSession() : %d", uniqueId); #ifdef ENABLE_PASSTHRU_DECRYPTION @@ -246,36 +247,38 @@ decryptHandle->decryptInfo = NULL; return DRM_NO_ERROR; #else - (void)(decryptHandle); // unused + (void)(decryptHandle.get()); // unused #endif return DRM_ERROR_CANNOT_HANDLE; } status_t DrmPassthruPlugIn::onOpenDecryptSession( - int /*uniqueId*/, DecryptHandle* /*decryptHandle*/, const char* /*uri*/) { + int /*uniqueId*/, sp<DecryptHandle>& /*decryptHandle*/, const char* /*uri*/) { return DRM_ERROR_CANNOT_HANDLE; } -status_t DrmPassthruPlugIn::onCloseDecryptSession(int uniqueId, DecryptHandle* decryptHandle) { +status_t DrmPassthruPlugIn::onCloseDecryptSession(int uniqueId, sp<DecryptHandle>& decryptHandle) { ALOGV("DrmPassthruPlugIn::onCloseDecryptSession() : %d", uniqueId); - if (NULL != decryptHandle) { + if (NULL != decryptHandle.get()) { if (NULL != decryptHandle->decryptInfo) { delete decryptHandle->decryptInfo; decryptHandle->decryptInfo = NULL; } - delete decryptHandle; decryptHandle = NULL; + decryptHandle.clear(); } return DRM_NO_ERROR; } -status_t DrmPassthruPlugIn::onInitializeDecryptUnit(int uniqueId, DecryptHandle* /*decryptHandle*/, - int /*decryptUnitId*/, const DrmBuffer* /*headerInfo*/) { +status_t DrmPassthruPlugIn::onInitializeDecryptUnit(int uniqueId, + sp<DecryptHandle>& /*decryptHandle*/, + int /*decryptUnitId*/, const DrmBuffer* /*headerInfo*/) { ALOGV("DrmPassthruPlugIn::onInitializeDecryptUnit() : %d", uniqueId); return DRM_NO_ERROR; } -status_t DrmPassthruPlugIn::onDecrypt(int uniqueId, DecryptHandle* /*decryptHandle*/, - int /*decryptUnitId*/, const DrmBuffer* encBuffer, DrmBuffer** decBuffer, DrmBuffer* /*IV*/) { +status_t DrmPassthruPlugIn::onDecrypt(int uniqueId, sp<DecryptHandle>& /*decryptHandle*/, + int /*decryptUnitId*/, const DrmBuffer* encBuffer, DrmBuffer** decBuffer, + DrmBuffer* /*IV*/) { ALOGV("DrmPassthruPlugIn::onDecrypt() : %d", uniqueId); /** * As a workaround implementation passthru would copy the given @@ -296,12 +299,12 @@ } status_t DrmPassthruPlugIn::onFinalizeDecryptUnit( - int uniqueId, DecryptHandle* /*decryptHandle*/, int /*decryptUnitId*/) { + int uniqueId, sp<DecryptHandle>& /*decryptHandle*/, int /*decryptUnitId*/) { ALOGV("DrmPassthruPlugIn::onFinalizeDecryptUnit() : %d", uniqueId); return DRM_NO_ERROR; } -ssize_t DrmPassthruPlugIn::onPread(int uniqueId, DecryptHandle* /*decryptHandle*/, +ssize_t DrmPassthruPlugIn::onPread(int uniqueId, sp<DecryptHandle>& /*decryptHandle*/, void* /*buffer*/, ssize_t /*numBytes*/, off64_t /*offset*/) { ALOGV("DrmPassthruPlugIn::onPread() : %d", uniqueId); return 0;
diff --git a/drm/libmediadrm/Android.bp b/drm/libmediadrm/Android.bp index 4991e50..d6db1d4 100644 --- a/drm/libmediadrm/Android.bp +++ b/drm/libmediadrm/Android.bp
@@ -27,11 +27,11 @@ "libmediadrmmetrics_lite", "libmediametrics", "libmediautils", - "libprotobuf-cpp-lite", "libstagefright_foundation", "libutils", "android.hardware.drm@1.0", "android.hardware.drm@1.1", + "android.hardware.drm@1.2", "libhidlallocatorutils", "libhidlbase", "libhidltransport", @@ -59,20 +59,18 @@ shared_libs: [ "android.hardware.drm@1.0", "android.hardware.drm@1.1", - "libbase", + "android.hardware.drm@1.2", "libbinder", "libhidlbase", "liblog", "libmediametrics", "libprotobuf-cpp-lite", - "libstagefright_foundation", "libutils", ], cflags: [ // Suppress unused parameter and no error options. These cause problems // with the when using the map type in a proto definition. "-Wno-unused-parameter", - "-Wno-error", ], } @@ -92,6 +90,7 @@ shared_libs: [ "android.hardware.drm@1.0", "android.hardware.drm@1.1", + "android.hardware.drm@1.2", "libbase", "libbinder", "libhidlbase", @@ -105,7 +104,6 @@ // Suppress unused parameter and no error options. These cause problems // when using the map type in a proto definition. "-Wno-unused-parameter", - "-Wno-error", ], }
diff --git a/drm/libmediadrm/CryptoHal.cpp b/drm/libmediadrm/CryptoHal.cpp index 3035c5a..d62ccd6 100644 --- a/drm/libmediadrm/CryptoHal.cpp +++ b/drm/libmediadrm/CryptoHal.cpp
@@ -30,16 +30,16 @@ #include <media/stagefright/MediaErrors.h> #include <mediadrm/CryptoHal.h> +using drm::V1_0::BufferType; +using drm::V1_0::DestinationBuffer; +using drm::V1_0::ICryptoFactory; +using drm::V1_0::ICryptoPlugin; +using drm::V1_0::Mode; +using drm::V1_0::Pattern; +using drm::V1_0::SharedBuffer; +using drm::V1_0::Status; +using drm::V1_0::SubSample; -using ::android::hardware::drm::V1_0::BufferType; -using ::android::hardware::drm::V1_0::DestinationBuffer; -using ::android::hardware::drm::V1_0::ICryptoFactory; -using ::android::hardware::drm::V1_0::ICryptoPlugin; -using ::android::hardware::drm::V1_0::Mode; -using ::android::hardware::drm::V1_0::Pattern; -using ::android::hardware::drm::V1_0::SharedBuffer; -using ::android::hardware::drm::V1_0::Status; -using ::android::hardware::drm::V1_0::SubSample; using ::android::hardware::hidl_array; using ::android::hardware::hidl_handle; using ::android::hardware::hidl_memory; @@ -50,6 +50,7 @@ using ::android::hidl::manager::V1_0::IServiceManager; using ::android::sp; +typedef drm::V1_2::Status Status_V1_2; namespace android { @@ -76,6 +77,18 @@ } } +static status_t toStatusT_1_2(Status_V1_2 status) { + switch (status) { + case Status_V1_2::ERROR_DRM_SESSION_LOST_STATE: + return ERROR_DRM_SESSION_LOST_STATE;; + case Status_V1_2::ERROR_DRM_FRAME_TOO_LARGE: + return ERROR_DRM_FRAME_TOO_LARGE; + case Status_V1_2::ERROR_DRM_INSUFFICIENT_SECURITY: + return ERROR_DRM_INSUFFICIENT_SECURITY; + default: + return toStatusT(static_cast<Status>(status)); + } +} static hidl_vec<uint8_t> toHidlVec(const Vector<uint8_t> &vector) { hidl_vec<uint8_t> vec; @@ -196,6 +209,9 @@ for (size_t i = 0; i < mFactories.size(); i++) { if (mFactories[i]->isCryptoSchemeSupported(uuid)) { mPlugin = makeCryptoPlugin(mFactories[i], uuid, data, size); + if (mPlugin != NULL) { + mPluginV1_2 = drm::V1_2::ICryptoPlugin::castFrom(mPlugin); + } } } @@ -216,6 +232,7 @@ } mPlugin.clear(); + mPluginV1_2.clear(); return OK; } @@ -284,7 +301,7 @@ ssize_t offset; size_t size; - if (memory == NULL && buffer == NULL) { + if (memory == NULL || buffer == NULL) { return UNEXPECTED_NULL; } @@ -389,21 +406,33 @@ status_t err = UNKNOWN_ERROR; uint32_t bytesWritten = 0; - Return<void> hResult = mPlugin->decrypt(secure, toHidlArray16(keyId), toHidlArray16(iv), hMode, - hPattern, hSubSamples, hSource, offset, hDestination, - [&](Status status, uint32_t hBytesWritten, hidl_string hDetailedError) { - if (status == Status::OK) { - bytesWritten = hBytesWritten; - *errorDetailMsg = toString8(hDetailedError); - } - err = toStatusT(status); - } - ); + Return<void> hResult; - if (!hResult.isOk()) { - err = DEAD_OBJECT; + if (mPluginV1_2 != NULL) { + hResult = mPluginV1_2->decrypt_1_2(secure, toHidlArray16(keyId), toHidlArray16(iv), + hMode, hPattern, hSubSamples, hSource, offset, hDestination, + [&](Status_V1_2 status, uint32_t hBytesWritten, hidl_string hDetailedError) { + if (status == Status_V1_2::OK) { + bytesWritten = hBytesWritten; + *errorDetailMsg = toString8(hDetailedError); + } + err = toStatusT_1_2(status); + } + ); + } else { + hResult = mPlugin->decrypt(secure, toHidlArray16(keyId), toHidlArray16(iv), + hMode, hPattern, hSubSamples, hSource, offset, hDestination, + [&](Status status, uint32_t hBytesWritten, hidl_string hDetailedError) { + if (status == Status::OK) { + bytesWritten = hBytesWritten; + *errorDetailMsg = toString8(hDetailedError); + } + err = toStatusT(status); + } + ); } + err = hResult.isOk() ? err : DEAD_OBJECT; if (err == OK) { return bytesWritten; }
diff --git a/drm/libmediadrm/DrmHal.cpp b/drm/libmediadrm/DrmHal.cpp index cf08610..919f4ee 100644 --- a/drm/libmediadrm/DrmHal.cpp +++ b/drm/libmediadrm/DrmHal.cpp
@@ -23,8 +23,8 @@ #include <binder/IPCThreadState.h> #include <binder/IServiceManager.h> -#include <android/hardware/drm/1.0/types.h> -#include <android/hidl/manager/1.0/IServiceManager.h> +#include <android/hardware/drm/1.2/types.h> +#include <android/hidl/manager/1.2/IServiceManager.h> #include <hidl/ServiceManagement.h> #include <media/EventMetric.h> @@ -40,15 +40,17 @@ #include <mediadrm/DrmSessionManager.h> using drm::V1_0::KeyedVector; -using drm::V1_0::KeyStatusType; +using drm::V1_0::KeyRequestType; using drm::V1_0::KeyType; using drm::V1_0::KeyValue; -using drm::V1_1::HdcpLevel;; using drm::V1_0::SecureStop; -using drm::V1_1::SecureStopRelease; using drm::V1_0::SecureStopId; -using drm::V1_1::SecurityLevel; using drm::V1_0::Status; +using drm::V1_1::HdcpLevel; +using drm::V1_1::SecureStopRelease; +using drm::V1_1::SecurityLevel; +using drm::V1_2::KeySetId; +using drm::V1_2::KeyStatusType; using ::android::hardware::drm::V1_1::DrmMetricGroup; using ::android::hardware::hidl_array; using ::android::hardware::hidl_string; @@ -59,6 +61,10 @@ using ::android::os::PersistableBundle; using ::android::sp; +typedef drm::V1_1::KeyRequestType KeyRequestType_V1_1; +typedef drm::V1_2::Status Status_V1_2; +typedef drm::V1_2::HdcpLevel HdcpLevel_V1_2; + namespace { // This constant corresponds to the PROPERTY_DEVICE_UNIQUE_ID constant @@ -139,26 +145,55 @@ } } -static DrmPlugin::HdcpLevel toHdcpLevel(HdcpLevel level) { +static SecurityLevel toHidlSecurityLevel(DrmPlugin::SecurityLevel level) { switch(level) { - case HdcpLevel::HDCP_NONE: + case DrmPlugin::kSecurityLevelSwSecureCrypto: + return SecurityLevel::SW_SECURE_CRYPTO; + case DrmPlugin::kSecurityLevelSwSecureDecode: + return SecurityLevel::SW_SECURE_DECODE; + case DrmPlugin::kSecurityLevelHwSecureCrypto: + return SecurityLevel::HW_SECURE_CRYPTO; + case DrmPlugin::kSecurityLevelHwSecureDecode: + return SecurityLevel::HW_SECURE_DECODE; + case DrmPlugin::kSecurityLevelHwSecureAll: + return SecurityLevel::HW_SECURE_ALL; + default: + return SecurityLevel::UNKNOWN; + } +} + +static DrmPlugin::OfflineLicenseState toOfflineLicenseState( + OfflineLicenseState licenseState) { + switch(licenseState) { + case OfflineLicenseState::USABLE: + return DrmPlugin::kOfflineLicenseStateUsable; + case OfflineLicenseState::INACTIVE: + return DrmPlugin::kOfflineLicenseStateReleased; + default: + return DrmPlugin::kOfflineLicenseStateUnknown; + } +} + +static DrmPlugin::HdcpLevel toHdcpLevel(HdcpLevel_V1_2 level) { + switch(level) { + case HdcpLevel_V1_2::HDCP_NONE: return DrmPlugin::kHdcpNone; - case HdcpLevel::HDCP_V1: + case HdcpLevel_V1_2::HDCP_V1: return DrmPlugin::kHdcpV1; - case HdcpLevel::HDCP_V2: + case HdcpLevel_V1_2::HDCP_V2: return DrmPlugin::kHdcpV2; - case HdcpLevel::HDCP_V2_1: + case HdcpLevel_V1_2::HDCP_V2_1: return DrmPlugin::kHdcpV2_1; - case HdcpLevel::HDCP_V2_2: + case HdcpLevel_V1_2::HDCP_V2_2: return DrmPlugin::kHdcpV2_2; - case HdcpLevel::HDCP_NO_OUTPUT: + case HdcpLevel_V1_2::HDCP_V2_3: + return DrmPlugin::kHdcpV2_3; + case HdcpLevel_V1_2::HDCP_NO_OUTPUT: return DrmPlugin::kHdcpNoOutput; default: return DrmPlugin::kHdcpLevelUnknown; } } - - static ::KeyedVector toHidlKeyedVector(const KeyedVector<String8, String8>& keyedVector) { std::vector<KeyValue> stdKeyedVector; @@ -199,6 +234,15 @@ return secureStopIds; } +static List<Vector<uint8_t>> toKeySetIds(const hidl_vec<KeySetId>& + hKeySetIds) { + List<Vector<uint8_t>> keySetIds; + for (size_t i = 0; i < hKeySetIds.size(); i++) { + keySetIds.push_back(toVector(hKeySetIds[i])); + } + return keySetIds; +} + static status_t toStatusT(Status status) { switch (status) { case Status::OK: @@ -217,7 +261,7 @@ return ERROR_DRM_CANNOT_HANDLE; break; case Status::ERROR_DRM_INVALID_STATE: - return ERROR_DRM_TAMPER_DETECTED; + return ERROR_DRM_INVALID_STATE; break; case Status::BAD_VALUE: return BAD_VALUE; @@ -238,6 +282,19 @@ } } +static status_t toStatusT_1_2(Status_V1_2 status) { + switch (status) { + case Status_V1_2::ERROR_DRM_RESOURCE_CONTENTION: + return ERROR_DRM_RESOURCE_CONTENTION; + case Status_V1_2::ERROR_DRM_FRAME_TOO_LARGE: + return ERROR_DRM_FRAME_TOO_LARGE; + case Status_V1_2::ERROR_DRM_INSUFFICIENT_SECURITY: + return ERROR_DRM_INSUFFICIENT_SECURITY; + default: + return toStatusT(static_cast<Status>(status)); + } +} + Mutex DrmHal::mLock; @@ -297,39 +354,51 @@ setListener(NULL); mInitCheck = NO_INIT; - - if (mPlugin != NULL) { + if (mPluginV1_2 != NULL) { + if (!mPluginV1_2->setListener(NULL).isOk()) { + mInitCheck = DEAD_OBJECT; + } + } else if (mPlugin != NULL) { if (!mPlugin->setListener(NULL).isOk()) { mInitCheck = DEAD_OBJECT; } } mPlugin.clear(); mPluginV1_1.clear(); + mPluginV1_2.clear(); } Vector<sp<IDrmFactory>> DrmHal::makeDrmFactories() { Vector<sp<IDrmFactory>> factories; - auto manager = hardware::defaultServiceManager(); + auto manager = hardware::defaultServiceManager1_2(); if (manager != NULL) { - manager->listByInterface(drm::V1_0::IDrmFactory::descriptor, + manager->listManifestByInterface(drm::V1_0::IDrmFactory::descriptor, [&factories](const hidl_vec<hidl_string> ®istered) { for (const auto &instance : registered) { auto factory = drm::V1_0::IDrmFactory::getService(instance); if (factory != NULL) { - ALOGD("found drm@1.0 IDrmFactory %s", instance.c_str()); factories.push_back(factory); } } } ); - manager->listByInterface(drm::V1_1::IDrmFactory::descriptor, + manager->listManifestByInterface(drm::V1_1::IDrmFactory::descriptor, [&factories](const hidl_vec<hidl_string> ®istered) { for (const auto &instance : registered) { auto factory = drm::V1_1::IDrmFactory::getService(instance); if (factory != NULL) { - ALOGD("found drm@1.1 IDrmFactory %s", instance.c_str()); + factories.push_back(factory); + } + } + } + ); + manager->listByInterface(drm::V1_2::IDrmFactory::descriptor, + [&factories](const hidl_vec<hidl_string> ®istered) { + for (const auto &instance : registered) { + auto factory = drm::V1_2::IDrmFactory::getService(instance); + if (factory != NULL) { factories.push_back(factory); } } @@ -448,6 +517,17 @@ } Return<void> DrmHal::sendKeysChange(const hidl_vec<uint8_t>& sessionId, + const hidl_vec<KeyStatus_V1_0>& keyStatusList_V1_0, bool hasNewUsableKey) { + std::vector<KeyStatus> keyStatusVec; + for (const auto &keyStatus_V1_0 : keyStatusList_V1_0) { + keyStatusVec.push_back({keyStatus_V1_0.keyId, + static_cast<KeyStatusType>(keyStatus_V1_0.type)}); + } + hidl_vec<KeyStatus> keyStatusList_V1_2(keyStatusVec); + return sendKeysChange_1_2(sessionId, keyStatusList_V1_2, hasNewUsableKey); +} + +Return<void> DrmHal::sendKeysChange_1_2(const hidl_vec<uint8_t>& sessionId, const hidl_vec<KeyStatus>& keyStatusList, bool hasNewUsableKey) { mEventLock.lock(); @@ -477,6 +557,9 @@ case KeyStatusType::STATUSPENDING: type = DrmPlugin::kKeyStatusType_StatusPending; break; + case KeyStatusType::USABLEINFUTURE: + type = DrmPlugin::kKeyStatusType_UsableInFuture; + break; case KeyStatusType::INTERNALERROR: default: type = DrmPlugin::kKeyStatusType_InternalError; @@ -501,32 +584,80 @@ return Void(); } -bool DrmHal::isCryptoSchemeSupported(const uint8_t uuid[16], const String8 &mimeType) { - Mutex::Autolock autoLock(mLock); +Return<void> DrmHal::sendSessionLostState( + const hidl_vec<uint8_t>& sessionId) { - for (size_t i = 0; i < mFactories.size(); i++) { + mEventLock.lock(); + sp<IDrmClient> listener = mListener; + mEventLock.unlock(); + + if (listener != NULL) { + Parcel obj; + writeByteArray(obj, sessionId); + Mutex::Autolock lock(mNotifyLock); + listener->notify(DrmPlugin::kDrmPluginEventSessionLostState, 0, &obj); + } + return Void(); +} + +status_t DrmHal::matchMimeTypeAndSecurityLevel(const sp<IDrmFactory> &factory, + const uint8_t uuid[16], + const String8 &mimeType, + DrmPlugin::SecurityLevel level, + bool *isSupported) { + *isSupported = false; + + // handle default value cases + if (level == DrmPlugin::kSecurityLevelUnknown) { + if (mimeType == "") { + // isCryptoSchemeSupported(uuid) + *isSupported = true; + } else { + // isCryptoSchemeSupported(uuid, mimeType) + *isSupported = factory->isContentTypeSupported(mimeType.string()); + } + return OK; + } else if (mimeType == "") { + return BAD_VALUE; + } + + sp<drm::V1_2::IDrmFactory> factoryV1_2 = drm::V1_2::IDrmFactory::castFrom(factory); + if (factoryV1_2 == NULL) { + return ERROR_UNSUPPORTED; + } else { + *isSupported = factoryV1_2->isCryptoSchemeSupported_1_2(uuid, + mimeType.string(), toHidlSecurityLevel(level)); + return OK; + } +} + +status_t DrmHal::isCryptoSchemeSupported(const uint8_t uuid[16], + const String8 &mimeType, + DrmPlugin::SecurityLevel level, + bool *isSupported) { + Mutex::Autolock autoLock(mLock); + *isSupported = false; + for (ssize_t i = mFactories.size() - 1; i >= 0; i--) { if (mFactories[i]->isCryptoSchemeSupported(uuid)) { - if (mimeType != "") { - if (mFactories[i]->isContentTypeSupported(mimeType.string())) { - return true; - } - } else { - return true; - } + return matchMimeTypeAndSecurityLevel(mFactories[i], + uuid, mimeType, level, isSupported); } } - return false; + return OK; } status_t DrmHal::createPlugin(const uint8_t uuid[16], const String8& appPackageName) { Mutex::Autolock autoLock(mLock); - for (size_t i = 0; i < mFactories.size(); i++) { + for (ssize_t i = mFactories.size() - 1; i >= 0; i--) { if (mFactories[i]->isCryptoSchemeSupported(uuid)) { - mPlugin = makeDrmPlugin(mFactories[i], uuid, appPackageName); - if (mPlugin != NULL) { + auto plugin = makeDrmPlugin(mFactories[i], uuid, appPackageName); + if (plugin != NULL) { + mPlugin = plugin; mPluginV1_1 = drm::V1_1::IDrmPlugin::castFrom(mPlugin); + mPluginV1_2 = drm::V1_2::IDrmPlugin::castFrom(mPlugin); + break; } } } @@ -534,13 +665,22 @@ if (mPlugin == NULL) { mInitCheck = ERROR_UNSUPPORTED; } else { - if (!mPlugin->setListener(this).isOk()) { + mInitCheck = OK; + if (mPluginV1_2 != NULL) { + if (!mPluginV1_2->setListener(this).isOk()) { + mInitCheck = DEAD_OBJECT; + } + } else if (!mPlugin->setListener(this).isOk()) { mInitCheck = DEAD_OBJECT; - } else { - mInitCheck = OK; + } + if (mInitCheck != OK) { + mPlugin.clear(); + mPluginV1_1.clear(); + mPluginV1_2.clear(); } } + return mInitCheck; } @@ -554,30 +694,15 @@ Mutex::Autolock autoLock(mLock); INIT_CHECK(); - SecurityLevel hSecurityLevel; + SecurityLevel hSecurityLevel = toHidlSecurityLevel(level); bool setSecurityLevel = true; - switch(level) { - case DrmPlugin::kSecurityLevelSwSecureCrypto: - hSecurityLevel = SecurityLevel::SW_SECURE_CRYPTO; - break; - case DrmPlugin::kSecurityLevelSwSecureDecode: - hSecurityLevel = SecurityLevel::SW_SECURE_DECODE; - break; - case DrmPlugin::kSecurityLevelHwSecureCrypto: - hSecurityLevel = SecurityLevel::HW_SECURE_CRYPTO; - break; - case DrmPlugin::kSecurityLevelHwSecureDecode: - hSecurityLevel = SecurityLevel::HW_SECURE_DECODE; - break; - case DrmPlugin::kSecurityLevelHwSecureAll: - hSecurityLevel = SecurityLevel::HW_SECURE_ALL; - break; - case DrmPlugin::kSecurityLevelMax: + if (level == DrmPlugin::kSecurityLevelMax) { setSecurityLevel = false; - break; - default: - return ERROR_DRM_CANNOT_HANDLE; + } else { + if (hSecurityLevel == SecurityLevel::UNKNOWN) { + return ERROR_DRM_CANNOT_HANDLE; + } } status_t err = UNKNOWN_ERROR; @@ -657,6 +782,39 @@ return DEAD_OBJECT; } +static DrmPlugin::KeyRequestType toKeyRequestType( + KeyRequestType keyRequestType) { + switch (keyRequestType) { + case KeyRequestType::INITIAL: + return DrmPlugin::kKeyRequestType_Initial; + break; + case KeyRequestType::RENEWAL: + return DrmPlugin::kKeyRequestType_Renewal; + break; + case KeyRequestType::RELEASE: + return DrmPlugin::kKeyRequestType_Release; + break; + default: + return DrmPlugin::kKeyRequestType_Unknown; + break; + } +} + +static DrmPlugin::KeyRequestType toKeyRequestType_1_1( + KeyRequestType_V1_1 keyRequestType) { + switch (keyRequestType) { + case KeyRequestType_V1_1::NONE: + return DrmPlugin::kKeyRequestType_None; + break; + case KeyRequestType_V1_1::UPDATE: + return DrmPlugin::kKeyRequestType_Update; + break; + default: + return toKeyRequestType(static_cast<KeyRequestType>(keyRequestType)); + break; + } +} + status_t DrmHal::getKeyRequest(Vector<uint8_t> const &sessionId, Vector<uint8_t> const &initData, String8 const &mimeType, DrmPlugin::KeyType keyType, KeyedVector<String8, @@ -683,73 +841,51 @@ ::KeyedVector hOptionalParameters = toHidlKeyedVector(optionalParameters); status_t err = UNKNOWN_ERROR; + Return<void> hResult; - if (mPluginV1_1 != NULL) { - Return<void> hResult = - mPluginV1_1->getKeyRequest_1_1( + if (mPluginV1_2 != NULL) { + hResult = mPluginV1_2->getKeyRequest_1_2( + toHidlVec(sessionId), toHidlVec(initData), + toHidlString(mimeType), hKeyType, hOptionalParameters, + [&](Status_V1_2 status, const hidl_vec<uint8_t>& hRequest, + KeyRequestType_V1_1 hKeyRequestType, + const hidl_string& hDefaultUrl) { + if (status == Status_V1_2::OK) { + request = toVector(hRequest); + defaultUrl = toString8(hDefaultUrl); + *keyRequestType = toKeyRequestType_1_1(hKeyRequestType); + } + err = toStatusT_1_2(status); + }); + } else if (mPluginV1_1 != NULL) { + hResult = mPluginV1_1->getKeyRequest_1_1( toHidlVec(sessionId), toHidlVec(initData), toHidlString(mimeType), hKeyType, hOptionalParameters, [&](Status status, const hidl_vec<uint8_t>& hRequest, - drm::V1_1::KeyRequestType hKeyRequestType, - const hidl_string& hDefaultUrl) { - - if (status == Status::OK) { - request = toVector(hRequest); - defaultUrl = toString8(hDefaultUrl); - - switch (hKeyRequestType) { - case drm::V1_1::KeyRequestType::INITIAL: - *keyRequestType = DrmPlugin::kKeyRequestType_Initial; - break; - case drm::V1_1::KeyRequestType::RENEWAL: - *keyRequestType = DrmPlugin::kKeyRequestType_Renewal; - break; - case drm::V1_1::KeyRequestType::RELEASE: - *keyRequestType = DrmPlugin::kKeyRequestType_Release; - break; - case drm::V1_1::KeyRequestType::NONE: - *keyRequestType = DrmPlugin::kKeyRequestType_None; - break; - case drm::V1_1::KeyRequestType::UPDATE: - *keyRequestType = DrmPlugin::kKeyRequestType_Update; - break; - default: - *keyRequestType = DrmPlugin::kKeyRequestType_Unknown; - break; - } - err = toStatusT(status); - } - }); - return hResult.isOk() ? err : DEAD_OBJECT; - } - - Return<void> hResult = mPlugin->getKeyRequest(toHidlVec(sessionId), - toHidlVec(initData), toHidlString(mimeType), hKeyType, hOptionalParameters, - [&](Status status, const hidl_vec<uint8_t>& hRequest, - drm::V1_0::KeyRequestType hKeyRequestType, - const hidl_string& hDefaultUrl) { - - if (status == Status::OK) { - request = toVector(hRequest); - defaultUrl = toString8(hDefaultUrl); - - switch (hKeyRequestType) { - case drm::V1_0::KeyRequestType::INITIAL: - *keyRequestType = DrmPlugin::kKeyRequestType_Initial; - break; - case drm::V1_0::KeyRequestType::RENEWAL: - *keyRequestType = DrmPlugin::kKeyRequestType_Renewal; - break; - case drm::V1_0::KeyRequestType::RELEASE: - *keyRequestType = DrmPlugin::kKeyRequestType_Release; - break; - default: - *keyRequestType = DrmPlugin::kKeyRequestType_Unknown; - break; + KeyRequestType_V1_1 hKeyRequestType, + const hidl_string& hDefaultUrl) { + if (status == Status::OK) { + request = toVector(hRequest); + defaultUrl = toString8(hDefaultUrl); + *keyRequestType = toKeyRequestType_1_1(hKeyRequestType); } err = toStatusT(status); - } - }); + }); + } else { + hResult = mPlugin->getKeyRequest( + toHidlVec(sessionId), toHidlVec(initData), + toHidlString(mimeType), hKeyType, hOptionalParameters, + [&](Status status, const hidl_vec<uint8_t>& hRequest, + KeyRequestType hKeyRequestType, + const hidl_string& hDefaultUrl) { + if (status == Status::OK) { + request = toVector(hRequest); + defaultUrl = toString8(hDefaultUrl); + *keyRequestType = toKeyRequestType(hKeyRequestType); + } + err = toStatusT(status); + }); + } err = hResult.isOk() ? err : DEAD_OBJECT; keyRequestTimer.SetAttribute(err); @@ -831,18 +967,33 @@ INIT_CHECK(); status_t err = UNKNOWN_ERROR; + Return<void> hResult; - Return<void> hResult = mPlugin->getProvisionRequest( - toHidlString(certType), toHidlString(certAuthority), - [&](Status status, const hidl_vec<uint8_t>& hRequest, - const hidl_string& hDefaultUrl) { - if (status == Status::OK) { - request = toVector(hRequest); - defaultUrl = toString8(hDefaultUrl); + if (mPluginV1_2 != NULL) { + Return<void> hResult = mPluginV1_2->getProvisionRequest_1_2( + toHidlString(certType), toHidlString(certAuthority), + [&](Status_V1_2 status, const hidl_vec<uint8_t>& hRequest, + const hidl_string& hDefaultUrl) { + if (status == Status_V1_2::OK) { + request = toVector(hRequest); + defaultUrl = toString8(hDefaultUrl); + } + err = toStatusT_1_2(status); } - err = toStatusT(status); - } - ); + ); + } else { + Return<void> hResult = mPlugin->getProvisionRequest( + toHidlString(certType), toHidlString(certAuthority), + [&](Status status, const hidl_vec<uint8_t>& hRequest, + const hidl_string& hDefaultUrl) { + if (status == Status::OK) { + request = toVector(hRequest); + defaultUrl = toString8(hDefaultUrl); + } + err = toStatusT(status); + } + ); + } err = hResult.isOk() ? err : DEAD_OBJECT; mMetrics.mGetProvisionRequestCounter.Increment(err); @@ -988,22 +1139,31 @@ } status_t err = UNKNOWN_ERROR; - if (mPluginV1_1 == NULL) { - return ERROR_DRM_CANNOT_HANDLE; - } - *connected = DrmPlugin::kHdcpLevelUnknown; *max = DrmPlugin::kHdcpLevelUnknown; - Return<void> hResult = mPluginV1_1->getHdcpLevels( - [&](Status status, const HdcpLevel& hConnected, const HdcpLevel& hMax) { - if (status == Status::OK) { - *connected = toHdcpLevel(hConnected); - *max = toHdcpLevel(hMax); - } - err = toStatusT(status); - } - ); + Return<void> hResult; + if (mPluginV1_2 != NULL) { + hResult = mPluginV1_2->getHdcpLevels_1_2( + [&](Status_V1_2 status, const HdcpLevel_V1_2& hConnected, const HdcpLevel_V1_2& hMax) { + if (status == Status_V1_2::OK) { + *connected = toHdcpLevel(hConnected); + *max = toHdcpLevel(hMax); + } + err = toStatusT_1_2(status); + }); + } else if (mPluginV1_1 != NULL) { + hResult = mPluginV1_1->getHdcpLevels( + [&](Status status, const HdcpLevel& hConnected, const HdcpLevel& hMax) { + if (status == Status::OK) { + *connected = toHdcpLevel(static_cast<HdcpLevel_V1_2>(hConnected)); + *max = toHdcpLevel(static_cast<HdcpLevel_V1_2>(hMax)); + } + err = toStatusT(status); + }); + } else { + return ERROR_DRM_CANNOT_HANDLE; + } return hResult.isOk() ? err : DEAD_OBJECT; } @@ -1065,6 +1225,73 @@ return hResult.isOk() ? err : DEAD_OBJECT; } +status_t DrmHal::getOfflineLicenseKeySetIds(List<Vector<uint8_t>> &keySetIds) const { + Mutex::Autolock autoLock(mLock); + + if (mInitCheck != OK) { + return mInitCheck; + } + + if (mPluginV1_2 == NULL) { + return ERROR_UNSUPPORTED; + } + + status_t err = UNKNOWN_ERROR; + + Return<void> hResult = mPluginV1_2->getOfflineLicenseKeySetIds( + [&](Status status, const hidl_vec<KeySetId>& hKeySetIds) { + if (status == Status::OK) { + keySetIds = toKeySetIds(hKeySetIds); + } + err = toStatusT(status); + } + ); + + return hResult.isOk() ? err : DEAD_OBJECT; +} + +status_t DrmHal::removeOfflineLicense(Vector<uint8_t> const &keySetId) { + Mutex::Autolock autoLock(mLock); + + if (mInitCheck != OK) { + return mInitCheck; + } + + if (mPluginV1_2 == NULL) { + return ERROR_UNSUPPORTED; + } + + Return<Status> status = mPluginV1_2->removeOfflineLicense(toHidlVec(keySetId)); + return status.isOk() ? toStatusT(status) : DEAD_OBJECT; +} + +status_t DrmHal::getOfflineLicenseState(Vector<uint8_t> const &keySetId, + DrmPlugin::OfflineLicenseState *licenseState) const { + Mutex::Autolock autoLock(mLock); + + if (mInitCheck != OK) { + return mInitCheck; + } + + if (mPluginV1_2 == NULL) { + return ERROR_UNSUPPORTED; + } + *licenseState = DrmPlugin::kOfflineLicenseStateUnknown; + + status_t err = UNKNOWN_ERROR; + + Return<void> hResult = mPluginV1_2->getOfflineLicenseState(toHidlVec(keySetId), + [&](Status status, OfflineLicenseState hLicenseState) { + if (status == Status::OK) { + *licenseState = toOfflineLicenseState(hLicenseState); + } + err = toStatusT(status); + } + ); + + return hResult.isOk() ? err : DEAD_OBJECT; +} + status_t DrmHal::getPropertyString(String8 const &name, String8 &value ) const { Mutex::Autolock autoLock(mLock); return getPropertyStringInternal(name, value); @@ -1343,22 +1570,22 @@ void DrmHal::reportFrameworkMetrics() const { - MediaAnalyticsItem item("mediadrm"); - item.generateSessionID(); - item.setPkgName(mMetrics.GetAppPackageName().c_str()); + std::unique_ptr<MediaAnalyticsItem> item(MediaAnalyticsItem::create("mediadrm")); + item->generateSessionID(); + item->setPkgName(mMetrics.GetAppPackageName().c_str()); String8 vendor; String8 description; status_t result = getPropertyStringInternal(String8("vendor"), vendor); if (result != OK) { ALOGE("Failed to get vendor from drm plugin: %d", result); } else { - item.setCString("vendor", vendor.c_str()); + item->setCString("vendor", vendor.c_str()); } result = getPropertyStringInternal(String8("description"), description); if (result != OK) { ALOGE("Failed to get description from drm plugin: %d", result); } else { - item.setCString("description", description.c_str()); + item->setCString("description", description.c_str()); } std::string serializedMetrics; @@ -1369,9 +1596,9 @@ std::string b64EncodedMetrics = toBase64StringNoPad(serializedMetrics.data(), serializedMetrics.size()); if (!b64EncodedMetrics.empty()) { - item.setCString("serialized_metrics", b64EncodedMetrics.c_str()); + item->setCString("serialized_metrics", b64EncodedMetrics.c_str()); } - if (!item.selfrecord()) { + if (!item->selfrecord()) { ALOGE("Failed to self record framework metrics"); } }
diff --git a/drm/libmediadrm/DrmMetrics.cpp b/drm/libmediadrm/DrmMetrics.cpp index 4fed707..3080802 100644 --- a/drm/libmediadrm/DrmMetrics.cpp +++ b/drm/libmediadrm/DrmMetrics.cpp
@@ -32,7 +32,7 @@ using ::android::hardware::hidl_string; using ::android::hardware::hidl_vec; using ::android::hardware::drm::V1_0::EventType; -using ::android::hardware::drm::V1_0::KeyStatusType; +using ::android::hardware::drm::V1_2::KeyStatusType; using ::android::hardware::drm::V1_1::DrmMetricGroup; using ::android::os::PersistableBundle;
diff --git a/drm/libmediadrm/IDrm.cpp b/drm/libmediadrm/IDrm.cpp index 509961f..51274d1 100644 --- a/drm/libmediadrm/IDrm.cpp +++ b/drm/libmediadrm/IDrm.cpp
@@ -61,7 +61,10 @@ GET_NUMBER_OF_SESSIONS, GET_SECURITY_LEVEL, REMOVE_SECURE_STOP, - GET_SECURE_STOP_IDS + GET_SECURE_STOP_IDS, + GET_OFFLINE_LICENSE_KEYSET_IDS, + REMOVE_OFFLINE_LICENSE, + GET_OFFLINE_LICENSE_STATE }; struct BpDrm : public BpInterface<IDrm> { @@ -80,18 +83,22 @@ return reply.readInt32(); } - virtual bool isCryptoSchemeSupported(const uint8_t uuid[16], const String8 &mimeType) { + virtual status_t isCryptoSchemeSupported(const uint8_t uuid[16], const String8 &mimeType, + DrmPlugin::SecurityLevel level, bool *isSupported) { Parcel data, reply; data.writeInterfaceToken(IDrm::getInterfaceDescriptor()); data.write(uuid, 16); data.writeString8(mimeType); + data.writeInt32(level); + status_t status = remote()->transact(IS_CRYPTO_SUPPORTED, data, &reply); if (status != OK) { ALOGE("isCryptoSchemeSupported: binder call failed: %d", status); - return false; + return status; } + *isSupported = static_cast<bool>(reply.readInt32()); - return reply.readInt32() != 0; + return reply.readInt32(); } virtual status_t createPlugin(const uint8_t uuid[16], @@ -120,11 +127,11 @@ return reply.readInt32(); } - virtual status_t openSession(DrmPlugin::SecurityLevel securityLevel, + virtual status_t openSession(DrmPlugin::SecurityLevel level, Vector<uint8_t> &sessionId) { Parcel data, reply; data.writeInterfaceToken(IDrm::getInterfaceDescriptor()); - data.writeInt32(securityLevel); + data.writeInt32(level); status_t status = remote()->transact(OPEN_SESSION, data, &reply); if (status != OK) { @@ -376,6 +383,52 @@ return reply.readInt32(); } + virtual status_t getOfflineLicenseKeySetIds(List<Vector<uint8_t> > &keySetIds) const { + Parcel data, reply; + data.writeInterfaceToken(IDrm::getInterfaceDescriptor()); + + status_t status = remote()->transact(GET_OFFLINE_LICENSE_KEYSET_IDS, data, &reply); + if (status != OK) { + return status; + } + + keySetIds.clear(); + uint32_t count = reply.readInt32(); + for (size_t i = 0; i < count; i++) { + Vector<uint8_t> keySetId; + readVector(reply, keySetId); + keySetIds.push_back(keySetId); + } + return reply.readInt32(); + } + + virtual status_t removeOfflineLicense(Vector<uint8_t> const &keySetId) { + Parcel data, reply; + data.writeInterfaceToken(IDrm::getInterfaceDescriptor()); + + writeVector(data, keySetId); + status_t status = remote()->transact(REMOVE_OFFLINE_LICENSE, data, &reply); + if (status != OK) { + return status; + } + return reply.readInt32(); + } + + virtual status_t getOfflineLicenseState(Vector<uint8_t> const &keySetId, + DrmPlugin::OfflineLicenseState *licenseState) const { + Parcel data, reply; + data.writeInterfaceToken(IDrm::getInterfaceDescriptor()); + + writeVector(data, keySetId); + status_t status = remote()->transact(GET_OFFLINE_LICENSE_STATE, data, &reply); + if (status != OK) { + *licenseState = DrmPlugin::OfflineLicenseState::kOfflineLicenseStateUnknown; + return status; + } + *licenseState = static_cast<DrmPlugin::OfflineLicenseState>(reply.readInt32()); + return reply.readInt32(); + } + virtual status_t getPropertyString(String8 const &name, String8 &value) const { Parcel data, reply; data.writeInterfaceToken(IDrm::getInterfaceDescriptor()); @@ -719,7 +772,12 @@ uint8_t uuid[16]; data.read(uuid, sizeof(uuid)); String8 mimeType = data.readString8(); - reply->writeInt32(isCryptoSchemeSupported(uuid, mimeType)); + DrmPlugin::SecurityLevel level = + static_cast<DrmPlugin::SecurityLevel>(data.readInt32()); + bool isSupported = false; + status_t result = isCryptoSchemeSupported(uuid, mimeType, level, &isSupported); + reply->writeInt32(isSupported); + reply->writeInt32(result); return OK; } @@ -980,6 +1038,45 @@ return OK; } + case GET_OFFLINE_LICENSE_KEYSET_IDS: + { + CHECK_INTERFACE(IDrm, data, reply); + List<Vector<uint8_t> > keySetIds; + status_t result = getOfflineLicenseKeySetIds(keySetIds); + size_t count = keySetIds.size(); + reply->writeInt32(count); + List<Vector<uint8_t> >::iterator iter = keySetIds.begin(); + while(iter != keySetIds.end()) { + size_t size = iter->size(); + reply->writeInt32(size); + reply->write(iter->array(), iter->size()); + iter++; + } + reply->writeInt32(result); + return OK; + } + + case REMOVE_OFFLINE_LICENSE: + { + CHECK_INTERFACE(IDrm, data, reply); + Vector<uint8_t> keySetId; + readVector(data, keySetId); + reply->writeInt32(removeOfflineLicense(keySetId)); + return OK; + } + + case GET_OFFLINE_LICENSE_STATE: + { + CHECK_INTERFACE(IDrm, data, reply); + Vector<uint8_t> keySetId; + readVector(data, keySetId); + DrmPlugin::OfflineLicenseState state; + status_t result = getOfflineLicenseState(keySetId, &state); + reply->writeInt32(static_cast<DrmPlugin::OfflineLicenseState>(state)); + reply->writeInt32(result); + return OK; + } + case GET_PROPERTY_STRING: { CHECK_INTERFACE(IDrm, data, reply);
diff --git a/drm/libmediadrm/PluginMetricsReporting.cpp b/drm/libmediadrm/PluginMetricsReporting.cpp index 5cb48bf..8cd6f96 100644 --- a/drm/libmediadrm/PluginMetricsReporting.cpp +++ b/drm/libmediadrm/PluginMetricsReporting.cpp
@@ -34,17 +34,17 @@ status_t reportVendorMetrics(const std::string& metrics, const String8& name, const String8& appPackageName) { - MediaAnalyticsItem analyticsItem(name.c_str()); - analyticsItem.generateSessionID(); + std::unique_ptr<MediaAnalyticsItem> analyticsItem(MediaAnalyticsItem::create(name.c_str())); + analyticsItem->generateSessionID(); std::string app_package_name(appPackageName.c_str(), appPackageName.size()); - analyticsItem.setPkgName(app_package_name); + analyticsItem->setPkgName(app_package_name); if (metrics.size() > 0) { - analyticsItem.setCString(kSerializedMetricsField, metrics.c_str()); + analyticsItem->setCString(kSerializedMetricsField, metrics.c_str()); } - if (!analyticsItem.selfrecord()) { - ALOGE("selfrecord() returned false. sessioId %" PRId64, analyticsItem.getSessionID()); + if (!analyticsItem->selfrecord()) { + ALOGE("selfrecord() returned false. sessioId %" PRId64, analyticsItem->getSessionID()); } return OK;
diff --git a/drm/libmediadrm/tests/Android.bp b/drm/libmediadrm/tests/Android.bp index 66c906f..9e0115e 100644 --- a/drm/libmediadrm/tests/Android.bp +++ b/drm/libmediadrm/tests/Android.bp
@@ -17,6 +17,7 @@ shared_libs: [ "android.hardware.drm@1.0", "android.hardware.drm@1.1", + "android.hardware.drm@1.2", "libbinder", "libhidlbase", "liblog", @@ -33,7 +34,6 @@ // Suppress unused parameter and no error options. These cause problems // when using the map type in a proto definition. "-Wno-unused-parameter", - "-Wno-error", ] }
diff --git a/drm/libmediadrm/tests/DrmMetrics_test.cpp b/drm/libmediadrm/tests/DrmMetrics_test.cpp index 64aa9d0..5c8a1b0 100644 --- a/drm/libmediadrm/tests/DrmMetrics_test.cpp +++ b/drm/libmediadrm/tests/DrmMetrics_test.cpp
@@ -30,7 +30,7 @@ using ::android::drm_metrics::DrmFrameworkMetrics; using ::android::hardware::hidl_vec; using ::android::hardware::drm::V1_0::EventType; -using ::android::hardware::drm::V1_0::KeyStatusType; +using ::android::hardware::drm::V1_2::KeyStatusType; using ::android::hardware::drm::V1_0::Status; using ::android::hardware::drm::V1_1::DrmMetricGroup; using ::android::os::PersistableBundle;
diff --git a/drm/mediacas/plugins/clearkey/ClearKeyCasPlugin.cpp b/drm/mediacas/plugins/clearkey/ClearKeyCasPlugin.cpp index 1558e8b..bf35224 100644 --- a/drm/mediacas/plugins/clearkey/ClearKeyCasPlugin.cpp +++ b/drm/mediacas/plugins/clearkey/ClearKeyCasPlugin.cpp
@@ -65,7 +65,20 @@ *plugin = new ClearKeyCasPlugin(appData, callback); return OK; } -/////////////////////////////////////////////////////////////////////////////// + +status_t ClearKeyCasFactory::createPlugin( + int32_t CA_system_id, + void *appData, + CasPluginCallbackExt callback, + CasPlugin **plugin) { + if (!isSystemIdSupported(CA_system_id)) { + return BAD_VALUE; + } + + *plugin = new ClearKeyCasPlugin(appData, callback); + return OK; +} +//////////////////////////////////////////////////////////////////////////////// bool ClearKeyDescramblerFactory::isSystemIdSupported( int32_t CA_system_id) const { return CA_system_id == sClearKeySystemId; @@ -84,7 +97,13 @@ /////////////////////////////////////////////////////////////////////////////// ClearKeyCasPlugin::ClearKeyCasPlugin( void *appData, CasPluginCallback callback) - : mCallback(callback), mAppData(appData) { + : mCallback(callback), mCallbackExt(NULL), mAppData(appData) { + ALOGV("CTOR"); +} + +ClearKeyCasPlugin::ClearKeyCasPlugin( + void *appData, CasPluginCallbackExt callback) + : mCallback(NULL), mCallbackExt(callback), mAppData(appData) { ALOGV("CTOR"); } @@ -167,11 +186,30 @@ // Echo the received event to the callback. // Clear key plugin doesn't use any event, echo'ing for testing only. if (mCallback != NULL) { - mCallback((void*)mAppData, event, arg, (uint8_t*)eventData.data(), eventData.size()); + mCallback((void*)mAppData, event, arg, (uint8_t*)eventData.data(), + eventData.size()); + } else if (mCallbackExt != NULL) { + mCallbackExt((void*)mAppData, event, arg, (uint8_t*)eventData.data(), + eventData.size(), NULL); } return OK; } +status_t ClearKeyCasPlugin::sendSessionEvent( + const CasSessionId &sessionId, int32_t event, + int arg, const CasData &eventData) { + ALOGV("sendSessionEvent: sessionId=%s, event=%d, arg=%d", + sessionIdToString(sessionId).string(), event, arg); + // Echo the received event to the callback. + // Clear key plugin doesn't use any event, echo'ing for testing only. + if (mCallbackExt != NULL) { + mCallbackExt((void*)mAppData, event, arg, (uint8_t*)eventData.data(), + eventData.size(), &sessionId); + } + + return OK; +} + status_t ClearKeyCasPlugin::provision(const String8 &str) { ALOGV("provision: provisionString=%s", str.string()); Mutex::Autolock lock(mKeyFetcherLock);
diff --git a/drm/mediacas/plugins/clearkey/ClearKeyCasPlugin.h b/drm/mediacas/plugins/clearkey/ClearKeyCasPlugin.h index 389e172..f48d5b1 100644 --- a/drm/mediacas/plugins/clearkey/ClearKeyCasPlugin.h +++ b/drm/mediacas/plugins/clearkey/ClearKeyCasPlugin.h
@@ -47,6 +47,11 @@ void *appData, CasPluginCallback callback, CasPlugin **plugin) override; + virtual status_t createPlugin( + int32_t CA_system_id, + void *appData, + CasPluginCallbackExt callback, + CasPlugin **plugin) override; }; class ClearKeyDescramblerFactory : public DescramblerFactory { @@ -63,6 +68,7 @@ class ClearKeyCasPlugin : public CasPlugin { public: ClearKeyCasPlugin(void *appData, CasPluginCallback callback); + ClearKeyCasPlugin(void *appData, CasPluginCallbackExt callback); virtual ~ClearKeyCasPlugin(); virtual status_t setPrivateData( @@ -85,6 +91,10 @@ virtual status_t sendEvent( int32_t event, int32_t arg, const CasData &eventData) override; + virtual status_t sendSessionEvent( + const CasSessionId &sessionId, + int32_t event, int32_t arg, const CasData &eventData) override; + virtual status_t provision(const String8 &str) override; virtual status_t refreshEntitlements( @@ -94,6 +104,7 @@ Mutex mKeyFetcherLock; std::unique_ptr<KeyFetcher> mKeyFetcher; CasPluginCallback mCallback; + CasPluginCallbackExt mCallbackExt; void* mAppData; };
diff --git a/drm/mediacas/plugins/mock/MockCasPlugin.cpp b/drm/mediacas/plugins/mock/MockCasPlugin.cpp index 8404a83..2964791 100644 --- a/drm/mediacas/plugins/mock/MockCasPlugin.cpp +++ b/drm/mediacas/plugins/mock/MockCasPlugin.cpp
@@ -60,6 +60,19 @@ return OK; } +status_t MockCasFactory::createPlugin( + int32_t CA_system_id, + void* /*appData*/, + CasPluginCallbackExt /*callback*/, + CasPlugin **plugin) { + if (!isSystemIdSupported(CA_system_id)) { + return BAD_VALUE; + } + + *plugin = new MockCasPlugin(); + return OK; +} + /////////////////////////////////////////////////////////////////////////////// bool MockDescramblerFactory::isSystemIdSupported(int32_t CA_system_id) const { @@ -170,6 +183,16 @@ return OK; } +status_t MockCasPlugin::sendSessionEvent( + const CasSessionId &sessionId, int32_t event, + int /*arg*/, const CasData& /*eventData*/) { + ALOGV("sendSessionEvent: sessionId=%s, event=%d", + arrayToString(sessionId).string(), event); + Mutex::Autolock lock(mLock); + + return OK; +} + status_t MockCasPlugin::provision(const String8 &str) { ALOGV("provision: provisionString=%s", str.string()); Mutex::Autolock lock(mLock);
diff --git a/drm/mediacas/plugins/mock/MockCasPlugin.h b/drm/mediacas/plugins/mock/MockCasPlugin.h index 8106990..74b540c 100644 --- a/drm/mediacas/plugins/mock/MockCasPlugin.h +++ b/drm/mediacas/plugins/mock/MockCasPlugin.h
@@ -42,6 +42,11 @@ void *appData, CasPluginCallback callback, CasPlugin **plugin) override; + virtual status_t createPlugin( + int32_t CA_system_id, + void *appData, + CasPluginCallbackExt callback, + CasPlugin **plugin) override; }; class MockDescramblerFactory : public DescramblerFactory { @@ -80,7 +85,11 @@ virtual status_t sendEvent( int32_t event, int32_t arg, const CasData &eventData) override; - virtual status_t provision(const String8 &str) override; + virtual status_t sendSessionEvent( + const CasSessionId &sessionId, + int32_t event, int32_t arg, const CasData &eventData) override; + + virtual status_t provision(const String8 &str) override; virtual status_t refreshEntitlements( int32_t refreshType, const CasData &refreshData) override;
diff --git a/drm/mediadrm/Android.bp b/drm/mediadrm/Android.bp deleted file mode 100644 index b9f07f1..0000000 --- a/drm/mediadrm/Android.bp +++ /dev/null
@@ -1 +0,0 @@ -subdirs = ["plugins/*"]
diff --git a/drm/mediadrm/plugins/clearkey/common/Utils.cpp b/drm/mediadrm/plugins/clearkey/common/Utils.cpp index 93c643b..d48b0a8 100644 --- a/drm/mediadrm/plugins/clearkey/common/Utils.cpp +++ b/drm/mediadrm/plugins/clearkey/common/Utils.cpp
@@ -27,4 +27,18 @@ return memcmp((void *)lhs.array(), (void *)rhs.array(), rhs.size()) < 0; } +std::string ByteArrayToHexString(const uint8_t* in_buffer, size_t length) { + static const char kHexChars[] = "0123456789ABCDEF"; + + // Each input byte creates two output hex characters. + std::string out_buffer(length * 2, '\0'); + + for (size_t i = 0; i < length; ++i) { + char byte = in_buffer[i]; + out_buffer[(i * 2)] = kHexChars[(byte >> 4) & 0xf]; + out_buffer[(i * 2) + 1] = kHexChars[byte & 0xf]; + } + return out_buffer; +} + } // namespace android
diff --git a/drm/mediadrm/plugins/clearkey/common/include/Utils.h b/drm/mediadrm/plugins/clearkey/common/include/Utils.h index 2543124..aa571c0 100644 --- a/drm/mediadrm/plugins/clearkey/common/include/Utils.h +++ b/drm/mediadrm/plugins/clearkey/common/include/Utils.h
@@ -17,14 +17,16 @@ #ifndef CLEARKEY_UTILS_H_ #define CLEARKEY_UTILS_H_ +#include <string> #include <utils/Vector.h> +namespace android { // Add a comparison operator for this Vector specialization so that it can be // used as a key in a KeyedVector. -namespace android { - bool operator<(const Vector<uint8_t> &lhs, const Vector<uint8_t> &rhs); +std::string ByteArrayToHexString(const uint8_t* in_buffer, size_t length); + } // namespace android #define UNUSED(x) (void)(x);
diff --git a/drm/mediadrm/plugins/clearkey/default/Android.bp b/drm/mediadrm/plugins/clearkey/default/Android.bp index 7ba5708..9803d32 100644 --- a/drm/mediadrm/plugins/clearkey/default/Android.bp +++ b/drm/mediadrm/plugins/clearkey/default/Android.bp
@@ -61,7 +61,3 @@ }, } -//######################################################################## -// Build unit tests - -subdirs = ["tests"]
diff --git a/drm/mediadrm/plugins/clearkey/hidl/AesCtrDecryptor.cpp b/drm/mediadrm/plugins/clearkey/hidl/AesCtrDecryptor.cpp index 2fce0790..0ac879c 100644 --- a/drm/mediadrm/plugins/clearkey/hidl/AesCtrDecryptor.cpp +++ b/drm/mediadrm/plugins/clearkey/hidl/AesCtrDecryptor.cpp
@@ -26,7 +26,7 @@ namespace android { namespace hardware { namespace drm { -namespace V1_1 { +namespace V1_2 { namespace clearkey { using ::android::hardware::drm::V1_0::SubSample; @@ -79,7 +79,7 @@ } } // namespace clearkey -} // namespace V1_1 +} // namespace V1_2 } // namespace drm } // namespace hardware } // namespace android
diff --git a/drm/mediadrm/plugins/clearkey/hidl/Android.bp b/drm/mediadrm/plugins/clearkey/hidl/Android.bp index 341d4f6..e91e918 100644 --- a/drm/mediadrm/plugins/clearkey/hidl/Android.bp +++ b/drm/mediadrm/plugins/clearkey/hidl/Android.bp
@@ -14,8 +14,8 @@ // limitations under the License. // -cc_binary { - name: "android.hardware.drm@1.1-service.clearkey", +cc_defaults { + name: "clearkey_service_defaults", vendor: true, srcs: [ @@ -25,23 +25,24 @@ "CreatePluginFactories.cpp", "CryptoFactory.cpp", "CryptoPlugin.cpp", + "DeviceFiles.cpp", "DrmFactory.cpp", "DrmPlugin.cpp", "InitDataParser.cpp", "JsonWebKey.cpp", + "MemoryFileSystem.cpp", "Session.cpp", "SessionLibrary.cpp", - "service.cpp", ], relative_install_path: "hw", cflags: ["-Wall", "-Werror"], - init_rc: ["android.hardware.drm@1.1-service.clearkey.rc"], shared_libs: [ "android.hardware.drm@1.0", "android.hardware.drm@1.1", + "android.hardware.drm@1.2", "libbase", "libbinder", "libcrypto", @@ -49,11 +50,13 @@ "libhidlmemory", "libhidltransport", "liblog", + "libprotobuf-cpp-lite", "libutils", ], static_libs: [ "libclearkeycommon", + "libclearkeydevicefiles-protos", "libjsmn", ], @@ -65,4 +68,26 @@ integer_overflow: true, }, } +cc_library_static { + name: "libclearkeydevicefiles-protos", + vendor: true, + proto: { + export_proto_headers: true, + type: "lite", + }, + srcs: ["protos/DeviceFiles.proto"], +} +cc_binary { + name: "android.hardware.drm@1.2-service.clearkey", + defaults: ["clearkey_service_defaults"], + srcs: ["service.cpp"], + init_rc: ["android.hardware.drm@1.2-service.clearkey.rc"], +} +cc_binary { + name: "android.hardware.drm@1.2-service-lazy.clearkey", + overrides: ["android.hardware.drm@1.2-service.clearkey"], + defaults: ["clearkey_service_defaults"], + srcs: ["serviceLazy.cpp"], + init_rc: ["android.hardware.drm@1.2-service-lazy.clearkey.rc"], +}
diff --git a/drm/mediadrm/plugins/clearkey/hidl/Base64.cpp b/drm/mediadrm/plugins/clearkey/hidl/Base64.cpp index c2ed751..657a42f 100644 --- a/drm/mediadrm/plugins/clearkey/hidl/Base64.cpp +++ b/drm/mediadrm/plugins/clearkey/hidl/Base64.cpp
@@ -21,7 +21,7 @@ namespace android { namespace hardware { namespace drm { -namespace V1_1 { +namespace V1_2 { namespace clearkey { sp<Buffer> decodeBase64(const std::string &s) { @@ -169,7 +169,7 @@ } } // namespace clearkey -} // namespace V1_1 +} // namespace V1_2 } // namespace drm } // namespace hardware } // namespace android
diff --git a/drm/mediadrm/plugins/clearkey/hidl/Buffer.cpp b/drm/mediadrm/plugins/clearkey/hidl/Buffer.cpp index e58f58a..75f8395 100644 --- a/drm/mediadrm/plugins/clearkey/hidl/Buffer.cpp +++ b/drm/mediadrm/plugins/clearkey/hidl/Buffer.cpp
@@ -21,7 +21,7 @@ namespace android { namespace hardware { namespace drm { -namespace V1_1 { +namespace V1_2 { namespace clearkey { Buffer::Buffer(size_t capacity) @@ -47,7 +47,7 @@ } } // namespace clearkey -} // namespace V1_1 +} // namespace V1_2 } // namespace drm } // namespace hardware } // namespace android
diff --git a/drm/mediadrm/plugins/clearkey/hidl/CreatePluginFactories.cpp b/drm/mediadrm/plugins/clearkey/hidl/CreatePluginFactories.cpp index 1ba5c6a..1410d77 100644 --- a/drm/mediadrm/plugins/clearkey/hidl/CreatePluginFactories.cpp +++ b/drm/mediadrm/plugins/clearkey/hidl/CreatePluginFactories.cpp
@@ -22,7 +22,7 @@ namespace android { namespace hardware { namespace drm { -namespace V1_1 { +namespace V1_2 { namespace clearkey { extern "C" { @@ -38,7 +38,7 @@ } // extern "C" } // namespace clearkey -} // namespace V1_1 +} // namespace V1_2 } // namespace drm } // namespace hardware } // namespace android
diff --git a/drm/mediadrm/plugins/clearkey/hidl/CryptoFactory.cpp b/drm/mediadrm/plugins/clearkey/hidl/CryptoFactory.cpp index 0848cef..2a48db6 100644 --- a/drm/mediadrm/plugins/clearkey/hidl/CryptoFactory.cpp +++ b/drm/mediadrm/plugins/clearkey/hidl/CryptoFactory.cpp
@@ -27,7 +27,7 @@ namespace android { namespace hardware { namespace drm { -namespace V1_1 { +namespace V1_2 { namespace clearkey { Return<bool> CryptoFactory::isCryptoSchemeSupported( @@ -60,7 +60,7 @@ } } // namespace clearkey -} // namespace V1_1 +} // namespace V1_2 } // namespace drm } // namespace hardware } // namespace android
diff --git a/drm/mediadrm/plugins/clearkey/hidl/CryptoPlugin.cpp b/drm/mediadrm/plugins/clearkey/hidl/CryptoPlugin.cpp index cd2224d..3ecf6d5 100644 --- a/drm/mediadrm/plugins/clearkey/hidl/CryptoPlugin.cpp +++ b/drm/mediadrm/plugins/clearkey/hidl/CryptoPlugin.cpp
@@ -27,7 +27,7 @@ namespace android { namespace hardware { namespace drm { -namespace V1_1 { +namespace V1_2 { namespace clearkey { using ::android::hardware::drm::V1_0::BufferType; @@ -42,10 +42,40 @@ return Void(); } +Return<void> CryptoPlugin::decrypt( + bool secure, + const hidl_array<uint8_t, 16>& keyId, + const hidl_array<uint8_t, 16>& iv, + Mode mode, + const Pattern& pattern, + const hidl_vec<SubSample>& subSamples, + const SharedBuffer& source, + uint64_t offset, + const DestinationBuffer& destination, + decrypt_cb _hidl_cb) { + + Status status = Status::ERROR_DRM_UNKNOWN; + hidl_string detailedError; + uint32_t bytesWritten = 0; + + Return<void> hResult = decrypt_1_2( + secure, keyId, iv, mode, pattern, subSamples, source, offset, destination, + [&](Status_V1_2 hStatus, uint32_t hBytesWritten, hidl_string hDetailedError) { + status = toStatus_1_0(hStatus); + bytesWritten = hBytesWritten; + detailedError = hDetailedError; + } + ); + + status = hResult.isOk() ? status : Status::ERROR_DRM_CANNOT_HANDLE; + _hidl_cb(status, bytesWritten, detailedError); + return Void(); +} + // Returns negative values for error code and positive values for the size of // decrypted data. In theory, the output size can be larger than the input // size, but in practice this will never happen for AES-CTR. -Return<void> CryptoPlugin::decrypt( +Return<void> CryptoPlugin::decrypt_1_2( bool secure, const hidl_array<uint8_t, KEY_ID_SIZE>& keyId, const hidl_array<uint8_t, KEY_IV_SIZE>& iv, @@ -55,17 +85,17 @@ const SharedBuffer& source, uint64_t offset, const DestinationBuffer& destination, - decrypt_cb _hidl_cb) { + decrypt_1_2_cb _hidl_cb) { UNUSED(pattern); if (secure) { - _hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, 0, + _hidl_cb(Status_V1_2::ERROR_DRM_CANNOT_HANDLE, 0, "Secure decryption is not supported with ClearKey."); return Void(); } if (mSharedBufferMap.find(source.bufferId) == mSharedBufferMap.end()) { - _hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, 0, + _hidl_cb(Status_V1_2::ERROR_DRM_CANNOT_HANDLE, 0, "source decrypt buffer base not set"); return Void(); } @@ -73,24 +103,24 @@ if (destination.type == BufferType::SHARED_MEMORY) { const SharedBuffer& dest = destination.nonsecureMemory; if (mSharedBufferMap.find(dest.bufferId) == mSharedBufferMap.end()) { - _hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, 0, + _hidl_cb(Status_V1_2::ERROR_DRM_CANNOT_HANDLE, 0, "destination decrypt buffer base not set"); return Void(); } } else { - _hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, 0, + _hidl_cb(Status_V1_2::ERROR_DRM_CANNOT_HANDLE, 0, "destination type not supported"); return Void(); } sp<IMemory> sourceBase = mSharedBufferMap[source.bufferId]; if (sourceBase == nullptr) { - _hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, 0, "source is a nullptr"); + _hidl_cb(Status_V1_2::ERROR_DRM_CANNOT_HANDLE, 0, "source is a nullptr"); return Void(); } if (source.offset + offset + source.size > sourceBase->getSize()) { - _hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, 0, "invalid buffer size"); + _hidl_cb(Status_V1_2::ERROR_DRM_CANNOT_HANDLE, 0, "invalid buffer size"); return Void(); } @@ -102,18 +132,19 @@ const SharedBuffer& destBuffer = destination.nonsecureMemory; sp<IMemory> destBase = mSharedBufferMap[destBuffer.bufferId]; if (destBase == nullptr) { - _hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, 0, "destination is a nullptr"); + _hidl_cb(Status_V1_2::ERROR_DRM_CANNOT_HANDLE, 0, "destination is a nullptr"); return Void(); } base = static_cast<uint8_t *>(static_cast<void *>(destBase->getPointer())); if (destBuffer.offset + destBuffer.size > destBase->getSize()) { - _hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, 0, "invalid buffer size"); + _hidl_cb(Status_V1_2::ERROR_DRM_FRAME_TOO_LARGE, 0, "invalid buffer size"); return Void(); } destPtr = static_cast<void *>(base + destination.nonsecureMemory.offset); + // Calculate the output buffer size and determine if any subsamples are // encrypted. size_t destSize = 0; @@ -121,11 +152,11 @@ for (size_t i = 0; i < subSamples.size(); i++) { const SubSample &subSample = subSamples[i]; if (__builtin_add_overflow(destSize, subSample.numBytesOfClearData, &destSize)) { - _hidl_cb(Status::BAD_VALUE, 0, "subsample clear size overflow"); + _hidl_cb(Status_V1_2::ERROR_DRM_FRAME_TOO_LARGE, 0, "subsample clear size overflow"); return Void(); } if (__builtin_add_overflow(destSize, subSample.numBytesOfEncryptedData, &destSize)) { - _hidl_cb(Status::BAD_VALUE, 0, "subsample encrypted size overflow"); + _hidl_cb(Status_V1_2::ERROR_DRM_FRAME_TOO_LARGE, 0, "subsample encrypted size overflow"); return Void(); } if (subSample.numBytesOfEncryptedData > 0) { @@ -134,13 +165,13 @@ } if (destSize > destBuffer.size) { - _hidl_cb(Status::BAD_VALUE, 0, "subsample sum too large"); + _hidl_cb(Status_V1_2::ERROR_DRM_FRAME_TOO_LARGE, 0, "subsample sum too large"); return Void(); } if (mode == Mode::UNENCRYPTED) { if (haveEncryptedSubsamples) { - _hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, 0, + _hidl_cb(Status_V1_2::ERROR_DRM_CANNOT_HANDLE, 0, "Encrypted subsamples found in allegedly unencrypted data."); return Void(); } @@ -156,22 +187,21 @@ } } - _hidl_cb(Status::OK, static_cast<ssize_t>(offset), ""); + _hidl_cb(Status_V1_2::OK, static_cast<ssize_t>(offset), ""); return Void(); } else if (mode == Mode::AES_CTR) { size_t bytesDecrypted; - Status res = mSession->decrypt(keyId.data(), iv.data(), srcPtr, + Status_V1_2 res = mSession->decrypt(keyId.data(), iv.data(), srcPtr, static_cast<uint8_t*>(destPtr), toVector(subSamples), &bytesDecrypted); - if (res == Status::OK) { - _hidl_cb(Status::OK, static_cast<ssize_t>(bytesDecrypted), ""); + if (res == Status_V1_2::OK) { + _hidl_cb(Status_V1_2::OK, static_cast<ssize_t>(bytesDecrypted), ""); return Void(); } else { - _hidl_cb(Status::ERROR_DRM_DECRYPT, static_cast<ssize_t>(res), - "Decryption Error"); + _hidl_cb(res, 0, "Decryption Error"); return Void(); } } else { - _hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, 0, + _hidl_cb(Status_V1_2::ERROR_DRM_CANNOT_HANDLE, 0, "Selected encryption mode is not supported by the ClearKey DRM Plugin."); return Void(); } @@ -191,7 +221,7 @@ } } // namespace clearkey -} // namespace V1_1 +} // namespace V1_2 } // namespace drm } // namespace hardware } // namespace android
diff --git a/drm/mediadrm/plugins/clearkey/hidl/DeviceFiles.cpp b/drm/mediadrm/plugins/clearkey/hidl/DeviceFiles.cpp new file mode 100644 index 0000000..2415b6f --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/hidl/DeviceFiles.cpp
@@ -0,0 +1,252 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include <utils/Log.h> + +#include <string> +#include <sys/stat.h> + +#include "DeviceFiles.h" +#include "Utils.h" + +#include <openssl/sha.h> + +// Protobuf generated classes. +using android::hardware::drm::V1_2::clearkey::OfflineFile; +using android::hardware::drm::V1_2::clearkey::HashedFile; +using android::hardware::drm::V1_2::clearkey::License; +using android::hardware::drm::V1_2::clearkey::License_LicenseState_ACTIVE; +using android::hardware::drm::V1_2::clearkey::License_LicenseState_RELEASING; + +namespace { +const char kLicenseFileNameExt[] = ".lic"; + +bool Hash(const std::string& data, std::string* hash) { + if (!hash) return false; + + hash->resize(SHA256_DIGEST_LENGTH); + + const unsigned char* input = reinterpret_cast<const unsigned char*>(data.data()); + unsigned char* output = reinterpret_cast<unsigned char*>(&(*hash)[0]); + SHA256(input, data.size(), output); + return true; +} + +} // namespace + +namespace android { +namespace hardware { +namespace drm { +namespace V1_2 { +namespace clearkey { + +bool DeviceFiles::StoreLicense( + const std::string& keySetId, LicenseState state, + const std::string& licenseResponse) { + + OfflineFile file; + file.set_type(OfflineFile::LICENSE); + file.set_version(OfflineFile::VERSION_1); + + License* license = file.mutable_license(); + switch (state) { + case kLicenseStateActive: + license->set_state(License_LicenseState_ACTIVE); + license->set_license(licenseResponse); + break; + case kLicenseStateReleasing: + license->set_state(License_LicenseState_RELEASING); + license->set_license(licenseResponse); + break; + default: + ALOGW("StoreLicense: Unknown license state: %u", state); + return false; + } + + std::string serializedFile; + file.SerializeToString(&serializedFile); + + return StoreFileWithHash(keySetId + kLicenseFileNameExt, serializedFile); +} + +bool DeviceFiles::StoreFileWithHash(const std::string& fileName, + const std::string& serializedFile) { + std::string hash; + if (!Hash(serializedFile, &hash)) { + ALOGE("StoreFileWithHash: Failed to compute hash"); + return false; + } + + HashedFile hashFile; + hashFile.set_file(serializedFile); + hashFile.set_hash(hash); + + std::string serializedHashFile; + hashFile.SerializeToString(&serializedHashFile); + + return StoreFileRaw(fileName, serializedHashFile); +} + +bool DeviceFiles::StoreFileRaw(const std::string& fileName, const std::string& serializedHashFile) { + MemoryFileSystem::MemoryFile memFile; + memFile.setFileName(fileName); + memFile.setContent(serializedHashFile); + memFile.setFileSize(serializedHashFile.size()); + size_t len = mFileHandle.Write(fileName, memFile); + + if (len != static_cast<size_t>(serializedHashFile.size())) { + ALOGE("StoreFileRaw: Failed to write %s", fileName.c_str()); + ALOGD("StoreFileRaw: expected=%zd, actual=%zu", serializedHashFile.size(), len); + return false; + } + + ALOGD("StoreFileRaw: wrote %zu bytes to %s", serializedHashFile.size(), fileName.c_str()); + return true; +} + +bool DeviceFiles::RetrieveLicense( + const std::string& keySetId, LicenseState* state, std::string* offlineLicense) { + + OfflineFile file; + if (!RetrieveHashedFile(keySetId + kLicenseFileNameExt, &file)) { + return false; + } + + if (file.type() != OfflineFile::LICENSE) { + ALOGE("RetrieveLicense: Invalid file type"); + return false; + } + + if (file.version() != OfflineFile::VERSION_1) { + ALOGE("RetrieveLicense: Invalid file version"); + return false; + } + + if (!file.has_license()) { + ALOGE("RetrieveLicense: License not present"); + return false; + } + + License license = file.license(); + switch (license.state()) { + case License_LicenseState_ACTIVE: + *state = kLicenseStateActive; + break; + case License_LicenseState_RELEASING: + *state = kLicenseStateReleasing; + break; + default: + ALOGW("RetrieveLicense: Unrecognized license state: %u", + kLicenseStateUnknown); + *state = kLicenseStateUnknown; + break; + } + *offlineLicense = license.license(); + return true; +} + +bool DeviceFiles::DeleteLicense(const std::string& keySetId) { + return mFileHandle.RemoveFile(keySetId + kLicenseFileNameExt); +} + +bool DeviceFiles::DeleteAllLicenses() { + return mFileHandle.RemoveAllFiles(); +} + +bool DeviceFiles::LicenseExists(const std::string& keySetId) { + return mFileHandle.FileExists(keySetId + kLicenseFileNameExt); +} + +std::vector<std::string> DeviceFiles::ListLicenses() const { + std::vector<std::string> licenses = mFileHandle.ListFiles(); + for (size_t i = 0; i < licenses.size(); i++) { + std::string& license = licenses[i]; + license = license.substr(0, license.size() - strlen(kLicenseFileNameExt)); + } + return licenses; +} + +bool DeviceFiles::RetrieveHashedFile(const std::string& fileName, OfflineFile* deSerializedFile) { + if (!deSerializedFile) { + ALOGE("RetrieveHashedFile: invalid file parameter"); + return false; + } + + if (!FileExists(fileName)) { + ALOGE("RetrieveHashedFile: %s does not exist", fileName.c_str()); + return false; + } + + ssize_t bytes = GetFileSize(fileName); + if (bytes <= 0) { + ALOGE("RetrieveHashedFile: invalid file size: %s", fileName.c_str()); + // Remove the corrupted file so the caller will not get the same error + // when trying to access the file repeatedly, causing the system to stall. + RemoveFile(fileName); + return false; + } + + std::string serializedHashFile; + serializedHashFile.resize(bytes); + bytes = mFileHandle.Read(fileName, &serializedHashFile); + + if (bytes != static_cast<ssize_t>(serializedHashFile.size())) { + ALOGE("RetrieveHashedFile: Failed to read from %s", fileName.c_str()); + ALOGV("RetrieveHashedFile: expected: %zd, actual: %zd", serializedHashFile.size(), bytes); + // Remove the corrupted file so the caller will not get the same error + // when trying to access the file repeatedly, causing the system to stall. + RemoveFile(fileName); + return false; + } + + ALOGV("RetrieveHashedFile: read %zd from %s", bytes, fileName.c_str()); + + HashedFile hashFile; + if (!hashFile.ParseFromString(serializedHashFile)) { + ALOGE("RetrieveHashedFile: Unable to parse hash file"); + // Remove corrupt file. + RemoveFile(fileName); + return false; + } + + std::string hash; + if (!Hash(hashFile.file(), &hash)) { + ALOGE("RetrieveHashedFile: Hash computation failed"); + return false; + } + + if (hash != hashFile.hash()) { + ALOGE("RetrieveHashedFile: Hash mismatch"); + // Remove corrupt file. + RemoveFile(fileName); + return false; + } + + if (!deSerializedFile->ParseFromString(hashFile.file())) { + ALOGE("RetrieveHashedFile: Unable to parse file"); + // Remove corrupt file. + RemoveFile(fileName); + return false; + } + + return true; +} + +bool DeviceFiles::FileExists(const std::string& fileName) const { + return mFileHandle.FileExists(fileName); +} + +bool DeviceFiles::RemoveFile(const std::string& fileName) { + return mFileHandle.RemoveFile(fileName); +} + +ssize_t DeviceFiles::GetFileSize(const std::string& fileName) const { + return mFileHandle.GetFileSize(fileName); +} + +} // namespace clearkey +} // namespace V1_2 +} // namespace drm +} // namespace hardware +} // namespace android
diff --git a/drm/mediadrm/plugins/clearkey/hidl/DrmFactory.cpp b/drm/mediadrm/plugins/clearkey/hidl/DrmFactory.cpp index 77557f9..9fb5bbe 100644 --- a/drm/mediadrm/plugins/clearkey/hidl/DrmFactory.cpp +++ b/drm/mediadrm/plugins/clearkey/hidl/DrmFactory.cpp
@@ -30,10 +30,11 @@ namespace android { namespace hardware { namespace drm { -namespace V1_1 { +namespace V1_2 { namespace clearkey { using ::android::hardware::drm::V1_0::Status; +using ::android::hardware::drm::V1_1::SecurityLevel; using ::android::hardware::Void; Return<bool> DrmFactory::isCryptoSchemeSupported( @@ -41,6 +42,13 @@ return clearkeydrm::isClearKeyUUID(uuid.data()); } +Return<bool> DrmFactory::isCryptoSchemeSupported_1_2(const hidl_array<uint8_t, 16>& uuid, + const hidl_string &mimeType, + SecurityLevel level) { + return isCryptoSchemeSupported(uuid) && isContentTypeSupported(mimeType) && + level == SecurityLevel::SW_SECURE_CRYPTO; +} + Return<bool> DrmFactory::isContentTypeSupported(const hidl_string &mimeType) { // This should match the mimeTypes handed by InitDataParser. return mimeType == kIsoBmffVideoMimeType || @@ -71,7 +79,7 @@ } } // namespace clearkey -} // namespace V1_1 +} // namespace V1_2 } // namespace drm } // namespace hardware } // namespace android
diff --git a/drm/mediadrm/plugins/clearkey/hidl/DrmPlugin.cpp b/drm/mediadrm/plugins/clearkey/hidl/DrmPlugin.cpp index 30f7459..7cb5a38 100644 --- a/drm/mediadrm/plugins/clearkey/hidl/DrmPlugin.cpp +++ b/drm/mediadrm/plugins/clearkey/hidl/DrmPlugin.cpp
@@ -25,11 +25,15 @@ #include "ClearKeyDrmProperties.h" #include "Session.h" #include "TypeConvert.h" +#include "Utils.h" namespace { +const std::string kKeySetIdPrefix("ckid"); +const int kKeySetIdLength = 16; const int kSecureStopIdStart = 100; +const std::string kOfflineLicense("\"type\":\"persistent-license\""); const std::string kStreaming("Streaming"); -const std::string kOffline("Offline"); +const std::string kTemporaryLicense("\"type\":\"temporary\""); const std::string kTrue("True"); const std::string kQueryKeyLicenseType("LicenseType"); @@ -54,18 +58,31 @@ namespace android { namespace hardware { namespace drm { -namespace V1_1 { +namespace V1_2 { namespace clearkey { +KeyRequestType toKeyRequestType_V1_0(KeyRequestType_V1_1 keyRequestType) { + switch (keyRequestType) { + case KeyRequestType_V1_1::NONE: + case KeyRequestType_V1_1::UPDATE: + return KeyRequestType::UNKNOWN; + default: + return static_cast<KeyRequestType>(keyRequestType); + } +} + DrmPlugin::DrmPlugin(SessionLibrary* sessionLibrary) : mSessionLibrary(sessionLibrary), mOpenSessionOkCount(0), mCloseSessionOkCount(0), mCloseSessionNotOpenedCount(0), - mNextSecureStopId(kSecureStopIdStart) { + mNextSecureStopId(kSecureStopIdStart), + mMockError(Status_V1_2::OK) { mPlayPolicy.clear(); initProperties(); mSecureStops.clear(); + mReleaseKeysMap.clear(); + std::srand(std::time(nullptr)); } void DrmPlugin::initProperties() { @@ -75,6 +92,7 @@ mStringProperties[kPluginDescriptionKey] = kPluginDescriptionValue; mStringProperties[kAlgorithmsKey] = kAlgorithmsValue; mStringProperties[kListenerTestSupportKey] = kListenerTestSupportValue; + mStringProperties[kDrmErrorTestKey] = kDrmErrorTestValue; std::vector<uint8_t> valueVector; valueVector.clear(); @@ -103,6 +121,7 @@ Return<void> DrmPlugin::openSession(openSession_cb _hidl_cb) { sp<Session> session = mSessionLibrary->createSession(); + processMockError(session); std::vector<uint8_t> sessionId = session->sessionId(); Status status = setSecurityLevel(sessionId, SecurityLevel::SW_SECURE_CRYPTO); @@ -114,6 +133,7 @@ Return<void> DrmPlugin::openSession_1_1(SecurityLevel securityLevel, openSession_1_1_cb _hidl_cb) { sp<Session> session = mSessionLibrary->createSession(); + processMockError(session); std::vector<uint8_t> sessionId = session->sessionId(); Status status = setSecurityLevel(sessionId, securityLevel); @@ -129,6 +149,10 @@ sp<Session> session = mSessionLibrary->findSession(toVector(sessionId)); if (session.get()) { + if (session->getMockError() != Status_V1_2::OK) { + sendSessionLostState(sessionId); + return Status::ERROR_DRM_INVALID_STATE; + } mCloseSessionOkCount++; mSessionLibrary->destroySession(session); return Status::OK; @@ -137,35 +161,75 @@ return Status::ERROR_DRM_SESSION_NOT_OPENED; } -Status DrmPlugin::getKeyRequestCommon(const hidl_vec<uint8_t>& scope, +Status_V1_2 DrmPlugin::getKeyRequestCommon(const hidl_vec<uint8_t>& scope, const hidl_vec<uint8_t>& initData, const hidl_string& mimeType, KeyType keyType, const hidl_vec<KeyValue>& optionalParameters, std::vector<uint8_t> *request, - KeyRequestType *keyRequestType, + KeyRequestType_V1_1 *keyRequestType, std::string *defaultUrl) { UNUSED(optionalParameters); + // GetKeyRequestOfflineKeyTypeNotSupported() in vts 1.0 and 1.1 expects + // KeyType::OFFLINE to return ERROR_DRM_CANNOT_HANDLE in clearkey plugin. + // Those tests pass in an empty initData, we use the empty initData to + // signal such specific use case. + if (keyType == KeyType::OFFLINE && 0 == initData.size()) { + return Status_V1_2::ERROR_DRM_CANNOT_HANDLE; + } + *defaultUrl = ""; - *keyRequestType = KeyRequestType::UNKNOWN; + *keyRequestType = KeyRequestType_V1_1::UNKNOWN; *request = std::vector<uint8_t>(); - if (scope.size() == 0) { - return Status::BAD_VALUE; + if (scope.size() == 0 || + (keyType != KeyType::STREAMING && + keyType != KeyType::OFFLINE && + keyType != KeyType::RELEASE)) { + return Status_V1_2::BAD_VALUE; } - if (keyType != KeyType::STREAMING) { - return Status::ERROR_DRM_CANNOT_HANDLE; + const std::vector<uint8_t> scopeId = toVector(scope); + sp<Session> session; + if (keyType == KeyType::STREAMING || keyType == KeyType::OFFLINE) { + std::vector<uint8_t> sessionId(scopeId.begin(), scopeId.end()); + session = mSessionLibrary->findSession(sessionId); + if (!session.get()) { + return Status_V1_2::ERROR_DRM_SESSION_NOT_OPENED; + } else if (session->getMockError() != Status_V1_2::OK) { + return session->getMockError(); + } + + *keyRequestType = KeyRequestType_V1_1::INITIAL; } - sp<Session> session = mSessionLibrary->findSession(toVector(scope)); - if (!session.get()) { - return Status::ERROR_DRM_SESSION_NOT_OPENED; - } + Status_V1_2 status = static_cast<Status_V1_2>( + session->getKeyRequest(initData, mimeType, keyType, request)); - Status status = session->getKeyRequest(initData, mimeType, request); - *keyRequestType = KeyRequestType::INITIAL; + if (keyType == KeyType::RELEASE) { + std::vector<uint8_t> keySetId(scopeId.begin(), scopeId.end()); + std::string requestString(request->begin(), request->end()); + if (requestString.find(kOfflineLicense) != std::string::npos) { + std::string emptyResponse; + std::string keySetIdString(keySetId.begin(), keySetId.end()); + if (!mFileHandle.StoreLicense(keySetIdString, + DeviceFiles::kLicenseStateReleasing, + emptyResponse)) { + ALOGE("Problem releasing offline license"); + return Status_V1_2::ERROR_DRM_UNKNOWN; + } + if (mReleaseKeysMap.find(keySetIdString) == mReleaseKeysMap.end()) { + sp<Session> session = mSessionLibrary->createSession(); + mReleaseKeysMap[keySetIdString] = session->sessionId(); + } else { + ALOGI("key is in use, ignore release request"); + } + } else { + ALOGE("Offline license not found, nothing to release"); + } + *keyRequestType = KeyRequestType_V1_1::RELEASE; + } return status; } @@ -178,15 +242,15 @@ getKeyRequest_cb _hidl_cb) { UNUSED(optionalParameters); - KeyRequestType keyRequestType = KeyRequestType::UNKNOWN; + KeyRequestType_V1_1 keyRequestType = KeyRequestType_V1_1::UNKNOWN; std::string defaultUrl(""); std::vector<uint8_t> request; - Status status = getKeyRequestCommon( + Status_V1_2 status = getKeyRequestCommon( scope, initData, mimeType, keyType, optionalParameters, &request, &keyRequestType, &defaultUrl); - _hidl_cb(status, toHidlVec(request), - static_cast<drm::V1_0::KeyRequestType>(keyRequestType), + _hidl_cb(toStatus_1_0(status), toHidlVec(request), + toKeyRequestType_V1_0(keyRequestType), hidl_string(defaultUrl)); return Void(); } @@ -200,10 +264,31 @@ getKeyRequest_1_1_cb _hidl_cb) { UNUSED(optionalParameters); - KeyRequestType keyRequestType = KeyRequestType::UNKNOWN; + KeyRequestType_V1_1 keyRequestType = KeyRequestType_V1_1::UNKNOWN; std::string defaultUrl(""); std::vector<uint8_t> request; - Status status = getKeyRequestCommon( + Status_V1_2 status = getKeyRequestCommon( + scope, initData, mimeType, keyType, optionalParameters, + &request, &keyRequestType, &defaultUrl); + + _hidl_cb(toStatus_1_0(status), toHidlVec(request), + keyRequestType, hidl_string(defaultUrl)); + return Void(); +} + +Return<void> DrmPlugin::getKeyRequest_1_2( + const hidl_vec<uint8_t>& scope, + const hidl_vec<uint8_t>& initData, + const hidl_string& mimeType, + KeyType keyType, + const hidl_vec<KeyValue>& optionalParameters, + getKeyRequest_1_2_cb _hidl_cb) { + UNUSED(optionalParameters); + + KeyRequestType_V1_1 keyRequestType = KeyRequestType_V1_1::UNKNOWN; + std::string defaultUrl(""); + std::vector<uint8_t> request; + Status_V1_2 status = getKeyRequestCommon( scope, initData, mimeType, keyType, optionalParameters, &request, &keyRequestType, &defaultUrl); @@ -227,6 +312,30 @@ mPlayPolicy.push_back(policy); } +bool DrmPlugin::makeKeySetId(std::string* keySetId) { + if (!keySetId) { + ALOGE("keySetId destination not provided"); + return false; + } + std::vector<uint8_t> ksid(kKeySetIdPrefix.begin(), kKeySetIdPrefix.end()); + ksid.resize(kKeySetIdLength); + std::vector<uint8_t> randomData((kKeySetIdLength - kKeySetIdPrefix.size()) / 2, 0); + + while (keySetId->empty()) { + for (auto itr = randomData.begin(); itr != randomData.end(); ++itr) { + *itr = std::rand() % 0xff; + } + *keySetId = kKeySetIdPrefix + ByteArrayToHexString( + reinterpret_cast<const uint8_t*>(randomData.data()), randomData.size()); + if (mFileHandle.LicenseExists(*keySetId)) { + // collision, regenerate + ALOGV("Retry generating KeySetId"); + keySetId->clear(); + } + } + return true; +} + Return<void> DrmPlugin::provideKeyResponse( const hidl_vec<uint8_t>& scope, const hidl_vec<uint8_t>& response, @@ -237,28 +346,124 @@ return Void(); } - sp<Session> session = mSessionLibrary->findSession(toVector(scope)); + std::string responseString( + reinterpret_cast<const char*>(response.data()), response.size()); + const std::vector<uint8_t> scopeId = toVector(scope); + std::vector<uint8_t> sessionId; + std::string keySetId; + + Status status = Status::OK; + bool isOfflineLicense = responseString.find(kOfflineLicense) != std::string::npos; + bool isRelease = (memcmp(scopeId.data(), kKeySetIdPrefix.data(), kKeySetIdPrefix.size()) == 0); + if (isRelease) { + keySetId.assign(scopeId.begin(), scopeId.end()); + + auto iter = mReleaseKeysMap.find(std::string(keySetId.begin(), keySetId.end())); + if (iter != mReleaseKeysMap.end()) { + sessionId.assign(iter->second.begin(), iter->second.end()); + } + } else { + sessionId.assign(scopeId.begin(), scopeId.end()); + // non offline license returns empty keySetId + keySetId.clear(); + } + + sp<Session> session = mSessionLibrary->findSession(sessionId); if (!session.get()) { _hidl_cb(Status::ERROR_DRM_SESSION_NOT_OPENED, hidl_vec<uint8_t>()); return Void(); } - setPlayPolicy(); - std::vector<uint8_t> keySetId; - Status status = session->provideKeyResponse(response); + + status = session->provideKeyResponse(response); if (status == Status::OK) { - // This is for testing AMediaDrm_setOnEventListener only. - sendEvent(EventType::VENDOR_DEFINED, 0, scope); - keySetId.clear(); + if (isOfflineLicense) { + if (isRelease) { + mFileHandle.DeleteLicense(keySetId); + } else { + if (!makeKeySetId(&keySetId)) { + _hidl_cb(Status::ERROR_DRM_UNKNOWN, hidl_vec<uint8_t>()); + return Void(); + } + + bool ok = mFileHandle.StoreLicense( + keySetId, + DeviceFiles::kLicenseStateActive, + std::string(response.begin(), response.end())); + if (!ok) { + ALOGE("Failed to store offline license"); + } + } + } + + // Test calling AMediaDrm listeners. + sendEvent(EventType::VENDOR_DEFINED, sessionId, sessionId); + + sendExpirationUpdate(sessionId, 100); + + std::vector<KeyStatus_V1_2> keysStatus; + KeyStatus_V1_2 keyStatus; + + std::vector<uint8_t> keyId1 = { 0xA, 0xB, 0xC }; + keyStatus.keyId = keyId1; + keyStatus.type = V1_2::KeyStatusType::USABLE; + keysStatus.push_back(keyStatus); + + std::vector<uint8_t> keyId2 = { 0xD, 0xE, 0xF }; + keyStatus.keyId = keyId2; + keyStatus.type = V1_2::KeyStatusType::EXPIRED; + keysStatus.push_back(keyStatus); + + std::vector<uint8_t> keyId3 = { 0x0, 0x1, 0x2 }; + keyStatus.keyId = keyId3; + keyStatus.type = V1_2::KeyStatusType::USABLEINFUTURE; + keysStatus.push_back(keyStatus); + + sendKeysChange_1_2(sessionId, keysStatus, true); + + installSecureStop(sessionId); + } else { + ALOGE("provideKeyResponse returns error=%d", status); } - installSecureStop(scope); - - // Returns status and empty keySetId - _hidl_cb(status, toHidlVec(keySetId)); + std::vector<uint8_t> keySetIdVec(keySetId.begin(), keySetId.end()); + _hidl_cb(status, toHidlVec(keySetIdVec)); return Void(); } +Return<Status> DrmPlugin::restoreKeys( + const hidl_vec<uint8_t>& sessionId, const hidl_vec<uint8_t>& keySetId) { + if (sessionId.size() == 0 || keySetId.size() == 0) { + return Status::BAD_VALUE; + } + + DeviceFiles::LicenseState licenseState; + std::string offlineLicense; + Status status = Status::OK; + if (!mFileHandle.RetrieveLicense(std::string(keySetId.begin(), keySetId.end()), + &licenseState, &offlineLicense)) { + ALOGE("Failed to restore offline license"); + return Status::ERROR_DRM_NO_LICENSE; + } + + if (DeviceFiles::kLicenseStateUnknown == licenseState || + DeviceFiles::kLicenseStateReleasing == licenseState) { + ALOGE("Invalid license state=%d", licenseState); + return Status::ERROR_DRM_NO_LICENSE; + } + + sp<Session> session = mSessionLibrary->findSession(toVector(sessionId)); + if (!session.get()) { + return Status::ERROR_DRM_SESSION_NOT_OPENED; + } + status = session->provideKeyResponse(std::vector<uint8_t>(offlineLicense.begin(), + offlineLicense.end())); + if (status != Status::OK) { + ALOGE("Failed to restore keys"); + } + return status; +} + Return<void> DrmPlugin::getPropertyString( const hidl_string& propertyName, getPropertyString_cb _hidl_cb) { std::string name(propertyName.c_str()); @@ -274,6 +479,8 @@ value = mStringProperties[kAlgorithmsKey]; } else if (name == kListenerTestSupportKey) { value = mStringProperties[kListenerTestSupportKey]; + } else if (name == kDrmErrorTestKey) { + value = mStringProperties[kDrmErrorTestKey]; } else { ALOGE("App requested unknown string property %s", name.c_str()); _hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, ""); @@ -318,6 +525,20 @@ return Status::BAD_VALUE; } + if (name == kDrmErrorTestKey) { + if (value == kResourceContentionValue) { + mMockError = Status_V1_2::ERROR_DRM_RESOURCE_CONTENTION; + } else if (value == kLostStateValue) { + mMockError = Status_V1_2::ERROR_DRM_SESSION_LOST_STATE; + } else if (value == kFrameTooLargeValue) { + mMockError = Status_V1_2::ERROR_DRM_FRAME_TOO_LARGE; + } else if (value == kInvalidStateValue) { + mMockError = Status_V1_2::ERROR_DRM_INVALID_STATE; + } else { + mMockError = Status_V1_2::ERROR_DRM_UNKNOWN; + } + } + mStringProperties[key] = std::string(value.c_str()); return Status::OK; } @@ -328,6 +549,9 @@ if (name == kDeviceIdKey) { ALOGD("Cannot set immutable property: %s", name.c_str()); return Status::BAD_VALUE; + } else if (name == kClientIdKey) { + mByteArrayProperties[kClientIdKey] = toVector(value); + return Status::OK; } // Setting of undefined properties is not supported @@ -465,6 +689,60 @@ return Void(); } +Return<void> DrmPlugin::getOfflineLicenseKeySetIds(getOfflineLicenseKeySetIds_cb _hidl_cb) { + std::vector<std::string> licenseNames = mFileHandle.ListLicenses(); + std::vector<KeySetId> keySetIds; + if (mMockError != Status_V1_2::OK) { + _hidl_cb(toStatus_1_0(mMockError), keySetIds); + return Void(); + } + for (const auto& name : licenseNames) { + std::vector<uint8_t> keySetId(name.begin(), name.end()); + keySetIds.push_back(keySetId); + } + _hidl_cb(Status::OK, keySetIds); + return Void(); +} + + +Return<Status> DrmPlugin::removeOfflineLicense(const KeySetId& keySetId) { + if (mMockError != Status_V1_2::OK) { + return toStatus_1_0(mMockError); + } + std::string licenseName(keySetId.begin(), keySetId.end()); + if (mFileHandle.DeleteLicense(licenseName)) { + return Status::OK; + } + return Status::BAD_VALUE; +} + +Return<void> DrmPlugin::getOfflineLicenseState(const KeySetId& keySetId, + getOfflineLicenseState_cb _hidl_cb) { + std::string licenseName(keySetId.begin(), keySetId.end()); + DeviceFiles::LicenseState state; + std::string license; + OfflineLicenseState hLicenseState; + if (mMockError != Status_V1_2::OK) { + _hidl_cb(toStatus_1_0(mMockError), OfflineLicenseState::UNKNOWN); + } else if (mFileHandle.RetrieveLicense(licenseName, &state, &license)) { + switch (state) { + case DeviceFiles::kLicenseStateActive: + hLicenseState = OfflineLicenseState::USABLE; + break; + case DeviceFiles::kLicenseStateReleasing: + hLicenseState = OfflineLicenseState::INACTIVE; + break; + case DeviceFiles::kLicenseStateUnknown: + hLicenseState = OfflineLicenseState::UNKNOWN; + break; + } + _hidl_cb(Status::OK, hLicenseState); + } else { + _hidl_cb(Status::BAD_VALUE, OfflineLicenseState::UNKNOWN); + } + return Void(); +} + Return<void> DrmPlugin::getSecureStops(getSecureStops_cb _hidl_cb) { std::vector<SecureStop> stops; for (auto itr = mSecureStops.begin(); itr != mSecureStops.end(); ++itr) { @@ -572,7 +850,7 @@ } } // namespace clearkey -} // namespace V1_1 +} // namespace V1_2 } // namespace drm } // namespace hardware } // namespace android
diff --git a/drm/mediadrm/plugins/clearkey/hidl/InitDataParser.cpp b/drm/mediadrm/plugins/clearkey/hidl/InitDataParser.cpp index e2bb651..b988ce0 100644 --- a/drm/mediadrm/plugins/clearkey/hidl/InitDataParser.cpp +++ b/drm/mediadrm/plugins/clearkey/hidl/InitDataParser.cpp
@@ -31,7 +31,7 @@ namespace android { namespace hardware { namespace drm { -namespace V1_1 { +namespace V1_2 { namespace clearkey { namespace { @@ -45,21 +45,22 @@ } Status InitDataParser::parse(const std::vector<uint8_t>& initData, - const std::string& type, + const std::string& mimeType, + V1_0::KeyType keyType, std::vector<uint8_t>* licenseRequest) { // Build a list of the key IDs std::vector<const uint8_t*> keyIds; - if (type == kIsoBmffVideoMimeType || - type == kIsoBmffAudioMimeType || - type == kCencInitDataFormat) { + if (mimeType == kIsoBmffVideoMimeType.c_str() || + mimeType == kIsoBmffAudioMimeType.c_str() || + mimeType == kCencInitDataFormat.c_str()) { Status res = parsePssh(initData, &keyIds); if (res != Status::OK) { return res; } - } else if (type == kWebmVideoMimeType || - type == kWebmAudioMimeType || - type == kWebmInitDataFormat) { + } else if (mimeType == kWebmVideoMimeType.c_str() || + mimeType == kWebmAudioMimeType.c_str() || + mimeType == kWebmInitDataFormat.c_str()) { // WebM "init data" is just a single key ID if (initData.size() != kKeyIdSize) { return Status::ERROR_DRM_CANNOT_HANDLE; @@ -69,8 +70,12 @@ return Status::ERROR_DRM_CANNOT_HANDLE; } + if (keyType == V1_0::KeyType::RELEASE) { + // restore key + } + // Build the request - std::string requestJson = generateRequest(keyIds); + std::string requestJson = generateRequest(keyType, keyIds); std::vector<uint8_t> requestJsonVec = StrToVector(requestJson); licenseRequest->clear(); @@ -131,9 +136,11 @@ return Status::OK; } -std::string InitDataParser::generateRequest(const std::vector<const uint8_t*>& keyIds) { +std::string InitDataParser::generateRequest(V1_0::KeyType keyType, + const std::vector<const uint8_t*>& keyIds) { const std::string kRequestPrefix("{\"kids\":["); - const std::string kRequestSuffix("],\"type\":\"temporary\"}"); + const std::string kTemporarySession("],\"type\":\"temporary\"}"); + const std::string kPersistentSession("],\"type\":\"persistent-license\"}"); std::string request(kRequestPrefix); std::string encodedId; @@ -147,7 +154,12 @@ request.append(encodedId); request.push_back('\"'); } - request.append(kRequestSuffix); + if (keyType == V1_0::KeyType::STREAMING) { + request.append(kTemporarySession); + } else if (keyType == V1_0::KeyType::OFFLINE || + keyType == V1_0::KeyType::RELEASE) { + request.append(kPersistentSession); + } // Android's Base64 encoder produces padding. EME forbids padding. const char kBase64Padding = '='; @@ -157,7 +169,7 @@ } } // namespace clearkey -} // namespace V1_1 +} // namespace V1_2 } // namespace drm } // namespace hardware } // namespace android
diff --git a/drm/mediadrm/plugins/clearkey/hidl/JsonWebKey.cpp b/drm/mediadrm/plugins/clearkey/hidl/JsonWebKey.cpp index cccb41e..d93777d 100644 --- a/drm/mediadrm/plugins/clearkey/hidl/JsonWebKey.cpp +++ b/drm/mediadrm/plugins/clearkey/hidl/JsonWebKey.cpp
@@ -22,18 +22,21 @@ #include "Base64.h" namespace { +const std::string kBase64Padding("="); const std::string kKeysTag("keys"); const std::string kKeyTypeTag("kty"); -const std::string kSymmetricKeyValue("oct"); const std::string kKeyTag("k"); const std::string kKeyIdTag("kid"); -const std::string kBase64Padding("="); +const std::string kMediaSessionType("type"); +const std::string kPersistentLicenseSession("persistent-license"); +const std::string kSymmetricKeyValue("oct"); +const std::string kTemporaryLicenseSession("temporary"); } namespace android { namespace hardware { namespace drm { -namespace V1_1 { +namespace V1_2 { namespace clearkey { JsonWebKey::JsonWebKey() { @@ -268,7 +271,7 @@ } } // namespace clearkey -} // namespace V1_1 +} // namespace V1_2 } // namespace drm } // namespace hardware } // namespace android
diff --git a/drm/mediadrm/plugins/clearkey/hidl/MemoryFileSystem.cpp b/drm/mediadrm/plugins/clearkey/hidl/MemoryFileSystem.cpp new file mode 100644 index 0000000..2dcd00f --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/hidl/MemoryFileSystem.cpp
@@ -0,0 +1,92 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include <utils/Log.h> +#include <string> + +#include "MemoryFileSystem.h" +#include "Utils.h" + +namespace android { +namespace hardware { +namespace drm { +namespace V1_2 { +namespace clearkey { + +std::string MemoryFileSystem::GetFileName(const std::string& path) { + size_t index = path.find_last_of("/"); + if (index != std::string::npos) { + return path.substr(index+1); + } else { + return path; + } +} + +bool MemoryFileSystem::FileExists(const std::string& fileName) const { + auto result = mMemoryFileSystem.find(fileName); + return result != mMemoryFileSystem.end(); +} + +ssize_t MemoryFileSystem::GetFileSize(const std::string& fileName) const { + auto result = mMemoryFileSystem.find(fileName); + if (result != mMemoryFileSystem.end()) { + return static_cast<ssize_t>(result->second.getFileSize()); + } else { + ALOGE("Failed to get size for %s", fileName.c_str()); + return -1; + } +} + +std::vector<std::string> MemoryFileSystem::ListFiles() const { + std::vector<std::string> list; + for (const auto& filename : mMemoryFileSystem) { + list.push_back(filename.first); + } + return list; +} + +size_t MemoryFileSystem::Read(const std::string& path, std::string* buffer) { + std::string key = GetFileName(path); + auto result = mMemoryFileSystem.find(key); + if (result != mMemoryFileSystem.end()) { + std::string serializedHashFile = result->second.getContent(); + buffer->assign(serializedHashFile); + return buffer->size(); + } else { + ALOGE("Failed to read from %s", path.c_str()); + return -1; + } +} + +size_t MemoryFileSystem::Write(const std::string& path, const MemoryFile& memoryFile) { + std::string key = GetFileName(path); + auto result = mMemoryFileSystem.find(key); + if (result != mMemoryFileSystem.end()) { + mMemoryFileSystem.erase(key); + } + mMemoryFileSystem.insert(std::pair<std::string, MemoryFile>(key, memoryFile)); + return memoryFile.getFileSize(); +} + +bool MemoryFileSystem::RemoveFile(const std::string& fileName) { + auto result = mMemoryFileSystem.find(fileName); + if (result != mMemoryFileSystem.end()) { + mMemoryFileSystem.erase(result); + return true; + } else { + ALOGE("Cannot find license to remove: %s", fileName.c_str()); + return false; + } +} + +bool MemoryFileSystem::RemoveAllFiles() { + mMemoryFileSystem.clear(); + return mMemoryFileSystem.empty(); +} + +} // namespace clearkey +} // namespace V1_2 +} // namespace drm +} // namespace hardware +} // namespace android
diff --git a/drm/mediadrm/plugins/clearkey/hidl/Session.cpp b/drm/mediadrm/plugins/clearkey/hidl/Session.cpp index 07c9269..a9d7016 100644 --- a/drm/mediadrm/plugins/clearkey/hidl/Session.cpp +++ b/drm/mediadrm/plugins/clearkey/hidl/Session.cpp
@@ -28,7 +28,7 @@ namespace android { namespace hardware { namespace drm { -namespace V1_1 { +namespace V1_2 { namespace clearkey { using ::android::hardware::drm::V1_0::KeyValue; @@ -42,9 +42,10 @@ Status Session::getKeyRequest( const std::vector<uint8_t>& initData, const std::string& mimeType, + V1_0::KeyType keyType, std::vector<uint8_t>* keyRequest) const { InitDataParser parser; - return parser.parse(initData, mimeType, keyRequest); + return parser.parse(initData, mimeType, keyType, keyRequest); } Status Session::provideKeyResponse(const std::vector<uint8_t>& response) { @@ -67,29 +68,34 @@ } } -Status Session::decrypt( +Status_V1_2 Session::decrypt( const KeyId keyId, const Iv iv, const uint8_t* srcPtr, uint8_t* destPtr, const std::vector<SubSample> subSamples, size_t* bytesDecryptedOut) { Mutex::Autolock lock(mMapLock); + if (getMockError() != Status_V1_2::OK) { + return getMockError(); + } + std::vector<uint8_t> keyIdVector; keyIdVector.clear(); keyIdVector.insert(keyIdVector.end(), keyId, keyId + kBlockSize); std::map<std::vector<uint8_t>, std::vector<uint8_t> >::iterator itr; itr = mKeyMap.find(keyIdVector); if (itr == mKeyMap.end()) { - return Status::ERROR_DRM_NO_LICENSE; + return Status_V1_2::ERROR_DRM_NO_LICENSE; } AesCtrDecryptor decryptor; - return decryptor.decrypt( + Status status = decryptor.decrypt( itr->second /*key*/, iv, srcPtr, destPtr, subSamples, subSamples.size(), bytesDecryptedOut); + return static_cast<Status_V1_2>(status); } } // namespace clearkey -} // namespace V1_1 +} // namespace V1_2 } // namespace drm } // namespace hardware } // namespace android
diff --git a/drm/mediadrm/plugins/clearkey/hidl/SessionLibrary.cpp b/drm/mediadrm/plugins/clearkey/hidl/SessionLibrary.cpp index b4319e6..99fb30f 100644 --- a/drm/mediadrm/plugins/clearkey/hidl/SessionLibrary.cpp +++ b/drm/mediadrm/plugins/clearkey/hidl/SessionLibrary.cpp
@@ -24,7 +24,7 @@ namespace android { namespace hardware { namespace drm { -namespace V1_1 { +namespace V1_2 { namespace clearkey { using ::android::hardware::hidl_string; @@ -59,7 +59,8 @@ mSessions.insert(std::pair<std::vector<uint8_t>, sp<Session> >(sessionId, new Session(sessionId))); - std::map<std::vector<uint8_t>, sp<Session> >::iterator itr = mSessions.find(sessionId); + std::map<std::vector<uint8_t>, sp<Session> >::iterator itr = + mSessions.find(sessionId); if (itr != mSessions.end()) { return itr->second; } else { @@ -70,7 +71,8 @@ sp<Session> SessionLibrary::findSession( const std::vector<uint8_t>& sessionId) { Mutex::Autolock lock(mSessionsLock); - std::map<std::vector<uint8_t>, sp<Session> >::iterator itr = mSessions.find(sessionId); + std::map<std::vector<uint8_t>, sp<Session> >::iterator itr = + mSessions.find(sessionId); if (itr != mSessions.end()) { return itr->second; } else { @@ -84,7 +86,7 @@ } } // namespace clearkey -} // namespace V1_1 +} // namespace V1_2 } // namespace drm } // namespace hardware } // namespace android
diff --git a/drm/mediadrm/plugins/clearkey/hidl/android.hardware.drm@1.1-service.clearkey.rc b/drm/mediadrm/plugins/clearkey/hidl/android.hardware.drm@1.1-service.clearkey.rc deleted file mode 100644 index ffe856a..0000000 --- a/drm/mediadrm/plugins/clearkey/hidl/android.hardware.drm@1.1-service.clearkey.rc +++ /dev/null
@@ -1,6 +0,0 @@ -service vendor.drm-clearkey-hal-1-1 /vendor/bin/hw/android.hardware.drm@1.1-service.clearkey - class hal - user media - group media mediadrm - ioprio rt 4 - writepid /dev/cpuset/foreground/tasks
diff --git a/drm/mediadrm/plugins/clearkey/hidl/android.hardware.drm@1.2-service-lazy.clearkey.rc b/drm/mediadrm/plugins/clearkey/hidl/android.hardware.drm@1.2-service-lazy.clearkey.rc new file mode 100644 index 0000000..9afd3d7 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/hidl/android.hardware.drm@1.2-service-lazy.clearkey.rc
@@ -0,0 +1,14 @@ +service vendor.drm-clearkey-hal-1-2 /vendor/bin/hw/android.hardware.drm@1.2-service-lazy.clearkey + interface android.hardware.drm@1.0::ICryptoFactory clearkey + interface android.hardware.drm@1.0::IDrmFactory clearkey + interface android.hardware.drm@1.1::ICryptoFactory clearkey + interface android.hardware.drm@1.1::IDrmFactory clearkey + interface android.hardware.drm@1.2::ICryptoFactory clearkey + interface android.hardware.drm@1.2::IDrmFactory clearkey + disabled + oneshot + class hal + user media + group media mediadrm + ioprio rt 4 + writepid /dev/cpuset/foreground/tasks
diff --git a/drm/mediadrm/plugins/clearkey/hidl/android.hardware.drm@1.2-service.clearkey.rc b/drm/mediadrm/plugins/clearkey/hidl/android.hardware.drm@1.2-service.clearkey.rc new file mode 100644 index 0000000..5ba669d --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/hidl/android.hardware.drm@1.2-service.clearkey.rc
@@ -0,0 +1,12 @@ +service vendor.drm-clearkey-hal-1-2 /vendor/bin/hw/android.hardware.drm@1.2-service.clearkey + interface android.hardware.drm@1.0::ICryptoFactory clearkey + interface android.hardware.drm@1.0::IDrmFactory clearkey + interface android.hardware.drm@1.1::ICryptoFactory clearkey + interface android.hardware.drm@1.1::IDrmFactory clearkey + interface android.hardware.drm@1.2::ICryptoFactory clearkey + interface android.hardware.drm@1.2::IDrmFactory clearkey + class hal + user media + group media mediadrm + ioprio rt 4 + writepid /dev/cpuset/foreground/tasks
diff --git a/drm/mediadrm/plugins/clearkey/hidl/include/AesCtrDecryptor.h b/drm/mediadrm/plugins/clearkey/hidl/include/AesCtrDecryptor.h index 0c7ef20..721f4c0 100644 --- a/drm/mediadrm/plugins/clearkey/hidl/include/AesCtrDecryptor.h +++ b/drm/mediadrm/plugins/clearkey/hidl/include/AesCtrDecryptor.h
@@ -22,7 +22,7 @@ namespace android { namespace hardware { namespace drm { -namespace V1_1 { +namespace V1_2 { namespace clearkey { using ::android::hardware::drm::V1_0::Status; @@ -42,7 +42,7 @@ }; } // namespace clearkey -} // namespace V1_1 +} // namespace V1_2 } // namespace drm } // namespace hardware } // namespace android
diff --git a/drm/mediadrm/plugins/clearkey/hidl/include/Base64.h b/drm/mediadrm/plugins/clearkey/hidl/include/Base64.h index 4a385bd..ec30cc1 100644 --- a/drm/mediadrm/plugins/clearkey/hidl/include/Base64.h +++ b/drm/mediadrm/plugins/clearkey/hidl/include/Base64.h
@@ -25,7 +25,7 @@ namespace android { namespace hardware { namespace drm { -namespace V1_1 { +namespace V1_2 { namespace clearkey { using ::android::sp; @@ -38,7 +38,7 @@ void encodeBase64Url(const void *data, size_t size, std::string *out); } // namespace clearkey -} // namespace V1_1 +} // namespace V1_2 } // namespace drm } // namespace hardware } // namespace android
diff --git a/drm/mediadrm/plugins/clearkey/hidl/include/Buffer.h b/drm/mediadrm/plugins/clearkey/hidl/include/Buffer.h index 5bbb28a..c497e37 100644 --- a/drm/mediadrm/plugins/clearkey/hidl/include/Buffer.h +++ b/drm/mediadrm/plugins/clearkey/hidl/include/Buffer.h
@@ -25,7 +25,7 @@ namespace android { namespace hardware { namespace drm { -namespace V1_1 { +namespace V1_2 { namespace clearkey { using ::android::sp; @@ -54,7 +54,7 @@ }; } // namespace clearkey -} // namespace V1_1 +} // namespace V1_2 } // namespace drm } // namespace hardware } // namespace android
diff --git a/drm/mediadrm/plugins/clearkey/hidl/include/ClearKeyDrmProperties.h b/drm/mediadrm/plugins/clearkey/hidl/include/ClearKeyDrmProperties.h index d65b25c..b83ce69 100644 --- a/drm/mediadrm/plugins/clearkey/hidl/include/ClearKeyDrmProperties.h +++ b/drm/mediadrm/plugins/clearkey/hidl/include/ClearKeyDrmProperties.h
@@ -22,30 +22,40 @@ namespace android { namespace hardware { namespace drm { -namespace V1_1 { +namespace V1_2 { namespace clearkey { static const std::string kVendorKey("vendor"); static const std::string kVendorValue("Google"); static const std::string kVersionKey("version"); -static const std::string kVersionValue("1.1"); +static const std::string kVersionValue("1.2"); static const std::string kPluginDescriptionKey("description"); static const std::string kPluginDescriptionValue("ClearKey CDM"); static const std::string kAlgorithmsKey("algorithms"); static const std::string kAlgorithmsValue(""); static const std::string kListenerTestSupportKey("listenerTestSupport"); static const std::string kListenerTestSupportValue("true"); +static const std::string kDrmErrorTestKey("drmErrorTest"); +static const std::string kDrmErrorTestValue(""); +static const std::string kResourceContentionValue("resourceContention"); +static const std::string kLostStateValue("lostState"); +static const std::string kFrameTooLargeValue("frameTooLarge"); +static const std::string kInvalidStateValue("invalidState"); static const std::string kDeviceIdKey("deviceId"); static const uint8_t kTestDeviceIdData[] = {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf}; + +// settable byte array property +static const std::string kClientIdKey("clientId"); + // TODO stub out metrics for nw static const std::string kMetricsKey("metrics"); static const uint8_t kMetricsData[] = { 0 }; } // namespace clearkey -} // namespace V1_1 +} // namespace V1_2 } // namespace drm } // namespace hardware } // namespace android
diff --git a/drm/mediadrm/plugins/clearkey/hidl/include/ClearKeyTypes.h b/drm/mediadrm/plugins/clearkey/hidl/include/ClearKeyTypes.h index 46cb5e4..03c434e 100644 --- a/drm/mediadrm/plugins/clearkey/hidl/include/ClearKeyTypes.h +++ b/drm/mediadrm/plugins/clearkey/hidl/include/ClearKeyTypes.h
@@ -17,17 +17,18 @@ #ifndef CLEARKEY_MACROS_H_ #define CLEARKEY_MACROS_H_ -#include <android/hardware/drm/1.0/types.h> +#include <android/hardware/drm/1.2/types.h> #include <map> namespace android { namespace hardware { namespace drm { -namespace V1_1 { +namespace V1_2 { namespace clearkey { using ::android::hardware::drm::V1_0::KeyValue; +using ::android::hardware::drm::V1_1::SecurityLevel; using ::android::hardware::hidl_vec; const uint8_t kBlockSize = 16; //AES_BLOCK_SIZE; @@ -47,7 +48,7 @@ void operator=(const TypeName&) = delete; } // namespace clearkey -} // namespace V1_1 +} // namespace V1_2 } // namespace drm } // namespace hardware } // namespace android
diff --git a/drm/mediadrm/plugins/clearkey/hidl/include/CreatePluginFactories.h b/drm/mediadrm/plugins/clearkey/hidl/include/CreatePluginFactories.h index 9952027..6368f3d 100644 --- a/drm/mediadrm/plugins/clearkey/hidl/include/CreatePluginFactories.h +++ b/drm/mediadrm/plugins/clearkey/hidl/include/CreatePluginFactories.h
@@ -17,17 +17,17 @@ #ifndef CLEARKEY_CREATE_PLUGIN_FACTORIES_H_ #define CLEARKEY_CREATE_PLUGIN_FACTORIES_H_ -#include <android/hardware/drm/1.1/ICryptoFactory.h> -#include <android/hardware/drm/1.1/IDrmFactory.h> +#include <android/hardware/drm/1.2/ICryptoFactory.h> +#include <android/hardware/drm/1.2/IDrmFactory.h> namespace android { namespace hardware { namespace drm { -namespace V1_1 { +namespace V1_2 { namespace clearkey { -using ::android::hardware::drm::V1_1::ICryptoFactory; -using ::android::hardware::drm::V1_1::IDrmFactory; +using ::android::hardware::drm::V1_2::ICryptoFactory; +using ::android::hardware::drm::V1_2::IDrmFactory; extern "C" { IDrmFactory* createDrmFactory(); @@ -35,7 +35,7 @@ } } // namespace clearkey -} // namespace V1_1 +} // namespace V1_2 } // namespace drm } // namespace hardware } // namespace android
diff --git a/drm/mediadrm/plugins/clearkey/hidl/include/CryptoFactory.h b/drm/mediadrm/plugins/clearkey/hidl/include/CryptoFactory.h index 175ab76..203bb2d 100644 --- a/drm/mediadrm/plugins/clearkey/hidl/include/CryptoFactory.h +++ b/drm/mediadrm/plugins/clearkey/hidl/include/CryptoFactory.h
@@ -18,17 +18,17 @@ #define CLEARKEY_CRYPTO_FACTORY_H_ #include <android/hardware/drm/1.0/ICryptoPlugin.h> -#include <android/hardware/drm/1.1/ICryptoFactory.h> +#include <android/hardware/drm/1.2/ICryptoFactory.h> #include "ClearKeyTypes.h" namespace android { namespace hardware { namespace drm { -namespace V1_1 { +namespace V1_2 { namespace clearkey { -using ::android::hardware::drm::V1_1::ICryptoFactory; +using ::android::hardware::drm::V1_2::ICryptoFactory; using ::android::hardware::drm::V1_0::ICryptoPlugin; using ::android::hardware::hidl_array; using ::android::hardware::hidl_string; @@ -52,7 +52,7 @@ }; } // namespace clearkey -} // namespace V1_1 +} // namespace V1_2 } // namespace drm } // namespace hardware } // namespace android
diff --git a/drm/mediadrm/plugins/clearkey/hidl/include/CryptoPlugin.h b/drm/mediadrm/plugins/clearkey/hidl/include/CryptoPlugin.h index 6a73806..8680f0c 100644 --- a/drm/mediadrm/plugins/clearkey/hidl/include/CryptoPlugin.h +++ b/drm/mediadrm/plugins/clearkey/hidl/include/CryptoPlugin.h
@@ -17,7 +17,7 @@ #ifndef CLEARKEY_CRYPTO_PLUGIN_H_ #define CLEARKEY_CRYPTO_PLUGIN_H_ -#include <android/hardware/drm/1.0/ICryptoPlugin.h> +#include <android/hardware/drm/1.2/ICryptoPlugin.h> #include <android/hidl/memory/1.0/IMemory.h> #include "ClearKeyTypes.h" @@ -32,16 +32,17 @@ namespace android { namespace hardware { namespace drm { -namespace V1_1 { +namespace V1_2 { namespace clearkey { -using ::android::hardware::drm::V1_0::DestinationBuffer; -using ::android::hardware::drm::V1_0::ICryptoPlugin; -using ::android::hardware::drm::V1_0::Mode; -using ::android::hardware::drm::V1_0::Pattern; -using ::android::hardware::drm::V1_0::SharedBuffer; -using ::android::hardware::drm::V1_0::Status; -using ::android::hardware::drm::V1_0::SubSample; +namespace drm = ::android::hardware::drm; +using drm::V1_0::DestinationBuffer; +using drm::V1_0::Mode; +using drm::V1_0::Pattern; +using drm::V1_0::SharedBuffer; +using drm::V1_0::Status; +using drm::V1_0::SubSample; + using ::android::hardware::hidl_array; using ::android::hardware::hidl_memory; using ::android::hardware::hidl_string; @@ -51,7 +52,9 @@ using ::android::hidl::memory::V1_0::IMemory; using ::android::sp; -struct CryptoPlugin : public ICryptoPlugin { +typedef drm::V1_2::Status Status_V1_2; + +struct CryptoPlugin : public drm::V1_2::ICryptoPlugin { explicit CryptoPlugin(const hidl_vec<uint8_t>& sessionId) { mInitStatus = setMediaDrmSession(sessionId); } @@ -80,6 +83,18 @@ const DestinationBuffer& destination, decrypt_cb _hidl_cb); + Return<void> decrypt_1_2( + bool secure, + const hidl_array<uint8_t, KEY_ID_SIZE>& keyId, + const hidl_array<uint8_t, KEY_IV_SIZE>& iv, + Mode mode, + const Pattern& pattern, + const hidl_vec<SubSample>& subSamples, + const SharedBuffer& source, + uint64_t offset, + const DestinationBuffer& destination, + decrypt_1_2_cb _hidl_cb); + Return<void> setSharedBufferBase(const hidl_memory& base, uint32_t bufferId); @@ -96,7 +111,7 @@ }; } // namespace clearkey -} // namespace V1_1 +} // namespace V1_2 } // namespace drm } // namespace hardware } // namespace android
diff --git a/drm/mediadrm/plugins/clearkey/hidl/include/DeviceFiles.h b/drm/mediadrm/plugins/clearkey/hidl/include/DeviceFiles.h new file mode 100644 index 0000000..554ae59 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/hidl/include/DeviceFiles.h
@@ -0,0 +1,71 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. +// +#ifndef CLEARKEY_DEVICE_FILES_H_ +#define CLEARKEY_DEVICE_FILES_H_ + +#include <errno.h> +#include <stdio.h> +#include <unistd.h> + +#include <set> +#include <string> +#include <vector> + +#include "protos/DeviceFiles.pb.h" +#include "ClearKeyTypes.h" +#include "MemoryFileSystem.h" + +namespace android { +namespace hardware { +namespace drm { +namespace V1_2 { +namespace clearkey { + +class DeviceFiles { + public: + typedef enum { + kLicenseStateUnknown, + kLicenseStateActive, + kLicenseStateReleasing, + } LicenseState; + + DeviceFiles() {}; + virtual ~DeviceFiles() {}; + + virtual bool StoreLicense(const std::string& keySetId, LicenseState state, + const std::string& keyResponse); + + virtual bool RetrieveLicense( + const std::string& key_set_id, LicenseState* state, std::string* offlineLicense); + + virtual bool LicenseExists(const std::string& keySetId); + + virtual std::vector<std::string> ListLicenses() const; + + virtual bool DeleteLicense(const std::string& keySetId); + + virtual bool DeleteAllLicenses(); + + private: + bool FileExists(const std::string& path) const; + ssize_t GetFileSize(const std::string& fileName) const; + bool RemoveFile(const std::string& fileName); + + bool RetrieveHashedFile(const std::string& fileName, OfflineFile* deSerializedFile); + bool StoreFileRaw(const std::string& fileName, const std::string& serializedFile); + bool StoreFileWithHash(const std::string& fileName, const std::string& serializedFile); + + MemoryFileSystem mFileHandle; + + CLEARKEY_DISALLOW_COPY_AND_ASSIGN(DeviceFiles); +}; + +} // namespace clearkey +} // namespace V1_2 +} // namespace drm +} // namespace hardware +} // namespace android + +#endif // CLEARKEY_DEVICE_FILES_H_
diff --git a/drm/mediadrm/plugins/clearkey/hidl/include/DrmFactory.h b/drm/mediadrm/plugins/clearkey/hidl/include/DrmFactory.h index 6f58195..4ca856d 100644 --- a/drm/mediadrm/plugins/clearkey/hidl/include/DrmFactory.h +++ b/drm/mediadrm/plugins/clearkey/hidl/include/DrmFactory.h
@@ -17,15 +17,15 @@ #ifndef CLEARKEY_DRM_FACTORY_H_ #define CLEARKEY_DRM_FACTORY_H_ -#include <android/hardware/drm/1.1/IDrmPlugin.h> -#include <android/hardware/drm/1.1/IDrmFactory.h> +#include <android/hardware/drm/1.2/IDrmPlugin.h> +#include <android/hardware/drm/1.2/IDrmFactory.h> #include "ClearKeyTypes.h" namespace android { namespace hardware { namespace drm { -namespace V1_1 { +namespace V1_2 { namespace clearkey { using ::android::hardware::hidl_array; @@ -39,6 +39,10 @@ Return<bool> isCryptoSchemeSupported(const hidl_array<uint8_t, 16>& uuid) override; + Return<bool> isCryptoSchemeSupported_1_2(const hidl_array<uint8_t, 16>& uuid, + const hidl_string& mimeType, + SecurityLevel level) override; + Return<bool> isContentTypeSupported(const hidl_string &mimeType) override; @@ -52,7 +56,7 @@ }; } // namespace clearkey -} // namespace V1_1 +} // namespace V1_2 } // namespace drm } // namespace hardware } // namespace android
diff --git a/drm/mediadrm/plugins/clearkey/hidl/include/DrmPlugin.h b/drm/mediadrm/plugins/clearkey/hidl/include/DrmPlugin.h index fb0695a..f294d4d 100644 --- a/drm/mediadrm/plugins/clearkey/hidl/include/DrmPlugin.h +++ b/drm/mediadrm/plugins/clearkey/hidl/include/DrmPlugin.h
@@ -17,32 +17,42 @@ #ifndef CLEARKEY_DRM_PLUGIN_H_ #define CLEARKEY_DRM_PLUGIN_H_ -#include <android/hardware/drm/1.1/IDrmPlugin.h> +#include <android/hardware/drm/1.2/IDrmPlugin.h> +#include <android/hardware/drm/1.2/IDrmPluginListener.h> -#include <stdio.h> #include <map> +#include <stdio.h> +#include <utils/List.h> + +#include "DeviceFiles.h" #include "SessionLibrary.h" #include "Utils.h" namespace android { namespace hardware { namespace drm { -namespace V1_1 { +namespace V1_2 { namespace clearkey { -using ::android::hardware::drm::V1_0::EventType; -using ::android::hardware::drm::V1_0::IDrmPluginListener; -using ::android::hardware::drm::V1_0::KeyStatus; -using ::android::hardware::drm::V1_0::KeyType; -using ::android::hardware::drm::V1_0::KeyValue; -using ::android::hardware::drm::V1_0::SecureStop; -using ::android::hardware::drm::V1_0::SecureStopId; -using ::android::hardware::drm::V1_0::SessionId; -using ::android::hardware::drm::V1_0::Status; -using ::android::hardware::drm::V1_1::DrmMetricGroup; -using ::android::hardware::drm::V1_1::IDrmPlugin; -using ::android::hardware::drm::V1_1::KeyRequestType; +namespace drm = ::android::hardware::drm; +using drm::V1_0::EventType; +using drm::V1_0::IDrmPluginListener; +using drm::V1_0::KeyRequestType; +using drm::V1_0::KeyStatus; +using drm::V1_0::KeyType; +using drm::V1_0::KeyValue; +using drm::V1_0::SecureStop; +using drm::V1_0::SecureStopId; +using drm::V1_0::SessionId; +using drm::V1_0::Status; +using drm::V1_1::DrmMetricGroup; +using drm::V1_1::HdcpLevel; +using drm::V1_1::SecureStopRelease; +using drm::V1_1::SecurityLevel; +using drm::V1_2::IDrmPlugin; +using drm::V1_2::KeySetId; +using drm::V1_2::OfflineLicenseState; using ::android::hardware::hidl_string; using ::android::hardware::hidl_vec; @@ -50,10 +60,16 @@ using ::android::hardware::Void; using ::android::sp; +typedef drm::V1_1::KeyRequestType KeyRequestType_V1_1; +typedef drm::V1_2::IDrmPluginListener IDrmPluginListener_V1_2; +typedef drm::V1_2::KeyStatus KeyStatus_V1_2; +typedef drm::V1_2::Status Status_V1_2; +typedef drm::V1_2::HdcpLevel HdcpLevel_V1_2; + struct DrmPlugin : public IDrmPlugin { explicit DrmPlugin(SessionLibrary* sessionLibrary); - virtual ~DrmPlugin() {} + virtual ~DrmPlugin() { mFileHandle.DeleteAllLicenses(); } Return<void> openSession(openSession_cb _hidl_cb) override; Return<void> openSession_1_1(SecurityLevel securityLevel, @@ -77,6 +93,14 @@ const hidl_vec<KeyValue>& optionalParameters, getKeyRequest_1_1_cb _hidl_cb) override; + Return<void> getKeyRequest_1_2( + const hidl_vec<uint8_t>& scope, + const hidl_vec<uint8_t>& initData, + const hidl_string& mimeType, + KeyType keyType, + const hidl_vec<KeyValue>& optionalParameters, + getKeyRequest_1_2_cb _hidl_cb) override; + Return<void> provideKeyResponse( const hidl_vec<uint8_t>& scope, const hidl_vec<uint8_t>& response, @@ -91,13 +115,7 @@ Return<Status> restoreKeys( const hidl_vec<uint8_t>& sessionId, - const hidl_vec<uint8_t>& keySetId) { - - if (sessionId.size() == 0 || keySetId.size() == 0) { - return Status::BAD_VALUE; - } - return Status::ERROR_DRM_CANNOT_HANDLE; - } + const hidl_vec<uint8_t>& keySetId) override; Return<void> queryKeyStatus( const hidl_vec<uint8_t>& sessionId, @@ -115,6 +133,18 @@ return Void(); } + Return<void> getProvisionRequest_1_2( + const hidl_string& certificateType, + const hidl_string& certificateAuthority, + getProvisionRequest_1_2_cb _hidl_cb) { + UNUSED(certificateType); + UNUSED(certificateAuthority); + + hidl_string defaultUrl; + _hidl_cb(Status_V1_2::ERROR_DRM_CANNOT_HANDLE, hidl_vec<uint8_t>(), defaultUrl); + return Void(); + } + Return<void> provideProvisionResponse( const hidl_vec<uint8_t>& response, provideProvisionResponse_cb _hidl_cb) { @@ -134,6 +164,13 @@ return Void(); } + Return<void> getHdcpLevels_1_2(getHdcpLevels_1_2_cb _hidl_cb) { + HdcpLevel_V1_2 connectedLevel = HdcpLevel_V1_2::HDCP_NONE; + HdcpLevel_V1_2 maxLevel = HdcpLevel_V1_2::HDCP_NO_OUTPUT; + _hidl_cb(Status_V1_2::OK, connectedLevel, maxLevel); + return Void(); + } + Return<void> getNumberOfSessions(getNumberOfSessions_cb _hidl_cb) override; Return<void> getSecurityLevel(const hidl_vec<uint8_t>& sessionId, @@ -141,6 +178,13 @@ Return<void> getMetrics(getMetrics_cb _hidl_cb) override; + Return<void> getOfflineLicenseKeySetIds(getOfflineLicenseKeySetIds_cb _hidl_cb) override; + + Return<Status> removeOfflineLicense(const KeySetId &keySetId) override; + + Return<void> getOfflineLicenseState(const KeySetId &keySetId, + getOfflineLicenseState_cb _hidl_cb) override; + Return<void> getPropertyString( const hidl_string& name, getPropertyString_cb _hidl_cb) override; @@ -248,12 +292,17 @@ Return<void> setListener(const sp<IDrmPluginListener>& listener) { mListener = listener; + mListenerV1_2 = IDrmPluginListener_V1_2::castFrom(listener); return Void(); }; - Return<void> sendEvent(EventType eventType, const hidl_vec<uint8_t>& sessionId, + Return<void> sendEvent( + EventType eventType, + const hidl_vec<uint8_t>& sessionId, const hidl_vec<uint8_t>& data) { - if (mListener != NULL) { + if (mListenerV1_2 != NULL) { + mListenerV1_2->sendEvent(eventType, sessionId, data); + } else if (mListener != NULL) { mListener->sendEvent(eventType, sessionId, data); } else { ALOGE("Null event listener, event not sent"); @@ -261,8 +310,12 @@ return Void(); } - Return<void> sendExpirationUpdate(const hidl_vec<uint8_t>& sessionId, int64_t expiryTimeInMS) { - if (mListener != NULL) { + Return<void> sendExpirationUpdate( + const hidl_vec<uint8_t>& sessionId, + int64_t expiryTimeInMS) { + if (mListenerV1_2 != NULL) { + mListenerV1_2->sendExpirationUpdate(sessionId, expiryTimeInMS); + } else if (mListener != NULL) { mListener->sendExpirationUpdate(sessionId, expiryTimeInMS); } else { ALOGE("Null event listener, event not sent"); @@ -270,9 +323,12 @@ return Void(); } - Return<void> sendKeysChange(const hidl_vec<uint8_t>& sessionId, + Return<void> sendKeysChange( + const hidl_vec<uint8_t>& sessionId, const hidl_vec<KeyStatus>& keyStatusList, bool hasNewUsableKey) { - if (mListener != NULL) { + if (mListenerV1_2 != NULL) { + mListenerV1_2->sendKeysChange(sessionId, keyStatusList, hasNewUsableKey); + } else if (mListener != NULL) { mListener->sendKeysChange(sessionId, keyStatusList, hasNewUsableKey); } else { ALOGE("Null event listener, event not sent"); @@ -280,6 +336,23 @@ return Void(); } + Return<void> sendKeysChange_1_2( + const hidl_vec<uint8_t>& sessionId, + const hidl_vec<KeyStatus_V1_2>& keyStatusList, bool hasNewUsableKey) { + if (mListenerV1_2 != NULL) { + mListenerV1_2->sendKeysChange_1_2(sessionId, keyStatusList, hasNewUsableKey); + } + return Void(); + } + + Return<void> sendSessionLostState( + const hidl_vec<uint8_t>& sessionId) { + if (mListenerV1_2 != NULL) { + mListenerV1_2->sendSessionLostState(sessionId); + } + return Void(); + } + Return<void> getSecureStops(getSecureStops_cb _hidl_cb); Return<void> getSecureStop(const hidl_vec<uint8_t>& secureStopId, @@ -300,18 +373,19 @@ private: void initProperties(); void installSecureStop(const hidl_vec<uint8_t>& sessionId); + bool makeKeySetId(std::string* keySetId); void setPlayPolicy(); Return<Status> setSecurityLevel(const hidl_vec<uint8_t>& sessionId, SecurityLevel level); - Status getKeyRequestCommon(const hidl_vec<uint8_t>& scope, + Status_V1_2 getKeyRequestCommon(const hidl_vec<uint8_t>& scope, const hidl_vec<uint8_t>& initData, const hidl_string& mimeType, KeyType keyType, const hidl_vec<KeyValue>& optionalParameters, std::vector<uint8_t> *request, - KeyRequestType *getKeyRequestType, + KeyRequestType_V1_1 *getKeyRequestType, std::string *defaultUrl); struct ClearkeySecureStop { @@ -323,19 +397,31 @@ std::vector<KeyValue> mPlayPolicy; std::map<std::string, std::string> mStringProperties; std::map<std::string, std::vector<uint8_t> > mByteArrayProperties; + std::map<std::string, std::vector<uint8_t> > mReleaseKeysMap; std::map<std::vector<uint8_t>, SecurityLevel> mSecurityLevel; sp<IDrmPluginListener> mListener; + sp<IDrmPluginListener_V1_2> mListenerV1_2; SessionLibrary *mSessionLibrary; int64_t mOpenSessionOkCount; int64_t mCloseSessionOkCount; int64_t mCloseSessionNotOpenedCount; uint32_t mNextSecureStopId; + // set by property to mock error scenarios + Status_V1_2 mMockError; + + void processMockError(const sp<Session> &session) { + session->setMockError(mMockError); + mMockError = Status_V1_2::OK; + } + + DeviceFiles mFileHandle; + CLEARKEY_DISALLOW_COPY_AND_ASSIGN_AND_NEW(DrmPlugin); }; } // namespace clearkey -} // namespace V1_1 +} // namespace V1_2 } // namespace drm } // namespace hardware } // namespace android
diff --git a/drm/mediadrm/plugins/clearkey/hidl/include/InitDataParser.h b/drm/mediadrm/plugins/clearkey/hidl/include/InitDataParser.h index 3189c4a..889f511 100644 --- a/drm/mediadrm/plugins/clearkey/hidl/include/InitDataParser.h +++ b/drm/mediadrm/plugins/clearkey/hidl/include/InitDataParser.h
@@ -24,7 +24,7 @@ namespace android { namespace hardware { namespace drm { -namespace V1_1 { +namespace V1_2 { namespace clearkey { using ::android::hardware::drm::V1_0::Status; @@ -34,7 +34,8 @@ InitDataParser() {} Status parse(const std::vector<uint8_t>& initData, - const std::string& type, + const std::string& mimeType, + V1_0::KeyType keyType, std::vector<uint8_t>* licenseRequest); private: @@ -43,12 +44,12 @@ Status parsePssh(const std::vector<uint8_t>& initData, std::vector<const uint8_t*>* keyIds); - std::string generateRequest( + std::string generateRequest(V1_0::KeyType keyType, const std::vector<const uint8_t*>& keyIds); }; } // namespace clearkey -} // namespace V1_1 +} // namespace V1_2 } // namespace drm } // namespace hardware } // namespace android
diff --git a/drm/mediadrm/plugins/clearkey/hidl/include/JsonWebKey.h b/drm/mediadrm/plugins/clearkey/hidl/include/JsonWebKey.h index 4ab034c..e57470c 100644 --- a/drm/mediadrm/plugins/clearkey/hidl/include/JsonWebKey.h +++ b/drm/mediadrm/plugins/clearkey/hidl/include/JsonWebKey.h
@@ -23,7 +23,7 @@ namespace android { namespace hardware { namespace drm { -namespace V1_1 { +namespace V1_2 { namespace clearkey { class JsonWebKey { @@ -54,7 +54,7 @@ }; } // namespace clearkey -} // namespace V1_1 +} // namespace V1_2 } // namespace drm } // namespace hardware } // namespace android
diff --git a/drm/mediadrm/plugins/clearkey/hidl/include/MemoryFileSystem.h b/drm/mediadrm/plugins/clearkey/hidl/include/MemoryFileSystem.h new file mode 100644 index 0000000..bcd9fd6 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/hidl/include/MemoryFileSystem.h
@@ -0,0 +1,68 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. +// +#ifndef CLEARKEY_MEMORY_FILE_SYSTEM_H_ +#define CLEARKEY_MEMORY_FILE_SYSTEM_H_ + +#include <map> +#include <string> + +#include "ClearKeyTypes.h" + +namespace android { +namespace hardware { +namespace drm { +namespace V1_2 { +namespace clearkey { + +// Using android file system requires clearkey plugin to update +// its sepolicy. However, we are unable to update sepolicy for +// older vendor partitions. To provide backward compatibility, +// clearkey plugin implements a very simple file system in memory. +// This memory file system does not support directory structure. +class MemoryFileSystem { + public: + struct MemoryFile { + std::string fileName; // excludes path + std::string content; + size_t fileSize; + + std::string getContent() const { return content; } + size_t getFileSize() const { return fileSize; } + void setContent(const std::string& file) { content = file; } + void setFileName(const std::string& name) { fileName = name; } + void setFileSize(size_t size) { + content.resize(size); fileSize = size; + } + }; + + MemoryFileSystem() {}; + virtual ~MemoryFileSystem() {}; + + bool FileExists(const std::string& fileName) const; + ssize_t GetFileSize(const std::string& fileName) const; + std::vector<std::string> ListFiles() const; + size_t Read(const std::string& pathName, std::string* buffer); + bool RemoveAllFiles(); + bool RemoveFile(const std::string& fileName); + size_t Write(const std::string& pathName, const MemoryFile& memoryFile); + + private: + // License file name is made up of a unique keySetId, therefore, + // the filename can be used as the key to locate licenses in the + // memory file system. + std::map<std::string, MemoryFile> mMemoryFileSystem; + + std::string GetFileName(const std::string& path); + + CLEARKEY_DISALLOW_COPY_AND_ASSIGN(MemoryFileSystem); +}; + +} // namespace clearkey +} // namespace V1_2 +} // namespace drm +} // namespace hardware +} // namespace android + +#endif // CLEARKEY_MEMORY_FILE_SYSTEM_H_
diff --git a/drm/mediadrm/plugins/clearkey/hidl/include/Session.h b/drm/mediadrm/plugins/clearkey/hidl/include/Session.h index cddfca5..a159e5a 100644 --- a/drm/mediadrm/plugins/clearkey/hidl/include/Session.h +++ b/drm/mediadrm/plugins/clearkey/hidl/include/Session.h
@@ -27,43 +27,53 @@ namespace android { namespace hardware { namespace drm { -namespace V1_1 { +namespace V1_2 { namespace clearkey { -using ::android::hardware::drm::V1_0::Status; -using ::android::hardware::drm::V1_0::SubSample; +namespace drm = ::android::hardware::drm; +using drm::V1_0::Status; +using drm::V1_0::SubSample; + +typedef drm::V1_2::Status Status_V1_2; class Session : public RefBase { public: explicit Session(const std::vector<uint8_t>& sessionId) - : mSessionId(sessionId) {} + : mSessionId(sessionId), mMockError(Status_V1_2::OK) {} virtual ~Session() {} const std::vector<uint8_t>& sessionId() const { return mSessionId; } Status getKeyRequest( - const std::vector<uint8_t>& mimeType, - const std::string& initDataType, + const std::vector<uint8_t>& initDataType, + const std::string& mimeType, + V1_0::KeyType keyType, std::vector<uint8_t>* keyRequest) const; Status provideKeyResponse( const std::vector<uint8_t>& response); - Status decrypt( + Status_V1_2 decrypt( const KeyId keyId, const Iv iv, const uint8_t* srcPtr, uint8_t* dstPtr, const std::vector<SubSample> subSamples, size_t* bytesDecryptedOut); + void setMockError(Status_V1_2 error) {mMockError = error;} + Status_V1_2 getMockError() const {return mMockError;} + private: CLEARKEY_DISALLOW_COPY_AND_ASSIGN(Session); const std::vector<uint8_t> mSessionId; KeyMap mKeyMap; Mutex mMapLock; + + // For mocking error return scenarios + Status_V1_2 mMockError; }; } // namespace clearkey -} // namespace V1_1 +} // namespace V1_2 } // namespace drm } // namespace hardware } // namespace android
diff --git a/drm/mediadrm/plugins/clearkey/hidl/include/SessionLibrary.h b/drm/mediadrm/plugins/clearkey/hidl/include/SessionLibrary.h index 326a0c1..1e567b8 100644 --- a/drm/mediadrm/plugins/clearkey/hidl/include/SessionLibrary.h +++ b/drm/mediadrm/plugins/clearkey/hidl/include/SessionLibrary.h
@@ -26,7 +26,7 @@ namespace android { namespace hardware { namespace drm { -namespace V1_1 { +namespace V1_2 { namespace clearkey { using ::android::sp; @@ -58,7 +58,7 @@ }; } // namespace clearkey -} // namespace V1_1 +} // namespace V1_2 } // namespace drm } // namespace hardware } // namespace android
diff --git a/drm/mediadrm/plugins/clearkey/hidl/include/TypeConvert.h b/drm/mediadrm/plugins/clearkey/hidl/include/TypeConvert.h index cc06329..b0f8607 100644 --- a/drm/mediadrm/plugins/clearkey/hidl/include/TypeConvert.h +++ b/drm/mediadrm/plugins/clearkey/hidl/include/TypeConvert.h
@@ -24,7 +24,7 @@ namespace android { namespace hardware { namespace drm { -namespace V1_1 { +namespace V1_2 { namespace clearkey { using ::android::hardware::hidl_array; @@ -68,8 +68,19 @@ return vec; } +inline Status toStatus_1_0(Status_V1_2 status) { + switch (status) { + case Status_V1_2::ERROR_DRM_INSUFFICIENT_SECURITY: + case Status_V1_2::ERROR_DRM_FRAME_TOO_LARGE: + case Status_V1_2::ERROR_DRM_SESSION_LOST_STATE: + return Status::ERROR_DRM_UNKNOWN; + default: + return static_cast<Status>(status); + } +} + } // namespace clearkey -} // namespace V1_1 +} // namespace V1_2 } // namespace drm } // namespace hardware } // namespace android
diff --git a/drm/mediadrm/plugins/clearkey/hidl/protos/DeviceFiles.proto b/drm/mediadrm/plugins/clearkey/hidl/protos/DeviceFiles.proto new file mode 100644 index 0000000..3e11f0b --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/hidl/protos/DeviceFiles.proto
@@ -0,0 +1,47 @@ +// ---------------------------------------------------------------------------- +// device_files.proto +// ---------------------------------------------------------------------------- +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. +// +// Description: +// Format of various files stored at the device. +// +syntax = "proto2"; + +package android.hardware.drm.V1_2.clearkey; + +// need this if we are using libprotobuf-cpp-2.3.0-lite +option optimize_for = LITE_RUNTIME; + +message License { + enum LicenseState { + ACTIVE = 1; + RELEASING = 2; + } + + optional LicenseState state = 1; + optional bytes license = 2; +} + +message OfflineFile { + enum FileType { + LICENSE = 1; + } + + enum FileVersion { + VERSION_1 = 1; + } + + optional FileType type = 1; + optional FileVersion version = 2 [default = VERSION_1]; + optional License license = 3; + +} + +message HashedFile { + optional bytes file = 1; + // A raw (not hex-encoded) SHA256, taken over the bytes of 'file'. + optional bytes hash = 2; +}
diff --git a/drm/mediadrm/plugins/clearkey/hidl/service.cpp b/drm/mediadrm/plugins/clearkey/hidl/service.cpp index 6a97b72..b39ea01 100644 --- a/drm/mediadrm/plugins/clearkey/hidl/service.cpp +++ b/drm/mediadrm/plugins/clearkey/hidl/service.cpp
@@ -13,32 +13,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#define LOG_TAG "android.hardware.drm@1.1-service.clearkey" - #include <CryptoFactory.h> #include <DrmFactory.h> #include <android-base/logging.h> #include <binder/ProcessState.h> +#include <hidl/HidlLazyUtils.h> #include <hidl/HidlTransportSupport.h> using ::android::hardware::configureRpcThreadpool; using ::android::hardware::joinRpcThreadpool; using ::android::sp; -using android::hardware::drm::V1_1::ICryptoFactory; -using android::hardware::drm::V1_1::IDrmFactory; -using android::hardware::drm::V1_1::clearkey::CryptoFactory; -using android::hardware::drm::V1_1::clearkey::DrmFactory; - +using android::hardware::drm::V1_2::ICryptoFactory; +using android::hardware::drm::V1_2::IDrmFactory; +using android::hardware::drm::V1_2::clearkey::CryptoFactory; +using android::hardware::drm::V1_2::clearkey::DrmFactory; int main(int /* argc */, char** /* argv */) { - ALOGD("android.hardware.drm@1.1-service.clearkey starting..."); - - // The DRM HAL may communicate to other vendor components via - // /dev/vndbinder - android::ProcessState::initWithDriver("/dev/vndbinder"); - sp<IDrmFactory> drmFactory = new DrmFactory; sp<ICryptoFactory> cryptoFactory = new CryptoFactory;
diff --git a/drm/mediadrm/plugins/clearkey/hidl/serviceLazy.cpp b/drm/mediadrm/plugins/clearkey/hidl/serviceLazy.cpp new file mode 100644 index 0000000..99fd883 --- /dev/null +++ b/drm/mediadrm/plugins/clearkey/hidl/serviceLazy.cpp
@@ -0,0 +1,50 @@ +/* + * Copyright 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 <CryptoFactory.h> +#include <DrmFactory.h> + +#include <android-base/logging.h> +#include <binder/ProcessState.h> +#include <hidl/HidlLazyUtils.h> +#include <hidl/HidlTransportSupport.h> + +using ::android::hardware::configureRpcThreadpool; +using ::android::hardware::joinRpcThreadpool; +using ::android::sp; + +using android::hardware::drm::V1_2::ICryptoFactory; +using android::hardware::drm::V1_2::IDrmFactory; +using android::hardware::drm::V1_2::clearkey::CryptoFactory; +using android::hardware::drm::V1_2::clearkey::DrmFactory; +using android::hardware::LazyServiceRegistrar; + +int main(int /* argc */, char** /* argv */) { + sp<IDrmFactory> drmFactory = new DrmFactory; + sp<ICryptoFactory> cryptoFactory = new CryptoFactory; + + configureRpcThreadpool(8, true /* callerWillJoin */); + + // Setup hwbinder service + LazyServiceRegistrar serviceRegistrar; + + // Setup hwbinder service + CHECK_EQ(serviceRegistrar.registerService(drmFactory, "clearkey"), android::NO_ERROR) + << "Failed to register Clearkey Factory HAL"; + CHECK_EQ(serviceRegistrar.registerService(cryptoFactory, "clearkey"), android::NO_ERROR) + << "Failed to register Clearkey Crypto HAL"; + + joinRpcThreadpool(); +}
diff --git a/include/common_time/ICommonClock.h b/include/common_time/ICommonClock.h deleted file mode 100644 index d7073f1..0000000 --- a/include/common_time/ICommonClock.h +++ /dev/null
@@ -1,108 +0,0 @@ -/* - * Copyright (C) 2011 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_ICOMMONCLOCK_H -#define ANDROID_ICOMMONCLOCK_H - -#include <stdint.h> -#include <linux/socket.h> - -#include <binder/IInterface.h> -#include <binder/IServiceManager.h> - -namespace android { - -class ICommonClockListener : public IInterface { - public: - DECLARE_META_INTERFACE(CommonClockListener); - - virtual void onTimelineChanged(uint64_t timelineID) = 0; -}; - -class BnCommonClockListener : public BnInterface<ICommonClockListener> { - public: - virtual status_t onTransact(uint32_t code, const Parcel& data, - Parcel* reply, uint32_t flags = 0); -}; - -class ICommonClock : public IInterface { - public: - DECLARE_META_INTERFACE(CommonClock); - - // Name of the ICommonClock service registered with the service manager. - static const String16 kServiceName; - - // a reserved invalid timeline ID - static const uint64_t kInvalidTimelineID; - - // a reserved invalid error estimate - static const int32_t kErrorEstimateUnknown; - - enum State { - // the device just came up and is trying to discover the master - STATE_INITIAL, - - // the device is a client of a master - STATE_CLIENT, - - // the device is acting as master - STATE_MASTER, - - // the device has lost contact with its master and needs to participate - // in the election of a new master - STATE_RONIN, - - // the device is waiting for announcement of the newly elected master - STATE_WAIT_FOR_ELECTION, - }; - - virtual status_t isCommonTimeValid(bool* valid, uint32_t* timelineID) = 0; - virtual status_t commonTimeToLocalTime(int64_t commonTime, - int64_t* localTime) = 0; - virtual status_t localTimeToCommonTime(int64_t localTime, - int64_t* commonTime) = 0; - virtual status_t getCommonTime(int64_t* commonTime) = 0; - virtual status_t getCommonFreq(uint64_t* freq) = 0; - virtual status_t getLocalTime(int64_t* localTime) = 0; - virtual status_t getLocalFreq(uint64_t* freq) = 0; - virtual status_t getEstimatedError(int32_t* estimate) = 0; - virtual status_t getTimelineID(uint64_t* id) = 0; - virtual status_t getState(State* state) = 0; - virtual status_t getMasterAddr(struct sockaddr_storage* addr) = 0; - - virtual status_t registerListener( - const sp<ICommonClockListener>& listener) = 0; - virtual status_t unregisterListener( - const sp<ICommonClockListener>& listener) = 0; - - // Simple helper to make it easier to connect to the CommonClock service. - static inline sp<ICommonClock> getInstance() { - sp<IBinder> binder = defaultServiceManager()->checkService( - ICommonClock::kServiceName); - sp<ICommonClock> clk = interface_cast<ICommonClock>(binder); - return clk; - } -}; - -class BnCommonClock : public BnInterface<ICommonClock> { - public: - virtual status_t onTransact(uint32_t code, const Parcel& data, - Parcel* reply, uint32_t flags = 0); -}; - -}; // namespace android - -#endif // ANDROID_ICOMMONCLOCK_H
diff --git a/include/common_time/ICommonTimeConfig.h b/include/common_time/ICommonTimeConfig.h deleted file mode 100644 index 497b666..0000000 --- a/include/common_time/ICommonTimeConfig.h +++ /dev/null
@@ -1,73 +0,0 @@ -/* - * Copyright (C) 2012 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_ICOMMONTIMECONFIG_H -#define ANDROID_ICOMMONTIMECONFIG_H - -#include <stdint.h> -#include <linux/socket.h> - -#include <binder/IInterface.h> -#include <binder/IServiceManager.h> - -namespace android { - -class String16; - -class ICommonTimeConfig : public IInterface { - public: - DECLARE_META_INTERFACE(CommonTimeConfig); - - // Name of the ICommonTimeConfig service registered with the service - // manager. - static const String16 kServiceName; - - virtual status_t getMasterElectionPriority(uint8_t *priority) = 0; - virtual status_t setMasterElectionPriority(uint8_t priority) = 0; - virtual status_t getMasterElectionEndpoint(struct sockaddr_storage *addr) = 0; - virtual status_t setMasterElectionEndpoint(const struct sockaddr_storage *addr) = 0; - virtual status_t getMasterElectionGroupId(uint64_t *id) = 0; - virtual status_t setMasterElectionGroupId(uint64_t id) = 0; - virtual status_t getInterfaceBinding(String16& ifaceName) = 0; - virtual status_t setInterfaceBinding(const String16& ifaceName) = 0; - virtual status_t getMasterAnnounceInterval(int *interval) = 0; - virtual status_t setMasterAnnounceInterval(int interval) = 0; - virtual status_t getClientSyncInterval(int *interval) = 0; - virtual status_t setClientSyncInterval(int interval) = 0; - virtual status_t getPanicThreshold(int *threshold) = 0; - virtual status_t setPanicThreshold(int threshold) = 0; - virtual status_t getAutoDisable(bool *autoDisable) = 0; - virtual status_t setAutoDisable(bool autoDisable) = 0; - virtual status_t forceNetworklessMasterMode() = 0; - - // Simple helper to make it easier to connect to the CommonTimeConfig service. - static inline sp<ICommonTimeConfig> getInstance() { - sp<IBinder> binder = defaultServiceManager()->checkService( - ICommonTimeConfig::kServiceName); - sp<ICommonTimeConfig> clk = interface_cast<ICommonTimeConfig>(binder); - return clk; - } -}; - -class BnCommonTimeConfig : public BnInterface<ICommonTimeConfig> { - public: - virtual status_t onTransact(uint32_t code, const Parcel& data, - Parcel* reply, uint32_t flags = 0); -}; - -}; // namespace android - -#endif // ANDROID_ICOMMONTIMECONFIG_H
diff --git a/include/common_time/cc_helper.h b/include/common_time/cc_helper.h deleted file mode 100644 index 8c4d5c0..0000000 --- a/include/common_time/cc_helper.h +++ /dev/null
@@ -1,72 +0,0 @@ -/* - * Copyright (C) 2011 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 __CC_HELPER_H__ -#define __CC_HELPER_H__ - -#include <stdint.h> -#include <common_time/ICommonClock.h> -#include <utils/threads.h> - -namespace android { - -// CCHelper is a simple wrapper class to help with centralizing access to the -// Common Clock service and implementing lifetime managment, as well as to -// implement a simple policy of making a basic attempt to reconnect to the -// common clock service when things go wrong. -// -// On platforms which run the native common_time service in auto-disable mode, -// the service will go into networkless mode whenever it has no active clients. -// It tracks active clients using registered CommonClockListeners (the callback -// interface for onTimelineChanged) since this provides a convienent death -// handler notification for when the service's clients die unexpectedly. This -// means that users of the common time service should really always have a -// CommonClockListener, unless they know that the time service is not running in -// auto disabled mode, or that there is at least one other registered listener -// active in the system. The CCHelper makes this a little easier by sharing a -// ref counted ICommonClock interface across all clients and automatically -// registering and unregistering a listener whenever there are CCHelper -// instances active in the process. -class CCHelper { - public: - CCHelper(); - ~CCHelper(); - - status_t isCommonTimeValid(bool* valid, uint32_t* timelineID); - status_t commonTimeToLocalTime(int64_t commonTime, int64_t* localTime); - status_t localTimeToCommonTime(int64_t localTime, int64_t* commonTime); - status_t getCommonTime(int64_t* commonTime); - status_t getCommonFreq(uint64_t* freq); - status_t getLocalTime(int64_t* localTime); - status_t getLocalFreq(uint64_t* freq); - - private: - class CommonClockListener : public BnCommonClockListener { - public: - void onTimelineChanged(uint64_t timelineID); - }; - - static bool verifyClock_l(); - - static Mutex lock_; - static sp<ICommonClock> common_clock_; - static sp<ICommonClockListener> common_clock_listener_; - static uint32_t ref_count_; -}; - - -} // namespace android -#endif // __CC_HELPER_H__
diff --git a/include/common_time/local_clock.h b/include/common_time/local_clock.h deleted file mode 100644 index 384c3de..0000000 --- a/include/common_time/local_clock.h +++ /dev/null
@@ -1,47 +0,0 @@ -/* - * Copyright (C) 2011 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 __LOCAL_CLOCK_H__ -#define __LOCAL_CLOCK_H__ - -#include <stdint.h> - -#include <hardware/local_time_hal.h> -#include <utils/Errors.h> -#include <utils/threads.h> - -namespace android { - -class LocalClock { - public: - LocalClock(); - - bool initCheck(); - - int64_t getLocalTime(); - uint64_t getLocalFreq(); - status_t setLocalSlew(int16_t rate); - int32_t getDebugLog(struct local_time_debug_event* records, - int max_records); - - private: - static Mutex dev_lock_; - static local_time_hw_device_t* dev_; -}; - -} // namespace android -#endif // __LOCAL_CLOCK_H__
diff --git a/include/media/AudioAttributes.h b/include/media/AudioAttributes.h new file mode 120000 index 0000000..27ba471 --- /dev/null +++ b/include/media/AudioAttributes.h
@@ -0,0 +1 @@ +../../media/libaudioclient/include/media/AudioAttributes.h \ No newline at end of file
diff --git a/include/media/AudioCommonTypes.h b/include/media/AudioCommonTypes.h new file mode 120000 index 0000000..ae7c99a --- /dev/null +++ b/include/media/AudioCommonTypes.h
@@ -0,0 +1 @@ +../../media/libaudioclient/include/media/AudioCommonTypes.h \ No newline at end of file
diff --git a/include/media/AudioPolicyHelper.h b/include/media/AudioPolicyHelper.h deleted file mode 120000 index 558657e..0000000 --- a/include/media/AudioPolicyHelper.h +++ /dev/null
@@ -1 +0,0 @@ -../../media/libaudioclient/include/media/AudioPolicyHelper.h \ No newline at end of file
diff --git a/include/media/AudioPresentationInfo.h b/include/media/AudioPresentationInfo.h deleted file mode 100644 index e91a992..0000000 --- a/include/media/AudioPresentationInfo.h +++ /dev/null
@@ -1,79 +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 AUDIO_PRESENTATION_INFO_H_ -#define AUDIO_PRESENTATION_INFO_H_ - -#include <sstream> -#include <stdint.h> - -#include <utils/KeyedVector.h> -#include <utils/RefBase.h> -#include <utils/String8.h> -#include <utils/Vector.h> - -namespace android { - -enum mastering_indication { - MASTERING_NOT_INDICATED, - MASTERED_FOR_STEREO, - MASTERED_FOR_SURROUND, - MASTERED_FOR_3D, - MASTERED_FOR_HEADPHONE, -}; - -struct AudioPresentation : public RefBase { - int32_t mPresentationId; - int32_t mProgramId; - KeyedVector<String8, String8> mLabels; - String8 mLanguage; - int32_t mMasteringIndication; - bool mAudioDescriptionAvailable; - bool mSpokenSubtitlesAvailable; - bool mDialogueEnhancementAvailable; - - AudioPresentation() { - mPresentationId = -1; - mProgramId = -1; - mLanguage = ""; - mMasteringIndication = MASTERING_NOT_INDICATED; - mAudioDescriptionAvailable = false; - mSpokenSubtitlesAvailable = false; - mDialogueEnhancementAvailable = false; - } -}; - -typedef Vector<sp<AudioPresentation>> AudioPresentations; - -class AudioPresentationInfo : public RefBase { - public: - AudioPresentationInfo(); - - ~AudioPresentationInfo(); - - void addPresentation(sp<AudioPresentation> presentation); - - size_t countPresentations() const; - - const sp<AudioPresentation> getPresentation(size_t index) const; - - private: - AudioPresentations mPresentations; -}; - -} // namespace android - -#endif // AUDIO_PRESENTATION_INFO_H_
diff --git a/include/media/AudioProductStrategy.h b/include/media/AudioProductStrategy.h new file mode 120000 index 0000000..6bfaf11 --- /dev/null +++ b/include/media/AudioProductStrategy.h
@@ -0,0 +1 @@ +../../media/libaudioclient/include/media/AudioProductStrategy.h \ No newline at end of file
diff --git a/include/media/AudioVolumeGroup.h b/include/media/AudioVolumeGroup.h new file mode 120000 index 0000000..d6f1c99 --- /dev/null +++ b/include/media/AudioVolumeGroup.h
@@ -0,0 +1 @@ +../../media/libaudioclient/include/media/AudioVolumeGroup.h \ No newline at end of file
diff --git a/include/media/DataSource.h b/include/media/DataSource.h index 905bec1..198b27e 120000 --- a/include/media/DataSource.h +++ b/include/media/DataSource.h
@@ -1 +1 @@ -../../media/libmediaextractor/include/media/DataSource.h \ No newline at end of file +stagefright/DataSource.h \ No newline at end of file
diff --git a/include/media/DataSourceBase.h b/include/media/DataSourceBase.h index 54c8047..d2ab2f1 120000 --- a/include/media/DataSourceBase.h +++ b/include/media/DataSourceBase.h
@@ -1 +1 @@ -../../media/libmediaextractor/include/media/DataSourceBase.h \ No newline at end of file +stagefright/DataSourceBase.h \ No newline at end of file
diff --git a/include/media/EventLog.h b/include/media/EventLog.h new file mode 120000 index 0000000..9b2c4bf --- /dev/null +++ b/include/media/EventLog.h
@@ -0,0 +1 @@ +../../media/utils/include/mediautils/EventLog.h \ No newline at end of file
diff --git a/include/media/ExtractorUtils.h b/include/media/ExtractorUtils.h deleted file mode 120000 index e2dd082..0000000 --- a/include/media/ExtractorUtils.h +++ /dev/null
@@ -1 +0,0 @@ -../../media/libmediaextractor/include/media/ExtractorUtils.h \ No newline at end of file
diff --git a/media/libmediaextractor/include/media/ExtractorUtils.h b/include/media/ExtractorUtils.h similarity index 100% rename from media/libmediaextractor/include/media/ExtractorUtils.h rename to include/media/ExtractorUtils.h
diff --git a/include/media/MediaExtractor.h b/include/media/MediaExtractor.h deleted file mode 120000 index 4b35fe1..0000000 --- a/include/media/MediaExtractor.h +++ /dev/null
@@ -1 +0,0 @@ -../../media/libmediaextractor/include/media/MediaExtractor.h \ No newline at end of file
diff --git a/include/media/MediaExtractorPluginApi.h b/include/media/MediaExtractorPluginApi.h new file mode 100644 index 0000000..916472c --- /dev/null +++ b/include/media/MediaExtractorPluginApi.h
@@ -0,0 +1,168 @@ +/* + * 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 MEDIA_EXTRACTOR_PLUGIN_API_H_ +#define MEDIA_EXTRACTOR_PLUGIN_API_H_ + +#include <utils/Errors.h> // for status_t +#include <media/NdkMediaError.h> +struct AMediaFormat; + +namespace android { + +struct MediaTrack; +class MetaDataBase; +class MediaBufferBase; + +extern "C" { + +struct CDataSource { + ssize_t (*readAt)(void *handle, off64_t offset, void *data, size_t size); + status_t (*getSize)(void *handle, off64_t *size); + uint32_t (*flags)(void *handle ); + bool (*getUri)(void *handle, char *uriString, size_t bufferSize); + void *handle; +}; + +enum CMediaTrackReadOptions : uint32_t { + SEEK_PREVIOUS_SYNC = 0, + SEEK_NEXT_SYNC = 1, + SEEK_CLOSEST_SYNC = 2, + SEEK_CLOSEST = 3, + SEEK_FRAME_INDEX = 4, + SEEK = 8, + NONBLOCKING = 16 +}; + +/** + * only use CMediaBuffer allocated from the CMediaBufferGroup that is + * provided to CMediaTrack::start() + */ +struct CMediaBuffer { + void *handle; + void (*release)(void *handle); + void* (*data)(void *handle); + size_t (*size)(void *handle); + size_t (*range_offset)(void *handle); + size_t (*range_length)(void *handle); + void (*set_range)(void *handle, size_t offset, size_t length); + AMediaFormat* (*meta_data)(void *handle); +}; + +struct CMediaBufferGroup { + void *handle; + bool (*init)(void *handle, size_t buffers, size_t buffer_size, size_t growthLimit); + void (*add_buffer)(void *handle, size_t size); + media_status_t (*acquire_buffer)(void *handle, + CMediaBuffer **buffer, bool nonBlocking, size_t requestedSize); + bool (*has_buffers)(void *handle); +}; + +struct CMediaTrack { + void *data; + void (*free)(void *data); + + media_status_t (*start)(void *data, CMediaBufferGroup *bufferGroup); + media_status_t (*stop)(void *data); + media_status_t (*getFormat)(void *data, AMediaFormat *format); + media_status_t (*read)(void *data, CMediaBuffer **buffer, uint32_t options, int64_t seekPosUs); + bool (*supportsNonBlockingRead)(void *data); +}; + +struct CMediaExtractor { + void *data; + + void (*free)(void *data); + size_t (*countTracks)(void *data); + CMediaTrack* (*getTrack)(void *data, size_t index); + media_status_t (*getTrackMetaData)( + void *data, + AMediaFormat *meta, + size_t index, uint32_t flags); + + media_status_t (*getMetaData)(void *data, AMediaFormat *meta); + uint32_t (*flags)(void *data); + media_status_t (*setMediaCas)(void *data, const uint8_t* casToken, size_t size); + const char * (*name)(void *data); +}; + +typedef CMediaExtractor* (*CreatorFunc)(CDataSource *source, void *meta); +typedef void (*FreeMetaFunc)(void *meta); + +// The sniffer can optionally fill in an opaque object, "meta", that helps +// the corresponding extractor initialize its state without duplicating +// effort already exerted by the sniffer. If "freeMeta" is given, it will be +// called against the opaque object when it is no longer used. +typedef CreatorFunc (*SnifferFunc)( + CDataSource *source, float *confidence, + void **meta, FreeMetaFunc *freeMeta); + +typedef CMediaExtractor CMediaExtractor; +typedef CreatorFunc CreatorFunc; + + +typedef struct { + const uint8_t b[16]; +} media_uuid_t; + +struct ExtractorDef { + // version number of this structure + const uint32_t def_version; + + // A unique identifier for this extractor. + // See below for a convenience macro to create this from a string. + media_uuid_t extractor_uuid; + + // Version number of this extractor. When two extractors with the same + // uuid are encountered, the one with the largest version number will + // be used. + const uint32_t extractor_version; + + // a human readable name + const char *extractor_name; + + union { + struct { + SnifferFunc sniff; + } v2; + struct { + SnifferFunc sniff; + // a NULL terminated list of container mime types and/or file extensions + // that this extractor supports + const char **supported_types; + } v3; + } u; +}; + +// the C++ based API which first shipped in P and is no longer supported +const uint32_t EXTRACTORDEF_VERSION_LEGACY = 1; + +// the first C/NDK based API +const uint32_t EXTRACTORDEF_VERSION_NDK_V1 = 2; + +// the second C/NDK based API +const uint32_t EXTRACTORDEF_VERSION_NDK_V2 = 3; + +const uint32_t EXTRACTORDEF_VERSION = EXTRACTORDEF_VERSION_NDK_V2; + +// each plugin library exports one function of this type +typedef ExtractorDef (*GetExtractorDef)(); + +} // extern "C" + +} // namespace android + +#endif // MEDIA_EXTRACTOR_PLUGIN_API_H_
diff --git a/include/media/MediaExtractorPluginHelper.h b/include/media/MediaExtractorPluginHelper.h new file mode 100644 index 0000000..b86f177 --- /dev/null +++ b/include/media/MediaExtractorPluginHelper.h
@@ -0,0 +1,494 @@ +/* + * 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 MEDIA_EXTRACTOR_PLUGIN_HELPER_H_ + +#define MEDIA_EXTRACTOR_PLUGIN_HELPER_H_ + +#include <arpa/inet.h> +#include <stdio.h> +#include <map> + +#include <utils/Errors.h> +#include <utils/Log.h> +#include <utils/RefBase.h> +#include <media/MediaExtractorPluginApi.h> +#include <media/NdkMediaFormat.h> + +namespace android { + +class DataSourceBase; +class MetaDataBase; +struct MediaTrack; + + +class MediaTrackHelper; + +class MediaBufferHelper { +private: + friend CMediaTrack *wrap(MediaTrackHelper *); + CMediaBuffer *mBuffer; +public: + MediaBufferHelper(CMediaBuffer *buf) { + mBuffer = buf; + } + + virtual ~MediaBufferHelper() {} + + virtual void release() { + mBuffer->release(mBuffer->handle); + } + + virtual void* data() { + return mBuffer->data(mBuffer->handle); + } + + virtual size_t size() { + return mBuffer->size(mBuffer->handle); + } + + virtual size_t range_offset() { + return mBuffer->range_offset(mBuffer->handle); + } + + virtual size_t range_length() { + return mBuffer->range_length(mBuffer->handle); + } + + virtual void set_range(size_t offset, size_t length) { + mBuffer->set_range(mBuffer->handle, offset, length); + } + virtual AMediaFormat *meta_data() { + return mBuffer->meta_data(mBuffer->handle); + } +}; + +class MediaBufferGroupHelper { +private: + CMediaBufferGroup *mGroup; + std::map<CMediaBuffer*, MediaBufferHelper*> mBufferHelpers; +public: + MediaBufferGroupHelper(CMediaBufferGroup *group) { + mGroup = group; + } + ~MediaBufferGroupHelper() { + // delete all entries in map + ALOGV("buffergroup %p map has %zu entries", this, mBufferHelpers.size()); + for (auto it = mBufferHelpers.begin(); it != mBufferHelpers.end(); ++it) { + delete it->second; + } + } + bool init(size_t buffers, size_t buffer_size, size_t growthLimit = 0) { + return mGroup->init(mGroup->handle, buffers, buffer_size, growthLimit); + } + void add_buffer(size_t size) { + mGroup->add_buffer(mGroup->handle, size); + } + media_status_t acquire_buffer( + MediaBufferHelper **buffer, bool nonBlocking = false, size_t requestedSize = 0) { + CMediaBuffer *buf = nullptr; + media_status_t ret = + mGroup->acquire_buffer(mGroup->handle, &buf, nonBlocking, requestedSize); + if (ret == AMEDIA_OK && buf != nullptr) { + auto helper = mBufferHelpers.find(buf); + if (helper == mBufferHelpers.end()) { + MediaBufferHelper* newHelper = new MediaBufferHelper(buf); + mBufferHelpers.insert(std::make_pair(buf, newHelper)); + *buffer = newHelper; + } else { + *buffer = helper->second; + } + } else { + *buffer = nullptr; + } + return ret; + } + bool has_buffers() { + return mGroup->has_buffers(mGroup->handle); + } +}; + +class MediaTrackHelper { +public: + MediaTrackHelper() : mBufferGroup(nullptr) { + } + virtual ~MediaTrackHelper() { + delete mBufferGroup; + } + virtual media_status_t start() = 0; + virtual media_status_t stop() = 0; + virtual media_status_t getFormat(AMediaFormat *format) = 0; + + class ReadOptions { + public: + enum SeekMode : int32_t { + SEEK_PREVIOUS_SYNC, + SEEK_NEXT_SYNC, + SEEK_CLOSEST_SYNC, + SEEK_CLOSEST, + SEEK_FRAME_INDEX, + }; + + ReadOptions(uint32_t options, int64_t seekPosUs) { + mOptions = options; + mSeekPosUs = seekPosUs; + } + bool getSeekTo(int64_t *time_us, SeekMode *mode) const { + if ((mOptions & CMediaTrackReadOptions::SEEK) == 0) { + return false; + } + *time_us = mSeekPosUs; + *mode = (SeekMode) (mOptions & 7); + return true; + } + bool getNonBlocking() const { + return mOptions & CMediaTrackReadOptions::NONBLOCKING; + } + private: + uint32_t mOptions; + int64_t mSeekPosUs; + }; + + virtual media_status_t read( + MediaBufferHelper **buffer, const ReadOptions *options = NULL) = 0; + virtual bool supportsNonBlockingRead() { return false; } +protected: + friend CMediaTrack *wrap(MediaTrackHelper *track); + MediaBufferGroupHelper *mBufferGroup; +}; + +inline CMediaTrack *wrap(MediaTrackHelper *track) { + if (track == nullptr) { + return nullptr; + } + CMediaTrack *wrapper = (CMediaTrack*) malloc(sizeof(CMediaTrack)); + wrapper->data = track; + wrapper->free = [](void *data) -> void { + delete (MediaTrackHelper*)(data); + }; + wrapper->start = [](void *data, CMediaBufferGroup *bufferGroup) -> media_status_t { + if (((MediaTrackHelper*)data)->mBufferGroup) { + // this shouldn't happen, but handle it anyway + delete ((MediaTrackHelper*)data)->mBufferGroup; + } + ((MediaTrackHelper*)data)->mBufferGroup = new MediaBufferGroupHelper(bufferGroup); + return ((MediaTrackHelper*)data)->start(); + }; + wrapper->stop = [](void *data) -> media_status_t { + return ((MediaTrackHelper*)data)->stop(); + }; + wrapper->getFormat = [](void *data, AMediaFormat *meta) -> media_status_t { + return ((MediaTrackHelper*)data)->getFormat(meta); + }; + wrapper->read = [](void *data, CMediaBuffer **buffer, uint32_t options, int64_t seekPosUs) + -> media_status_t { + MediaTrackHelper::ReadOptions opts(options, seekPosUs); + MediaBufferHelper *buf = NULL; + media_status_t ret = ((MediaTrackHelper*)data)->read(&buf, &opts); + if (ret == AMEDIA_OK && buf != nullptr) { + *buffer = buf->mBuffer; + } + return ret; + }; + wrapper->supportsNonBlockingRead = [](void *data) -> bool { + return ((MediaTrackHelper*)data)->supportsNonBlockingRead(); + }; + return wrapper; +} + + +// extractor plugins can derive from this class which looks remarkably +// like MediaExtractor and can be easily wrapped in the required C API +class MediaExtractorPluginHelper +{ +public: + virtual ~MediaExtractorPluginHelper() {} + virtual size_t countTracks() = 0; + virtual MediaTrackHelper *getTrack(size_t index) = 0; + + enum GetTrackMetaDataFlags { + kIncludeExtensiveMetaData = 1 + }; + virtual media_status_t getTrackMetaData( + AMediaFormat *meta, + size_t index, uint32_t flags = 0) = 0; + + // Return container specific meta-data. The default implementation + // returns an empty metadata object. + virtual media_status_t getMetaData(AMediaFormat *meta) = 0; + + enum Flags { + CAN_SEEK_BACKWARD = 1, // the "seek 10secs back button" + CAN_SEEK_FORWARD = 2, // the "seek 10secs forward button" + CAN_PAUSE = 4, + CAN_SEEK = 8, // the "seek bar" + }; + + // If subclasses do _not_ override this, the default is + // CAN_SEEK_BACKWARD | CAN_SEEK_FORWARD | CAN_SEEK | CAN_PAUSE + virtual uint32_t flags() const { + return CAN_SEEK_BACKWARD | CAN_SEEK_FORWARD | CAN_SEEK | CAN_PAUSE; + }; + + virtual media_status_t setMediaCas(const uint8_t* /*casToken*/, size_t /*size*/) { + return AMEDIA_ERROR_INVALID_OPERATION; + } + + virtual const char * name() { return "<unspecified>"; } + +protected: + MediaExtractorPluginHelper() {} + +private: + MediaExtractorPluginHelper(const MediaExtractorPluginHelper &); + MediaExtractorPluginHelper &operator=(const MediaExtractorPluginHelper &); +}; + +inline CMediaExtractor *wrap(MediaExtractorPluginHelper *extractor) { + CMediaExtractor *wrapper = (CMediaExtractor*) malloc(sizeof(CMediaExtractor)); + wrapper->data = extractor; + wrapper->free = [](void *data) -> void { + delete (MediaExtractorPluginHelper*)(data); + }; + wrapper->countTracks = [](void *data) -> size_t { + return ((MediaExtractorPluginHelper*)data)->countTracks(); + }; + wrapper->getTrack = [](void *data, size_t index) -> CMediaTrack* { + return wrap(((MediaExtractorPluginHelper*)data)->getTrack(index)); + }; + wrapper->getTrackMetaData = []( + void *data, + AMediaFormat *meta, + size_t index, uint32_t flags) -> media_status_t { + return ((MediaExtractorPluginHelper*)data)->getTrackMetaData(meta, index, flags); + }; + wrapper->getMetaData = []( + void *data, + AMediaFormat *meta) -> media_status_t { + return ((MediaExtractorPluginHelper*)data)->getMetaData(meta); + }; + wrapper->flags = []( + void *data) -> uint32_t { + return ((MediaExtractorPluginHelper*)data)->flags(); + }; + wrapper->setMediaCas = []( + void *data, const uint8_t *casToken, size_t size) -> media_status_t { + return ((MediaExtractorPluginHelper*)data)->setMediaCas(casToken, size); + }; + wrapper->name = []( + void *data) -> const char * { + return ((MediaExtractorPluginHelper*)data)->name(); + }; + return wrapper; +} + +/* adds some convience methods */ +class DataSourceHelper { +public: + explicit DataSourceHelper(CDataSource *csource) { + mSource = csource; + } + + explicit DataSourceHelper(DataSourceHelper *source) { + mSource = source->mSource; + } + + virtual ~DataSourceHelper() {} + + virtual ssize_t readAt(off64_t offset, void *data, size_t size) { + return mSource->readAt(mSource->handle, offset, data, size); + } + + virtual status_t getSize(off64_t *size) { + return mSource->getSize(mSource->handle, size); + } + + bool getUri(char *uriString, size_t bufferSize) { + return mSource->getUri(mSource->handle, uriString, bufferSize); + } + + virtual uint32_t flags() { + return mSource->flags(mSource->handle); + } + + // Convenience methods: + bool getUInt16(off64_t offset, uint16_t *x) { + *x = 0; + + uint8_t byte[2]; + if (readAt(offset, byte, 2) != 2) { + return false; + } + + *x = (byte[0] << 8) | byte[1]; + + return true; + } + + // 3 byte int, returned as a 32-bit int + bool getUInt24(off64_t offset, uint32_t *x) { + *x = 0; + + uint8_t byte[3]; + if (readAt(offset, byte, 3) != 3) { + return false; + } + + *x = (byte[0] << 16) | (byte[1] << 8) | byte[2]; + + return true; + } + + bool getUInt32(off64_t offset, uint32_t *x) { + *x = 0; + + uint32_t tmp; + if (readAt(offset, &tmp, 4) != 4) { + return false; + } + + *x = ntohl(tmp); + + return true; + } + + bool getUInt64(off64_t offset, uint64_t *x) { + *x = 0; + + uint64_t tmp; + if (readAt(offset, &tmp, 8) != 8) { + return false; + } + + *x = ((uint64_t)ntohl(tmp & 0xffffffff) << 32) | ntohl(tmp >> 32); + + return true; + } + + // read either int<N> or int<2N> into a uint<2N>_t, size is the int size in bytes. + bool getUInt16Var(off64_t offset, uint16_t *x, size_t size) { + if (size == 2) { + return getUInt16(offset, x); + } + if (size == 1) { + uint8_t tmp; + if (readAt(offset, &tmp, 1) == 1) { + *x = tmp; + return true; + } + } + return false; + } + + bool getUInt32Var(off64_t offset, uint32_t *x, size_t size) { + if (size == 4) { + return getUInt32(offset, x); + } + if (size == 2) { + uint16_t tmp; + if (getUInt16(offset, &tmp)) { + *x = tmp; + return true; + } + } + return false; + } + + bool getUInt64Var(off64_t offset, uint64_t *x, size_t size) { + if (size == 8) { + return getUInt64(offset, x); + } + if (size == 4) { + uint32_t tmp; + if (getUInt32(offset, &tmp)) { + *x = tmp; + return true; + } + } + return false; + } + +protected: + CDataSource *mSource; +}; + + + +// helpers to create a media_uuid_t from a string literal + +// purposely not defined anywhere so that this will fail to link if +// expressions below are not evaluated at compile time +int invalid_uuid_string(const char *); + +template <typename T, size_t N> +constexpr uint8_t _digitAt_(const T (&s)[N], const size_t n) { + return s[n] >= '0' && s[n] <= '9' ? s[n] - '0' + : s[n] >= 'a' && s[n] <= 'f' ? s[n] - 'a' + 10 + : s[n] >= 'A' && s[n] <= 'F' ? s[n] - 'A' + 10 + : invalid_uuid_string("uuid: bad digits"); +} + +template <typename T, size_t N> +constexpr uint8_t _hexByteAt_(const T (&s)[N], size_t n) { + return (_digitAt_(s, n) << 4) + _digitAt_(s, n + 1); +} + +constexpr bool _assertIsDash_(char c) { + return c == '-' ? true : invalid_uuid_string("Wrong format"); +} + +template <size_t N> +constexpr media_uuid_t constUUID(const char (&s) [N]) { + static_assert(N == 37, "uuid: wrong length"); + return + _assertIsDash_(s[8]), + _assertIsDash_(s[13]), + _assertIsDash_(s[18]), + _assertIsDash_(s[23]), + media_uuid_t {{ + _hexByteAt_(s, 0), + _hexByteAt_(s, 2), + _hexByteAt_(s, 4), + _hexByteAt_(s, 6), + _hexByteAt_(s, 9), + _hexByteAt_(s, 11), + _hexByteAt_(s, 14), + _hexByteAt_(s, 16), + _hexByteAt_(s, 19), + _hexByteAt_(s, 21), + _hexByteAt_(s, 24), + _hexByteAt_(s, 26), + _hexByteAt_(s, 28), + _hexByteAt_(s, 30), + _hexByteAt_(s, 32), + _hexByteAt_(s, 34), + }}; +} +// Convenience macro to create a media_uuid_t from a string literal, which should +// be formatted as "12345678-1234-1234-1234-123456789abc", as generated by +// e.g. https://www.uuidgenerator.net/ or the 'uuidgen' linux command. +// Hex digits may be upper or lower case. +// +// The macro call is otherwise equivalent to specifying the structure directly +// (e.g. UUID("7d613858-5837-4a38-84c5-332d1cddee27") is the same as +// {{0x7d, 0x61, 0x38, 0x58, 0x58, 0x37, 0x4a, 0x38, +// 0x84, 0xc5, 0x33, 0x2d, 0x1c, 0xdd, 0xee, 0x27}}) + +#define UUID(str) []{ constexpr media_uuid_t uuid = constUUID(str); return uuid; }() + +} // namespace android + +#endif // MEDIA_EXTRACTOR_PLUGIN_HELPER_H_
diff --git a/include/media/MediaMetrics.h b/include/media/MediaMetrics.h new file mode 120000 index 0000000..5f757e4 --- /dev/null +++ b/include/media/MediaMetrics.h
@@ -0,0 +1 @@ +../../media/libmediametrics/include/MediaMetrics.h \ No newline at end of file
diff --git a/include/media/MediaSource.h b/include/media/MediaSource.h index 2e147c4..34bf65d 120000 --- a/include/media/MediaSource.h +++ b/include/media/MediaSource.h
@@ -1 +1 @@ -../../media/libmediaextractor/include/media/MediaSource.h \ No newline at end of file +../../media/libstagefright/include/media/stagefright/MediaSource.h \ No newline at end of file
diff --git a/include/media/MediaTrack.h b/include/media/MediaTrack.h deleted file mode 120000 index 5a63287a..0000000 --- a/include/media/MediaTrack.h +++ /dev/null
@@ -1 +0,0 @@ -../../media/libmediaextractor/include/media/MediaTrack.h \ No newline at end of file
diff --git a/include/media/MediaTrack.h b/include/media/MediaTrack.h new file mode 100644 index 0000000..493eba3 --- /dev/null +++ b/include/media/MediaTrack.h
@@ -0,0 +1,165 @@ +/* + * 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 MEDIA_SOURCE_BASE_H_ + +#define MEDIA_SOURCE_BASE_H_ + +#include <sys/types.h> + +#include <binder/IMemory.h> +#include <binder/MemoryDealer.h> +#include <media/MediaExtractorPluginApi.h> +#include <media/stagefright/MediaBufferGroup.h> +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/MetaData.h> +#include <media/MediaExtractorPluginApi.h> +#include <utils/Log.h> +#include <utils/RefBase.h> +#include <utils/Vector.h> + +namespace android { + +class MediaBufferBase; +struct CMediaTrack; + +class SourceBaseAllocTracker { +public: + SourceBaseAllocTracker() { + ALOGD("sourcebase allocated: %p", this); + } + virtual ~SourceBaseAllocTracker() { + ALOGD("sourcebase freed: %p", this); + } +}; + +struct MediaTrack +// : public SourceBaseAllocTracker +{ + MediaTrack(); + + // To be called before any other methods on this object, except + // getFormat(). + virtual status_t start() = 0; + + // Any blocking read call returns immediately with a result of NO_INIT. + // It is an error to call any methods other than start after this call + // returns. Any buffers the object may be holding onto at the time of + // the stop() call are released. + // Also, it is imperative that any buffers output by this object and + // held onto by callers be released before a call to stop() !!! + virtual status_t stop() = 0; + + // Returns the format of the data output by this media track. + virtual status_t getFormat(MetaDataBase& format) = 0; + + // Options that modify read() behaviour. The default is to + // a) not request a seek + // b) not be late, i.e. lateness_us = 0 + struct ReadOptions { + enum SeekMode : int32_t { + SEEK_PREVIOUS_SYNC = CMediaTrackReadOptions::SEEK_PREVIOUS_SYNC, + SEEK_NEXT_SYNC = CMediaTrackReadOptions::SEEK_NEXT_SYNC, + SEEK_CLOSEST_SYNC = CMediaTrackReadOptions::SEEK_CLOSEST_SYNC, + SEEK_CLOSEST = CMediaTrackReadOptions::SEEK_CLOSEST, + SEEK_FRAME_INDEX = CMediaTrackReadOptions::SEEK_FRAME_INDEX, + }; + + ReadOptions() { + reset(); + } + + // Reset everything back to defaults. + void reset() { + mOptions = 0; + mSeekTimeUs = 0; + mNonBlocking = false; + } + + void setSeekTo(int64_t time_us, SeekMode mode = SEEK_CLOSEST_SYNC); + void clearSeekTo() { + mOptions &= ~kSeekTo_Option; + mSeekTimeUs = 0; + mSeekMode = SEEK_CLOSEST_SYNC; + } + bool getSeekTo(int64_t *time_us, SeekMode *mode) const; + + void setNonBlocking(); + void clearNonBlocking(); + bool getNonBlocking() const; + + // Used to clear all non-persistent options for multiple buffer reads. + void clearNonPersistent() { + clearSeekTo(); + } + + private: + enum Options { + kSeekTo_Option = 1, + }; + + uint32_t mOptions; + int64_t mSeekTimeUs; + SeekMode mSeekMode; + bool mNonBlocking; + } __attribute__((packed)); // sent through Binder + + // Returns a new buffer of data. Call blocks until a + // buffer is available, an error is encountered of the end of the stream + // is reached. + // End of stream is signalled by a result of ERROR_END_OF_STREAM. + // A result of INFO_FORMAT_CHANGED indicates that the format of this + // MediaSource has changed mid-stream, the client can continue reading + // but should be prepared for buffers of the new configuration. + virtual status_t read( + MediaBufferBase **buffer, const ReadOptions *options = NULL) = 0; + + // Returns true if |read| supports nonblocking option, otherwise false. + // |readMultiple| if supported, always allows the nonblocking option. + virtual bool supportNonblockingRead() { + return false; + } + + virtual ~MediaTrack(); + +private: + MediaTrack(const MediaTrack &); + MediaTrack &operator=(const MediaTrack &); +}; + +class MediaTrackCUnwrapper : public MediaTrack { +public: + static MediaTrackCUnwrapper *create(CMediaTrack *wrapper); + + virtual status_t start(); + virtual status_t stop(); + virtual status_t getFormat(MetaDataBase& format); + virtual status_t read(MediaBufferBase **buffer, const ReadOptions *options = NULL); + + virtual bool supportNonblockingRead(); + +protected: + virtual ~MediaTrackCUnwrapper(); + +private: + explicit MediaTrackCUnwrapper(CMediaTrack *wrapper); + CMediaTrack *wrapper; + MediaBufferGroup *bufferGroup; +}; + +} // namespace android + +#endif // MEDIA_SOURCE_BASE_H_
diff --git a/include/media/NdkMediaErrorPriv.h b/include/media/NdkMediaErrorPriv.h new file mode 100644 index 0000000..3bbba79 --- /dev/null +++ b/include/media/NdkMediaErrorPriv.h
@@ -0,0 +1,27 @@ +/* + * 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 _NDK_MEDIA_ERROR_PRIV_H +#define _NDK_MEDIA_ERROR_PRIV_H + +#include <media/NdkMediaError.h> +#include <utils/Errors.h> + +media_status_t translate_error(android::status_t); + +android::status_t reverse_translate_error(media_status_t); + +#endif // _NDK_MEDIA_ERROR_PRIV_H
diff --git a/include/media/NdkMediaFormatPriv.h b/include/media/NdkMediaFormatPriv.h new file mode 100644 index 0000000..1fda4a8 --- /dev/null +++ b/include/media/NdkMediaFormatPriv.h
@@ -0,0 +1,57 @@ +/* + * Copyright (C) 2014 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. + */ + +/* + * This file defines an NDK API. + * Do not remove methods. + * Do not change method signatures. + * Do not change the value of constants. + * Do not change the size of any of the classes defined in here. + * Do not reference types that are not part of the NDK. + * Do not #include files that aren't part of the NDK. + */ + +#ifndef _NDK_MEDIA_FORMAT_PRIV_H +#define _NDK_MEDIA_FORMAT_PRIV_H + +#include <utils/KeyedVector.h> +#include <utils/String8.h> +#include <utils/StrongPointer.h> +#include <media/stagefright/foundation/AMessage.h> + +#ifdef __cplusplus +extern "C" { +#endif + +struct AMediaFormat { + android::sp<android::AMessage> mFormat; + android::String8 mDebug; + android::KeyedVector<android::String8, android::String8> mStringCache; +}; + +#ifdef __cplusplus +} // extern "C" +#endif + +namespace android { + +AMediaFormat* AMediaFormat_fromMsg(sp<AMessage> *); +void AMediaFormat_getFormat(const AMediaFormat* mData, sp<AMessage> *dest); + +} // namespace android + +#endif // _NDK_MEDIA_FORMAT_PRIV_H +
diff --git a/include/media/TimeCheck.h b/include/media/TimeCheck.h index e3ef134..85e17f9 120000 --- a/include/media/TimeCheck.h +++ b/include/media/TimeCheck.h
@@ -1 +1 @@ -../../media/libmedia/include/media/TimeCheck.h \ No newline at end of file +../../media/utils/include/mediautils/TimeCheck.h \ No newline at end of file
diff --git a/include/media/VolumeShaper.h b/include/media/VolumeShaper.h index a3aaece..79afd6c 100644 --- a/include/media/VolumeShaper.h +++ b/include/media/VolumeShaper.h
@@ -551,7 +551,7 @@ static int64_t convertTimespecToUs(const struct timespec &tv) { - return tv.tv_sec * 1000000ll + tv.tv_nsec / 1000; + return tv.tv_sec * 1000000LL + tv.tv_nsec / 1000; } // current monotonic time in microseconds.
diff --git a/include/media/VorbisComment.h b/include/media/VorbisComment.h deleted file mode 120000 index adaa489..0000000 --- a/include/media/VorbisComment.h +++ /dev/null
@@ -1 +0,0 @@ -../../media/libmediaextractor/include/media/VorbisComment.h \ No newline at end of file
diff --git a/include/mediadrm/Crypto.h b/include/mediadrm/Crypto.h deleted file mode 120000 index 9af6495..0000000 --- a/include/mediadrm/Crypto.h +++ /dev/null
@@ -1 +0,0 @@ -../../media/libmedia/include/media/Crypto.h \ No newline at end of file
diff --git a/include/mediadrm/Drm.h b/include/mediadrm/Drm.h deleted file mode 120000 index ac60003..0000000 --- a/include/mediadrm/Drm.h +++ /dev/null
@@ -1 +0,0 @@ -../../media/libmedia/include/media/Drm.h \ No newline at end of file
diff --git a/include/private/media/AudioTrackShared.h b/include/private/media/AudioTrackShared.h index ca119d5..5f19f74 100644 --- a/include/private/media/AudioTrackShared.h +++ b/include/private/media/AudioTrackShared.h
@@ -538,6 +538,10 @@ mTimestampMutator.push(timestamp); } + virtual ExtendedTimestamp getTimestamp() const { + return mTimestampMutator.last(); + } + // Flushes the shared ring buffer if the client had requested it using mStreaming.mFlush. // If flush occurs then: // cblk->u.mStreaming.mFront, ServerProxy::mFlush and ServerProxy::mFlushed will be modified @@ -551,6 +555,9 @@ // Total count of the number of flushed frames since creation (never reset). virtual int64_t framesFlushed() const { return mFlushed; } + // Safe frames ready query with no side effects. + virtual size_t framesReadySafe() const = 0; + // Get dynamic buffer size from the shared control block. uint32_t getBufferSizeInFrames() const { return android_atomic_acquire_load((int32_t *)&mCblk->mBufferSizeInFrames); @@ -588,8 +595,7 @@ // which may include non-contiguous frames virtual size_t framesReady(); - // Safe frames ready query used by dump() - this has no side effects. - virtual size_t framesReadySafe() const; + size_t framesReadySafe() const override; // frames available to read by server. // Currently AudioFlinger will call framesReady() for a fast track from two threads: // FastMixer thread, and normal mixer thread. This is dangerous, as the proxy is intended @@ -693,6 +699,8 @@ return mCblk->u.mStreaming.mRear; // For completeness only; mRear written by server. } + size_t framesReadySafe() const override; // frames available to read by client. + protected: virtual ~AudioRecordServerProxy() { } };
diff --git a/include/soundtrigger/ISoundTrigger.h b/include/soundtrigger/ISoundTrigger.h index 5fd8eb2..c357caa 100644 --- a/include/soundtrigger/ISoundTrigger.h +++ b/include/soundtrigger/ISoundTrigger.h
@@ -40,6 +40,7 @@ virtual status_t startRecognition(sound_model_handle_t handle, const sp<IMemory>& dataMemory) = 0; virtual status_t stopRecognition(sound_model_handle_t handle) = 0; + virtual status_t getModelState(sound_model_handle_t handle) = 0; };
diff --git a/include/soundtrigger/ISoundTriggerHwService.h b/include/soundtrigger/ISoundTriggerHwService.h index ae0cb01..1faeb0f 100644 --- a/include/soundtrigger/ISoundTriggerHwService.h +++ b/include/soundtrigger/ISoundTriggerHwService.h
@@ -33,12 +33,14 @@ DECLARE_META_INTERFACE(SoundTriggerHwService); - virtual status_t listModules(struct sound_trigger_module_descriptor *modules, + virtual status_t listModules(const String16& opPackageName, + struct sound_trigger_module_descriptor *modules, uint32_t *numModules) = 0; - virtual status_t attach(const sound_trigger_module_handle_t handle, - const sp<ISoundTriggerClient>& client, - sp<ISoundTrigger>& module) = 0; + virtual status_t attach(const String16& opPackageName, + const sound_trigger_module_handle_t handle, + const sp<ISoundTriggerClient>& client, + sp<ISoundTrigger>& module) = 0; virtual status_t setCaptureState(bool active) = 0; };
diff --git a/include/soundtrigger/SoundTrigger.h b/include/soundtrigger/SoundTrigger.h index 7a29e31..ccc61dc 100644 --- a/include/soundtrigger/SoundTrigger.h +++ b/include/soundtrigger/SoundTrigger.h
@@ -36,10 +36,12 @@ virtual ~SoundTrigger(); - static status_t listModules(struct sound_trigger_module_descriptor *modules, + static status_t listModules(const String16& opPackageName, + struct sound_trigger_module_descriptor *modules, uint32_t *numModules); - static sp<SoundTrigger> attach(const sound_trigger_module_handle_t module, - const sp<SoundTriggerCallback>& callback); + static sp<SoundTrigger> attach(const String16& opPackageName, + const sound_trigger_module_handle_t module, + const sp<SoundTriggerCallback>& callback); static status_t setCaptureState(bool active); @@ -52,6 +54,7 @@ status_t startRecognition(sound_model_handle_t handle, const sp<IMemory>& dataMemory); status_t stopRecognition(sound_model_handle_t handle); + status_t getModelState(sound_model_handle_t handle); // BpSoundTriggerClient virtual void onRecognitionEvent(const sp<IMemory>& eventMemory);
diff --git a/media/OWNERS b/media/OWNERS index d49eb8d..1afc253 100644 --- a/media/OWNERS +++ b/media/OWNERS
@@ -1,9 +1,12 @@ +andrewlewis@google.com chz@google.com dwkang@google.com elaurent@google.com essick@google.com +gkasten@google.com hkuang@google.com hunga@google.com +jiabin@google.com jmtrivi@google.com krocard@google.com lajos@google.com @@ -15,5 +18,6 @@ rachad@google.com rago@google.com robertshih@google.com +taklee@google.com wjia@google.com wonsik@google.com
diff --git a/media/audioserver/Android.mk b/media/audioserver/Android.mk index 70c281a..33b36b8 100644 --- a/media/audioserver/Android.mk +++ b/media/audioserver/Android.mk
@@ -4,7 +4,6 @@ LOCAL_SRC_FILES := \ main_audioserver.cpp \ - ../libaudioclient/aidl/android/media/IAudioRecord.aidl LOCAL_SHARED_LIBRARIES := \ libaaudioservice \ @@ -13,13 +12,17 @@ libbinder \ libcutils \ liblog \ + libhidlbase \ libhidltransport \ libhwbinder \ libmedia \ libmedialogservice \ + libmediautils \ libnbaio \ + libnblog \ libsoundtriggerservice \ - libutils + libutils \ + libvibrator # TODO oboeservice is the old folder name for aaudioservice. It will be changed. LOCAL_C_INCLUDES := \ @@ -40,9 +43,6 @@ $(call include-path-for, audio-utils) \ external/sonic \ -LOCAL_AIDL_INCLUDES := \ - frameworks/av/media/libaudioclient/aidl - # If AUDIOSERVER_MULTILIB in device.mk is non-empty then it is used to control # the LOCAL_MULTILIB for all audioserver exclusive libraries. # This is relevant for 64 bit architectures where either or both @@ -54,13 +54,9 @@ # both to build both 32 bit and 64 bit libraries, # and use primary target architecture (32 or 64) for audioserver. # first to build libraries and audioserver for the primary target architecture only. -# <empty> to build both 32 and 64 bit libraries and 32 bit audioserver. +# <empty> to build both 32 and 64 bit libraries and primary target audioserver. -ifeq ($(strip $(AUDIOSERVER_MULTILIB)),) -LOCAL_MULTILIB := 32 -else LOCAL_MULTILIB := $(AUDIOSERVER_MULTILIB) -endif LOCAL_MODULE := audioserver
diff --git a/media/audioserver/audioserver.rc b/media/audioserver/audioserver.rc index 75675a9..dfb1a3f 100644 --- a/media/audioserver/audioserver.rc +++ b/media/audioserver/audioserver.rc
@@ -2,10 +2,12 @@ class core user audioserver # media gid needed for /dev/fm (radio) and for /data/misc/media (tee) - group audio camera drmrpc inet media mediadrm net_bt net_bt_admin net_bw_acct + group audio camera drmrpc inet media mediadrm net_bt net_bt_admin net_bw_acct wakelock + capabilities BLOCK_SUSPEND ioprio rt 4 writepid /dev/cpuset/foreground/tasks /dev/stune/foreground/tasks onrestart restart vendor.audio-hal-2-0 + onrestart restart vendor.audio-hal-4-0-msd # Keep the original service name for backward compatibility when upgrading # O-MR1 devices with framework-only. onrestart restart audio-hal-2-0 @@ -14,3 +16,6 @@ stop audioserver on property:vts.native_server.on=0 start audioserver + +on init + mkdir /dev/socket/audioserver 0775 audioserver audioserver
diff --git a/media/bufferpool/1.0/Accessor.cpp b/media/bufferpool/1.0/Accessor.cpp new file mode 100644 index 0000000..b1dfc08 --- /dev/null +++ b/media/bufferpool/1.0/Accessor.cpp
@@ -0,0 +1,201 @@ +/* + * 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/media/bufferpool/1.0/Accessor.h b/media/bufferpool/1.0/Accessor.h new file mode 100644 index 0000000..2f86f7b --- /dev/null +++ b/media/bufferpool/1.0/Accessor.h
@@ -0,0 +1,188 @@ +/* + * 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/media/bufferpool/1.0/AccessorImpl.cpp b/media/bufferpool/1.0/AccessorImpl.cpp new file mode 100644 index 0000000..fa17f15 --- /dev/null +++ b/media/bufferpool/1.0/AccessorImpl.cpp
@@ -0,0 +1,543 @@ +/* + * 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(nullptr); + +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/media/bufferpool/1.0/AccessorImpl.h b/media/bufferpool/1.0/AccessorImpl.h new file mode 100644 index 0000000..c04dbf3 --- /dev/null +++ b/media/bufferpool/1.0/AccessorImpl.h
@@ -0,0 +1,300 @@ +/* + * 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/media/bufferpool/1.0/Android.bp b/media/bufferpool/1.0/Android.bp new file mode 100644 index 0000000..c7ea70f --- /dev/null +++ b/media/bufferpool/1.0/Android.bp
@@ -0,0 +1,29 @@ +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/media/bufferpool/1.0/BufferPoolClient.cpp b/media/bufferpool/1.0/BufferPoolClient.cpp new file mode 100644 index 0000000..d712398 --- /dev/null +++ b/media/bufferpool/1.0/BufferPoolClient.cpp
@@ -0,0 +1,712 @@ +/* + * 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 = nullptr; + 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 != nullptr, (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 = nullptr; + 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; + }); + if(!transResult.isOk()) { + ALOGD("sync from client %lld failed: bufferpool process died.", + (long long)mConnectionId); + } + } + 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 = nullptr; + 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/media/bufferpool/1.0/BufferPoolClient.h b/media/bufferpool/1.0/BufferPoolClient.h new file mode 100644 index 0000000..577efed --- /dev/null +++ b/media/bufferpool/1.0/BufferPoolClient.h
@@ -0,0 +1,102 @@ +/* + * 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/media/bufferpool/1.0/BufferStatus.cpp b/media/bufferpool/1.0/BufferStatus.cpp new file mode 100644 index 0000000..169abce --- /dev/null +++ b/media/bufferpool/1.0/BufferStatus.cpp
@@ -0,0 +1,191 @@ +/* + * 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 = nullptr; + return ResultStatus::NO_MEMORY; + } else { + *fmqDescPtr = queue->getDesc(); + } + auto result = mBufferStatusQueues.insert( + std::make_pair(id, std::move(queue))); + if (!result.second) { + *fmqDescPtr = nullptr; + 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/media/bufferpool/1.0/BufferStatus.h b/media/bufferpool/1.0/BufferStatus.h new file mode 100644 index 0000000..a18a921 --- /dev/null +++ b/media/bufferpool/1.0/BufferStatus.h
@@ -0,0 +1,143 @@ +/* + * 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/media/bufferpool/1.0/ClientManager.cpp b/media/bufferpool/1.0/ClientManager.cpp new file mode 100644 index 0000000..ecea0a4 --- /dev/null +++ b/media/bufferpool/1.0/ClientManager.cpp
@@ -0,0 +1,504 @@ +/* + * 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/media/bufferpool/1.0/Connection.cpp b/media/bufferpool/1.0/Connection.cpp new file mode 100644 index 0000000..be89701 --- /dev/null +++ b/media/bufferpool/1.0/Connection.cpp
@@ -0,0 +1,97 @@ +/* + * 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 = nullptr; + status = mAccessor->fetch( + mConnectionId, transactionId, bufferId, &handle); + if (status == ResultStatus::OK) { + Buffer buffer = {}; + buffer.id = bufferId; + buffer.buffer = handle; + _hidl_cb(status, buffer); + return Void(); + } + } else { + mAccessor->cleanUp(false); + } + } + + Buffer buffer = {}; + buffer.id = 0; + buffer.buffer = nullptr; + + _hidl_cb(status, buffer); + 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/media/bufferpool/1.0/Connection.h b/media/bufferpool/1.0/Connection.h new file mode 100644 index 0000000..e19cb67 --- /dev/null +++ b/media/bufferpool/1.0/Connection.h
@@ -0,0 +1,103 @@ +/* + * 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/media/bufferpool/1.0/include/bufferpool/BufferPoolTypes.h b/media/bufferpool/1.0/include/bufferpool/BufferPoolTypes.h new file mode 100644 index 0000000..710f015 --- /dev/null +++ b/media/bufferpool/1.0/include/bufferpool/BufferPoolTypes.h
@@ -0,0 +1,118 @@ +/* + * 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/media/bufferpool/1.0/include/bufferpool/ClientManager.h b/media/bufferpool/1.0/include/bufferpool/ClientManager.h new file mode 100644 index 0000000..be5779f --- /dev/null +++ b/media/bufferpool/1.0/include/bufferpool/ClientManager.h
@@ -0,0 +1,179 @@ +/* + * 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/media/bufferpool/1.0/vts/Android.bp b/media/bufferpool/1.0/vts/Android.bp new file mode 100644 index 0000000..ee5a757 --- /dev/null +++ b/media/bufferpool/1.0/vts/Android.bp
@@ -0,0 +1,51 @@ +/* + * 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", + ], + 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", + ], + compile_multilib: "both", +}
diff --git a/media/bufferpool/1.0/vts/OWNERS b/media/bufferpool/1.0/vts/OWNERS new file mode 100644 index 0000000..6733e0c --- /dev/null +++ b/media/bufferpool/1.0/vts/OWNERS
@@ -0,0 +1,9 @@ +# Media team +lajos@google.com +pawin@google.com +taklee@google.com +wonsik@google.com + +# VTS team +yim@google.com +zhuoyao@google.com
diff --git a/media/bufferpool/1.0/vts/allocator.cpp b/media/bufferpool/1.0/vts/allocator.cpp new file mode 100644 index 0000000..843f7ea --- /dev/null +++ b/media/bufferpool/1.0/vts/allocator.cpp
@@ -0,0 +1,209 @@ +/* + * 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 <cutils/ashmem.h> +#include <sys/mman.h> +#include "allocator.h" + +union Params { + struct { + uint32_t capacity; + } data; + uint8_t array[0]; + Params() : data{0} {} + Params(uint32_t size) + : data{size} {} +}; + + +namespace { + +struct HandleAshmem : public native_handle_t { + HandleAshmem(int ashmemFd, size_t size) + : native_handle_t(cHeader), + mFds{ ashmemFd }, + mInts{ int (size & 0xFFFFFFFF), int((uint64_t(size) >> 32) & 0xFFFFFFFF), kMagic } {} + + int ashmemFd() const { return mFds.mAshmem; } + size_t size() const { + return size_t(unsigned(mInts.mSizeLo)) + | size_t(uint64_t(unsigned(mInts.mSizeHi)) << 32); + } + + static bool isValid(const native_handle_t * const o); + +protected: + struct { + int mAshmem; + } mFds; + struct { + int mSizeLo; + int mSizeHi; + int mMagic; + } mInts; + +private: + enum { + kMagic = 'ahm\x00', + numFds = sizeof(mFds) / sizeof(int), + numInts = sizeof(mInts) / sizeof(int), + version = sizeof(native_handle_t) + }; + const static native_handle_t cHeader; +}; + +const native_handle_t HandleAshmem::cHeader = { + HandleAshmem::version, + HandleAshmem::numFds, + HandleAshmem::numInts, + {} +}; + +bool HandleAshmem::isValid(const native_handle_t * const o) { + if (!o || memcmp(o, &cHeader, sizeof(cHeader))) { + return false; + } + const HandleAshmem *other = static_cast<const HandleAshmem*>(o); + return other->mInts.mMagic == kMagic; +} + +class AllocationAshmem { +private: + AllocationAshmem(int ashmemFd, size_t capacity, bool res) + : mHandle(ashmemFd, capacity), + mInit(res) {} + +public: + static AllocationAshmem *Alloc(size_t size) { + constexpr static const char *kAllocationTag = "bufferpool_test"; + int ashmemFd = ashmem_create_region(kAllocationTag, size); + return new AllocationAshmem(ashmemFd, size, ashmemFd >= 0); + } + + ~AllocationAshmem() { + if (mInit) { + native_handle_close(&mHandle); + } + } + + const HandleAshmem *handle() { + return &mHandle; + } + +private: + HandleAshmem mHandle; + bool mInit; + // TODO: mapping and map fd +}; + +struct AllocationDtor { + AllocationDtor(const std::shared_ptr<AllocationAshmem> &alloc) + : mAlloc(alloc) {} + + void operator()(BufferPoolAllocation *poolAlloc) { delete poolAlloc; } + + const std::shared_ptr<AllocationAshmem> mAlloc; +}; + +} + + +ResultStatus TestBufferPoolAllocator::allocate( + const std::vector<uint8_t> ¶ms, + std::shared_ptr<BufferPoolAllocation> *alloc, + size_t *allocSize) { + Params ashmemParams; + memcpy(&ashmemParams, params.data(), std::min(sizeof(Params), params.size())); + + std::shared_ptr<AllocationAshmem> ashmemAlloc = + std::shared_ptr<AllocationAshmem>( + AllocationAshmem::Alloc(ashmemParams.data.capacity)); + if (ashmemAlloc) { + BufferPoolAllocation *ptr = new BufferPoolAllocation(ashmemAlloc->handle()); + if (ptr) { + *alloc = std::shared_ptr<BufferPoolAllocation>(ptr, AllocationDtor(ashmemAlloc)); + if (*alloc) { + *allocSize = ashmemParams.data.capacity; + return ResultStatus::OK; + } + delete ptr; + return ResultStatus::NO_MEMORY; + } + } + return ResultStatus::CRITICAL_ERROR; +} + +bool TestBufferPoolAllocator::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; +} + +bool TestBufferPoolAllocator::Fill(const native_handle_t *handle, const unsigned char val) { + if (!HandleAshmem::isValid(handle)) { + return false; + } + const HandleAshmem *o = static_cast<const HandleAshmem*>(handle); + unsigned char *ptr = (unsigned char *)mmap( + NULL, o->size(), PROT_READ|PROT_WRITE, MAP_SHARED, o->ashmemFd(), 0); + + if (ptr != MAP_FAILED) { + for (size_t i = 0; i < o->size(); ++i) { + ptr[i] = val; + } + munmap(ptr, o->size()); + return true; + } + return false; +} + +bool TestBufferPoolAllocator::Verify(const native_handle_t *handle, const unsigned char val) { + if (!HandleAshmem::isValid(handle)) { + return false; + } + const HandleAshmem *o = static_cast<const HandleAshmem*>(handle); + unsigned char *ptr = (unsigned char *)mmap( + NULL, o->size(), PROT_READ, MAP_SHARED, o->ashmemFd(), 0); + + if (ptr != MAP_FAILED) { + bool res = true; + for (size_t i = 0; i < o->size(); ++i) { + if (ptr[i] != val) { + res = false; + break; + } + } + munmap(ptr, o->size()); + return res; + } + return false; +} + +void getTestAllocatorParams(std::vector<uint8_t> *params) { + constexpr static int kAllocationSize = 1024 * 10; + Params ashmemParams(kAllocationSize); + + params->assign(ashmemParams.array, ashmemParams.array + sizeof(ashmemParams)); +}
diff --git a/media/bufferpool/1.0/vts/allocator.h b/media/bufferpool/1.0/vts/allocator.h new file mode 100644 index 0000000..886e5f2 --- /dev/null +++ b/media/bufferpool/1.0/vts/allocator.h
@@ -0,0 +1,51 @@ +/* + * 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 VNDK_HIDL_BUFFERPOOL_V1_0_ALLOCATOR_H +#define 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 TestBufferPoolAllocator : public BufferPoolAllocator { + public: + TestBufferPoolAllocator() {} + + ~TestBufferPoolAllocator() 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; + + static bool Fill(const native_handle_t *handle, const unsigned char val); + + static bool Verify(const native_handle_t *handle, const unsigned char val); + +}; + +// retrieve buffer allocator paramters +void getTestAllocatorParams(std::vector<uint8_t> *params); + +#endif // VNDK_HIDL_BUFFERPOOL_V1_0_ALLOCATOR_H
diff --git a/media/bufferpool/1.0/vts/multi.cpp b/media/bufferpool/1.0/vts/multi.cpp new file mode 100644 index 0000000..1796819 --- /dev/null +++ b/media/bufferpool/1.0/vts/multi.cpp
@@ -0,0 +1,223 @@ +/* + * 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 <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::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); + + mAllocator = std::make_shared<TestBufferPoolAllocator>(); + 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) { + if (!TestBufferPoolAllocator::Verify(rhandle, 0x77)) { + 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; + + getTestAllocatorParams(&vecParams); + status = mManager->allocate(mConnectionId, vecParams, &shandle, &sbuffer); + ASSERT_TRUE(status == ResultStatus::OK); + + ASSERT_TRUE(TestBufferPoolAllocator::Fill(shandle, 0x77)); + + 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/media/bufferpool/1.0/vts/single.cpp b/media/bufferpool/1.0/vts/single.cpp new file mode 100644 index 0000000..f73eb62 --- /dev/null +++ b/media/bufferpool/1.0/vts/single.cpp
@@ -0,0 +1,166 @@ +/* + * 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 <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::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); + + mAllocator = std::make_shared<TestBufferPoolAllocator>(); + 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; + getTestAllocatorParams(&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; + getTestAllocatorParams(&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; + getTestAllocatorParams(&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); + ASSERT_TRUE(TestBufferPoolAllocator::Fill(allocHandle, 0x77)); + 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); + ASSERT_TRUE(TestBufferPoolAllocator::Verify(recvHandle, 0x77)); +} + +} // 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/media/bufferpool/2.0/Accessor.cpp b/media/bufferpool/2.0/Accessor.cpp new file mode 100644 index 0000000..57b4609 --- /dev/null +++ b/media/bufferpool/2.0/Accessor.cpp
@@ -0,0 +1,226 @@ +/* + * 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 V2_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; +} + +void Accessor::createInvalidator() { + Accessor::Impl::createInvalidator(); +} + +// Methods from ::android::hardware::media::bufferpool::V2_0::IAccessor follow. +Return<void> Accessor::connect( + const sp<::android::hardware::media::bufferpool::V2_0::IObserver>& observer, + connect_cb _hidl_cb) { + sp<Connection> connection; + ConnectionId connectionId; + uint32_t msgId; + const StatusDescriptor* fmqDesc; + const InvalidationDescriptor* invDesc; + + ResultStatus status = connect( + observer, false, &connection, &connectionId, &msgId, &fmqDesc, &invDesc); + if (status == ResultStatus::OK) { + _hidl_cb(status, connection, connectionId, msgId, *fmqDesc, *invDesc); + } else { + _hidl_cb(status, nullptr, -1LL, 0, + android::hardware::MQDescriptorSync<BufferStatusMessage>( + std::vector<android::hardware::GrantorDescriptor>(), + nullptr /* nhandle */, 0 /* size */), + android::hardware::MQDescriptorUnsync<BufferInvalidationMessage>( + 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 && mImpl->isValid(); +} + +ResultStatus Accessor::flush() { + if (mImpl) { + mImpl->flush(); + return ResultStatus::OK; + } + return ResultStatus::CRITICAL_ERROR; +} + +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( + const sp<IObserver> &observer, bool local, + sp<Connection> *connection, ConnectionId *pConnectionId, + uint32_t *pMsgId, + const StatusDescriptor** statusDescPtr, + const InvalidationDescriptor** invDescPtr) { + if (mImpl) { + ResultStatus status = mImpl->connect( + this, observer, connection, pConnectionId, pMsgId, + statusDescPtr, invDescPtr); + 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 V2_0 +} // namespace bufferpool +} // namespace media +} // namespace hardware +} // namespace android
diff --git a/media/bufferpool/2.0/Accessor.h b/media/bufferpool/2.0/Accessor.h new file mode 100644 index 0000000..8d02519 --- /dev/null +++ b/media/bufferpool/2.0/Accessor.h
@@ -0,0 +1,202 @@ +/* + * 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_V2_0_ACCESSOR_H +#define ANDROID_HARDWARE_MEDIA_BUFFERPOOL_V2_0_ACCESSOR_H + +#include <android/hardware/media/bufferpool/2.0/IAccessor.h> +#include <android/hardware/media/bufferpool/2.0/IObserver.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 V2_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::V2_0::IAccessor follow. + Return<void> connect(const sp<::android::hardware::media::bufferpool::V2_0::IObserver>& observer, 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(); + + /** Invalidates all buffers which are owned by bufferpool */ + ResultStatus flush(); + + /** 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 observer client observer for buffer invalidation + * @param local true when a connection request comes from local process, + * false otherwise. + * @param connection created connection + * @param pConnectionId the id of the created connection + * @param pMsgId the id of the recent buffer pool message + * @param statusDescPtr FMQ descriptor for shared buffer status message + * queue between a buffer pool and the client. + * @param invDescPtr FMQ descriptor for buffer invalidation message + * queue from a buffer pool to the client. + * + * @return OK when a connection is successfully made. + * NO_MEMORY when there is no memory. + * CRITICAL_ERROR otherwise. + */ + ResultStatus connect( + const sp<IObserver>& observer, + bool local, + sp<Connection> *connection, ConnectionId *pConnectionId, + uint32_t *pMsgId, + const StatusDescriptor** statusDescPtr, + const InvalidationDescriptor** invDescPtr); + + /** + * 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(); + + static void createInvalidator(); + +private: + class Impl; + std::shared_ptr<Impl> mImpl; +}; + +} // namespace implementation +} // namespace V2_0 +} // namespace bufferpool +} // namespace media +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_MEDIA_BUFFERPOOL_V2_0_ACCESSOR_H
diff --git a/media/bufferpool/2.0/AccessorImpl.cpp b/media/bufferpool/2.0/AccessorImpl.cpp new file mode 100644 index 0000000..94cf006 --- /dev/null +++ b/media/bufferpool/2.0/AccessorImpl.cpp
@@ -0,0 +1,861 @@ +/* + * 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 <thread> +#include "AccessorImpl.h" +#include "Connection.h" + +namespace android { +namespace hardware { +namespace media { +namespace bufferpool { +namespace V2_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; + bool mInvalidated; + + 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), + mInvalidated(false) {} + + const native_handle_t *handle() { + return mAllocation->handle(); + } + + void invalidate() { + mInvalidated = true; + } +}; + +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(nullptr); + +Accessor::Impl::Impl( + const std::shared_ptr<BufferPoolAllocator> &allocator) + : mAllocator(allocator) {} + +Accessor::Impl::~Impl() { +} + +ResultStatus Accessor::Impl::connect( + const sp<Accessor> &accessor, const sp<IObserver> &observer, + sp<Connection> *connection, + ConnectionId *pConnectionId, + uint32_t *pMsgId, + const StatusDescriptor** statusDescPtr, + const InvalidationDescriptor** invDescPtr) { + 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, statusDescPtr); + if (status == ResultStatus::OK) { + newConnection->initialize(accessor, id); + *connection = newConnection; + *pConnectionId = id; + *pMsgId = mBufferPool.mInvalidation.mInvalidationId; + mBufferPool.mInvalidationChannel.getDesc(invDescPtr); + mBufferPool.mInvalidation.onConnect(id, observer); + ++sSeqId; + } + + } + mBufferPool.processStatusMessages(); + mBufferPool.cleanUp(); + } + return status; +} + +ResultStatus Accessor::Impl::close(ConnectionId connectionId) { + std::lock_guard<std::mutex> lock(mBufferPool.mMutex); + ALOGV("connection close %lld: %u", (long long)connectionId, mBufferPool.mInvalidation.mId); + mBufferPool.processStatusMessages(); + mBufferPool.handleClose(connectionId); + mBufferPool.mObserver.close(connectionId); + mBufferPool.mInvalidation.onClose(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); +} + +void Accessor::Impl::flush() { + std::lock_guard<std::mutex> lock(mBufferPool.mMutex); + mBufferPool.processStatusMessages(); + mBufferPool.flush(shared_from_this()); +} + +void Accessor::Impl::handleInvalidateAck() { + std::map<ConnectionId, const sp<IObserver>> observers; + uint32_t invalidationId; + { + std::lock_guard<std::mutex> lock(mBufferPool.mMutex); + mBufferPool.processStatusMessages(); + mBufferPool.mInvalidation.onHandleAck(&observers, &invalidationId); + } + // Do not hold lock for send invalidations + size_t deadClients = 0; + for (auto it = observers.begin(); it != observers.end(); ++it) { + const sp<IObserver> observer = it->second; + if (observer) { + Return<void> transResult = observer->onMessage(it->first, invalidationId); + if (!transResult.isOk()) { + ++deadClients; + } + } + } + if (deadClients > 0) { + ALOGD("During invalidation found %zu dead clients", deadClients); + } +} + +bool Accessor::Impl::isValid() { + return mBufferPool.isValid(); +} + +Accessor::Impl::Impl::BufferPool::BufferPool() + : mTimestampUs(getTimestampNow()), + mLastCleanUpUs(mTimestampUs), + mLastLogUs(mTimestampUs), + mSeq(0), + mStartSeq(0) { + mValid = mInvalidationChannel.isValid(); +} + + +// 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); +} + +std::atomic<std::uint32_t> Accessor::Impl::BufferPool::Invalidation::sInvSeqId(0); + +Accessor::Impl::Impl::BufferPool::~BufferPool() { + std::lock_guard<std::mutex> lock(mMutex); + ALOGD("Destruction - bufferpool2 %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)); +} + +void Accessor::Impl::BufferPool::Invalidation::onConnect( + ConnectionId conId, const sp<IObserver>& observer) { + mAcks[conId] = mInvalidationId; // starts from current invalidationId + mObservers.insert(std::make_pair(conId, observer)); +} + +void Accessor::Impl::BufferPool::Invalidation::onClose(ConnectionId conId) { + mAcks.erase(conId); + mObservers.erase(conId); +} + +void Accessor::Impl::BufferPool::Invalidation::onAck( + ConnectionId conId, + uint32_t msgId) { + auto it = mAcks.find(conId); + if (it == mAcks.end()) { + ALOGW("ACK from inconsistent connection! %lld", (long long)conId); + return; + } + if (isMessageLater(msgId, it->second)) { + mAcks[conId] = msgId; + } +} + +void Accessor::Impl::BufferPool::Invalidation::onBufferInvalidated( + BufferId bufferId, + BufferInvalidationChannel &channel) { + for (auto it = mPendings.begin(); it != mPendings.end();) { + if (it->isInvalidated(bufferId)) { + uint32_t msgId = 0; + if (it->mNeedsAck) { + msgId = ++mInvalidationId; + if (msgId == 0) { + // wrap happens + msgId = ++mInvalidationId; + } + } + channel.postInvalidation(msgId, it->mFrom, it->mTo); + it = mPendings.erase(it); + continue; + } + ++it; + } +} + +void Accessor::Impl::BufferPool::Invalidation::onInvalidationRequest( + bool needsAck, + uint32_t from, + uint32_t to, + size_t left, + BufferInvalidationChannel &channel, + const std::shared_ptr<Accessor::Impl> &impl) { + uint32_t msgId = 0; + if (needsAck) { + msgId = ++mInvalidationId; + if (msgId == 0) { + // wrap happens + msgId = ++mInvalidationId; + } + } + ALOGV("bufferpool2 invalidation requested and queued"); + if (left == 0) { + channel.postInvalidation(msgId, from, to); + } else { + // TODO: sending hint message? + ALOGV("bufferpoo2 invalidation requested and pending"); + Pending pending(needsAck, from, to, left, impl); + mPendings.push_back(pending); + } + sInvalidator->addAccessor(mId, impl); +} + +void Accessor::Impl::BufferPool::Invalidation::onHandleAck( + std::map<ConnectionId, const sp<IObserver>> *observers, + uint32_t *invalidationId) { + if (mInvalidationId != 0) { + *invalidationId = mInvalidationId; + std::set<int> deads; + for (auto it = mAcks.begin(); it != mAcks.end(); ++it) { + if (it->second != mInvalidationId) { + const sp<IObserver> observer = mObservers[it->first]; + if (observer) { + observers->emplace(it->first, observer); + ALOGV("connection %lld will call observer (%u: %u)", + (long long)it->first, it->second, mInvalidationId); + // N.B: onMessage will be called later. ignore possibility of + // onMessage# oneway call being lost. + it->second = mInvalidationId; + } else { + ALOGV("bufferpool2 observer died %lld", (long long)it->first); + deads.insert(it->first); + } + } + } + if (deads.size() > 0) { + for (auto it = deads.begin(); it != deads.end(); ++it) { + onClose(*it); + } + } + } + if (mPendings.size() == 0) { + // All invalidation Ids are synced and no more pending invalidations. + sInvalidator->delAccessor(mId); + } +} + +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) { + if (!iter->second->mInvalidated) { + mStats.onBufferUnused(iter->second->mAllocSize); + mFreeBuffers.insert(bufferId); + } else { + mStats.onBufferUnused(iter->second->mAllocSize); + mStats.onBufferEvicted(iter->second->mAllocSize); + mBuffers.erase(iter); + mInvalidation.onBufferInvalidated(bufferId, mInvalidationChannel); + } + } + } + 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) { + if (!bufferIter->second->mInvalidated) { + mStats.onBufferUnused(bufferIter->second->mAllocSize); + mFreeBuffers.insert(message.bufferId); + } else { + mStats.onBufferUnused(bufferIter->second->mAllocSize); + mStats.onBufferEvicted(bufferIter->second->mAllocSize); + mBuffers.erase(bufferIter); + mInvalidation.onBufferInvalidated(message.bufferId, mInvalidationChannel); + } + } + 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; + case BufferStatus::INVALIDATION_ACK: + mInvalidation.onAck(message.connectionId, message.bufferId); + ret = true; + 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 + if (!bufferIter->second->mInvalidated) { + mStats.onBufferUnused(bufferIter->second->mAllocSize); + mFreeBuffers.insert(bufferId); + } else { + mStats.onBufferUnused(bufferIter->second->mAllocSize); + mStats.onBufferEvicted(bufferIter->second->mAllocSize); + mBuffers.erase(bufferIter); + mInvalidation.onBufferInvalidated(bufferId, mInvalidationChannel); + } + } + } + } + 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 + if (!bufferIter->second->mInvalidated) { + mStats.onBufferUnused(bufferIter->second->mAllocSize); + mFreeBuffers.insert(bufferId); + } else { + mStats.onBufferUnused(bufferIter->second->mAllocSize); + mStats.onBufferEvicted(bufferIter->second->mAllocSize); + mBuffers.erase(bufferIter); + mInvalidation.onBufferInvalidated(bufferId, mInvalidationChannel); + } + } + 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("bufferpool2 %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("bufferpool2 inconsistent!"); + } + } + } +} + +void Accessor::Impl::BufferPool::invalidate( + bool needsAck, BufferId from, BufferId to, + const std::shared_ptr<Accessor::Impl> &impl) { + for (auto freeIt = mFreeBuffers.begin(); freeIt != mFreeBuffers.end();) { + if (isBufferInRange(from, to, *freeIt)) { + 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); + continue; + } else { + ALOGW("bufferpool2 inconsistent!"); + } + } + ++freeIt; + } + + size_t left = 0; + for (auto it = mBuffers.begin(); it != mBuffers.end(); ++it) { + if (isBufferInRange(from, to, it->first)) { + it->second->invalidate(); + ++left; + } + } + mInvalidation.onInvalidationRequest(needsAck, from, to, left, mInvalidationChannel, impl); +} + +void Accessor::Impl::BufferPool::flush(const std::shared_ptr<Accessor::Impl> &impl) { + BufferId from = mStartSeq; + BufferId to = mSeq; + mStartSeq = mSeq; + // TODO: needsAck params + ALOGV("buffer invalidation request bp:%u %u %u", mInvalidation.mId, from, to); + if (from != to) { + invalidate(true, from, to, impl); + } +} + +void Accessor::Impl::invalidatorThread( + std::map<uint32_t, const std::weak_ptr<Accessor::Impl>> &accessors, + std::mutex &mutex, + std::condition_variable &cv, + bool &ready) { + while(true) { + std::map<uint32_t, const std::weak_ptr<Accessor::Impl>> copied; + { + std::unique_lock<std::mutex> lock(mutex); + if (!ready) { + cv.wait(lock); + } + copied.insert(accessors.begin(), accessors.end()); + } + std::list<ConnectionId> erased; + for (auto it = copied.begin(); it != copied.end(); ++it) { + const std::shared_ptr<Accessor::Impl> impl = it->second.lock(); + if (!impl) { + erased.push_back(it->first); + } else { + impl->handleInvalidateAck(); + } + } + { + std::unique_lock<std::mutex> lock(mutex); + for (auto it = erased.begin(); it != erased.end(); ++it) { + accessors.erase(*it); + } + if (accessors.size() == 0) { + ready = false; + } else { + // prevent draining cpu. + lock.unlock(); + std::this_thread::yield(); + } + } + } +} + +Accessor::Impl::AccessorInvalidator::AccessorInvalidator() : mReady(false) { + std::thread invalidator( + invalidatorThread, + std::ref(mAccessors), + std::ref(mMutex), + std::ref(mCv), + std::ref(mReady)); + invalidator.detach(); +} + +void Accessor::Impl::AccessorInvalidator::addAccessor( + uint32_t accessorId, const std::weak_ptr<Accessor::Impl> &impl) { + bool notify = false; + std::unique_lock<std::mutex> lock(mMutex); + if (mAccessors.find(accessorId) == mAccessors.end()) { + if (!mReady) { + mReady = true; + notify = true; + } + mAccessors.insert(std::make_pair(accessorId, impl)); + ALOGV("buffer invalidation added bp:%u %d", accessorId, notify); + } + lock.unlock(); + if (notify) { + mCv.notify_one(); + } +} + +void Accessor::Impl::AccessorInvalidator::delAccessor(uint32_t accessorId) { + std::lock_guard<std::mutex> lock(mMutex); + mAccessors.erase(accessorId); + ALOGV("buffer invalidation deleted bp:%u", accessorId); + if (mAccessors.size() == 0) { + mReady = false; + } +} + +std::unique_ptr<Accessor::Impl::AccessorInvalidator> Accessor::Impl::sInvalidator; + +void Accessor::Impl::createInvalidator() { + if (!sInvalidator) { + sInvalidator = std::make_unique<Accessor::Impl::AccessorInvalidator>(); + } +} + +} // namespace implementation +} // namespace V2_0 +} // namespace bufferpool +} // namespace media +} // namespace hardware +} // namespace android
diff --git a/media/bufferpool/2.0/AccessorImpl.h b/media/bufferpool/2.0/AccessorImpl.h new file mode 100644 index 0000000..eea72b9 --- /dev/null +++ b/media/bufferpool/2.0/AccessorImpl.h
@@ -0,0 +1,401 @@ +/* + * 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_V2_0_ACCESSORIMPL_H +#define ANDROID_HARDWARE_MEDIA_BUFFERPOOL_V2_0_ACCESSORIMPL_H + +#include <map> +#include <set> +#include <condition_variable> +#include "Accessor.h" + +namespace android { +namespace hardware { +namespace media { +namespace bufferpool { +namespace V2_0 { +namespace implementation { + +struct InternalBuffer; +struct TransactionStatus; + +/** + * An implementation of a buffer pool accessor(or a buffer pool implementation.) */ +class Accessor::Impl + : public std::enable_shared_from_this<Accessor::Impl> { +public: + Impl(const std::shared_ptr<BufferPoolAllocator> &allocator); + + ~Impl(); + + ResultStatus connect( + const sp<Accessor> &accessor, const sp<IObserver> &observer, + sp<Connection> *connection, + ConnectionId *pConnectionId, + uint32_t *pMsgId, + const StatusDescriptor** statusDescPtr, + const InvalidationDescriptor** invDescPtr); + + 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 flush(); + + void cleanUp(bool clearCache); + + bool isValid(); + + void handleInvalidateAck(); + + static void createInvalidator(); + +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; + BufferId mStartSeq; + bool mValid; + BufferStatusObserver mObserver; + BufferInvalidationChannel mInvalidationChannel; + + 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; + + struct Invalidation { + static std::atomic<std::uint32_t> sInvSeqId; + + struct Pending { + bool mNeedsAck; + uint32_t mFrom; + uint32_t mTo; + size_t mLeft; + const std::weak_ptr<Accessor::Impl> mImpl; + Pending(bool needsAck, uint32_t from, uint32_t to, size_t left, + const std::shared_ptr<Accessor::Impl> &impl) + : mNeedsAck(needsAck), + mFrom(from), + mTo(to), + mLeft(left), + mImpl(impl) + {} + + bool isInvalidated(uint32_t bufferId) { + return isBufferInRange(mFrom, mTo, bufferId) && --mLeft == 0; + } + }; + + std::list<Pending> mPendings; + std::map<ConnectionId, uint32_t> mAcks; + std::map<ConnectionId, const sp<IObserver>> mObservers; + uint32_t mInvalidationId; + uint32_t mId; + + Invalidation() : mInvalidationId(0), mId(sInvSeqId.fetch_add(1)) {} + + void onConnect(ConnectionId conId, const sp<IObserver> &observer); + + void onClose(ConnectionId conId); + + void onAck(ConnectionId conId, uint32_t msgId); + + void onBufferInvalidated( + BufferId bufferId, + BufferInvalidationChannel &channel); + + void onInvalidationRequest( + bool needsAck, uint32_t from, uint32_t to, size_t left, + BufferInvalidationChannel &channel, + const std::shared_ptr<Accessor::Impl> &impl); + + void onHandleAck( + std::map<ConnectionId, const sp<IObserver>> *observers, + uint32_t *invalidationId); + } mInvalidation; + /// 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; + + bool isValid() { + return mValid; + } + + void invalidate(bool needsAck, BufferId from, BufferId to, + const std::shared_ptr<Accessor::Impl> &impl); + + static void createInvalidator(); + + 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); + + /** + * Processes pending buffer status messages and invalidate all current + * free buffers. Active buffers are invalidated after being inactive. + */ + void flush(const std::shared_ptr<Accessor::Impl> &impl); + + friend class Accessor::Impl; + } mBufferPool; + + struct AccessorInvalidator { + std::map<uint32_t, const std::weak_ptr<Accessor::Impl>> mAccessors; + std::mutex mMutex; + std::condition_variable mCv; + bool mReady; + + AccessorInvalidator(); + void addAccessor(uint32_t accessorId, const std::weak_ptr<Accessor::Impl> &impl); + void delAccessor(uint32_t accessorId); + }; + + static std::unique_ptr<AccessorInvalidator> sInvalidator; + + static void invalidatorThread( + std::map<uint32_t, const std::weak_ptr<Accessor::Impl>> &accessors, + std::mutex &mutex, + std::condition_variable &cv, + bool &ready); +}; + +} // namespace implementation +} // namespace V2_0 +} // namespace ufferpool +} // namespace media +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_MEDIA_BUFFERPOOL_V2_0_ACCESSORIMPL_H
diff --git a/media/bufferpool/2.0/Android.bp b/media/bufferpool/2.0/Android.bp new file mode 100644 index 0000000..c71ac17 --- /dev/null +++ b/media/bufferpool/2.0/Android.bp
@@ -0,0 +1,33 @@ +cc_library { + name: "libstagefright_bufferpool@2.0", + vendor_available: true, + vndk: { + enabled: true, + }, + srcs: [ + "Accessor.cpp", + "AccessorImpl.cpp", + "BufferPoolClient.cpp", + "BufferStatus.cpp", + "ClientManager.cpp", + "Connection.cpp", + "Observer.cpp", + ], + export_include_dirs: [ + "include", + ], + shared_libs: [ + "libcutils", + "libfmq", + "libhidlbase", + "libhwbinder", + "libhidltransport", + "liblog", + "libutils", + "android.hardware.media.bufferpool@2.0", + ], + export_shared_lib_headers: [ + "libfmq", + "android.hardware.media.bufferpool@2.0", + ], +}
diff --git a/media/bufferpool/2.0/BufferPoolClient.cpp b/media/bufferpool/2.0/BufferPoolClient.cpp new file mode 100644 index 0000000..342fef6 --- /dev/null +++ b/media/bufferpool/2.0/BufferPoolClient.cpp
@@ -0,0 +1,868 @@ +/* + * 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 V2_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, const sp<IObserver> &observer); + + explicit Impl(const sp<IAccessor> &accessor, const sp<IObserver> &observer); + + 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); + + void receiveInvalidation(uint32_t msgID); + + ResultStatus flush(); + + 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(uint32_t msgId = 0); + + void evictCaches(bool clearCache = false); + + void invalidateBuffer(BufferId id); + + void invalidateRange(BufferId from, BufferId to); + + 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; + std::unique_ptr<BufferInvalidationListener> mInvalidationListener; + + // 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 ReleaseCache { + std::mutex mLock; + // TODO: use only one list?(using one list may dealy sending messages?) + std::list<BufferId> mReleasingIds; + std::list<BufferId> mReleasedIds; + uint32_t mInvalidateId; // TODO: invalidation ACK to bufferpool + bool mInvalidateAck; + std::unique_ptr<BufferStatusChannel> mStatusChannel; + + ReleaseCache() : mInvalidateId(0), mInvalidateAck(true) {} + } 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: + 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) + : mHasCache(false), mConnectionId(connectionId), + mId(id), mHandle(handle) { + mExpireUs = getTimestampNow() + kCacheTtlUs; + } + + ~ClientBuffer() { + if (mHandle) { + native_handle_close(mHandle); + native_handle_delete(mHandle); + } + } + + BufferId id() const { + return mId; + } + + 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, const sp<IObserver> &observer) + : mLocal(true), mValid(false), mAccessor(accessor), mSeqId(0), + mLastEvictCacheUs(getTimestampNow()) { + const StatusDescriptor *statusDesc; + const InvalidationDescriptor *invDesc; + ResultStatus status = accessor->connect( + observer, true, + &mLocalConnection, &mConnectionId, &mReleasing.mInvalidateId, + &statusDesc, &invDesc); + if (status == ResultStatus::OK) { + mReleasing.mStatusChannel = + std::make_unique<BufferStatusChannel>(*statusDesc); + mInvalidationListener = + std::make_unique<BufferInvalidationListener>(*invDesc); + mValid = mReleasing.mStatusChannel && + mReleasing.mStatusChannel->isValid() && + mInvalidationListener && + mInvalidationListener->isValid(); + } +} + +BufferPoolClient::Impl::Impl(const sp<IAccessor> &accessor, const sp<IObserver> &observer) + : mLocal(false), mValid(false), mAccessor(accessor), mSeqId(0), + mLastEvictCacheUs(getTimestampNow()) { + bool valid = false; + sp<IConnection>& outConnection = mRemoteConnection; + ConnectionId& id = mConnectionId; + uint32_t& outMsgId = mReleasing.mInvalidateId; + std::unique_ptr<BufferStatusChannel>& outChannel = + mReleasing.mStatusChannel; + std::unique_ptr<BufferInvalidationListener>& outObserver = + mInvalidationListener; + Return<void> transResult = accessor->connect( + observer, + [&valid, &outConnection, &id, &outMsgId, &outChannel, &outObserver] + (ResultStatus status, sp<IConnection> connection, + ConnectionId connectionId, uint32_t msgId, + const StatusDescriptor& statusDesc, + const InvalidationDescriptor& invDesc) { + if (status == ResultStatus::OK) { + outConnection = connection; + id = connectionId; + outMsgId = msgId; + outChannel = std::make_unique<BufferStatusChannel>(statusDesc); + outObserver = std::make_unique<BufferInvalidationListener>(invDesc); + if (outChannel && outChannel->isValid() && + outObserver && outObserver->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; +} + +void BufferPoolClient::Impl::receiveInvalidation(uint32_t messageId) { + std::lock_guard<std::mutex> lock(mCache.mLock); + syncReleased(messageId); + // TODO: evict cache required? +} + +ResultStatus BufferPoolClient::Impl::flush() { + if (!mLocal || !mLocalConnection || !mValid) { + return ResultStatus::CRITICAL_ERROR; + } + { + std::unique_lock<std::mutex> lock(mCache.mLock); + syncReleased(); + evictCaches(); + return mLocalConnection->flush(); + } +} + +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 = nullptr; + 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 != nullptr, (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 = nullptr; + 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) { + { + // TODO: don't need to call syncReleased every time + std::lock_guard<std::mutex> lock(mCache.mLock); + syncReleased(); + } + 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; + }); + if (!transResult.isOk()) { + ALOGD("sync from client %lld failed: bufferpool process died.", + (long long)mConnectionId); + } + } + mRemoteSyncLock.unlock(); + } +} + +// should have mCache.mLock +bool BufferPoolClient::Impl::syncReleased(uint32_t messageId) { + bool cleared = false; + { + 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(); + cleared = true; + } + } + std::vector<BufferInvalidationMessage> invalidations; + mInvalidationListener->getInvalidations(invalidations); + uint32_t lastMsgId = 0; + if (invalidations.size() > 0) { + for (auto it = invalidations.begin(); it != invalidations.end(); ++it) { + if (it->messageId != 0) { + lastMsgId = it->messageId; + } + if (it->fromBufferId == it->toBufferId) { + // TODO: handle fromBufferId = UINT32_MAX + invalidateBuffer(it->fromBufferId); + } else { + invalidateRange(it->fromBufferId, it->toBufferId); + } + } + } + { + std::lock_guard<std::mutex> lock(mReleasing.mLock); + if (lastMsgId != 0) { + if (isMessageLater(lastMsgId, mReleasing.mInvalidateId)) { + mReleasing.mInvalidateId = lastMsgId; + mReleasing.mInvalidateAck = false; + } + } else if (messageId != 0) { + // messages are drained. + if (isMessageLater(messageId, mReleasing.mInvalidateId)) { + mReleasing.mInvalidateId = messageId; + mReleasing.mInvalidateAck = true; + } + } + if (!mReleasing.mInvalidateAck) { + // post ACK + mReleasing.mStatusChannel->postBufferInvalidateAck( + mConnectionId, + mReleasing.mInvalidateId, &mReleasing.mInvalidateAck); + ALOGV("client %lld invalidateion ack (%d) %u", + (long long)mConnectionId, + mReleasing.mInvalidateAck, mReleasing.mInvalidateId); + } + } + return cleared; +} + +// 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; + } +} + +// should have mCache.mLock +void BufferPoolClient::Impl::invalidateBuffer(BufferId id) { + for (auto it = mCache.mBuffers.begin(); it != mCache.mBuffers.end(); ++it) { + if (id == it->second->id()) { + if (!it->second->hasCache()) { + mCache.mBuffers.erase(it); + ALOGV("cache invalidated %lld : buffer %u", + (long long)mConnectionId, id); + } else { + ALOGW("Inconsitent invalidation %lld : activer buffer!! %u", + (long long)mConnectionId, (unsigned int)id); + } + break; + } + } +} + +// should have mCache.mLock +void BufferPoolClient::Impl::invalidateRange(BufferId from, BufferId to) { + size_t invalidated = 0; + for (auto it = mCache.mBuffers.begin(); it != mCache.mBuffers.end();) { + if (!it->second->hasCache()) { + BufferId bid = it->second->id(); + if (from < to) { + if (from <= bid && bid < to) { + ++invalidated; + it = mCache.mBuffers.erase(it); + continue; + } + } else { + if (from <= bid || bid < to) { + ++invalidated; + it = mCache.mBuffers.erase(it); + continue; + } + } + } + ++it; + } + ALOGV("cache invalidated %lld : # of invalidated %zu", + (long long)mConnectionId, invalidated); +} + +ResultStatus BufferPoolClient::Impl::allocateBufferHandle( + const std::vector<uint8_t>& params, BufferId *bufferId, + native_handle_t** handle) { + if (mLocalConnection) { + const native_handle_t* allocHandle = nullptr; + 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, + const sp<IObserver> &observer) { + mImpl = std::make_shared<Impl>(accessor, observer); +} + +BufferPoolClient::BufferPoolClient(const sp<IAccessor> &accessor, + const sp<IObserver> &observer) { + mImpl = std::make_shared<Impl>(accessor, observer); +} + +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; +} + +void BufferPoolClient::receiveInvalidation(uint32_t msgId) { + ALOGV("bufferpool2 client recv inv %u", msgId); + if (isValid()) { + mImpl->receiveInvalidation(msgId); + } +} + +ResultStatus BufferPoolClient::flush() { + if (isValid()) { + return mImpl->flush(); + } + 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 V2_0 +} // namespace bufferpool +} // namespace media +} // namespace hardware +} // namespace android
diff --git a/media/bufferpool/2.0/BufferPoolClient.h b/media/bufferpool/2.0/BufferPoolClient.h new file mode 100644 index 0000000..e8d9ae6 --- /dev/null +++ b/media/bufferpool/2.0/BufferPoolClient.h
@@ -0,0 +1,111 @@ +/* + * 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_V2_0_BUFFERPOOLCLIENT_H +#define ANDROID_HARDWARE_MEDIA_BUFFERPOOL_V2_0_BUFFERPOOLCLIENT_H + +#include <memory> +#include <android/hardware/media/bufferpool/2.0/IAccessor.h> +#include <android/hardware/media/bufferpool/2.0/IConnection.h> +#include <android/hardware/media/bufferpool/2.0/IObserver.h> +#include <bufferpool/BufferPoolTypes.h> +#include <cutils/native_handle.h> +#include "Accessor.h" + +namespace android { +namespace hardware { +namespace media { +namespace bufferpool { +namespace V2_0 { +namespace implementation { + +using ::android::hardware::media::bufferpool::V2_0::IAccessor; +using ::android::hardware::media::bufferpool::V2_0::IConnection; +using ::android::hardware::media::bufferpool::V2_0::IObserver; +using ::android::hardware::media::bufferpool::V2_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, + const sp<IObserver> &observer); + + /** + * 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, + const sp<IObserver> &observer); + + /** 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); + + void receiveInvalidation(uint32_t msgId); + + ResultStatus flush(); + + 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; + friend struct Observer; +}; + +} // namespace implementation +} // namespace V2_0 +} // namespace bufferpool +} // namespace media +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_MEDIA_BUFFERPOOL_V2_0_BUFFERPOOLCLIENT_H
diff --git a/media/bufferpool/2.0/BufferStatus.cpp b/media/bufferpool/2.0/BufferStatus.cpp new file mode 100644 index 0000000..6937260 --- /dev/null +++ b/media/bufferpool/2.0/BufferStatus.cpp
@@ -0,0 +1,304 @@ +/* + * 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 <thread> +#include <time.h> +#include "BufferStatus.h" + +namespace android { +namespace hardware { +namespace media { +namespace bufferpool { +namespace V2_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; +} + +bool isMessageLater(uint32_t curMsgId, uint32_t prevMsgId) { + return curMsgId != prevMsgId && curMsgId - prevMsgId < prevMsgId - curMsgId; +} + +bool isBufferInRange(BufferId from, BufferId to, BufferId bufferId) { + if (from < to) { + return from <= bufferId && bufferId < to; + } else { // wrap happens + return from <= bufferId || bufferId < to; + } +} + +static constexpr int kNumElementsInQueue = 1024*16; +static constexpr int kMinElementsToSyncInQueue = 128; + +ResultStatus BufferStatusObserver::open( + ConnectionId id, const StatusDescriptor** 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 = nullptr; + return ResultStatus::NO_MEMORY; + } else { + *fmqDescPtr = queue->getDesc(); + } + auto result = mBufferStatusQueues.insert( + std::make_pair(id, std::move(queue))); + if (!result.second) { + *fmqDescPtr = nullptr; + 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 StatusDescriptor &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); + } + } +} + +void BufferStatusChannel::postBufferInvalidateAck( + ConnectionId connectionId, + uint32_t invalidateId, + bool *invalidated) { + if (mValid && !*invalidated) { + size_t avail = mBufferStatusQueue->availableToWrite(); + if (avail > 0) { + BufferStatusMessage message; + message.newStatus = BufferStatus::INVALIDATION_ACK; + message.bufferId = invalidateId; + 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; + } + *invalidated = true; + } + } +} + +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; +} + +BufferInvalidationListener::BufferInvalidationListener( + const InvalidationDescriptor &fmqDesc) { + std::unique_ptr<BufferInvalidationQueue> queue = + std::make_unique<BufferInvalidationQueue>(fmqDesc); + if (!queue || queue->isValid() == false) { + mValid = false; + return; + } + mValid = true; + mBufferInvalidationQueue = std::move(queue); + // drain previous messages + size_t avail = std::min( + mBufferInvalidationQueue->availableToRead(), (size_t) kNumElementsInQueue); + std::vector<BufferInvalidationMessage> temp(avail); + if (avail > 0) { + mBufferInvalidationQueue->read(temp.data(), avail); + } +} + +void BufferInvalidationListener::getInvalidations( + std::vector<BufferInvalidationMessage> &messages) { + // Try twice in case of overflow. + // TODO: handling overflow though it may not happen. + for (int i = 0; i < 2; ++i) { + size_t avail = std::min( + mBufferInvalidationQueue->availableToRead(), (size_t) kNumElementsInQueue); + if (avail > 0) { + std::vector<BufferInvalidationMessage> temp(avail); + if (mBufferInvalidationQueue->read(temp.data(), avail)) { + messages.reserve(messages.size() + avail); + for (auto it = temp.begin(); it != temp.end(); ++it) { + messages.push_back(*it); + } + break; + } + } else { + return; + } + } +} + +bool BufferInvalidationListener::isValid() { + return mValid; +} + +BufferInvalidationChannel::BufferInvalidationChannel() + : mValid(true), + mBufferInvalidationQueue( + std::make_unique<BufferInvalidationQueue>(kNumElementsInQueue, true)) { + if (!mBufferInvalidationQueue || mBufferInvalidationQueue->isValid() == false) { + mValid = false; + } +} + +bool BufferInvalidationChannel::isValid() { + return mValid; +} + +void BufferInvalidationChannel::getDesc(const InvalidationDescriptor **fmqDescPtr) { + if (mValid) { + *fmqDescPtr = mBufferInvalidationQueue->getDesc(); + } else { + *fmqDescPtr = nullptr; + } +} + +void BufferInvalidationChannel::postInvalidation( + uint32_t msgId, BufferId fromId, BufferId toId) { + BufferInvalidationMessage message; + + message.messageId = msgId; + message.fromBufferId = fromId; + message.toBufferId = toId; + // TODO: handle failure (it does not happen normally.) + mBufferInvalidationQueue->write(&message); +} + +} // namespace implementation +} // namespace V2_0 +} // namespace bufferpool +} // namespace media +} // namespace hardware +} // namespace android +
diff --git a/media/bufferpool/2.0/BufferStatus.h b/media/bufferpool/2.0/BufferStatus.h new file mode 100644 index 0000000..fa65838 --- /dev/null +++ b/media/bufferpool/2.0/BufferStatus.h
@@ -0,0 +1,230 @@ +/* + * 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_V2_0_BUFFERSTATUS_H +#define ANDROID_HARDWARE_MEDIA_BUFFERPOOL_V2_0_BUFFERSTATUS_H + +#include <android/hardware/media/bufferpool/2.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 V2_0 { +namespace implementation { + +/** Returns monotonic timestamp in Us since fixed point in time. */ +int64_t getTimestampNow(); + +bool isMessageLater(uint32_t curMsgId, uint32_t prevMsgId); + +bool isBufferInRange(BufferId from, BufferId to, BufferId bufferId); + +/** + * A collection of buffer status message 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 a buffer status message 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 StatusDescriptor** fmqDescPtr); + + /** Closes a buffer status message 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); +}; + +/** + * A buffer status message 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 a buffer status message FMQ from a descriptor of + * the created FMQ. + * + * @param fmqDesc Descriptor of the created FMQ. + */ + BufferStatusChannel(const StatusDescriptor &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); + + /** + * Posts a buffer invaliadation messge to the buffer pool. + * + * @param connectionId connection Id of the client. + * @param invalidateId invalidation ack to the buffer pool. + * if invalidation id is zero, the ack will not be + * posted. + * @param invalidated sets {@code true} only when the invalidation ack is + * posted. + */ + void postBufferInvalidateAck( + ConnectionId connectionId, + uint32_t invalidateId, + bool *invalidated); +}; + +/** + * A buffer invalidation FMQ for a buffer pool client. Buffer invalidation + * messages are received via the fmq from the buffer pool. Buffer invalidation + * messages are handled as soon as possible. + */ +class BufferInvalidationListener { +private: + bool mValid; + std::unique_ptr<BufferInvalidationQueue> mBufferInvalidationQueue; + +public: + /** + * Connects to a buffer invalidation FMQ from a descriptor of the created FMQ. + * + * @param fmqDesc Descriptor of the created FMQ. + */ + BufferInvalidationListener(const InvalidationDescriptor &fmqDesc); + + /** Retrieves all pending buffer invalidation messages from the buffer pool. + * + * @param messages retrieved pending messages. + */ + void getInvalidations(std::vector<BufferInvalidationMessage> &messages); + + /** Returns whether the FMQ is connected succesfully. */ + bool isValid(); +}; + +/** + * A buffer invalidation FMQ for a buffer pool. A buffer pool will send buffer + * invalidation messages to the clients via the FMQ. The FMQ is shared among + * buffer pool clients. + */ +class BufferInvalidationChannel { +private: + bool mValid; + std::unique_ptr<BufferInvalidationQueue> mBufferInvalidationQueue; + +public: + /** + * Creates a buffer invalidation FMQ for a buffer pool. + */ + BufferInvalidationChannel(); + + /** Returns whether the FMQ is connected succesfully. */ + bool isValid(); + + /** + * Retrieves the descriptor of a buffer invalidation FMQ. the descriptor may + * be passed to the client for buffer invalidation handling. + * + * @param fmqDescPtr double ptr of created FMQ's descriptor. + */ + void getDesc(const InvalidationDescriptor **fmqDescPtr); + + /** Posts a buffer invalidation for invalidated buffers. + * + * @param msgId Invalidation message id which is used when clients send + * acks back via BufferStatusMessage + * @param fromId The start bufferid of the invalidated buffers(inclusive) + * @param toId The end bufferId of the invalidated buffers(inclusive) + */ + void postInvalidation(uint32_t msgId, BufferId fromId, BufferId toId); +}; + +} // namespace implementation +} // namespace V2_0 +} // namespace bufferpool +} // namespace media +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_MEDIA_BUFFERPOOL_V2_0_BUFFERSTATUS_H
diff --git a/media/bufferpool/2.0/ClientManager.cpp b/media/bufferpool/2.0/ClientManager.cpp new file mode 100644 index 0000000..c31d313 --- /dev/null +++ b/media/bufferpool/2.0/ClientManager.cpp
@@ -0,0 +1,539 @@ +/* + * 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" +#include "Observer.h" +#include "Accessor.h" + +namespace android { +namespace hardware { +namespace media { +namespace bufferpool { +namespace V2_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 flush(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; + + sp<Observer> mObserver; + + ClientManagerCookieHolder mRemoteClientCookies; +}; + +ClientManager::Impl::Impl() + : mObserver(new Observer()) {} + +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, mObserver); + 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(); + mObserver->addClient(conId, wclient); + { + 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; + } + // TODO: observer is local. use direct call instead of hidl call. + std::shared_ptr<BufferPoolClient> client = + std::make_shared<BufferPoolClient>(accessor, mObserver); + 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(); + mObserver->addClient(conId, wclient); + { + 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::unique_lock<std::mutex> lock1(mCache.mMutex); + std::unique_lock<std::mutex> lock2(mActive.mMutex); + auto it = mActive.mClients.find(connectionId); + if (it != mActive.mClients.end()) { + sp<IAccessor> accessor; + it->second->getAccessor(&accessor); + std::shared_ptr<BufferPoolClient> closing = it->second; + 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++; + } + } + lock2.unlock(); + lock1.unlock(); + closing->flush(); + return ResultStatus::OK; + } + return ResultStatus::NOT_FOUND; +} + +ResultStatus ClientManager::Impl::flush(ConnectionId connectionId) { + 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->flush(); +} + +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::V2_0::IClientManager follow. +Return<void> ClientManager::registerSender(const sp<::android::hardware::media::bufferpool::V2_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(); + } + Accessor::createInvalidator(); + 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::flush(ConnectionId connectionId) { + if (mImpl) { + return mImpl->flush(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 V2_0 +} // namespace bufferpool +} // namespace media +} // namespace hardware +} // namespace android
diff --git a/media/bufferpool/2.0/Connection.cpp b/media/bufferpool/2.0/Connection.cpp new file mode 100644 index 0000000..57d0c7e --- /dev/null +++ b/media/bufferpool/2.0/Connection.cpp
@@ -0,0 +1,104 @@ +/* + * 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 V2_0 { +namespace implementation { + +// Methods from ::android::hardware::media::bufferpool::V2_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 = nullptr; + status = mAccessor->fetch( + mConnectionId, transactionId, bufferId, &handle); + if (status == ResultStatus::OK) { + Buffer buffer = {}; + buffer.id = bufferId; + buffer.buffer = handle; + _hidl_cb(status, buffer); + return Void(); + } + } else { + mAccessor->cleanUp(false); + } + } + + Buffer buffer = {}; + buffer.id = 0; + buffer.buffer = nullptr; + + _hidl_cb(status, buffer); + 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::flush() { + if (mInitialized && mAccessor) { + return mAccessor->flush(); + } + return ResultStatus::CRITICAL_ERROR; +} + +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 V2_0 +} // namespace bufferpool +} // namespace media +} // namespace hardware +} // namespace android
diff --git a/media/bufferpool/2.0/Connection.h b/media/bufferpool/2.0/Connection.h new file mode 100644 index 0000000..8507749 --- /dev/null +++ b/media/bufferpool/2.0/Connection.h
@@ -0,0 +1,108 @@ +/* + * 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_V2_0_CONNECTION_H +#define ANDROID_HARDWARE_MEDIA_BUFFERPOOL_V2_0_CONNECTION_H + +#include <android/hardware/media/bufferpool/2.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 V2_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::V2_0::implementation::Accessor; +using ::android::hardware::Return; +using ::android::hardware::Void; +using ::android::sp; + +struct Connection : public IConnection { + // Methods from ::android::hardware::media::bufferpool::V2_0::IConnection follow. + Return<void> fetch(uint64_t transactionId, uint32_t bufferId, fetch_cb _hidl_cb) override; + + /** + * Invalidates all buffers which are active and/or are ready to be recycled. + */ + ResultStatus flush(); + + /** + * 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 V2_0 +} // namespace bufferpool +} // namespace media +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_MEDIA_BUFFERPOOL_V2_0_CONNECTION_H
diff --git a/media/bufferpool/2.0/Observer.cpp b/media/bufferpool/2.0/Observer.cpp new file mode 100644 index 0000000..5b23160 --- /dev/null +++ b/media/bufferpool/2.0/Observer.cpp
@@ -0,0 +1,73 @@ +/* + * 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 "Observer.h" + +namespace android { +namespace hardware { +namespace media { +namespace bufferpool { +namespace V2_0 { +namespace implementation { + +Observer::Observer() { +} + +Observer::~Observer() { +} + +// Methods from ::android::hardware::media::bufferpool::V2_0::IObserver follow. +Return<void> Observer::onMessage(int64_t connectionId, uint32_t msgId) { + std::unique_lock<std::mutex> lock(mLock); + auto it = mClients.find(connectionId); + if (it != mClients.end()) { + const std::shared_ptr<BufferPoolClient> client = it->second.lock(); + if (!client) { + mClients.erase(it); + } else { + lock.unlock(); + client->receiveInvalidation(msgId); + } + } + return Void(); +} + +void Observer::addClient(ConnectionId connectionId, + const std::weak_ptr<BufferPoolClient> &wclient) { + std::lock_guard<std::mutex> lock(mLock); + for (auto it = mClients.begin(); it != mClients.end();) { + if (!it->second.lock() || it->first == connectionId) { + it = mClients.erase(it); + } else { + ++it; + } + } + mClients.insert(std::make_pair(connectionId, wclient)); + +} + +void Observer::delClient(ConnectionId connectionId) { + std::lock_guard<std::mutex> lock(mLock); + mClients.erase(connectionId); +} + + +} // namespace implementation +} // namespace V2_0 +} // namespace bufferpool +} // namespace media +} // namespace hardware +} // namespace android
diff --git a/media/bufferpool/2.0/Observer.h b/media/bufferpool/2.0/Observer.h new file mode 100644 index 0000000..42bd7c1 --- /dev/null +++ b/media/bufferpool/2.0/Observer.h
@@ -0,0 +1,67 @@ +/* + * 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_V2_0_OBSERVER_H +#define ANDROID_HARDWARE_MEDIA_BUFFERPOOL_V2_0_OBSERVER_H + +#include <android/hardware/media/bufferpool/2.0/IObserver.h> +#include <hidl/MQDescriptor.h> +#include <hidl/Status.h> +#include "BufferPoolClient.h" + +namespace android { +namespace hardware { +namespace media { +namespace bufferpool { +namespace V2_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 Observer : public IObserver { + // Methods from ::android::hardware::media::bufferpool::V2_0::IObserver follow. + Return<void> onMessage(int64_t connectionId, uint32_t msgId) override; + + ~Observer(); + + void addClient(ConnectionId connectionId, + const std::weak_ptr<BufferPoolClient> &wclient); + + void delClient(ConnectionId connectionId); + +private: + Observer(); + + friend struct ClientManager; + + std::mutex mLock; + std::map<ConnectionId, const std::weak_ptr<BufferPoolClient>> mClients; +}; + +} // namespace implementation +} // namespace V2_0 +} // namespace bufferpool +} // namespace media +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_MEDIA_BUFFERPOOL_V2_0_OBSERVER_H
diff --git a/media/bufferpool/2.0/include/bufferpool/BufferPoolTypes.h b/media/bufferpool/2.0/include/bufferpool/BufferPoolTypes.h new file mode 100644 index 0000000..7c906cb --- /dev/null +++ b/media/bufferpool/2.0/include/bufferpool/BufferPoolTypes.h
@@ -0,0 +1,123 @@ +/* + * 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_V2_0_BUFFERPOOLTYPES_H +#define ANDROID_HARDWARE_MEDIA_BUFFERPOOL_V2_0_BUFFERPOOLTYPES_H + +#include <android/hardware/media/bufferpool/2.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@2.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 V2_0 { +namespace implementation { + +using ::android::hardware::kSynchronizedReadWrite; +using ::android::hardware::kUnsynchronizedWrite; + +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 StatusDescriptor; + +typedef android::hardware::MessageQueue<BufferInvalidationMessage, kUnsynchronizedWrite> + BufferInvalidationQueue; +typedef BufferInvalidationQueue::Descriptor InvalidationDescriptor; + +/** + * 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 V2_0 +} // namespace bufferpool +} // namespace media +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_MEDIA_BUFFERPOOL_V2_0_BUFFERPOOLTYPES_H
diff --git a/media/bufferpool/2.0/include/bufferpool/ClientManager.h b/media/bufferpool/2.0/include/bufferpool/ClientManager.h new file mode 100644 index 0000000..953c304 --- /dev/null +++ b/media/bufferpool/2.0/include/bufferpool/ClientManager.h
@@ -0,0 +1,191 @@ +/* + * 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_V2_0_CLIENTMANAGER_H +#define ANDROID_HARDWARE_MEDIA_BUFFERPOOL_V2_0_CLIENTMANAGER_H + +#include <android/hardware/media/bufferpool/2.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 V2_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::V2_0::IAccessor; +using ::android::hardware::media::bufferpool::V2_0::ResultStatus; +using ::android::hardware::Return; +using ::android::hardware::Void; +using ::android::sp; + +struct ClientManager : public IClientManager { + // Methods from ::android::hardware::media::bufferpool::V2_0::IClientManager follow. + Return<void> registerSender(const sp<::android::hardware::media::bufferpool::V2_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); + + /** + * Evicts cached allocations. If it's local connection, release the + * previous allocations and do not recycle current active allocations. + * + * @param connectionId The id of the connection. + * + * @return OK when the connection is resetted. + * NOT_FOUND when the specified connection was not found. + * CRITICAL_ERROR otherwise. + */ + ResultStatus flush(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 V2_0 +} // namespace bufferpool +} // namespace media +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_MEDIA_BUFFERPOOL_V2_0_CLIENTMANAGER_H
diff --git a/media/bufferpool/2.0/tests/Android.bp b/media/bufferpool/2.0/tests/Android.bp new file mode 100644 index 0000000..8b44f61 --- /dev/null +++ b/media/bufferpool/2.0/tests/Android.bp
@@ -0,0 +1,51 @@ +/* + * 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: "VtsVndkHidlBufferpoolV2_0TargetSingleTest", + defaults: ["VtsHalTargetTestDefaults"], + srcs: [ + "allocator.cpp", + "single.cpp", + ], + static_libs: [ + "android.hardware.media.bufferpool@2.0", + "libcutils", + "libstagefright_bufferpool@2.0", + ], + shared_libs: [ + "libfmq", + ], + compile_multilib: "both", +} + +cc_test { + name: "VtsVndkHidlBufferpoolV2_0TargetMultiTest", + defaults: ["VtsHalTargetTestDefaults"], + srcs: [ + "allocator.cpp", + "multi.cpp", + ], + static_libs: [ + "android.hardware.media.bufferpool@2.0", + "libcutils", + "libstagefright_bufferpool@2.0", + ], + shared_libs: [ + "libfmq", + ], + compile_multilib: "both", +}
diff --git a/media/bufferpool/2.0/tests/OWNERS b/media/bufferpool/2.0/tests/OWNERS new file mode 100644 index 0000000..6733e0c --- /dev/null +++ b/media/bufferpool/2.0/tests/OWNERS
@@ -0,0 +1,9 @@ +# Media team +lajos@google.com +pawin@google.com +taklee@google.com +wonsik@google.com + +# VTS team +yim@google.com +zhuoyao@google.com
diff --git a/media/bufferpool/2.0/tests/allocator.cpp b/media/bufferpool/2.0/tests/allocator.cpp new file mode 100644 index 0000000..843f7ea --- /dev/null +++ b/media/bufferpool/2.0/tests/allocator.cpp
@@ -0,0 +1,209 @@ +/* + * 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 <cutils/ashmem.h> +#include <sys/mman.h> +#include "allocator.h" + +union Params { + struct { + uint32_t capacity; + } data; + uint8_t array[0]; + Params() : data{0} {} + Params(uint32_t size) + : data{size} {} +}; + + +namespace { + +struct HandleAshmem : public native_handle_t { + HandleAshmem(int ashmemFd, size_t size) + : native_handle_t(cHeader), + mFds{ ashmemFd }, + mInts{ int (size & 0xFFFFFFFF), int((uint64_t(size) >> 32) & 0xFFFFFFFF), kMagic } {} + + int ashmemFd() const { return mFds.mAshmem; } + size_t size() const { + return size_t(unsigned(mInts.mSizeLo)) + | size_t(uint64_t(unsigned(mInts.mSizeHi)) << 32); + } + + static bool isValid(const native_handle_t * const o); + +protected: + struct { + int mAshmem; + } mFds; + struct { + int mSizeLo; + int mSizeHi; + int mMagic; + } mInts; + +private: + enum { + kMagic = 'ahm\x00', + numFds = sizeof(mFds) / sizeof(int), + numInts = sizeof(mInts) / sizeof(int), + version = sizeof(native_handle_t) + }; + const static native_handle_t cHeader; +}; + +const native_handle_t HandleAshmem::cHeader = { + HandleAshmem::version, + HandleAshmem::numFds, + HandleAshmem::numInts, + {} +}; + +bool HandleAshmem::isValid(const native_handle_t * const o) { + if (!o || memcmp(o, &cHeader, sizeof(cHeader))) { + return false; + } + const HandleAshmem *other = static_cast<const HandleAshmem*>(o); + return other->mInts.mMagic == kMagic; +} + +class AllocationAshmem { +private: + AllocationAshmem(int ashmemFd, size_t capacity, bool res) + : mHandle(ashmemFd, capacity), + mInit(res) {} + +public: + static AllocationAshmem *Alloc(size_t size) { + constexpr static const char *kAllocationTag = "bufferpool_test"; + int ashmemFd = ashmem_create_region(kAllocationTag, size); + return new AllocationAshmem(ashmemFd, size, ashmemFd >= 0); + } + + ~AllocationAshmem() { + if (mInit) { + native_handle_close(&mHandle); + } + } + + const HandleAshmem *handle() { + return &mHandle; + } + +private: + HandleAshmem mHandle; + bool mInit; + // TODO: mapping and map fd +}; + +struct AllocationDtor { + AllocationDtor(const std::shared_ptr<AllocationAshmem> &alloc) + : mAlloc(alloc) {} + + void operator()(BufferPoolAllocation *poolAlloc) { delete poolAlloc; } + + const std::shared_ptr<AllocationAshmem> mAlloc; +}; + +} + + +ResultStatus TestBufferPoolAllocator::allocate( + const std::vector<uint8_t> ¶ms, + std::shared_ptr<BufferPoolAllocation> *alloc, + size_t *allocSize) { + Params ashmemParams; + memcpy(&ashmemParams, params.data(), std::min(sizeof(Params), params.size())); + + std::shared_ptr<AllocationAshmem> ashmemAlloc = + std::shared_ptr<AllocationAshmem>( + AllocationAshmem::Alloc(ashmemParams.data.capacity)); + if (ashmemAlloc) { + BufferPoolAllocation *ptr = new BufferPoolAllocation(ashmemAlloc->handle()); + if (ptr) { + *alloc = std::shared_ptr<BufferPoolAllocation>(ptr, AllocationDtor(ashmemAlloc)); + if (*alloc) { + *allocSize = ashmemParams.data.capacity; + return ResultStatus::OK; + } + delete ptr; + return ResultStatus::NO_MEMORY; + } + } + return ResultStatus::CRITICAL_ERROR; +} + +bool TestBufferPoolAllocator::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; +} + +bool TestBufferPoolAllocator::Fill(const native_handle_t *handle, const unsigned char val) { + if (!HandleAshmem::isValid(handle)) { + return false; + } + const HandleAshmem *o = static_cast<const HandleAshmem*>(handle); + unsigned char *ptr = (unsigned char *)mmap( + NULL, o->size(), PROT_READ|PROT_WRITE, MAP_SHARED, o->ashmemFd(), 0); + + if (ptr != MAP_FAILED) { + for (size_t i = 0; i < o->size(); ++i) { + ptr[i] = val; + } + munmap(ptr, o->size()); + return true; + } + return false; +} + +bool TestBufferPoolAllocator::Verify(const native_handle_t *handle, const unsigned char val) { + if (!HandleAshmem::isValid(handle)) { + return false; + } + const HandleAshmem *o = static_cast<const HandleAshmem*>(handle); + unsigned char *ptr = (unsigned char *)mmap( + NULL, o->size(), PROT_READ, MAP_SHARED, o->ashmemFd(), 0); + + if (ptr != MAP_FAILED) { + bool res = true; + for (size_t i = 0; i < o->size(); ++i) { + if (ptr[i] != val) { + res = false; + break; + } + } + munmap(ptr, o->size()); + return res; + } + return false; +} + +void getTestAllocatorParams(std::vector<uint8_t> *params) { + constexpr static int kAllocationSize = 1024 * 10; + Params ashmemParams(kAllocationSize); + + params->assign(ashmemParams.array, ashmemParams.array + sizeof(ashmemParams)); +}
diff --git a/media/bufferpool/2.0/tests/allocator.h b/media/bufferpool/2.0/tests/allocator.h new file mode 100644 index 0000000..5281dc3 --- /dev/null +++ b/media/bufferpool/2.0/tests/allocator.h
@@ -0,0 +1,51 @@ +/* + * 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 VNDK_HIDL_BUFFERPOOL_V2_0_ALLOCATOR_H +#define VNDK_HIDL_BUFFERPOOL_V2_0_ALLOCATOR_H + +#include <bufferpool/BufferPoolTypes.h> + +using android::hardware::media::bufferpool::V2_0::ResultStatus; +using android::hardware::media::bufferpool::V2_0::implementation:: + BufferPoolAllocation; +using android::hardware::media::bufferpool::V2_0::implementation:: + BufferPoolAllocator; + +// buffer allocator for the tests +class TestBufferPoolAllocator : public BufferPoolAllocator { + public: + TestBufferPoolAllocator() {} + + ~TestBufferPoolAllocator() 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; + + static bool Fill(const native_handle_t *handle, const unsigned char val); + + static bool Verify(const native_handle_t *handle, const unsigned char val); + +}; + +// retrieve buffer allocator paramters +void getTestAllocatorParams(std::vector<uint8_t> *params); + +#endif // VNDK_HIDL_BUFFERPOOL_V2_0_ALLOCATOR_H
diff --git a/media/bufferpool/2.0/tests/multi.cpp b/media/bufferpool/2.0/tests/multi.cpp new file mode 100644 index 0000000..68b6992 --- /dev/null +++ b/media/bufferpool/2.0/tests/multi.cpp
@@ -0,0 +1,223 @@ +/* + * 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 <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::hardware::configureRpcThreadpool; +using android::hardware::hidl_handle; +using android::hardware::media::bufferpool::V2_0::IClientManager; +using android::hardware::media::bufferpool::V2_0::ResultStatus; +using android::hardware::media::bufferpool::V2_0::implementation::BufferId; +using android::hardware::media::bufferpool::V2_0::implementation::ClientManager; +using android::hardware::media::bufferpool::V2_0::implementation::ConnectionId; +using android::hardware::media::bufferpool::V2_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); + + mAllocator = std::make_shared<TestBufferPoolAllocator>(); + 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) { + if (!TestBufferPoolAllocator::Verify(rhandle, 0x77)) { + 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; + + getTestAllocatorParams(&vecParams); + status = mManager->allocate(mConnectionId, vecParams, &shandle, &sbuffer); + ASSERT_TRUE(status == ResultStatus::OK); + + ASSERT_TRUE(TestBufferPoolAllocator::Fill(shandle, 0x77)); + + 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/media/bufferpool/2.0/tests/single.cpp b/media/bufferpool/2.0/tests/single.cpp new file mode 100644 index 0000000..777edcf --- /dev/null +++ b/media/bufferpool/2.0/tests/single.cpp
@@ -0,0 +1,166 @@ +/* + * 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 <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::hardware::hidl_handle; +using android::hardware::media::bufferpool::V2_0::ResultStatus; +using android::hardware::media::bufferpool::V2_0::implementation::BufferId; +using android::hardware::media::bufferpool::V2_0::implementation::ClientManager; +using android::hardware::media::bufferpool::V2_0::implementation::ConnectionId; +using android::hardware::media::bufferpool::V2_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); + + mAllocator = std::make_shared<TestBufferPoolAllocator>(); + 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; + getTestAllocatorParams(&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; + getTestAllocatorParams(&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; + getTestAllocatorParams(&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); + ASSERT_TRUE(TestBufferPoolAllocator::Fill(allocHandle, 0x77)); + 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); + ASSERT_TRUE(TestBufferPoolAllocator::Verify(recvHandle, 0x77)); +} + +} // 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/media/codec2/Android.mk b/media/codec2/Android.mk new file mode 100644 index 0000000..82d739f --- /dev/null +++ b/media/codec2/Android.mk
@@ -0,0 +1,48 @@ +# ============================================================================= +# 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/media/codec2/components/aac/Android.bp b/media/codec2/components/aac/Android.bp new file mode 100644 index 0000000..9eca585 --- /dev/null +++ b/media/codec2/components/aac/Android.bp
@@ -0,0 +1,30 @@ +cc_library_shared { + name: "libcodec2_soft_aacdec", + defaults: [ + "libcodec2_soft-defaults", + "libcodec2_soft_sanitize_all-defaults", + ], + + srcs: [ + "C2SoftAacDec.cpp", + "DrcPresModeWrap.cpp", + ], + + static_libs: [ + "libFraunhoferAAC", + ], +} + +cc_library_shared { + name: "libcodec2_soft_aacenc", + defaults: [ + "libcodec2_soft-defaults", + "libcodec2_soft_sanitize_all-defaults", + ], + + srcs: ["C2SoftAacEnc.cpp"], + + static_libs: [ + "libFraunhoferAAC", + ], +}
diff --git a/media/codec2/components/aac/C2SoftAacDec.cpp b/media/codec2/components/aac/C2SoftAacDec.cpp new file mode 100644 index 0000000..2d4e126 --- /dev/null +++ b/media/codec2/components/aac/C2SoftAacDec.cpp
@@ -0,0 +1,928 @@ +/* + * Copyright (C) 2017 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 "C2SoftAacDec" +#include <log/log.h> + +#include <inttypes.h> +#include <math.h> +#include <numeric> + +#include <cutils/properties.h> +#include <media/stagefright/foundation/MediaDefs.h> +#include <media/stagefright/foundation/hexdump.h> +#include <media/stagefright/MediaErrors.h> +#include <utils/misc.h> + +#include <C2PlatformSupport.h> +#include <SimpleC2Interface.h> + +#include "C2SoftAacDec.h" + +#define FILEREAD_MAX_LAYERS 2 + +#define DRC_DEFAULT_MOBILE_REF_LEVEL -16.0 /* 64*-0.25dB = -16 dB below full scale for mobile conf */ +#define DRC_DEFAULT_MOBILE_DRC_CUT 1.0 /* maximum compression of dynamic range for mobile conf */ +#define DRC_DEFAULT_MOBILE_DRC_BOOST 1.0 /* maximum compression of dynamic range for mobile conf */ +#define DRC_DEFAULT_MOBILE_DRC_HEAVY C2Config::DRC_COMPRESSION_HEAVY /* switch for heavy compression for mobile conf */ +#define DRC_DEFAULT_MOBILE_DRC_EFFECT 3 /* MPEG-D DRC effect type; 3 => Limited playback range */ +#define DRC_DEFAULT_MOBILE_ENC_LEVEL (0.25) /* encoder target level; -1 => the value is unknown, otherwise dB step value (e.g. 64 for -16 dB) */ +#define MAX_CHANNEL_COUNT 8 /* maximum number of audio channels that can be decoded */ +// names of properties that can be used to override the default DRC settings +#define PROP_DRC_OVERRIDE_REF_LEVEL "aac_drc_reference_level" +#define PROP_DRC_OVERRIDE_CUT "aac_drc_cut" +#define PROP_DRC_OVERRIDE_BOOST "aac_drc_boost" +#define PROP_DRC_OVERRIDE_HEAVY "aac_drc_heavy" +#define PROP_DRC_OVERRIDE_ENC_LEVEL "aac_drc_enc_target_level" +#define PROP_DRC_OVERRIDE_EFFECT "ro.aac_drc_effect_type" + +namespace android { + +constexpr char COMPONENT_NAME[] = "c2.android.aac.decoder"; + +class C2SoftAacDec::IntfImpl : public SimpleInterface<void>::BaseParams { +public: + explicit IntfImpl(const std::shared_ptr<C2ReflectorHelper> &helper) + : SimpleInterface<void>::BaseParams( + helper, + COMPONENT_NAME, + C2Component::KIND_DECODER, + C2Component::DOMAIN_AUDIO, + MEDIA_MIMETYPE_AUDIO_AAC) { + noPrivateBuffers(); + noInputReferences(); + noOutputReferences(); + noInputLatency(); + noTimeStretch(); + + addParameter( + DefineParam(mActualOutputDelay, C2_PARAMKEY_OUTPUT_DELAY) + .withConstValue(new C2PortActualDelayTuning::output(2u)) + .build()); + + addParameter( + DefineParam(mSampleRate, C2_PARAMKEY_SAMPLE_RATE) + .withDefault(new C2StreamSampleRateInfo::output(0u, 44100)) + .withFields({C2F(mSampleRate, value).oneOf({ + 7350, 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 + })}) + .withSetter(Setter<decltype(*mSampleRate)>::NonStrictValueWithNoDeps) + .build()); + + addParameter( + DefineParam(mChannelCount, C2_PARAMKEY_CHANNEL_COUNT) + .withDefault(new C2StreamChannelCountInfo::output(0u, 1)) + .withFields({C2F(mChannelCount, value).inRange(1, 8)}) + .withSetter(Setter<decltype(*mChannelCount)>::StrictValueWithNoDeps) + .build()); + + addParameter( + DefineParam(mBitrate, C2_PARAMKEY_BITRATE) + .withDefault(new C2StreamBitrateInfo::input(0u, 64000)) + .withFields({C2F(mBitrate, value).inRange(8000, 960000)}) + .withSetter(Setter<decltype(*mBitrate)>::NonStrictValueWithNoDeps) + .build()); + + addParameter( + DefineParam(mInputMaxBufSize, C2_PARAMKEY_INPUT_MAX_BUFFER_SIZE) + .withConstValue(new C2StreamMaxBufferSizeInfo::input(0u, 8192)) + .build()); + + addParameter( + DefineParam(mAacFormat, C2_PARAMKEY_AAC_PACKAGING) + .withDefault(new C2StreamAacFormatInfo::input(0u, C2Config::AAC_PACKAGING_RAW)) + .withFields({C2F(mAacFormat, value).oneOf({ + C2Config::AAC_PACKAGING_RAW, C2Config::AAC_PACKAGING_ADTS + })}) + .withSetter(Setter<decltype(*mAacFormat)>::StrictValueWithNoDeps) + .build()); + + addParameter( + DefineParam(mProfileLevel, C2_PARAMKEY_PROFILE_LEVEL) + .withDefault(new C2StreamProfileLevelInfo::input(0u, + C2Config::PROFILE_AAC_LC, C2Config::LEVEL_UNUSED)) + .withFields({ + C2F(mProfileLevel, profile).oneOf({ + C2Config::PROFILE_AAC_LC, + C2Config::PROFILE_AAC_HE, + C2Config::PROFILE_AAC_HE_PS, + C2Config::PROFILE_AAC_LD, + C2Config::PROFILE_AAC_ELD, + C2Config::PROFILE_AAC_ER_SCALABLE, + C2Config::PROFILE_AAC_XHE}), + C2F(mProfileLevel, level).oneOf({ + C2Config::LEVEL_UNUSED + }) + }) + .withSetter(ProfileLevelSetter) + .build()); + + addParameter( + DefineParam(mDrcCompressMode, C2_PARAMKEY_DRC_COMPRESSION_MODE) + .withDefault(new C2StreamDrcCompressionModeTuning::input(0u, C2Config::DRC_COMPRESSION_HEAVY)) + .withFields({ + C2F(mDrcCompressMode, value).oneOf({ + C2Config::DRC_COMPRESSION_ODM_DEFAULT, + C2Config::DRC_COMPRESSION_NONE, + C2Config::DRC_COMPRESSION_LIGHT, + C2Config::DRC_COMPRESSION_HEAVY}) + }) + .withSetter(Setter<decltype(*mDrcCompressMode)>::StrictValueWithNoDeps) + .build()); + + addParameter( + DefineParam(mDrcTargetRefLevel, C2_PARAMKEY_DRC_TARGET_REFERENCE_LEVEL) + .withDefault(new C2StreamDrcTargetReferenceLevelTuning::input(0u, DRC_DEFAULT_MOBILE_REF_LEVEL)) + .withFields({C2F(mDrcTargetRefLevel, value).inRange(-31.75, 0.25)}) + .withSetter(Setter<decltype(*mDrcTargetRefLevel)>::StrictValueWithNoDeps) + .build()); + + addParameter( + DefineParam(mDrcEncTargetLevel, C2_PARAMKEY_DRC_ENCODED_TARGET_LEVEL) + .withDefault(new C2StreamDrcEncodedTargetLevelTuning::input(0u, DRC_DEFAULT_MOBILE_ENC_LEVEL)) + .withFields({C2F(mDrcEncTargetLevel, value).inRange(-31.75, 0.25)}) + .withSetter(Setter<decltype(*mDrcEncTargetLevel)>::StrictValueWithNoDeps) + .build()); + + addParameter( + DefineParam(mDrcBoostFactor, C2_PARAMKEY_DRC_BOOST_FACTOR) + .withDefault(new C2StreamDrcBoostFactorTuning::input(0u, DRC_DEFAULT_MOBILE_DRC_BOOST)) + .withFields({C2F(mDrcBoostFactor, value).inRange(0, 1.)}) + .withSetter(Setter<decltype(*mDrcBoostFactor)>::StrictValueWithNoDeps) + .build()); + + addParameter( + DefineParam(mDrcAttenuationFactor, C2_PARAMKEY_DRC_ATTENUATION_FACTOR) + .withDefault(new C2StreamDrcAttenuationFactorTuning::input(0u, DRC_DEFAULT_MOBILE_DRC_CUT)) + .withFields({C2F(mDrcAttenuationFactor, value).inRange(0, 1.)}) + .withSetter(Setter<decltype(*mDrcAttenuationFactor)>::StrictValueWithNoDeps) + .build()); + + addParameter( + DefineParam(mDrcEffectType, C2_PARAMKEY_DRC_EFFECT_TYPE) + .withDefault(new C2StreamDrcEffectTypeTuning::input(0u, C2Config::DRC_EFFECT_LIMITED_PLAYBACK_RANGE)) + .withFields({ + C2F(mDrcEffectType, value).oneOf({ + C2Config::DRC_EFFECT_ODM_DEFAULT, + C2Config::DRC_EFFECT_OFF, + C2Config::DRC_EFFECT_NONE, + C2Config::DRC_EFFECT_LATE_NIGHT, + C2Config::DRC_EFFECT_NOISY_ENVIRONMENT, + C2Config::DRC_EFFECT_LIMITED_PLAYBACK_RANGE, + C2Config::DRC_EFFECT_LOW_PLAYBACK_LEVEL, + C2Config::DRC_EFFECT_DIALOG_ENHANCEMENT, + C2Config::DRC_EFFECT_GENERAL_COMPRESSION}) + }) + .withSetter(Setter<decltype(*mDrcEffectType)>::StrictValueWithNoDeps) + .build()); + } + + bool isAdts() const { return mAacFormat->value == C2Config::AAC_PACKAGING_ADTS; } + static C2R ProfileLevelSetter(bool mayBlock, C2P<C2StreamProfileLevelInfo::input> &me) { + (void)mayBlock; + (void)me; // TODO: validate + return C2R::Ok(); + } + int32_t getDrcCompressMode() const { return mDrcCompressMode->value == C2Config::DRC_COMPRESSION_HEAVY ? 1 : 0; } + int32_t getDrcTargetRefLevel() const { return (mDrcTargetRefLevel->value <= 0 ? -mDrcTargetRefLevel->value * 4. + 0.5 : -1); } + int32_t getDrcEncTargetLevel() const { return (mDrcEncTargetLevel->value <= 0 ? -mDrcEncTargetLevel->value * 4. + 0.5 : -1); } + int32_t getDrcBoostFactor() const { return mDrcBoostFactor->value * 127. + 0.5; } + int32_t getDrcAttenuationFactor() const { return mDrcAttenuationFactor->value * 127. + 0.5; } + int32_t getDrcEffectType() const { return mDrcEffectType->value; } + +private: + std::shared_ptr<C2StreamSampleRateInfo::output> mSampleRate; + std::shared_ptr<C2StreamChannelCountInfo::output> mChannelCount; + std::shared_ptr<C2StreamBitrateInfo::input> mBitrate; + std::shared_ptr<C2StreamMaxBufferSizeInfo::input> mInputMaxBufSize; + std::shared_ptr<C2StreamAacFormatInfo::input> mAacFormat; + std::shared_ptr<C2StreamProfileLevelInfo::input> mProfileLevel; + std::shared_ptr<C2StreamDrcCompressionModeTuning::input> mDrcCompressMode; + std::shared_ptr<C2StreamDrcTargetReferenceLevelTuning::input> mDrcTargetRefLevel; + std::shared_ptr<C2StreamDrcEncodedTargetLevelTuning::input> mDrcEncTargetLevel; + std::shared_ptr<C2StreamDrcBoostFactorTuning::input> mDrcBoostFactor; + std::shared_ptr<C2StreamDrcAttenuationFactorTuning::input> mDrcAttenuationFactor; + std::shared_ptr<C2StreamDrcEffectTypeTuning::input> mDrcEffectType; + // TODO Add : C2StreamAacSbrModeTuning +}; + +C2SoftAacDec::C2SoftAacDec( + const char *name, + c2_node_id_t id, + const std::shared_ptr<IntfImpl> &intfImpl) + : SimpleC2Component(std::make_shared<SimpleInterface<IntfImpl>>(name, id, intfImpl)), + mIntf(intfImpl), + mAACDecoder(nullptr), + mStreamInfo(nullptr), + mSignalledError(false), + mOutputDelayRingBuffer(nullptr) { +} + +C2SoftAacDec::~C2SoftAacDec() { + onRelease(); +} + +c2_status_t C2SoftAacDec::onInit() { + status_t err = initDecoder(); + return err == OK ? C2_OK : C2_CORRUPTED; +} + +c2_status_t C2SoftAacDec::onStop() { + drainDecoder(); + // reset the "configured" state + mOutputDelayCompensated = 0; + mOutputDelayRingBufferWritePos = 0; + mOutputDelayRingBufferReadPos = 0; + mOutputDelayRingBufferFilled = 0; + mBuffersInfo.clear(); + + // To make the codec behave the same before and after a reset, we need to invalidate the + // streaminfo struct. This does that: + mStreamInfo->sampleRate = 0; // TODO: mStreamInfo is read only + + mSignalledError = false; + + return C2_OK; +} + +void C2SoftAacDec::onReset() { + (void)onStop(); +} + +void C2SoftAacDec::onRelease() { + if (mAACDecoder) { + aacDecoder_Close(mAACDecoder); + mAACDecoder = nullptr; + } + if (mOutputDelayRingBuffer) { + delete[] mOutputDelayRingBuffer; + mOutputDelayRingBuffer = nullptr; + } +} + +status_t C2SoftAacDec::initDecoder() { + ALOGV("initDecoder()"); + status_t status = UNKNOWN_ERROR; + mAACDecoder = aacDecoder_Open(TT_MP4_ADIF, /* num layers */ 1); + if (mAACDecoder != nullptr) { + mStreamInfo = aacDecoder_GetStreamInfo(mAACDecoder); + if (mStreamInfo != nullptr) { + status = OK; + } + } + + mOutputDelayCompensated = 0; + mOutputDelayRingBufferSize = 2048 * MAX_CHANNEL_COUNT * kNumDelayBlocksMax; + mOutputDelayRingBuffer = new short[mOutputDelayRingBufferSize]; + mOutputDelayRingBufferWritePos = 0; + mOutputDelayRingBufferReadPos = 0; + mOutputDelayRingBufferFilled = 0; + + if (mAACDecoder == nullptr) { + ALOGE("AAC decoder is null. TODO: Can not call aacDecoder_SetParam in the following code"); + } + + //aacDecoder_SetParam(mAACDecoder, AAC_PCM_LIMITER_ENABLE, 0); + + //init DRC wrapper + mDrcWrap.setDecoderHandle(mAACDecoder); + mDrcWrap.submitStreamData(mStreamInfo); + + // for streams that contain metadata, use the mobile profile DRC settings unless overridden by platform properties + // TODO: change the DRC settings depending on audio output device type (HDMI, loadspeaker, headphone) + + // DRC_PRES_MODE_WRAP_DESIRED_TARGET + int32_t targetRefLevel = mIntf->getDrcTargetRefLevel(); + ALOGV("AAC decoder using desired DRC target reference level of %d", targetRefLevel); + mDrcWrap.setParam(DRC_PRES_MODE_WRAP_DESIRED_TARGET, (unsigned)targetRefLevel); + + // DRC_PRES_MODE_WRAP_DESIRED_ATT_FACTOR + + int32_t attenuationFactor = mIntf->getDrcAttenuationFactor(); + ALOGV("AAC decoder using desired DRC attenuation factor of %d", attenuationFactor); + mDrcWrap.setParam(DRC_PRES_MODE_WRAP_DESIRED_ATT_FACTOR, (unsigned)attenuationFactor); + + // DRC_PRES_MODE_WRAP_DESIRED_BOOST_FACTOR + int32_t boostFactor = mIntf->getDrcBoostFactor(); + ALOGV("AAC decoder using desired DRC boost factor of %d", boostFactor); + mDrcWrap.setParam(DRC_PRES_MODE_WRAP_DESIRED_BOOST_FACTOR, (unsigned)boostFactor); + + // DRC_PRES_MODE_WRAP_DESIRED_HEAVY + int32_t compressMode = mIntf->getDrcCompressMode(); + ALOGV("AAC decoder using desried DRC heavy compression switch of %d", compressMode); + mDrcWrap.setParam(DRC_PRES_MODE_WRAP_DESIRED_HEAVY, (unsigned)compressMode); + + // DRC_PRES_MODE_WRAP_ENCODER_TARGET + int32_t encTargetLevel = mIntf->getDrcEncTargetLevel(); + ALOGV("AAC decoder using encoder-side DRC reference level of %d", encTargetLevel); + mDrcWrap.setParam(DRC_PRES_MODE_WRAP_ENCODER_TARGET, (unsigned)encTargetLevel); + + // AAC_UNIDRC_SET_EFFECT + int32_t effectType = mIntf->getDrcEffectType(); + ALOGV("AAC decoder using MPEG-D DRC effect type %d", effectType); + aacDecoder_SetParam(mAACDecoder, AAC_UNIDRC_SET_EFFECT, effectType); + + // By default, the decoder creates a 5.1 channel downmix signal. + // For seven and eight channel input streams, enable 6.1 and 7.1 channel output + aacDecoder_SetParam(mAACDecoder, AAC_PCM_MAX_OUTPUT_CHANNELS, -1); + + return status; +} + +bool C2SoftAacDec::outputDelayRingBufferPutSamples(INT_PCM *samples, int32_t numSamples) { + if (numSamples == 0) { + return true; + } + if (outputDelayRingBufferSpaceLeft() < numSamples) { + ALOGE("RING BUFFER WOULD OVERFLOW"); + return false; + } + if (mOutputDelayRingBufferWritePos + numSamples <= mOutputDelayRingBufferSize + && (mOutputDelayRingBufferReadPos <= mOutputDelayRingBufferWritePos + || mOutputDelayRingBufferReadPos > mOutputDelayRingBufferWritePos + numSamples)) { + // faster memcopy loop without checks, if the preconditions allow this + for (int32_t i = 0; i < numSamples; i++) { + mOutputDelayRingBuffer[mOutputDelayRingBufferWritePos++] = samples[i]; + } + + if (mOutputDelayRingBufferWritePos >= mOutputDelayRingBufferSize) { + mOutputDelayRingBufferWritePos -= mOutputDelayRingBufferSize; + } + } else { + ALOGV("slow C2SoftAacDec::outputDelayRingBufferPutSamples()"); + + for (int32_t i = 0; i < numSamples; i++) { + mOutputDelayRingBuffer[mOutputDelayRingBufferWritePos] = samples[i]; + mOutputDelayRingBufferWritePos++; + if (mOutputDelayRingBufferWritePos >= mOutputDelayRingBufferSize) { + mOutputDelayRingBufferWritePos -= mOutputDelayRingBufferSize; + } + } + } + mOutputDelayRingBufferFilled += numSamples; + return true; +} + +int32_t C2SoftAacDec::outputDelayRingBufferGetSamples(INT_PCM *samples, int32_t numSamples) { + + if (numSamples > mOutputDelayRingBufferFilled) { + ALOGE("RING BUFFER WOULD UNDERRUN"); + return -1; + } + + if (mOutputDelayRingBufferReadPos + numSamples <= mOutputDelayRingBufferSize + && (mOutputDelayRingBufferWritePos < mOutputDelayRingBufferReadPos + || mOutputDelayRingBufferWritePos >= mOutputDelayRingBufferReadPos + numSamples)) { + // faster memcopy loop without checks, if the preconditions allow this + if (samples != nullptr) { + for (int32_t i = 0; i < numSamples; i++) { + samples[i] = mOutputDelayRingBuffer[mOutputDelayRingBufferReadPos++]; + } + } else { + mOutputDelayRingBufferReadPos += numSamples; + } + if (mOutputDelayRingBufferReadPos >= mOutputDelayRingBufferSize) { + mOutputDelayRingBufferReadPos -= mOutputDelayRingBufferSize; + } + } else { + ALOGV("slow C2SoftAacDec::outputDelayRingBufferGetSamples()"); + + for (int32_t i = 0; i < numSamples; i++) { + if (samples != nullptr) { + samples[i] = mOutputDelayRingBuffer[mOutputDelayRingBufferReadPos]; + } + mOutputDelayRingBufferReadPos++; + if (mOutputDelayRingBufferReadPos >= mOutputDelayRingBufferSize) { + mOutputDelayRingBufferReadPos -= mOutputDelayRingBufferSize; + } + } + } + mOutputDelayRingBufferFilled -= numSamples; + return numSamples; +} + +int32_t C2SoftAacDec::outputDelayRingBufferSamplesAvailable() { + return mOutputDelayRingBufferFilled; +} + +int32_t C2SoftAacDec::outputDelayRingBufferSpaceLeft() { + return mOutputDelayRingBufferSize - outputDelayRingBufferSamplesAvailable(); +} + +void C2SoftAacDec::drainRingBuffer( + const std::unique_ptr<C2Work> &work, + const std::shared_ptr<C2BlockPool> &pool, + bool eos) { + while (!mBuffersInfo.empty() && outputDelayRingBufferSamplesAvailable() + >= mStreamInfo->frameSize * mStreamInfo->numChannels) { + Info &outInfo = mBuffersInfo.front(); + ALOGV("outInfo.frameIndex = %" PRIu64, outInfo.frameIndex); + int samplesize __unused = mStreamInfo->numChannels * sizeof(int16_t); + + int available = outputDelayRingBufferSamplesAvailable(); + int numFrames = outInfo.decodedSizes.size(); + int numSamples = numFrames * (mStreamInfo->frameSize * mStreamInfo->numChannels); + if (available < numSamples) { + if (eos) { + numSamples = available; + } else { + break; + } + } + ALOGV("%d samples available (%d), or %d frames", + numSamples, available, numFrames); + ALOGV("getting %d from ringbuffer", numSamples); + + std::shared_ptr<C2LinearBlock> block; + std::function<void(const std::unique_ptr<C2Work>&)> fillWork = + [&block, numSamples, pool, this]() + -> std::function<void(const std::unique_ptr<C2Work>&)> { + auto fillEmptyWork = []( + const std::unique_ptr<C2Work> &work, c2_status_t err) { + work->result = err; + C2FrameData &output = work->worklets.front()->output; + output.flags = work->input.flags; + output.buffers.clear(); + output.ordinal = work->input.ordinal; + + work->workletsProcessed = 1u; + }; + + using namespace std::placeholders; + if (numSamples == 0) { + return std::bind(fillEmptyWork, _1, C2_OK); + } + + // TODO: error handling, proper usage, etc. + C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE }; + c2_status_t err = pool->fetchLinearBlock( + numSamples * sizeof(int16_t), usage, &block); + if (err != C2_OK) { + ALOGD("failed to fetch a linear block (%d)", err); + return std::bind(fillEmptyWork, _1, C2_NO_MEMORY); + } + C2WriteView wView = block->map().get(); + // TODO + INT_PCM *outBuffer = reinterpret_cast<INT_PCM *>(wView.data()); + int32_t ns = outputDelayRingBufferGetSamples(outBuffer, numSamples); + if (ns != numSamples) { + ALOGE("not a complete frame of samples available"); + mSignalledError = true; + return std::bind(fillEmptyWork, _1, C2_CORRUPTED); + } + return [buffer = createLinearBuffer(block)]( + const std::unique_ptr<C2Work> &work) { + work->result = C2_OK; + C2FrameData &output = work->worklets.front()->output; + output.flags = work->input.flags; + output.buffers.clear(); + output.buffers.push_back(buffer); + output.ordinal = work->input.ordinal; + work->workletsProcessed = 1u; + }; + }(); + + if (work && work->input.ordinal.frameIndex == c2_cntr64_t(outInfo.frameIndex)) { + fillWork(work); + } else { + finish(outInfo.frameIndex, fillWork); + } + + ALOGV("out timestamp %" PRIu64 " / %u", outInfo.timestamp, block ? block->capacity() : 0); + mBuffersInfo.pop_front(); + } +} + +void C2SoftAacDec::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.configUpdate.clear(); + work->worklets.front()->output.flags = work->input.flags; + + if (mSignalledError) { + return; + } + + UCHAR* inBuffer[FILEREAD_MAX_LAYERS]; + UINT inBufferLength[FILEREAD_MAX_LAYERS] = {0}; + UINT bytesValid[FILEREAD_MAX_LAYERS] = {0}; + + INT_PCM tmpOutBuffer[2048 * MAX_CHANNEL_COUNT]; + C2ReadView view = mDummyReadView; + size_t offset = 0u; + size_t size = 0u; + if (!work->input.buffers.empty()) { + view = work->input.buffers[0]->data().linearBlocks().front().map().get(); + size = view.capacity(); + } + + bool eos = (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0; + bool codecConfig = (work->input.flags & C2FrameData::FLAG_CODEC_CONFIG) != 0; + + //TODO +#if 0 + if (mInputBufferCount == 0 && !codecConfig) { + ALOGW("first buffer should have FLAG_CODEC_CONFIG set"); + codecConfig = true; + } +#endif + if (codecConfig && size > 0u) { + // const_cast because of libAACdec method signature. + inBuffer[0] = const_cast<UCHAR *>(view.data() + offset); + inBufferLength[0] = size; + + AAC_DECODER_ERROR decoderErr = + aacDecoder_ConfigRaw(mAACDecoder, + inBuffer, + inBufferLength); + + if (decoderErr != AAC_DEC_OK) { + ALOGE("aacDecoder_ConfigRaw decoderErr = 0x%4.4x", decoderErr); + mSignalledError = true; + 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(); + return; + } + + Info inInfo; + inInfo.frameIndex = work->input.ordinal.frameIndex.peeku(); + inInfo.timestamp = work->input.ordinal.timestamp.peeku(); + inInfo.bufferSize = size; + inInfo.decodedSizes.clear(); + while (size > 0u) { + ALOGV("size = %zu", size); + if (mIntf->isAdts()) { + size_t adtsHeaderSize = 0; + // skip 30 bits, aac_frame_length follows. + // ssssssss ssssiiip ppffffPc ccohCCll llllllll lll????? + + const uint8_t *adtsHeader = view.data() + offset; + + bool signalError = false; + if (size < 7) { + ALOGE("Audio data too short to contain even the ADTS header. " + "Got %zu bytes.", size); + hexdump(adtsHeader, size); + signalError = true; + } else { + bool protectionAbsent = (adtsHeader[1] & 1); + + unsigned aac_frame_length = + ((adtsHeader[3] & 3) << 11) + | (adtsHeader[4] << 3) + | (adtsHeader[5] >> 5); + + if (size < aac_frame_length) { + ALOGE("Not enough audio data for the complete frame. " + "Got %zu bytes, frame size according to the ADTS " + "header is %u bytes.", + size, aac_frame_length); + hexdump(adtsHeader, size); + signalError = true; + } else { + adtsHeaderSize = (protectionAbsent ? 7 : 9); + if (aac_frame_length < adtsHeaderSize) { + signalError = true; + } else { + // const_cast because of libAACdec method signature. + inBuffer[0] = const_cast<UCHAR *>(adtsHeader + adtsHeaderSize); + inBufferLength[0] = aac_frame_length - adtsHeaderSize; + + offset += adtsHeaderSize; + size -= adtsHeaderSize; + } + } + } + + if (signalError) { + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } + } else { + // const_cast because of libAACdec method signature. + inBuffer[0] = const_cast<UCHAR *>(view.data() + offset); + inBufferLength[0] = size; + } + + // Fill and decode + bytesValid[0] = inBufferLength[0]; + + INT prevSampleRate = mStreamInfo->sampleRate; + INT prevNumChannels = mStreamInfo->numChannels; + + aacDecoder_Fill(mAACDecoder, + inBuffer, + inBufferLength, + bytesValid); + + // run DRC check + mDrcWrap.submitStreamData(mStreamInfo); + mDrcWrap.update(); + + UINT inBufferUsedLength = inBufferLength[0] - bytesValid[0]; + size -= inBufferUsedLength; + offset += inBufferUsedLength; + + AAC_DECODER_ERROR decoderErr; + do { + if (outputDelayRingBufferSpaceLeft() < + (mStreamInfo->frameSize * mStreamInfo->numChannels)) { + ALOGV("skipping decode: not enough space left in ringbuffer"); + // discard buffer + size = 0; + break; + } + + int numConsumed = mStreamInfo->numTotalBytes; + decoderErr = aacDecoder_DecodeFrame(mAACDecoder, + tmpOutBuffer, + 2048 * MAX_CHANNEL_COUNT, + 0 /* flags */); + + numConsumed = mStreamInfo->numTotalBytes - numConsumed; + + if (decoderErr == AAC_DEC_NOT_ENOUGH_BITS) { + break; + } + inInfo.decodedSizes.push_back(numConsumed); + + if (decoderErr != AAC_DEC_OK) { + ALOGW("aacDecoder_DecodeFrame decoderErr = 0x%4.4x", decoderErr); + } + + if (bytesValid[0] != 0) { + ALOGE("bytesValid[0] != 0 should never happen"); + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } + + size_t numOutBytes = + mStreamInfo->frameSize * sizeof(int16_t) * mStreamInfo->numChannels; + + if (decoderErr == AAC_DEC_OK) { + if (!outputDelayRingBufferPutSamples(tmpOutBuffer, + mStreamInfo->frameSize * mStreamInfo->numChannels)) { + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } + } else { + ALOGW("AAC decoder returned error 0x%4.4x, substituting silence", decoderErr); + + memset(tmpOutBuffer, 0, numOutBytes); // TODO: check for overflow + + if (!outputDelayRingBufferPutSamples(tmpOutBuffer, + mStreamInfo->frameSize * mStreamInfo->numChannels)) { + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } + + // Discard input buffer. + size = 0; + + aacDecoder_SetParam(mAACDecoder, AAC_TPDEC_CLEAR_BUFFER, 1); + + // After an error, replace bufferSize with the sum of the + // decodedSizes to resynchronize the in/out lists. + inInfo.decodedSizes.pop_back(); + inInfo.bufferSize = std::accumulate( + inInfo.decodedSizes.begin(), inInfo.decodedSizes.end(), 0); + + // fall through + } + + /* + * AAC+/eAAC+ streams can be signalled in two ways: either explicitly + * or implicitly, according to MPEG4 spec. AAC+/eAAC+ is a dual + * rate system and the sampling rate in the final output is actually + * doubled compared with the core AAC decoder sampling rate. + * + * Explicit signalling is done by explicitly defining SBR audio object + * type in the bitstream. Implicit signalling is done by embedding + * SBR content in AAC extension payload specific to SBR, and hence + * requires an AAC decoder to perform pre-checks on actual audio frames. + * + * Thus, we could not say for sure whether a stream is + * AAC+/eAAC+ until the first data frame is decoded. + */ + if (!mStreamInfo->sampleRate || !mStreamInfo->numChannels) { + // if ((mInputBufferCount > 2) && (mOutputBufferCount <= 1)) { + ALOGD("Invalid AAC stream"); + // TODO: notify(OMX_EventError, OMX_ErrorUndefined, decoderErr, NULL); + // mSignalledError = true; + // } + } else if ((mStreamInfo->sampleRate != prevSampleRate) || + (mStreamInfo->numChannels != prevNumChannels)) { + ALOGI("Reconfiguring decoder: %d->%d Hz, %d->%d channels", + prevSampleRate, mStreamInfo->sampleRate, + prevNumChannels, mStreamInfo->numChannels); + + C2StreamSampleRateInfo::output sampleRateInfo(0u, mStreamInfo->sampleRate); + C2StreamChannelCountInfo::output channelCountInfo(0u, mStreamInfo->numChannels); + std::vector<std::unique_ptr<C2SettingResult>> failures; + c2_status_t err = mIntf->config( + { &sampleRateInfo, &channelCountInfo }, + C2_MAY_BLOCK, + &failures); + if (err == OK) { + // TODO: this does not handle the case where the values are + // altered during config. + 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; + } + } + ALOGV("size = %zu", size); + } while (decoderErr == AAC_DEC_OK); + } + + 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; + int32_t discard = outputDelayRingBufferSamplesAvailable(); + if (discard > toCompensate) { + discard = toCompensate; + } + int32_t discarded = outputDelayRingBufferGetSamples(nullptr, discard); + mOutputDelayCompensated += discarded; + return; + } + + if (eos) { + drainInternal(DRAIN_COMPONENT_WITH_EOS, pool, work); + } else { + drainRingBuffer(work, pool, false /* not EOS */); + } +} + +c2_status_t C2SoftAacDec::drainInternal( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool, + const std::unique_ptr<C2Work> &work) { + if (drainMode == NO_DRAIN) { + ALOGW("drain with NO_DRAIN: no-op"); + return C2_OK; + } + if (drainMode == DRAIN_CHAIN) { + ALOGW("DRAIN_CHAIN not supported"); + return C2_OMITTED; + } + + bool eos = (drainMode == DRAIN_COMPONENT_WITH_EOS); + + drainDecoder(); + drainRingBuffer(work, pool, eos); + + if (eos) { + auto fillEmptyWork = [](const std::unique_ptr<C2Work> &work) { + 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; + }; + while (mBuffersInfo.size() > 1u) { + finish(mBuffersInfo.front().frameIndex, fillEmptyWork); + mBuffersInfo.pop_front(); + } + if (work && work->workletsProcessed == 0u) { + fillEmptyWork(work); + } + mBuffersInfo.clear(); + } + + return C2_OK; +} + +c2_status_t C2SoftAacDec::drain( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool) { + return drainInternal(drainMode, pool, nullptr); +} + +c2_status_t C2SoftAacDec::onFlush_sm() { + drainDecoder(); + mBuffersInfo.clear(); + + int avail; + while ((avail = outputDelayRingBufferSamplesAvailable()) > 0) { + if (avail > mStreamInfo->frameSize * mStreamInfo->numChannels) { + avail = mStreamInfo->frameSize * mStreamInfo->numChannels; + } + int32_t ns = outputDelayRingBufferGetSamples(nullptr, avail); + if (ns != avail) { + ALOGW("not a complete frame of samples available"); + break; + } + } + mOutputDelayRingBufferReadPos = mOutputDelayRingBufferWritePos; + + return C2_OK; +} + +void C2SoftAacDec::drainDecoder() { + // flush decoder until outputDelay is compensated + while (mOutputDelayCompensated > 0) { + // a buffer big enough for MAX_CHANNEL_COUNT channels of decoded HE-AAC + INT_PCM tmpOutBuffer[2048 * MAX_CHANNEL_COUNT]; + + // run DRC check + mDrcWrap.submitStreamData(mStreamInfo); + mDrcWrap.update(); + + AAC_DECODER_ERROR decoderErr = + aacDecoder_DecodeFrame(mAACDecoder, + tmpOutBuffer, + 2048 * MAX_CHANNEL_COUNT, + AACDEC_FLUSH); + if (decoderErr != AAC_DEC_OK) { + ALOGW("aacDecoder_DecodeFrame decoderErr = 0x%4.4x", decoderErr); + } + + int32_t tmpOutBufferSamples = mStreamInfo->frameSize * mStreamInfo->numChannels; + if (tmpOutBufferSamples > mOutputDelayCompensated) { + tmpOutBufferSamples = mOutputDelayCompensated; + } + outputDelayRingBufferPutSamples(tmpOutBuffer, tmpOutBufferSamples); + + mOutputDelayCompensated -= tmpOutBufferSamples; + } +} + +class C2SoftAacDecFactory : public C2ComponentFactory { +public: + C2SoftAacDecFactory() : mHelper(std::static_pointer_cast<C2ReflectorHelper>( + GetCodec2PlatformComponentStore()->getParamReflector())) { + } + + virtual c2_status_t createComponent( + c2_node_id_t id, + std::shared_ptr<C2Component>* const component, + std::function<void(C2Component*)> deleter) override { + *component = std::shared_ptr<C2Component>( + new C2SoftAacDec(COMPONENT_NAME, + id, + std::make_shared<C2SoftAacDec::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual c2_status_t createInterface( + c2_node_id_t id, std::shared_ptr<C2ComponentInterface>* const interface, + std::function<void(C2ComponentInterface*)> deleter) override { + *interface = std::shared_ptr<C2ComponentInterface>( + new SimpleInterface<C2SoftAacDec::IntfImpl>( + COMPONENT_NAME, id, std::make_shared<C2SoftAacDec::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual ~C2SoftAacDecFactory() override = default; + +private: + std::shared_ptr<C2ReflectorHelper> mHelper; +}; + +} // namespace android + +extern "C" ::C2ComponentFactory* CreateCodec2Factory() { + ALOGV("in %s", __func__); + return new ::android::C2SoftAacDecFactory(); +} + +extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) { + ALOGV("in %s", __func__); + delete factory; +}
diff --git a/media/codec2/components/aac/C2SoftAacDec.h b/media/codec2/components/aac/C2SoftAacDec.h new file mode 100644 index 0000000..965c29e --- /dev/null +++ b/media/codec2/components/aac/C2SoftAacDec.h
@@ -0,0 +1,109 @@ +/* + * Copyright (C) 2017 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_C2_SOFT_AAC_DEC_H_ +#define ANDROID_C2_SOFT_AAC_DEC_H_ + +#include <SimpleC2Component.h> + + +#include "aacdecoder_lib.h" +#include "DrcPresModeWrap.h" + +namespace android { + +struct C2SoftAacDec : public SimpleC2Component { + class IntfImpl; + + C2SoftAacDec(const char *name, c2_node_id_t id, const std::shared_ptr<IntfImpl> &intfImpl); + virtual ~C2SoftAacDec(); + + // From SimpleC2Component + c2_status_t onInit() override; + c2_status_t onStop() override; + void onReset() override; + void onRelease() override; + c2_status_t onFlush_sm() override; + void process( + const std::unique_ptr<C2Work> &work, + const std::shared_ptr<C2BlockPool> &pool) override; + c2_status_t drain( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool) override; + +private: + enum { + kNumDelayBlocksMax = 8, + }; + + std::shared_ptr<IntfImpl> mIntf; + + HANDLE_AACDECODER mAACDecoder; + CStreamInfo *mStreamInfo; + bool mIsFirst; + size_t mInputBufferCount; + size_t mOutputBufferCount; + bool mSignalledError; + struct Info { + uint64_t frameIndex; + size_t bufferSize; + uint64_t timestamp; + std::vector<int32_t> decodedSizes; + }; + std::list<Info> mBuffersInfo; + + CDrcPresModeWrapper mDrcWrap; + + enum { + NONE, + AWAITING_DISABLED, + AWAITING_ENABLED + } mOutputPortSettingsChange; + + void initPorts(); + status_t initDecoder(); + bool isConfigured() const; + void drainDecoder(); + + void drainRingBuffer( + const std::unique_ptr<C2Work> &work, + const std::shared_ptr<C2BlockPool> &pool, + bool eos); + c2_status_t drainInternal( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool, + const std::unique_ptr<C2Work> &work); + +// delay compensation + bool mEndOfInput; + bool mEndOfOutput; + int32_t mOutputDelayCompensated; + int32_t mOutputDelayRingBufferSize; + short *mOutputDelayRingBuffer; + int32_t mOutputDelayRingBufferWritePos; + int32_t mOutputDelayRingBufferReadPos; + int32_t mOutputDelayRingBufferFilled; + bool outputDelayRingBufferPutSamples(INT_PCM *samples, int numSamples); + int32_t outputDelayRingBufferGetSamples(INT_PCM *samples, int numSamples); + int32_t outputDelayRingBufferSamplesAvailable(); + int32_t outputDelayRingBufferSpaceLeft(); + + C2_DO_NOT_COPY(C2SoftAacDec); +}; + +} // namespace android + +#endif // ANDROID_C2_SOFT_AAC_DEC_H_
diff --git a/media/codec2/components/aac/C2SoftAacEnc.cpp b/media/codec2/components/aac/C2SoftAacEnc.cpp new file mode 100644 index 0000000..8e3852c --- /dev/null +++ b/media/codec2/components/aac/C2SoftAacEnc.cpp
@@ -0,0 +1,622 @@ +/* + * Copyright (C) 2012 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 "C2SoftAacEnc" +#include <utils/Log.h> + +#include <inttypes.h> + +#include <C2PlatformSupport.h> +#include <SimpleC2Interface.h> +#include <media/stagefright/foundation/MediaDefs.h> +#include <media/stagefright/foundation/hexdump.h> + +#include "C2SoftAacEnc.h" + +namespace android { + +namespace { + +constexpr char COMPONENT_NAME[] = "c2.android.aac.encoder"; + +} // namespace + +class C2SoftAacEnc::IntfImpl : public SimpleInterface<void>::BaseParams { +public: + explicit IntfImpl(const std::shared_ptr<C2ReflectorHelper> &helper) + : SimpleInterface<void>::BaseParams( + helper, + COMPONENT_NAME, + C2Component::KIND_ENCODER, + C2Component::DOMAIN_AUDIO, + MEDIA_MIMETYPE_AUDIO_AAC) { + noPrivateBuffers(); + noInputReferences(); + noOutputReferences(); + noInputLatency(); + noTimeStretch(); + setDerivedInstance(this); + + addParameter( + DefineParam(mAttrib, C2_PARAMKEY_COMPONENT_ATTRIBUTES) + .withConstValue(new C2ComponentAttributesSetting( + C2Component::ATTRIB_IS_TEMPORAL)) + .build()); + + addParameter( + DefineParam(mSampleRate, C2_PARAMKEY_SAMPLE_RATE) + .withDefault(new C2StreamSampleRateInfo::input(0u, 44100)) + .withFields({C2F(mSampleRate, value).oneOf({ + 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 + })}) + .withSetter((Setter<decltype(*mSampleRate)>::StrictValueWithNoDeps)) + .build()); + + addParameter( + DefineParam(mChannelCount, C2_PARAMKEY_CHANNEL_COUNT) + .withDefault(new C2StreamChannelCountInfo::input(0u, 1)) + .withFields({C2F(mChannelCount, value).inRange(1, 6)}) + .withSetter(Setter<decltype(*mChannelCount)>::StrictValueWithNoDeps) + .build()); + + addParameter( + DefineParam(mBitrate, C2_PARAMKEY_BITRATE) + .withDefault(new C2StreamBitrateInfo::output(0u, 64000)) + .withFields({C2F(mBitrate, value).inRange(8000, 960000)}) + .withSetter(Setter<decltype(*mBitrate)>::NonStrictValueWithNoDeps) + .build()); + + addParameter( + DefineParam(mInputMaxBufSize, C2_PARAMKEY_INPUT_MAX_BUFFER_SIZE) + .withDefault(new C2StreamMaxBufferSizeInfo::input(0u, 8192)) + .calculatedAs(MaxBufSizeCalculator, mChannelCount) + .build()); + + addParameter( + DefineParam(mProfileLevel, C2_PARAMKEY_PROFILE_LEVEL) + .withDefault(new C2StreamProfileLevelInfo::output(0u, + C2Config::PROFILE_AAC_LC, C2Config::LEVEL_UNUSED)) + .withFields({ + C2F(mProfileLevel, profile).oneOf({ + C2Config::PROFILE_AAC_LC, + C2Config::PROFILE_AAC_HE, + C2Config::PROFILE_AAC_HE_PS, + C2Config::PROFILE_AAC_LD, + C2Config::PROFILE_AAC_ELD}), + C2F(mProfileLevel, level).oneOf({ + C2Config::LEVEL_UNUSED + }) + }) + .withSetter(ProfileLevelSetter) + .build()); + + addParameter( + DefineParam(mSBRMode, C2_PARAMKEY_AAC_SBR_MODE) + .withDefault(new C2StreamAacSbrModeTuning::input(0u, AAC_SBR_AUTO)) + .withFields({C2F(mSBRMode, value).oneOf({ + C2Config::AAC_SBR_OFF, + C2Config::AAC_SBR_SINGLE_RATE, + C2Config::AAC_SBR_DUAL_RATE, + C2Config::AAC_SBR_AUTO })}) + .withSetter(Setter<decltype(*mSBRMode)>::NonStrictValueWithNoDeps) + .build()); + } + + uint32_t getSampleRate() const { return mSampleRate->value; } + uint32_t getChannelCount() const { return mChannelCount->value; } + uint32_t getBitrate() const { return mBitrate->value; } + uint32_t getSBRMode() const { return mSBRMode->value; } + uint32_t getProfile() const { return mProfileLevel->profile; } + static C2R ProfileLevelSetter(bool mayBlock, C2P<C2StreamProfileLevelInfo::output> &me) { + (void)mayBlock; + (void)me; // TODO: validate + return C2R::Ok(); + } + + static C2R MaxBufSizeCalculator( + bool mayBlock, + C2P<C2StreamMaxBufferSizeInfo::input> &me, + const C2P<C2StreamChannelCountInfo::input> &channelCount) { + (void)mayBlock; + me.set().value = 1024 * sizeof(short) * channelCount.v.value; + return C2R::Ok(); + } + +private: + std::shared_ptr<C2StreamSampleRateInfo::input> mSampleRate; + std::shared_ptr<C2StreamChannelCountInfo::input> mChannelCount; + std::shared_ptr<C2StreamBitrateInfo::output> mBitrate; + std::shared_ptr<C2StreamMaxBufferSizeInfo::input> mInputMaxBufSize; + std::shared_ptr<C2StreamProfileLevelInfo::output> mProfileLevel; + std::shared_ptr<C2StreamAacSbrModeTuning::input> mSBRMode; +}; + +C2SoftAacEnc::C2SoftAacEnc( + const char *name, + c2_node_id_t id, + const std::shared_ptr<IntfImpl> &intfImpl) + : SimpleC2Component(std::make_shared<SimpleInterface<IntfImpl>>(name, id, intfImpl)), + mIntf(intfImpl), + mAACEncoder(nullptr), + mNumBytesPerInputFrame(0u), + mOutBufferSize(0u), + mSentCodecSpecificData(false), + mInputTimeSet(false), + mInputSize(0), + mInputTimeUs(0), + mSignalledError(false), + mOutIndex(0u) { +} + +C2SoftAacEnc::~C2SoftAacEnc() { + onReset(); +} + +c2_status_t C2SoftAacEnc::onInit() { + status_t err = initEncoder(); + return err == OK ? C2_OK : C2_CORRUPTED; +} + +status_t C2SoftAacEnc::initEncoder() { + if (AACENC_OK != aacEncOpen(&mAACEncoder, 0, 0)) { + ALOGE("Failed to init AAC encoder"); + return UNKNOWN_ERROR; + } + return setAudioParams(); +} + +c2_status_t C2SoftAacEnc::onStop() { + mSentCodecSpecificData = false; + mInputTimeSet = false; + mInputSize = 0u; + mInputTimeUs = 0; + mSignalledError = false; + return C2_OK; +} + +void C2SoftAacEnc::onReset() { + (void)onStop(); + aacEncClose(&mAACEncoder); +} + +void C2SoftAacEnc::onRelease() { + // no-op +} + +c2_status_t C2SoftAacEnc::onFlush_sm() { + mSentCodecSpecificData = false; + mInputTimeSet = false; + mInputSize = 0u; + mInputTimeUs = 0; + return C2_OK; +} + +static CHANNEL_MODE getChannelMode(uint32_t nChannels) { + CHANNEL_MODE chMode = MODE_INVALID; + switch (nChannels) { + case 1: chMode = MODE_1; break; + case 2: chMode = MODE_2; break; + case 3: chMode = MODE_1_2; break; + case 4: chMode = MODE_1_2_1; break; + case 5: chMode = MODE_1_2_2; break; + case 6: chMode = MODE_1_2_2_1; break; + default: chMode = MODE_INVALID; + } + return chMode; +} + +static AUDIO_OBJECT_TYPE getAOTFromProfile(uint32_t profile) { + if (profile == C2Config::PROFILE_AAC_LC) { + return AOT_AAC_LC; + } else if (profile == C2Config::PROFILE_AAC_HE) { + return AOT_SBR; + } else if (profile == C2Config::PROFILE_AAC_HE_PS) { + return AOT_PS; + } else if (profile == C2Config::PROFILE_AAC_LD) { + return AOT_ER_AAC_LD; + } else if (profile == C2Config::PROFILE_AAC_ELD) { + return AOT_ER_AAC_ELD; + } else { + ALOGW("Unsupported AAC profile - defaulting to AAC-LC"); + return AOT_AAC_LC; + } +} + +status_t C2SoftAacEnc::setAudioParams() { + // We call this whenever sample rate, number of channels, bitrate or SBR mode change + // in reponse to setParameter calls. + int32_t sbrRatio = 0; + uint32_t sbrMode = mIntf->getSBRMode(); + if (sbrMode == AAC_SBR_SINGLE_RATE) sbrRatio = 1; + else if (sbrMode == AAC_SBR_DUAL_RATE) sbrRatio = 2; + + ALOGV("setAudioParams: %u Hz, %u channels, %u bps, %i sbr mode, %i sbr ratio", + mIntf->getSampleRate(), mIntf->getChannelCount(), mIntf->getBitrate(), + sbrMode, sbrRatio); + + uint32_t aacProfile = mIntf->getProfile(); + if (AACENC_OK != aacEncoder_SetParam(mAACEncoder, AACENC_AOT, getAOTFromProfile(aacProfile))) { + ALOGE("Failed to set AAC encoder parameters"); + return UNKNOWN_ERROR; + } + + if (AACENC_OK != aacEncoder_SetParam(mAACEncoder, AACENC_SAMPLERATE, mIntf->getSampleRate())) { + ALOGE("Failed to set AAC encoder parameters"); + return UNKNOWN_ERROR; + } + if (AACENC_OK != aacEncoder_SetParam(mAACEncoder, AACENC_BITRATE, mIntf->getBitrate())) { + ALOGE("Failed to set AAC encoder parameters"); + return UNKNOWN_ERROR; + } + if (AACENC_OK != aacEncoder_SetParam(mAACEncoder, AACENC_CHANNELMODE, + getChannelMode(mIntf->getChannelCount()))) { + ALOGE("Failed to set AAC encoder parameters"); + return UNKNOWN_ERROR; + } + if (AACENC_OK != aacEncoder_SetParam(mAACEncoder, AACENC_TRANSMUX, TT_MP4_RAW)) { + ALOGE("Failed to set AAC encoder parameters"); + return UNKNOWN_ERROR; + } + + if (sbrMode != -1 && aacProfile == C2Config::PROFILE_AAC_ELD) { + if (AACENC_OK != aacEncoder_SetParam(mAACEncoder, AACENC_SBR_MODE, sbrMode)) { + ALOGE("Failed to set AAC encoder parameters"); + return UNKNOWN_ERROR; + } + } + + /* SBR ratio parameter configurations: + 0: Default configuration wherein SBR ratio is configured depending on audio object type by + the FDK. + 1: Downsampled SBR (default for ELD) + 2: Dualrate SBR (default for HE-AAC) + */ + if (AACENC_OK != aacEncoder_SetParam(mAACEncoder, AACENC_SBR_RATIO, sbrRatio)) { + ALOGE("Failed to set AAC encoder parameters"); + return UNKNOWN_ERROR; + } + + return OK; +} + +void C2SoftAacEnc::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) { + return; + } + bool eos = (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0; + + uint32_t sampleRate = mIntf->getSampleRate(); + uint32_t channelCount = mIntf->getChannelCount(); + + if (!mSentCodecSpecificData) { + // The very first thing we want to output is the codec specific + // data. + + if (AACENC_OK != aacEncEncode(mAACEncoder, nullptr, nullptr, nullptr, nullptr)) { + ALOGE("Unable to initialize encoder for profile / sample-rate / bit-rate / channels"); + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } + + uint32_t bitrate = mIntf->getBitrate(); + uint32_t actualBitRate = aacEncoder_GetParam(mAACEncoder, AACENC_BITRATE); + if (bitrate != actualBitRate) { + ALOGW("Requested bitrate %u unsupported, using %u", bitrate, actualBitRate); + } + + AACENC_InfoStruct encInfo; + if (AACENC_OK != aacEncInfo(mAACEncoder, &encInfo)) { + ALOGE("Failed to get AAC encoder info"); + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } + + std::unique_ptr<C2StreamInitDataInfo::output> csd = + C2StreamInitDataInfo::output::AllocUnique(encInfo.confSize, 0u); + 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 + hexdump(csd->m.value, csd->flexCount()); +#endif + work->worklets.front()->output.configUpdate.push_back(std::move(csd)); + + mOutBufferSize = encInfo.maxOutBufBytes; + mNumBytesPerInputFrame = encInfo.frameLength * channelCount * sizeof(int16_t); + + mSentCodecSpecificData = true; + } + + uint8_t temp[1]; + C2ReadView view = mDummyReadView; + const uint8_t *data = temp; + size_t capacity = 0u; + if (!work->input.buffers.empty()) { + view = work->input.buffers[0]->data().linearBlocks().front().map().get(); + 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; + ALOGV("capacity = %zu; mInputSize = %zu; numFrames = %zu mNumBytesPerInputFrame = %u", + capacity, mInputSize, numFrames, mNumBytesPerInputFrame); + + std::shared_ptr<C2LinearBlock> block; + std::shared_ptr<C2Buffer> buffer; + std::unique_ptr<C2WriteView> wView; + uint8_t *outPtr = temp; + size_t outAvailable = 0u; + uint64_t inputIndex = work->input.ordinal.frameIndex.peeku(); + + AACENC_InArgs inargs; + AACENC_OutArgs outargs; + memset(&inargs, 0, sizeof(inargs)); + memset(&outargs, 0, sizeof(outargs)); + inargs.numInSamples = capacity / sizeof(int16_t); + + void* inBuffer[] = { (unsigned char *)data }; + INT inBufferIds[] = { IN_AUDIO_DATA }; + INT inBufferSize[] = { (INT)capacity }; + INT inBufferElSize[] = { sizeof(int16_t) }; + + AACENC_BufDesc inBufDesc; + inBufDesc.numBufs = sizeof(inBuffer) / sizeof(void*); + inBufDesc.bufs = (void**)&inBuffer; + inBufDesc.bufferIdentifiers = inBufferIds; + inBufDesc.bufSizes = inBufferSize; + inBufDesc.bufElSizes = inBufferElSize; + + void* outBuffer[] = { outPtr }; + INT outBufferIds[] = { OUT_BITSTREAM_DATA }; + INT outBufferSize[] = { 0 }; + INT outBufferElSize[] = { sizeof(UCHAR) }; + + AACENC_BufDesc outBufDesc; + outBufDesc.numBufs = sizeof(outBuffer) / sizeof(void*); + outBufDesc.bufs = (void**)&outBuffer; + outBufDesc.bufferIdentifiers = outBufferIds; + outBufDesc.bufSizes = outBufferSize; + outBufDesc.bufElSizes = outBufferElSize; + + AACENC_ERROR encoderErr = AACENC_OK; + + class FillWork { + public: + FillWork(uint32_t flags, C2WorkOrdinalStruct ordinal, + const std::shared_ptr<C2Buffer> &buffer) + : mFlags(flags), mOrdinal(ordinal), mBuffer(buffer) { + } + ~FillWork() = default; + + void operator()(const std::unique_ptr<C2Work> &work) { + work->worklets.front()->output.flags = (C2FrameData::flags_t)mFlags; + work->worklets.front()->output.buffers.clear(); + work->worklets.front()->output.ordinal = mOrdinal; + work->workletsProcessed = 1u; + work->result = C2_OK; + if (mBuffer) { + work->worklets.front()->output.buffers.push_back(mBuffer); + } + ALOGV("timestamp = %lld, index = %lld, w/%s buffer", + mOrdinal.timestamp.peekll(), + mOrdinal.frameIndex.peekll(), + mBuffer ? "" : "o"); + } + + private: + const uint32_t mFlags; + const C2WorkOrdinalStruct mOrdinal; + const std::shared_ptr<C2Buffer> mBuffer; + }; + + C2WorkOrdinalStruct outOrdinal = work->input.ordinal; + + while (encoderErr == AACENC_OK && inargs.numInSamples > 0) { + if (numFrames && !block) { + C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE }; + // TODO: error handling, proper usage, etc. + c2_status_t err = pool->fetchLinearBlock(mOutBufferSize, usage, &block); + if (err != C2_OK) { + ALOGE("fetchLinearBlock failed : err = %d", err); + work->result = C2_NO_MEMORY; + return; + } + + wView.reset(new C2WriteView(block->map().get())); + outPtr = wView->data(); + outAvailable = wView->size(); + --numFrames; + } + + memset(&outargs, 0, sizeof(outargs)); + + outBuffer[0] = outPtr; + outBufferSize[0] = outAvailable; + + encoderErr = aacEncEncode(mAACEncoder, + &inBufDesc, + &outBufDesc, + &inargs, + &outargs); + + if (encoderErr == AACENC_OK) { + if (buffer) { + outOrdinal.frameIndex = mOutIndex++; + outOrdinal.timestamp = mInputTimeUs; + cloneAndSend( + inputIndex, + work, + FillWork(C2FrameData::FLAG_INCOMPLETE, outOrdinal, buffer)); + buffer.reset(); + } + + if (outargs.numOutBytes > 0) { + mInputSize = 0; + int consumed = (capacity / sizeof(int16_t)) - inargs.numInSamples + + outargs.numInSamples; + mInputTimeUs = work->input.ordinal.timestamp + + (consumed * 1000000ll / channelCount / sampleRate); + buffer = createLinearBuffer(block, 0, outargs.numOutBytes); +#if defined(LOG_NDEBUG) && !LOG_NDEBUG + hexdump(outPtr, std::min(outargs.numOutBytes, 256)); +#endif + outPtr = temp; + outAvailable = 0; + block.reset(); + } else { + mInputSize += outargs.numInSamples * sizeof(int16_t); + } + + if (outargs.numInSamples > 0) { + inBuffer[0] = (int16_t *)inBuffer[0] + outargs.numInSamples; + inBufferSize[0] -= outargs.numInSamples * sizeof(int16_t); + inargs.numInSamples -= outargs.numInSamples; + } + } + ALOGV("encoderErr = %d mInputSize = %zu inargs.numInSamples = %d, mInputTimeUs = %lld", + encoderErr, mInputSize, inargs.numInSamples, mInputTimeUs.peekll()); + } + + if (eos && inBufferSize[0] > 0) { + if (numFrames && !block) { + C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE }; + // TODO: error handling, proper usage, etc. + c2_status_t err = pool->fetchLinearBlock(mOutBufferSize, usage, &block); + if (err != C2_OK) { + ALOGE("fetchLinearBlock failed : err = %d", err); + work->result = C2_NO_MEMORY; + return; + } + + wView.reset(new C2WriteView(block->map().get())); + outPtr = wView->data(); + outAvailable = wView->size(); + --numFrames; + } + + memset(&outargs, 0, sizeof(outargs)); + + outBuffer[0] = outPtr; + outBufferSize[0] = outAvailable; + + // Flush + inargs.numInSamples = -1; + + (void)aacEncEncode(mAACEncoder, + &inBufDesc, + &outBufDesc, + &inargs, + &outargs); + } + + outOrdinal.frameIndex = mOutIndex++; + outOrdinal.timestamp = mInputTimeUs; + FillWork((C2FrameData::flags_t)(eos ? C2FrameData::FLAG_END_OF_STREAM : 0), + outOrdinal, buffer)(work); +} + +c2_status_t C2SoftAacEnc::drain( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool) { + switch (drainMode) { + case DRAIN_COMPONENT_NO_EOS: + [[fallthrough]]; + case NO_DRAIN: + // no-op + return C2_OK; + case DRAIN_CHAIN: + return C2_OMITTED; + case DRAIN_COMPONENT_WITH_EOS: + break; + default: + return C2_BAD_VALUE; + } + + (void)pool; + mSentCodecSpecificData = false; + mInputTimeSet = false; + mInputSize = 0u; + mInputTimeUs = 0; + + // TODO: we don't have any pending work at this time to drain. + return C2_OK; +} + +class C2SoftAacEncFactory : public C2ComponentFactory { +public: + C2SoftAacEncFactory() : mHelper(std::static_pointer_cast<C2ReflectorHelper>( + GetCodec2PlatformComponentStore()->getParamReflector())) { + } + + virtual c2_status_t createComponent( + c2_node_id_t id, + std::shared_ptr<C2Component>* const component, + std::function<void(C2Component*)> deleter) override { + *component = std::shared_ptr<C2Component>( + new C2SoftAacEnc(COMPONENT_NAME, + id, + std::make_shared<C2SoftAacEnc::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual c2_status_t createInterface( + c2_node_id_t id, std::shared_ptr<C2ComponentInterface>* const interface, + std::function<void(C2ComponentInterface*)> deleter) override { + *interface = std::shared_ptr<C2ComponentInterface>( + new SimpleInterface<C2SoftAacEnc::IntfImpl>( + COMPONENT_NAME, id, std::make_shared<C2SoftAacEnc::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual ~C2SoftAacEncFactory() override = default; + +private: + std::shared_ptr<C2ReflectorHelper> mHelper; +}; + +} // namespace android + +extern "C" ::C2ComponentFactory* CreateCodec2Factory() { + ALOGV("in %s", __func__); + return new ::android::C2SoftAacEncFactory(); +} + +extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) { + ALOGV("in %s", __func__); + delete factory; +}
diff --git a/media/codec2/components/aac/C2SoftAacEnc.h b/media/codec2/components/aac/C2SoftAacEnc.h new file mode 100644 index 0000000..a38be19 --- /dev/null +++ b/media/codec2/components/aac/C2SoftAacEnc.h
@@ -0,0 +1,73 @@ +/* + * 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_C2_SOFT_AAC_ENC_H_ +#define ANDROID_C2_SOFT_AAC_ENC_H_ + +#include <atomic> + +#include <SimpleC2Component.h> + +#include "aacenc_lib.h" + +namespace android { + +class C2SoftAacEnc : public SimpleC2Component { +public: + class IntfImpl; + + C2SoftAacEnc(const char *name, c2_node_id_t id, const std::shared_ptr<IntfImpl> &intfImpl); + virtual ~C2SoftAacEnc(); + + // From SimpleC2Component + c2_status_t onInit() override; + c2_status_t onStop() override; + void onReset() override; + void onRelease() override; + c2_status_t onFlush_sm() override; + void process( + const std::unique_ptr<C2Work> &work, + const std::shared_ptr<C2BlockPool> &pool) override; + c2_status_t drain( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool) override; + +private: + std::shared_ptr<IntfImpl> mIntf; + + HANDLE_AACENCODER mAACEncoder; + + UINT mNumBytesPerInputFrame; + UINT mOutBufferSize; + + bool mSentCodecSpecificData; + bool mInputTimeSet; + size_t mInputSize; + c2_cntr64_t mInputTimeUs; + + bool mSignalledError; + std::atomic_uint64_t mOutIndex; + + status_t initEncoder(); + + status_t setAudioParams(); + + C2_DO_NOT_COPY(C2SoftAacEnc); +}; + +} // namespace android + +#endif // ANDROID_C2_SOFT_AAC_ENC_H_
diff --git a/media/codec2/components/aac/DrcPresModeWrap.cpp b/media/codec2/components/aac/DrcPresModeWrap.cpp new file mode 100644 index 0000000..5b9aebc --- /dev/null +++ b/media/codec2/components/aac/DrcPresModeWrap.cpp
@@ -0,0 +1,372 @@ +/* + * Copyright (C) 2014 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 "DrcPresModeWrap.h" + +#include <assert.h> + +#define LOG_TAG "C2SoftAacDrcWrapper" +//#define LOG_NDEBUG 0 +#include <utils/Log.h> + +//#define DRC_PRES_MODE_WRAP_DEBUG + +#define GPM_ENCODER_TARGET_LEVEL 64 +#define MAX_TARGET_LEVEL 40 + +CDrcPresModeWrapper::CDrcPresModeWrapper() +{ + mDataUpdate = true; + + /* Data from streamInfo. */ + /* Initialized to the same values as in the aac decoder */ + mStreamPRL = -1; + mStreamDRCPresMode = -1; + mStreamNrAACChan = 0; + mStreamNrOutChan = 0; + + /* Desired values (set by user). */ + /* Initialized to the same values as in the aac decoder */ + mDesTarget = -1; + mDesAttFactor = 0; + mDesBoostFactor = 0; + mDesHeavy = 0; + + mEncoderTarget = -1; + + /* Values from last time. */ + /* Initialized to the same values as the desired values */ + mLastTarget = -1; + mLastAttFactor = 0; + mLastBoostFactor = 0; + mLastHeavy = 0; +} + +CDrcPresModeWrapper::~CDrcPresModeWrapper() +{ +} + +void +CDrcPresModeWrapper::setDecoderHandle(const HANDLE_AACDECODER handle) +{ + mHandleDecoder = handle; +} + +void +CDrcPresModeWrapper::submitStreamData(CStreamInfo* pStreamInfo) +{ + assert(pStreamInfo); + + if (mStreamPRL != pStreamInfo->drcProgRefLev) { + mStreamPRL = pStreamInfo->drcProgRefLev; + mDataUpdate = true; +#ifdef DRC_PRES_MODE_WRAP_DEBUG + ALOGV("DRC presentation mode wrapper: drcProgRefLev is %d\n", mStreamPRL); +#endif + } + + if (mStreamDRCPresMode != pStreamInfo->drcPresMode) { + mStreamDRCPresMode = pStreamInfo->drcPresMode; + mDataUpdate = true; +#ifdef DRC_PRES_MODE_WRAP_DEBUG + ALOGV("DRC presentation mode wrapper: drcPresMode is %d\n", mStreamDRCPresMode); +#endif + } + + if (mStreamNrAACChan != pStreamInfo->aacNumChannels) { + mStreamNrAACChan = pStreamInfo->aacNumChannels; + mDataUpdate = true; +#ifdef DRC_PRES_MODE_WRAP_DEBUG + ALOGV("DRC presentation mode wrapper: aacNumChannels is %d\n", mStreamNrAACChan); +#endif + } + + if (mStreamNrOutChan != pStreamInfo->numChannels) { + mStreamNrOutChan = pStreamInfo->numChannels; + mDataUpdate = true; +#ifdef DRC_PRES_MODE_WRAP_DEBUG + ALOGV("DRC presentation mode wrapper: numChannels is %d\n", mStreamNrOutChan); +#endif + } + + + + if (mStreamNrOutChan<mStreamNrAACChan) { + mIsDownmix = true; + } else { + mIsDownmix = false; + } + + if (mIsDownmix && (mStreamNrOutChan == 1)) { + mIsMonoDownmix = true; + } else { + mIsMonoDownmix = false; + } + + if (mIsDownmix && mStreamNrOutChan == 2){ + mIsStereoDownmix = true; + } else { + mIsStereoDownmix = false; + } + +} + +void +CDrcPresModeWrapper::setParam(const DRC_PRES_MODE_WRAP_PARAM param, const int value) +{ + switch (param) { + case DRC_PRES_MODE_WRAP_DESIRED_TARGET: + mDesTarget = value; + break; + case DRC_PRES_MODE_WRAP_DESIRED_ATT_FACTOR: + mDesAttFactor = value; + break; + case DRC_PRES_MODE_WRAP_DESIRED_BOOST_FACTOR: + mDesBoostFactor = value; + break; + case DRC_PRES_MODE_WRAP_DESIRED_HEAVY: + mDesHeavy = value; + break; + case DRC_PRES_MODE_WRAP_ENCODER_TARGET: + mEncoderTarget = value; + break; + default: + break; + } + mDataUpdate = true; +} + +void +CDrcPresModeWrapper::update() +{ + // Get Data from Decoder + int progRefLevel = mStreamPRL; + int drcPresMode = mStreamDRCPresMode; + + // by default, do as desired + int newTarget = mDesTarget; + int newAttFactor = mDesAttFactor; + int newBoostFactor = mDesBoostFactor; + int newHeavy = mDesHeavy; + + if (mDataUpdate) { + // sanity check + if (mDesTarget < MAX_TARGET_LEVEL){ + mDesTarget = MAX_TARGET_LEVEL; // limit target level to -10 dB or below + newTarget = MAX_TARGET_LEVEL; + } + + if (mEncoderTarget != -1) { + if (mDesTarget<124) { // if target level > -31 dB + if ((mIsStereoDownmix == false) && (mIsMonoDownmix == false)) { + // no stereo or mono downmixing, calculated scaling of light DRC + /* use as little compression as possible */ + newAttFactor = 0; + newBoostFactor = 0; + if (mDesTarget<progRefLevel) { // if target level > PRL + if (mEncoderTarget < mDesTarget) { // if mEncoderTarget > target level + // mEncoderTarget > target level > PRL + int calcFactor; + float calcFactor_norm; + // 0.0f < calcFactor_norm < 1.0f + calcFactor_norm = (float)(mDesTarget - progRefLevel) / + (float)(mEncoderTarget - progRefLevel); + calcFactor = (int)(calcFactor_norm*127.0f); // 0 <= calcFactor < 127 + // calcFactor is the lower limit + newAttFactor = (calcFactor>newAttFactor) ? calcFactor : newAttFactor; + // new AttFactor will be always = calcFactor, as it is set to 0 before. + newBoostFactor = newAttFactor; + } else { + /* target level > mEncoderTarget > PRL */ + // newTDLimiterEnable = 1; + // the time domain limiter must always be active in this case. + // It is assumed that the framework activates it by default + newAttFactor = 127; + newBoostFactor = 127; + } + } else { // target level <= PRL + // no restrictions required + // newAttFactor = newAttFactor; + } + } else { // downmixing + // if target level > -23 dB or mono downmix + if ( (mDesTarget<92) || mIsMonoDownmix ) { + newHeavy = 1; + } else { + // we perform a downmix, so, we need at least full light DRC + newAttFactor = 127; + } + } + } else { // target level <= -31 dB + // playback -31 dB: light DRC only needed if we perform downmixing + if (mIsDownmix) { // we do downmixing + newAttFactor = 127; + } + } + } + else { // handle other used encoder target levels + + // Sanity check: DRC presentation mode is only specified for max. 5.1 channels + if (mStreamNrAACChan > 6) { + drcPresMode = 0; + } + + switch (drcPresMode) { + case 0: + default: // presentation mode not indicated + { + + if (mDesTarget<124) { // if target level > -31 dB + // no stereo or mono downmixing + if ((mIsStereoDownmix == false) && (mIsMonoDownmix == false)) { + if (mDesTarget<progRefLevel) { // if target level > PRL + // newTDLimiterEnable = 1; + // the time domain limiter must always be active in this case. + // It is assumed that the framework activates it by default + newAttFactor = 127; // at least, use light compression + } else { // target level <= PRL + // no restrictions required + // newAttFactor = newAttFactor; + } + } else { // downmixing + // newTDLimiterEnable = 1; + // the time domain limiter must always be active in this case. + // It is assumed that the framework activates it by default + + // if target level > -23 dB or mono downmix + if ( (mDesTarget < 92) || mIsMonoDownmix ) { + newHeavy = 1; + } else{ + // we perform a downmix, so, we need at least full light DRC + newAttFactor = 127; + } + } + } else { // target level <= -31 dB + if (mIsDownmix) { // we do downmixing. + // newTDLimiterEnable = 1; + // the time domain limiter must always be active in this case. + // It is assumed that the framework activates it by default + newAttFactor = 127; + } + } + } + break; + + // Presentation mode 1 and 2 according to ETSI TS 101 154: + // Digital Video Broadcasting (DVB); Specification for the use of Video and Audio Coding + // in Broadcasting Applications based on the MPEG-2 Transport Stream, + // section C.5.4., "Decoding", and Table C.33 + // ISO DRC -> newHeavy = 0 (Use light compression, MPEG-style) + // Compression_value -> newHeavy = 1 (Use heavy compression, DVB-style) + // scaling restricted -> newAttFactor = 127 + + case 1: // presentation mode 1, Light:-31/Heavy:-23 + { + if (mDesTarget < 124) { // if target level > -31 dB + // playback up to -23 dB + newHeavy = 1; + } else { // target level <= -31 dB + // playback -31 dB + if (mIsDownmix) { // we do downmixing. + newAttFactor = 127; + } + } + } + break; + + case 2: // presentation mode 2, Light:-23/Heavy:-23 + { + if (mDesTarget < 124) { // if target level > -31 dB + // playback up to -23 dB + if (mIsMonoDownmix) { // if mono downmix + newHeavy = 1; + } else { + newHeavy = 0; + newAttFactor = 127; + } + } else { // target level <= -31 dB + // playback -31 dB + newHeavy = 0; + if (mIsDownmix) { // we do downmixing. + newAttFactor = 127; + } + } + } + break; + + } // switch() + } // if (mEncoderTarget == GPM_ENCODER_TARGET_LEVEL) + + // sanity again + if (newHeavy == 1) { + newBoostFactor=127; // not really needed as the same would be done by the decoder anyway + newAttFactor = 127; + } + + // update the decoder + if (newTarget != mLastTarget) { + aacDecoder_SetParam(mHandleDecoder, AAC_DRC_REFERENCE_LEVEL, newTarget); + mLastTarget = newTarget; +#ifdef DRC_PRES_MODE_WRAP_DEBUG + if (newTarget != mDesTarget) + ALOGV("DRC presentation mode wrapper: forced target level to %d (from %d)\n", newTarget, mDesTarget); + else + ALOGV("DRC presentation mode wrapper: set target level to %d\n", newTarget); +#endif + } + + if (newAttFactor != mLastAttFactor) { + aacDecoder_SetParam(mHandleDecoder, AAC_DRC_ATTENUATION_FACTOR, newAttFactor); + mLastAttFactor = newAttFactor; +#ifdef DRC_PRES_MODE_WRAP_DEBUG + if (newAttFactor != mDesAttFactor) + ALOGV("DRC presentation mode wrapper: forced attenuation factor to %d (from %d)\n", newAttFactor, mDesAttFactor); + else + ALOGV("DRC presentation mode wrapper: set attenuation factor to %d\n", newAttFactor); +#endif + } + + if (newBoostFactor != mLastBoostFactor) { + aacDecoder_SetParam(mHandleDecoder, AAC_DRC_BOOST_FACTOR, newBoostFactor); + mLastBoostFactor = newBoostFactor; +#ifdef DRC_PRES_MODE_WRAP_DEBUG + if (newBoostFactor != mDesBoostFactor) + ALOGV("DRC presentation mode wrapper: forced boost factor to %d (from %d)\n", + newBoostFactor, mDesBoostFactor); + else + ALOGV("DRC presentation mode wrapper: set boost factor to %d\n", newBoostFactor); +#endif + } + + if (newHeavy != mLastHeavy) { + aacDecoder_SetParam(mHandleDecoder, AAC_DRC_HEAVY_COMPRESSION, newHeavy); + mLastHeavy = newHeavy; +#ifdef DRC_PRES_MODE_WRAP_DEBUG + if (newHeavy != mDesHeavy) + ALOGV("DRC presentation mode wrapper: forced heavy compression to %d (from %d)\n", + newHeavy, mDesHeavy); + else + ALOGV("DRC presentation mode wrapper: set heavy compression to %d\n", newHeavy); +#endif + } + +#ifdef DRC_PRES_MODE_WRAP_DEBUG + ALOGV("DRC config: tgt_lev: %3d, cut: %3d, boost: %3d, heavy: %d\n", newTarget, + newAttFactor, newBoostFactor, newHeavy); +#endif + mDataUpdate = false; + + } // if (mDataUpdate) +}
diff --git a/media/codec2/components/aac/DrcPresModeWrap.h b/media/codec2/components/aac/DrcPresModeWrap.h new file mode 100644 index 0000000..f0b6cf2 --- /dev/null +++ b/media/codec2/components/aac/DrcPresModeWrap.h
@@ -0,0 +1,62 @@ +/* + * Copyright (C) 2014 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. + */ +#pragma once +#include "aacdecoder_lib.h" + +typedef enum +{ + DRC_PRES_MODE_WRAP_DESIRED_TARGET = 0x0000, + DRC_PRES_MODE_WRAP_DESIRED_ATT_FACTOR = 0x0001, + DRC_PRES_MODE_WRAP_DESIRED_BOOST_FACTOR = 0x0002, + DRC_PRES_MODE_WRAP_DESIRED_HEAVY = 0x0003, + DRC_PRES_MODE_WRAP_ENCODER_TARGET = 0x0004 +} DRC_PRES_MODE_WRAP_PARAM; + + +class CDrcPresModeWrapper { +public: + CDrcPresModeWrapper(); + ~CDrcPresModeWrapper(); + void setDecoderHandle(const HANDLE_AACDECODER handle); + void setParam(const DRC_PRES_MODE_WRAP_PARAM param, const int value); + void submitStreamData(CStreamInfo*); + void update(); + +protected: + HANDLE_AACDECODER mHandleDecoder; + int mDesTarget; + int mDesAttFactor; + int mDesBoostFactor; + int mDesHeavy; + + int mEncoderTarget; + + int mLastTarget; + int mLastAttFactor; + int mLastBoostFactor; + int mLastHeavy; + + SCHAR mStreamPRL; + SCHAR mStreamDRCPresMode; + INT mStreamNrAACChan; + INT mStreamNrOutChan; + + bool mIsDownmix; + bool mIsMonoDownmix; + bool mIsStereoDownmix; + + bool mDataUpdate; +};
diff --git a/media/common_time/MODULE_LICENSE_APACHE2 b/media/codec2/components/aac/MODULE_LICENSE_APACHE2 similarity index 100% rename from media/common_time/MODULE_LICENSE_APACHE2 rename to media/codec2/components/aac/MODULE_LICENSE_APACHE2
diff --git a/media/common_time/NOTICE b/media/codec2/components/aac/NOTICE similarity index 100% rename from media/common_time/NOTICE rename to media/codec2/components/aac/NOTICE
diff --git a/media/codec2/components/aac/patent_disclaimer.txt b/media/codec2/components/aac/patent_disclaimer.txt new file mode 100644 index 0000000..b4bf11d --- /dev/null +++ b/media/codec2/components/aac/patent_disclaimer.txt
@@ -0,0 +1,9 @@ + +THIS IS NOT A GRANT OF PATENT RIGHTS. + +Google makes no representation or warranty that the codecs for which +source code is made available hereunder are unencumbered by +third-party patents. Those intending to use this source code in +hardware or software products are advised that implementations of +these codecs, including in open source software or shareware, may +require patent licenses from the relevant patent holders.
diff --git a/media/codec2/components/amr_nb_wb/Android.bp b/media/codec2/components/amr_nb_wb/Android.bp new file mode 100644 index 0000000..ce25bc9 --- /dev/null +++ b/media/codec2/components/amr_nb_wb/Android.bp
@@ -0,0 +1,77 @@ +cc_library_shared { + name: "libcodec2_soft_amrnbdec", + defaults: [ + "libcodec2_soft-defaults", + "libcodec2_soft_sanitize_all-defaults", + ], + + srcs: ["C2SoftAmrDec.cpp"], + + cflags: [ + "-DAMRNB", + ], + + static_libs: [ + "libstagefright_amrnbdec", + "libstagefright_amrwbdec", + ], + + shared_libs: [ + "libstagefright_amrnb_common", + ], +} + +cc_library_shared { + name: "libcodec2_soft_amrwbdec", + defaults: [ + "libcodec2_soft-defaults", + "libcodec2_soft_sanitize_all-defaults", + ], + + srcs: ["C2SoftAmrDec.cpp"], + + static_libs: [ + "libstagefright_amrnbdec", + "libstagefright_amrwbdec", + ], + + shared_libs: [ + "libstagefright_amrnb_common", + ], +} + +cc_library_shared { + name: "libcodec2_soft_amrnbenc", + defaults: [ + "libcodec2_soft-defaults", + "libcodec2_soft_sanitize_all-defaults", + ], + + srcs: ["C2SoftAmrNbEnc.cpp"], + + static_libs: [ + "libstagefright_amrnbenc", + ], + + shared_libs: [ + "libstagefright_amrnb_common", + ], +} + +cc_library_shared { + name: "libcodec2_soft_amrwbenc", + defaults: [ + "libcodec2_soft-defaults", + "libcodec2_soft_sanitize_all-defaults", + ], + + srcs: ["C2SoftAmrWbEnc.cpp"], + + static_libs: [ + "libstagefright_amrwbenc", + ], + + shared_libs: [ + "libstagefright_enc_common", + ], +}
diff --git a/media/codec2/components/amr_nb_wb/C2SoftAmrDec.cpp b/media/codec2/components/amr_nb_wb/C2SoftAmrDec.cpp new file mode 100644 index 0000000..6578ad2 --- /dev/null +++ b/media/codec2/components/amr_nb_wb/C2SoftAmrDec.cpp
@@ -0,0 +1,432 @@ +/* + * 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 +#ifdef AMRNB +#define LOG_TAG "C2SoftAmrNbDec" +#else +#define LOG_TAG "C2SoftAmrWbDec" +#endif +#include <log/log.h> + +#include <media/stagefright/foundation/MediaDefs.h> + +#include <C2PlatformSupport.h> +#include <SimpleC2Interface.h> + +#include "C2SoftAmrDec.h" +#include "gsmamr_dec.h" +#include "pvamrwbdecoder.h" + +namespace android { + +namespace { + +#ifdef AMRNB + constexpr char COMPONENT_NAME[] = "c2.android.amrnb.decoder"; +#else + constexpr char COMPONENT_NAME[] = "c2.android.amrwb.decoder"; +#endif + +} // namespace + +class C2SoftAmrDec::IntfImpl : public SimpleInterface<void>::BaseParams { +public: + explicit IntfImpl(const std::shared_ptr<C2ReflectorHelper> &helper) + : SimpleInterface<void>::BaseParams( + helper, + COMPONENT_NAME, + C2Component::KIND_DECODER, + C2Component::DOMAIN_AUDIO, +#ifdef AMRNB + MEDIA_MIMETYPE_AUDIO_AMR_NB +#else + MEDIA_MIMETYPE_AUDIO_AMR_WB +#endif + ) { + noPrivateBuffers(); + noInputReferences(); + noOutputReferences(); + noInputLatency(); + noTimeStretch(); + setDerivedInstance(this); + + addParameter( + DefineParam(mAttrib, C2_PARAMKEY_COMPONENT_ATTRIBUTES) + .withConstValue(new C2ComponentAttributesSetting( + C2Component::ATTRIB_IS_TEMPORAL)) + .build()); + + addParameter( + DefineParam(mSampleRate, C2_PARAMKEY_SAMPLE_RATE) +#ifdef AMRNB + .withDefault(new C2StreamSampleRateInfo::output(0u, 8000)) + .withFields({C2F(mSampleRate, value).equalTo(8000)}) +#else + .withDefault(new C2StreamSampleRateInfo::output(0u, 16000)) + .withFields({C2F(mSampleRate, value).equalTo(16000)}) +#endif + .withSetter((Setter<decltype(*mSampleRate)>::StrictValueWithNoDeps)) + .build()); + + addParameter( + DefineParam(mChannelCount, C2_PARAMKEY_CHANNEL_COUNT) + .withDefault(new C2StreamChannelCountInfo::output(0u, 1)) + .withFields({C2F(mChannelCount, value).equalTo(1)}) + .withSetter((Setter<decltype(*mChannelCount)>::StrictValueWithNoDeps)) + .build()); + + addParameter( + DefineParam(mBitrate, C2_PARAMKEY_BITRATE) +#ifdef AMRNB + .withDefault(new C2StreamBitrateInfo::input(0u, 4750)) + .withFields({C2F(mBitrate, value).inRange(4750, 12200)}) +#else + .withDefault(new C2StreamBitrateInfo::input(0u, 6600)) + .withFields({C2F(mBitrate, value).inRange(6600, 23850)}) +#endif + .withSetter(Setter<decltype(*mBitrate)>::NonStrictValueWithNoDeps) + .build()); + + addParameter( + DefineParam(mInputMaxBufSize, C2_PARAMKEY_INPUT_MAX_BUFFER_SIZE) + .withConstValue(new C2StreamMaxBufferSizeInfo::input(0u, 8192)) + .build()); + } + +private: + std::shared_ptr<C2StreamSampleRateInfo::output> mSampleRate; + std::shared_ptr<C2StreamChannelCountInfo::output> mChannelCount; + std::shared_ptr<C2StreamBitrateInfo::input> mBitrate; + std::shared_ptr<C2StreamMaxBufferSizeInfo::input> mInputMaxBufSize; +}; + +C2SoftAmrDec::C2SoftAmrDec( + const char *name, + c2_node_id_t id, + const std::shared_ptr<IntfImpl> &intfImpl) + : SimpleC2Component(std::make_shared<SimpleInterface<IntfImpl>>(name, id, intfImpl)), + mIntf(intfImpl), + mAmrHandle(nullptr), + mDecoderBuf(nullptr), + mDecoderCookie(nullptr) { +#ifdef AMRNB + mIsWide = false; +#else + mIsWide = true; +#endif +} + +C2SoftAmrDec::~C2SoftAmrDec() { + (void)onRelease(); +} + +c2_status_t C2SoftAmrDec::onInit() { + status_t err = initDecoder(); + return err == OK ? C2_OK : C2_NO_MEMORY; +} + +c2_status_t C2SoftAmrDec::onStop() { + if (!mIsWide) { + Speech_Decode_Frame_reset(mAmrHandle); + } else { + pvDecoder_AmrWb_Reset(mAmrHandle, 0 /* reset_all */); + } + mSignalledError = false; + mSignalledOutputEos = false; + + return C2_OK; +} + +void C2SoftAmrDec::onReset() { + (void)onStop(); +} + +void C2SoftAmrDec::onRelease() { + if (!mIsWide) { + if (mAmrHandle) { + GSMDecodeFrameExit(&mAmrHandle); + } + mAmrHandle = nullptr; + } else { + if (mDecoderBuf) { + free(mDecoderBuf); + } + mDecoderBuf = nullptr; + mAmrHandle = nullptr; + mDecoderCookie = nullptr; + } +} + +c2_status_t C2SoftAmrDec::onFlush_sm() { + return onStop(); +} + +status_t C2SoftAmrDec::initDecoder() { + if (!mIsWide) { + if (GSMInitDecode(&mAmrHandle, (int8_t *)"AMRNBDecoder")) + return UNKNOWN_ERROR; + } else { + uint32_t memReq = pvDecoder_AmrWbMemRequirements(); + mDecoderBuf = malloc(memReq); + if (mDecoderBuf) { + pvDecoder_AmrWb_Init(&mAmrHandle, mDecoderBuf, &mDecoderCookie); + } + else { + return NO_MEMORY; + } + } + mSignalledError = false; + mSignalledOutputEos = false; + + return OK; +} + +static size_t getFrameSize(bool isWide, unsigned FM) { + static const size_t kFrameSizeNB[16] = { + 12, 13, 15, 17, 19, 20, 26, 31, + 5, 6, 5, 5, // SID + 0, 0, 0, // future use + 0 // no data + }; + static const size_t kFrameSizeWB[16] = { + 17, 23, 32, 36, 40, 46, 50, 58, 60, + 5, // SID + 0, 0, 0, 0, // future use + 0, // speech lost + 0 // no data + }; + + if (FM > 15 || (isWide && FM > 9 && FM < 14) || (!isWide && FM > 11 && FM < 15)) { + ALOGE("illegal AMR frame mode %d", FM); + return 0; + } + // add 1 for header byte + return (isWide ? kFrameSizeWB[FM] : kFrameSizeNB[FM]) + 1; +} + +static status_t calculateNumFrames(const uint8 *input, size_t inSize, + std::vector<size_t> *frameSizeList, bool isWide) { + for (size_t k = 0; k < inSize;) { + int16_t FM = ((input[0] >> 3) & 0x0f); + size_t frameSize = getFrameSize(isWide, FM); + if (frameSize == 0) { + return UNKNOWN_ERROR; + } + if ((inSize - k) >= frameSize) { + input += frameSize; + k += frameSize; + } + else break; + frameSizeList->push_back(frameSize); + } + return OK; +} + +void C2SoftAmrDec::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; + } + + C2ReadView rView = mDummyReadView; + size_t inOffset = 0u; + size_t inSize = 0u; + if (!work->input.buffers.empty()) { + rView = work->input.buffers[0]->data().linearBlocks().front().map().get(); + inSize = rView.capacity(); + if (inSize && rView.error()) { + ALOGE("read view map failed %d", rView.error()); + work->result = rView.error(); + return; + } + } + + bool eos = (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0; + if (inSize == 0) { + work->worklets.front()->output.flags = work->input.flags; + work->worklets.front()->output.buffers.clear(); + work->worklets.front()->output.ordinal = work->input.ordinal; + if (eos) { + mSignalledOutputEos = true; + ALOGV("signalled EOS"); + } + return; + } + + ALOGV("in buffer attr. size %zu timestamp %d frameindex %d", inSize, + (int)work->input.ordinal.timestamp.peeku(), (int)work->input.ordinal.frameIndex.peeku()); + + std::vector<size_t> frameSizeList; + if (OK != calculateNumFrames(rView.data() + inOffset, inSize, &frameSizeList, + mIsWide)) { + work->result = C2_CORRUPTED; + mSignalledError = true; + return; + } + if (frameSizeList.empty()) { + ALOGE("input size smaller than expected"); + work->result = C2_CORRUPTED; + mSignalledError = true; + return; + } + + int16_t outSamples = mIsWide ? kNumSamplesPerFrameWB : kNumSamplesPerFrameNB; + size_t calOutSize = outSamples * frameSizeList.size() * sizeof(int16_t); + std::shared_ptr<C2LinearBlock> block; + C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE }; + c2_status_t err = pool->fetchLinearBlock(calOutSize, usage, &block); + if (err != C2_OK) { + ALOGE("fetchLinearBlock for Output failed with status %d", err); + work->result = C2_NO_MEMORY; + return; + } + C2WriteView wView = block->map().get(); + if (wView.error()) { + ALOGE("write view map failed %d", wView.error()); + work->result = wView.error(); + return; + } + + int16_t *output = reinterpret_cast<int16_t *>(wView.data()); + auto it = frameSizeList.begin(); + const uint8_t *inPtr = rView.data() + inOffset; + size_t inPos = 0; + while (inPos < inSize) { + if (it == frameSizeList.end()) { + ALOGD("unexpected trailing bytes, ignoring them"); + break; + } + uint8_t *input = const_cast<uint8_t *>(inPtr + inPos); + int16_t FM = ((*input >> 3) & 0x0f); + if (!mIsWide) { + int32_t numBytesRead = AMRDecode(mAmrHandle, + (Frame_Type_3GPP) FM, + input + 1, output, MIME_IETF); + if (static_cast<size_t>(numBytesRead + 1) != *it) { + ALOGE("panic, parsed size does not match decoded size"); + work->result = C2_CORRUPTED; + mSignalledError = true; + return; + } + } else { + if (FM >= 9) { + // Produce silence instead of comfort noise and for + // speech lost/no data. + memset(output, 0, outSamples * sizeof(int16_t)); + } else { + int16_t FT; + RX_State_wb rx_state; + int16_t numRecSamples; + + mime_unsorting(const_cast<uint8_t *>(&input[1]), + mInputSampleBuffer, &FT, &FM, 1, &rx_state); + pvDecoder_AmrWb(FM, mInputSampleBuffer, output, &numRecSamples, + mDecoderBuf, FT, mDecoderCookie); + if (numRecSamples != outSamples) { + ALOGE("Sample output per frame incorrect"); + work->result = C2_CORRUPTED; + mSignalledError = true; + return; + } + /* Delete the 2 LSBs (14-bit output) */ + for (int i = 0; i < numRecSamples; ++i) { + output[i] &= 0xfffC; + } + } + } + inPos += *it; + output += outSamples; + ++it; + } + + work->worklets.front()->output.flags = work->input.flags; + work->worklets.front()->output.buffers.clear(); + work->worklets.front()->output.buffers.push_back(createLinearBuffer(block)); + work->worklets.front()->output.ordinal = work->input.ordinal; + if (eos) { + mSignalledOutputEos = true; + ALOGV("signalled EOS"); + } +} + +c2_status_t C2SoftAmrDec::drain( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool) { + (void)pool; + if (drainMode == NO_DRAIN) { + ALOGW("drain with NO_DRAIN: no-op"); + return C2_OK; + } + if (drainMode == DRAIN_CHAIN) { + ALOGW("DRAIN_CHAIN not supported"); + return C2_OMITTED; + } + return C2_OK; +} + +class C2SoftAMRDecFactory : public C2ComponentFactory { +public: + C2SoftAMRDecFactory() : mHelper(std::static_pointer_cast<C2ReflectorHelper>( + GetCodec2PlatformComponentStore()->getParamReflector())) { + } + + virtual c2_status_t createComponent( + c2_node_id_t id, + std::shared_ptr<C2Component>* const component, + std::function<void(C2Component*)> deleter) override { + *component = std::shared_ptr<C2Component>( + new C2SoftAmrDec(COMPONENT_NAME, id, + std::make_shared<C2SoftAmrDec::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual c2_status_t createInterface( + c2_node_id_t id, + std::shared_ptr<C2ComponentInterface>* const interface, + std::function<void(C2ComponentInterface*)> deleter) override { + *interface = std::shared_ptr<C2ComponentInterface>( + new SimpleInterface<C2SoftAmrDec::IntfImpl>( + COMPONENT_NAME, id, std::make_shared<C2SoftAmrDec::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual ~C2SoftAMRDecFactory() override = default; + +private: + std::shared_ptr<C2ReflectorHelper> mHelper; +}; + +} // namespace android + +extern "C" ::C2ComponentFactory* CreateCodec2Factory() { + ALOGV("in %s", __func__); + return new ::android::C2SoftAMRDecFactory(); +} + +extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) { + ALOGV("in %s", __func__); + delete factory; +}
diff --git a/media/codec2/components/amr_nb_wb/C2SoftAmrDec.h b/media/codec2/components/amr_nb_wb/C2SoftAmrDec.h new file mode 100644 index 0000000..6384450 --- /dev/null +++ b/media/codec2/components/amr_nb_wb/C2SoftAmrDec.h
@@ -0,0 +1,68 @@ +/* + * 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_C2_SOFT_AMR_DEC_H_ +#define ANDROID_C2_SOFT_AMR_DEC_H_ + +#include <SimpleC2Component.h> + + +namespace android { + +struct C2SoftAmrDec : public SimpleC2Component { + class IntfImpl; + + C2SoftAmrDec(const char *name, c2_node_id_t id, + const std::shared_ptr<IntfImpl> &intfImpl); + virtual ~C2SoftAmrDec(); + + // From SimpleC2Component + c2_status_t onInit() override; + c2_status_t onStop() override; + void onReset() override; + void onRelease() override; + c2_status_t onFlush_sm() override; + void process( + const std::unique_ptr<C2Work> &work, + const std::shared_ptr<C2BlockPool> &pool) override; + c2_status_t drain( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool) override; +private: + enum { + kNumSamplesPerFrameNB = 160, + kNumSamplesPerFrameWB = 320, + }; + + std::shared_ptr<IntfImpl> mIntf; + void *mAmrHandle; + void *mDecoderBuf; + int16_t *mDecoderCookie; + + int16_t mInputSampleBuffer[477]; + + bool mIsWide; + bool mSignalledError; + bool mSignalledOutputEos; + + status_t initDecoder(); + + C2_DO_NOT_COPY(C2SoftAmrDec); +}; + +} // namespace android + +#endif // ANDROID_C2_SOFT_AMR_DEC_H_
diff --git a/media/codec2/components/amr_nb_wb/C2SoftAmrNbEnc.cpp b/media/codec2/components/amr_nb_wb/C2SoftAmrNbEnc.cpp new file mode 100644 index 0000000..e2d8cb6 --- /dev/null +++ b/media/codec2/components/amr_nb_wb/C2SoftAmrNbEnc.cpp
@@ -0,0 +1,348 @@ +/* + * 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 "C2SoftAmrNbEnc" +#include <log/log.h> + +#include <media/stagefright/foundation/MediaDefs.h> + +#include <C2PlatformSupport.h> +#include <SimpleC2Interface.h> + +#include "C2SoftAmrNbEnc.h" + +namespace android { + +namespace { + +constexpr char COMPONENT_NAME[] = "c2.android.amrnb.encoder"; + +} // namespace + + +class C2SoftAmrNbEnc::IntfImpl : public SimpleInterface<void>::BaseParams { +public: + explicit IntfImpl(const std::shared_ptr<C2ReflectorHelper> &helper) + : SimpleInterface<void>::BaseParams( + helper, + COMPONENT_NAME, + C2Component::KIND_ENCODER, + C2Component::DOMAIN_AUDIO, + MEDIA_MIMETYPE_AUDIO_AMR_NB) { + noPrivateBuffers(); + noInputReferences(); + noOutputReferences(); + noInputLatency(); + noTimeStretch(); + setDerivedInstance(this); + + addParameter( + DefineParam(mAttrib, C2_PARAMKEY_COMPONENT_ATTRIBUTES) + .withConstValue(new C2ComponentAttributesSetting( + C2Component::ATTRIB_IS_TEMPORAL)) + .build()); + + addParameter( + DefineParam(mChannelCount, C2_PARAMKEY_CHANNEL_COUNT) + .withDefault(new C2StreamChannelCountInfo::input(0u, 1)) + .withFields({C2F(mChannelCount, value).equalTo(1)}) + .withSetter((Setter<decltype(*mChannelCount)>::StrictValueWithNoDeps)) + .build()); + + addParameter( + DefineParam(mSampleRate, C2_PARAMKEY_SAMPLE_RATE) + .withDefault(new C2StreamSampleRateInfo::input(0u, 8000)) + .withFields({C2F(mSampleRate, value).equalTo(8000)}) + .withSetter( + (Setter<decltype(*mSampleRate)>::StrictValueWithNoDeps)) + .build()); + + addParameter( + DefineParam(mBitrate, C2_PARAMKEY_BITRATE) + .withDefault(new C2StreamBitrateInfo::output(0u, 4750)) + .withFields({C2F(mBitrate, value).inRange(4750, 12200)}) + .withSetter(Setter<decltype(*mBitrate)>::NonStrictValueWithNoDeps) + .build()); + + addParameter( + DefineParam(mInputMaxBufSize, C2_PARAMKEY_INPUT_MAX_BUFFER_SIZE) + .withConstValue(new C2StreamMaxBufferSizeInfo::input(0u, 8192)) + .build()); + } + + uint32_t getSampleRate() const { return mSampleRate->value; } + uint32_t getChannelCount() const { return mChannelCount->value; } + uint32_t getBitrate() const { return mBitrate->value; } + +private: + std::shared_ptr<C2StreamSampleRateInfo::input> mSampleRate; + std::shared_ptr<C2StreamChannelCountInfo::input> mChannelCount; + std::shared_ptr<C2StreamBitrateInfo::output> mBitrate; + std::shared_ptr<C2StreamMaxBufferSizeInfo::input> mInputMaxBufSize; +}; + +C2SoftAmrNbEnc::C2SoftAmrNbEnc(const char* name, c2_node_id_t id, + const std::shared_ptr<IntfImpl>& intfImpl) + : SimpleC2Component( + std::make_shared<SimpleInterface<IntfImpl>>(name, id, intfImpl)), + mIntf(intfImpl), + mEncState(nullptr), + mSidState(nullptr) { +} + +C2SoftAmrNbEnc::~C2SoftAmrNbEnc() { + onRelease(); +} + +c2_status_t C2SoftAmrNbEnc::onInit() { + bool dtx_enable = false; + + if (AMREncodeInit(&mEncState, &mSidState, dtx_enable) != 0) + return C2_CORRUPTED; + // TODO: get mode directly from config + switch(mIntf->getBitrate()) { + case 4750: mMode = MR475; + break; + case 5150: mMode = MR515; + break; + case 5900: mMode = MR59; + break; + case 6700: mMode = MR67; + break; + case 7400: mMode = MR74; + break; + case 7950: mMode = MR795; + break; + case 10200: mMode = MR102; + break; + case 12200: mMode = MR122; + break; + default: mMode = MR795; + } + mIsFirst = true; + mSignalledError = false; + mSignalledOutputEos = false; + mAnchorTimeStamp = 0; + mProcessedSamples = 0; + mFilledLen = 0; + + return C2_OK; +} + +void C2SoftAmrNbEnc::onRelease() { + if (mEncState) { + AMREncodeExit(&mEncState, &mSidState); + mEncState = mSidState = nullptr; + } +} + +c2_status_t C2SoftAmrNbEnc::onStop() { + if (AMREncodeReset(mEncState, mSidState) != 0) + return C2_CORRUPTED; + mIsFirst = true; + mSignalledError = false; + mSignalledOutputEos = false; + mAnchorTimeStamp = 0; + mProcessedSamples = 0; + mFilledLen = 0; + + return C2_OK; +} + +void C2SoftAmrNbEnc::onReset() { + (void) onStop(); +} + +c2_status_t C2SoftAmrNbEnc::onFlush_sm() { + return onStop(); +} + +static void fillEmptyWork(const std::unique_ptr<C2Work> &work) { + 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; +} + +void C2SoftAmrNbEnc::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; + } + + bool eos = ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0); + size_t inOffset = 0u; + size_t inSize = 0u; + C2ReadView rView = mDummyReadView; + if (!work->input.buffers.empty()) { + rView = work->input.buffers[0]->data().linearBlocks().front().map().get(); + inSize = rView.capacity(); + if (inSize && rView.error()) { + ALOGE("read view map failed %d", rView.error()); + work->result = C2_CORRUPTED; + return; + } + } + + ALOGV("in buffer attr. size %zu timestamp %d frameindex %d, flags %x", + inSize, (int)work->input.ordinal.timestamp.peeku(), + (int)work->input.ordinal.frameIndex.peeku(), work->input.flags); + + size_t outCapacity = kNumBytesPerInputFrame; + outCapacity += mFilledLen + inSize; + std::shared_ptr<C2LinearBlock> outputBlock; + C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE }; + c2_status_t err = pool->fetchLinearBlock(outCapacity, usage, &outputBlock); + if (err != C2_OK) { + ALOGE("fetchLinearBlock for Output failed with status %d", err); + work->result = C2_NO_MEMORY; + return; + } + C2WriteView wView = outputBlock->map().get(); + if (wView.error()) { + ALOGE("write view map failed %d", wView.error()); + work->result = C2_CORRUPTED; + return; + } + uint64_t outTimeStamp = + mProcessedSamples * 1000000ll / mIntf->getSampleRate(); + size_t inPos = 0; + size_t outPos = 0; + while (inPos < inSize) { + const uint8_t *inPtr = rView.data() + inOffset; + int validSamples = mFilledLen / sizeof(int16_t); + if ((inPos + (kNumBytesPerInputFrame - mFilledLen)) <= inSize) { + memcpy(mInputFrame + validSamples, inPtr + inPos, + (kNumBytesPerInputFrame - mFilledLen)); + inPos += (kNumBytesPerInputFrame - mFilledLen); + } else { + memcpy(mInputFrame + validSamples, inPtr + inPos, (inSize - inPos)); + mFilledLen += (inSize - inPos); + inPos += (inSize - inPos); + if (eos) { + validSamples = mFilledLen / sizeof(int16_t); + memset(mInputFrame + validSamples, 0, (kNumBytesPerInputFrame - mFilledLen)); + } else break; + + } + Frame_Type_3GPP frameType; + int numEncBytes = AMREncode(mEncState, mSidState, mMode, mInputFrame, + wView.data() + outPos, &frameType, + AMR_TX_WMF); + if (numEncBytes < 0 || numEncBytes > ((int)outCapacity - (int)outPos)) { + ALOGE("encodeFrame call failed, state [%d %zu %zu]", numEncBytes, outPos, outCapacity); + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } + // Convert header byte from WMF to IETF format. + if (numEncBytes > 0) + wView.data()[outPos] = ((wView.data()[outPos] << 3) | 4) & 0x7c; + outPos += numEncBytes; + mProcessedSamples += kNumSamplesPerFrame; + mFilledLen = 0; + } + ALOGV("causal sample size %d", mFilledLen); + if (mIsFirst && outPos != 0) { + mIsFirst = false; + mAnchorTimeStamp = work->input.ordinal.timestamp.peekull(); + } + fillEmptyWork(work); + if (outPos != 0) { + work->worklets.front()->output.buffers.push_back( + createLinearBuffer(std::move(outputBlock), 0, outPos)); + work->worklets.front()->output.ordinal.timestamp = mAnchorTimeStamp + outTimeStamp; + + } + if (eos) { + mSignalledOutputEos = true; + ALOGV("signalled EOS"); + if (mFilledLen) ALOGV("Discarding trailing %d bytes", mFilledLen); + } +} + +c2_status_t C2SoftAmrNbEnc::drain( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool) { + (void) pool; + if (drainMode == NO_DRAIN) { + ALOGW("drain with NO_DRAIN: no-op"); + return C2_OK; + } + if (drainMode == DRAIN_CHAIN) { + ALOGW("DRAIN_CHAIN not supported"); + return C2_OMITTED; + } + + onFlush_sm(); + return C2_OK; +} + +class C2SoftAmrNbEncFactory : public C2ComponentFactory { +public: + C2SoftAmrNbEncFactory() + : mHelper(std::static_pointer_cast<C2ReflectorHelper>( + GetCodec2PlatformComponentStore()->getParamReflector())) {} + + virtual c2_status_t createComponent( + c2_node_id_t id, + std::shared_ptr<C2Component>* const component, + std::function<void(C2Component*)> deleter) override { + *component = std::shared_ptr<C2Component>( + new C2SoftAmrNbEnc( + COMPONENT_NAME, id, + std::make_shared<C2SoftAmrNbEnc::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual c2_status_t createInterface( + c2_node_id_t id, + std::shared_ptr<C2ComponentInterface>* const interface, + std::function<void(C2ComponentInterface*)> deleter) override { + *interface = std::shared_ptr<C2ComponentInterface>( + new SimpleInterface<C2SoftAmrNbEnc::IntfImpl>( + COMPONENT_NAME, id, + std::make_shared<C2SoftAmrNbEnc::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual ~C2SoftAmrNbEncFactory() override = default; + +private: + std::shared_ptr<C2ReflectorHelper> mHelper; +}; + +} // namespace android + +extern "C" ::C2ComponentFactory* CreateCodec2Factory() { + ALOGV("in %s", __func__); + return new ::android::C2SoftAmrNbEncFactory(); +} + +extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) { + ALOGV("in %s", __func__); + delete factory; +}
diff --git a/media/codec2/components/amr_nb_wb/C2SoftAmrNbEnc.h b/media/codec2/components/amr_nb_wb/C2SoftAmrNbEnc.h new file mode 100644 index 0000000..6ab14db --- /dev/null +++ b/media/codec2/components/amr_nb_wb/C2SoftAmrNbEnc.h
@@ -0,0 +1,67 @@ +/* + * 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_C2_SOFT_AMR_NB_ENC_H_ +#define ANDROID_C2_SOFT_AMR_NB_ENC_H_ + +#include <SimpleC2Component.h> + +#include "gsmamr_enc.h" + +namespace android { + +class C2SoftAmrNbEnc : public SimpleC2Component { + public: + class IntfImpl; + C2SoftAmrNbEnc(const char* name, c2_node_id_t id, + const std::shared_ptr<IntfImpl>& intfImpl); + virtual ~C2SoftAmrNbEnc(); + + // From SimpleC2Component + c2_status_t onInit() override; + c2_status_t onStop() override; + void onReset() override; + void onRelease() override; + c2_status_t onFlush_sm() override; + void process( + const std::unique_ptr<C2Work> &work, + const std::shared_ptr<C2BlockPool> &pool) override; + c2_status_t drain( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool) override; + +private: + std::shared_ptr<IntfImpl> mIntf; + static const int32_t kNumSamplesPerFrame = L_FRAME; + static const int32_t kNumBytesPerInputFrame = kNumSamplesPerFrame * sizeof(int16_t); + + void *mEncState; + void *mSidState; + Mode mMode; + bool mIsFirst; + bool mSignalledError; + bool mSignalledOutputEos; + uint64_t mAnchorTimeStamp; + uint64_t mProcessedSamples; + int32_t mFilledLen; + int16_t mInputFrame[kNumSamplesPerFrame]; + + C2_DO_NOT_COPY(C2SoftAmrNbEnc); +}; + +} // namespace android + +#endif // ANDROID_C2_SOFT_AMR_NB_ENC_H_
diff --git a/media/codec2/components/amr_nb_wb/C2SoftAmrWbEnc.cpp b/media/codec2/components/amr_nb_wb/C2SoftAmrWbEnc.cpp new file mode 100644 index 0000000..84ae4b7 --- /dev/null +++ b/media/codec2/components/amr_nb_wb/C2SoftAmrWbEnc.cpp
@@ -0,0 +1,422 @@ +/* + * 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 "C2SoftAmrWbEnc" +#include <log/log.h> + +#include <media/stagefright/foundation/MediaDefs.h> + +#include <C2Debug.h> +#include <C2PlatformSupport.h> +#include <SimpleC2Interface.h> + +#include "C2SoftAmrWbEnc.h" +#include "cmnMemory.h" + +namespace android { + +namespace { + +constexpr char COMPONENT_NAME[] = "c2.android.amrwb.encoder"; + +} // namespace + +class C2SoftAmrWbEnc::IntfImpl : public SimpleInterface<void>::BaseParams { +public: + explicit IntfImpl(const std::shared_ptr<C2ReflectorHelper> &helper) + : SimpleInterface<void>::BaseParams( + helper, + COMPONENT_NAME, + C2Component::KIND_ENCODER, + C2Component::DOMAIN_AUDIO, + MEDIA_MIMETYPE_AUDIO_AMR_WB) { + noPrivateBuffers(); + noInputReferences(); + noOutputReferences(); + noInputLatency(); + noTimeStretch(); + setDerivedInstance(this); + + addParameter( + DefineParam(mAttrib, C2_PARAMKEY_COMPONENT_ATTRIBUTES) + .withConstValue(new C2ComponentAttributesSetting( + C2Component::ATTRIB_IS_TEMPORAL)) + .build()); + + addParameter( + DefineParam(mChannelCount, C2_PARAMKEY_CHANNEL_COUNT) + .withDefault(new C2StreamChannelCountInfo::input(0u, 1)) + .withFields({C2F(mChannelCount, value).equalTo(1)}) + .withSetter((Setter<decltype(*mChannelCount)>::StrictValueWithNoDeps)) + .build()); + + addParameter( + DefineParam(mSampleRate, C2_PARAMKEY_SAMPLE_RATE) + .withDefault(new C2StreamSampleRateInfo::input(0u, 16000)) + .withFields({C2F(mSampleRate, value).equalTo(16000)}) + .withSetter( + (Setter<decltype(*mSampleRate)>::StrictValueWithNoDeps)) + .build()); + + addParameter( + DefineParam(mBitrate, C2_PARAMKEY_BITRATE) + .withDefault(new C2StreamBitrateInfo::output(0u, 6600)) + .withFields({C2F(mBitrate, value).inRange(6600, 23850)}) + .withSetter(Setter<decltype(*mBitrate)>::NonStrictValueWithNoDeps) + .build()); + + addParameter( + DefineParam(mInputMaxBufSize, C2_PARAMKEY_INPUT_MAX_BUFFER_SIZE) + .withConstValue(new C2StreamMaxBufferSizeInfo::input(0u, 8192)) + .build()); + } + + uint32_t getSampleRate() const { return mSampleRate->value; } + uint32_t getChannelCount() const { return mChannelCount->value; } + uint32_t getBitrate() const { return mBitrate->value; } + +private: + std::shared_ptr<C2StreamSampleRateInfo::input> mSampleRate; + std::shared_ptr<C2StreamChannelCountInfo::input> mChannelCount; + std::shared_ptr<C2StreamBitrateInfo::output> mBitrate; + std::shared_ptr<C2StreamMaxBufferSizeInfo::input> mInputMaxBufSize; +}; + +C2SoftAmrWbEnc::C2SoftAmrWbEnc(const char* name, c2_node_id_t id, + const std::shared_ptr<IntfImpl>& intfImpl) + : SimpleC2Component( + std::make_shared<SimpleInterface<IntfImpl>>(name, id, intfImpl)), + mIntf(intfImpl), + mEncoderHandle(nullptr), + mApiHandle(nullptr), + mMemOperator(nullptr) { +} + +C2SoftAmrWbEnc::~C2SoftAmrWbEnc() { + onRelease(); +} + +c2_status_t C2SoftAmrWbEnc::onInit() { + // TODO: get mode directly from config + switch(mIntf->getBitrate()) { + case 6600: mMode = VOAMRWB_MD66; + break; + case 8850: mMode = VOAMRWB_MD885; + break; + case 12650: mMode = VOAMRWB_MD1265; + break; + case 14250: mMode = VOAMRWB_MD1425; + break; + case 15850: mMode = VOAMRWB_MD1585; + break; + case 18250: mMode = VOAMRWB_MD1825; + break; + case 19850: mMode = VOAMRWB_MD1985; + break; + case 23050: mMode = VOAMRWB_MD2305; + break; + case 23850: mMode = VOAMRWB_MD2385; + break; + default: mMode = VOAMRWB_MD2305; + } + status_t err = initEncoder(); + mIsFirst = true; + mSignalledError = false; + mSignalledOutputEos = false; + mAnchorTimeStamp = 0; + mProcessedSamples = 0; + mFilledLen = 0; + + return err == OK ? C2_OK : C2_NO_MEMORY; +} + +void C2SoftAmrWbEnc::onRelease() { + if (mEncoderHandle) { + CHECK_EQ((VO_U32)VO_ERR_NONE, mApiHandle->Uninit(mEncoderHandle)); + mEncoderHandle = nullptr; + } + if (mApiHandle) { + delete mApiHandle; + mApiHandle = nullptr; + } + if (mMemOperator) { + delete mMemOperator; + mMemOperator = nullptr; + } +} + +c2_status_t C2SoftAmrWbEnc::onStop() { + for (int i = 0; i < kNumSamplesPerFrame; i++) { + mInputFrame[i] = 0x0008; /* EHF_MASK */ + } + uint8_t outBuffer[kNumBytesPerInputFrame]; + (void) encodeInput(outBuffer, kNumBytesPerInputFrame); + mIsFirst = true; + mSignalledError = false; + mSignalledOutputEos = false; + mAnchorTimeStamp = 0; + mProcessedSamples = 0; + mFilledLen = 0; + + return C2_OK; +} + +void C2SoftAmrWbEnc::onReset() { + (void) onStop(); +} + +c2_status_t C2SoftAmrWbEnc::onFlush_sm() { + return onStop(); +} + +status_t C2SoftAmrWbEnc::initEncoder() { + mApiHandle = new VO_AUDIO_CODECAPI; + if (!mApiHandle) return NO_MEMORY; + + if (VO_ERR_NONE != voGetAMRWBEncAPI(mApiHandle)) { + ALOGE("Failed to get api handle"); + return UNKNOWN_ERROR; + } + + mMemOperator = new VO_MEM_OPERATOR; + if (!mMemOperator) return NO_MEMORY; + + mMemOperator->Alloc = cmnMemAlloc; + mMemOperator->Copy = cmnMemCopy; + mMemOperator->Free = cmnMemFree; + mMemOperator->Set = cmnMemSet; + mMemOperator->Check = cmnMemCheck; + + VO_CODEC_INIT_USERDATA userData; + memset(&userData, 0, sizeof(userData)); + userData.memflag = VO_IMF_USERMEMOPERATOR; + userData.memData = (VO_PTR) mMemOperator; + + if (VO_ERR_NONE != mApiHandle->Init( + &mEncoderHandle, VO_AUDIO_CodingAMRWB, &userData)) { + ALOGE("Failed to init AMRWB encoder"); + return UNKNOWN_ERROR; + } + + VOAMRWBFRAMETYPE type = VOAMRWB_RFC3267; + if (VO_ERR_NONE != mApiHandle->SetParam( + mEncoderHandle, VO_PID_AMRWB_FRAMETYPE, &type)) { + ALOGE("Failed to set AMRWB encoder frame type to %d", type); + return UNKNOWN_ERROR; + } + + if (VO_ERR_NONE != + mApiHandle->SetParam( + mEncoderHandle, VO_PID_AMRWB_MODE, &mMode)) { + ALOGE("Failed to set AMRWB encoder mode to %d", mMode); + return UNKNOWN_ERROR; + } + + return OK; +} + +int C2SoftAmrWbEnc::encodeInput(uint8_t *buffer, uint32_t length) { + VO_CODECBUFFER inputData; + memset(&inputData, 0, sizeof(inputData)); + inputData.Buffer = (unsigned char *) mInputFrame; + inputData.Length = kNumBytesPerInputFrame; + + CHECK_EQ((VO_U32)VO_ERR_NONE, + mApiHandle->SetInputData(mEncoderHandle, &inputData)); + + VO_AUDIO_OUTPUTINFO outputInfo; + memset(&outputInfo, 0, sizeof(outputInfo)); + VO_CODECBUFFER outputData; + memset(&outputData, 0, sizeof(outputData)); + outputData.Buffer = buffer; + outputData.Length = length; + VO_U32 ret = mApiHandle->GetOutputData( + mEncoderHandle, &outputData, &outputInfo); + if (ret != VO_ERR_NONE && ret != VO_ERR_INPUT_BUFFER_SMALL) { + ALOGD("encountered error during encode call"); + return -1; + } + return outputData.Length; +} + +static void fillEmptyWork(const std::unique_ptr<C2Work> &work) { + 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; +} + +void C2SoftAmrWbEnc::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; + } + + size_t inOffset = 0u; + size_t inSize = 0u; + C2ReadView rView = mDummyReadView; + if (!work->input.buffers.empty()) { + rView = work->input.buffers[0]->data().linearBlocks().front().map().get(); + inSize = rView.capacity(); + if (inSize && rView.error()) { + ALOGE("read view map failed %d", rView.error()); + work->result = rView.error(); + return; + } + } + bool eos = (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0; + + ALOGV("in buffer attr. size %zu timestamp %d frameindex %d, flags %x", + inSize, (int)work->input.ordinal.timestamp.peeku(), + (int)work->input.ordinal.frameIndex.peeku(), work->input.flags); + + size_t outCapacity = kNumBytesPerInputFrame; + outCapacity += mFilledLen + inSize; + std::shared_ptr<C2LinearBlock> outputBlock; + C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE }; + c2_status_t err = pool->fetchLinearBlock(outCapacity, usage, &outputBlock); + if (err != C2_OK) { + ALOGE("fetchLinearBlock for Output failed with status %d", err); + work->result = C2_NO_MEMORY; + return; + } + C2WriteView wView = outputBlock->map().get(); + if (wView.error()) { + ALOGE("write view map failed %d", wView.error()); + work->result = wView.error(); + return; + } + uint64_t outTimeStamp = + mProcessedSamples * 1000000ll / mIntf->getSampleRate(); + size_t inPos = 0; + size_t outPos = 0; + while (inPos < inSize) { + const uint8_t *inPtr = rView.data() + inOffset; + int validSamples = mFilledLen / sizeof(int16_t); + if ((inPos + (kNumBytesPerInputFrame - mFilledLen)) <= inSize) { + memcpy(mInputFrame + validSamples, inPtr + inPos, + (kNumBytesPerInputFrame - mFilledLen)); + inPos += (kNumBytesPerInputFrame - mFilledLen); + } else { + memcpy(mInputFrame + validSamples, inPtr + inPos, (inSize - inPos)); + mFilledLen += (inSize - inPos); + inPos += (inSize - inPos); + if (eos) { + validSamples = mFilledLen / sizeof(int16_t); + memset(mInputFrame + validSamples, 0, (kNumBytesPerInputFrame - mFilledLen)); + } else break; + } + int numEncBytes = encodeInput((wView.data() + outPos), outCapacity - outPos); + if (numEncBytes < 0) { + ALOGE("encodeFrame call failed, state [%d %zu %zu]", numEncBytes, outPos, outCapacity); + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } + outPos += numEncBytes; + mProcessedSamples += kNumSamplesPerFrame; + mFilledLen = 0; + } + ALOGV("causal sample size %d", mFilledLen); + if (mIsFirst && outPos != 0) { + mIsFirst = false; + mAnchorTimeStamp = work->input.ordinal.timestamp.peekull(); + } + fillEmptyWork(work); + if (outPos != 0) { + work->worklets.front()->output.buffers.push_back( + createLinearBuffer(std::move(outputBlock), 0, outPos)); + work->worklets.front()->output.ordinal.timestamp = mAnchorTimeStamp + outTimeStamp; + } + if (eos) { + mSignalledOutputEos = true; + ALOGV("signalled EOS"); + if (mFilledLen) ALOGV("Discarding trailing %d bytes", mFilledLen); + } +} + +c2_status_t C2SoftAmrWbEnc::drain( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool) { + (void) pool; + if (drainMode == NO_DRAIN) { + ALOGW("drain with NO_DRAIN: no-op"); + return C2_OK; + } + if (drainMode == DRAIN_CHAIN) { + ALOGW("DRAIN_CHAIN not supported"); + return C2_OMITTED; + } + + onFlush_sm(); + return C2_OK; +} + +class C2SoftAmrWbEncFactory : public C2ComponentFactory { +public: + C2SoftAmrWbEncFactory() + : mHelper(std::static_pointer_cast<C2ReflectorHelper>( + GetCodec2PlatformComponentStore()->getParamReflector())) {} + + virtual c2_status_t createComponent( + c2_node_id_t id, + std::shared_ptr<C2Component>* const component, + std::function<void(C2Component*)> deleter) override { + *component = std::shared_ptr<C2Component>( + new C2SoftAmrWbEnc( + COMPONENT_NAME, id, + std::make_shared<C2SoftAmrWbEnc::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual c2_status_t createInterface( + c2_node_id_t id, + std::shared_ptr<C2ComponentInterface>* const interface, + std::function<void(C2ComponentInterface*)> deleter) override { + *interface = std::shared_ptr<C2ComponentInterface>( + new SimpleInterface<C2SoftAmrWbEnc::IntfImpl>( + COMPONENT_NAME, id, + std::make_shared<C2SoftAmrWbEnc::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual ~C2SoftAmrWbEncFactory() override = default; + +private: + std::shared_ptr<C2ReflectorHelper> mHelper; +}; + +} // namespace android + +extern "C" ::C2ComponentFactory* CreateCodec2Factory() { + ALOGV("in %s", __func__); + return new ::android::C2SoftAmrWbEncFactory(); +} + +extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) { + ALOGV("in %s", __func__); + delete factory; +}
diff --git a/media/codec2/components/amr_nb_wb/C2SoftAmrWbEnc.h b/media/codec2/components/amr_nb_wb/C2SoftAmrWbEnc.h new file mode 100644 index 0000000..0cc9e9f --- /dev/null +++ b/media/codec2/components/amr_nb_wb/C2SoftAmrWbEnc.h
@@ -0,0 +1,71 @@ +/* + * 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_C2_SOFT_AMR_WB_ENC_H_ +#define ANDROID_C2_SOFT_AMR_WB_ENC_H_ + +#include <SimpleC2Component.h> + +#include "voAMRWB.h" + +namespace android { + +class C2SoftAmrWbEnc : public SimpleC2Component { +public: + class IntfImpl; + C2SoftAmrWbEnc(const char* name, c2_node_id_t id, + const std::shared_ptr<IntfImpl>& intfImpl); + virtual ~C2SoftAmrWbEnc(); + + // From SimpleC2Component + c2_status_t onInit() override; + c2_status_t onStop() override; + void onReset() override; + void onRelease() override; + c2_status_t onFlush_sm() override; + void process( + const std::unique_ptr<C2Work> &work, + const std::shared_ptr<C2BlockPool> &pool) override; + c2_status_t drain( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool) override; + +private: + std::shared_ptr<IntfImpl> mIntf; + static const int32_t kNumSamplesPerFrame = 320; + static const int32_t kNumBytesPerInputFrame = kNumSamplesPerFrame * sizeof(int16_t); + + void *mEncoderHandle; + VO_AUDIO_CODECAPI *mApiHandle; + VO_MEM_OPERATOR *mMemOperator; + VOAMRWBMODE mMode; + bool mIsFirst; + bool mSignalledError; + bool mSignalledOutputEos; + uint64_t mAnchorTimeStamp; + uint64_t mProcessedSamples; + int32_t mFilledLen; + int16_t mInputFrame[kNumSamplesPerFrame]; + + status_t initEncoder(); + int encodeInput(uint8_t *buffer, uint32_t length); + + C2_DO_NOT_COPY(C2SoftAmrWbEnc); +}; + +} // namespace android + +#endif // ANDROID_C2_SOFT_AMR_WB_ENC_H_
diff --git a/media/common_time/MODULE_LICENSE_APACHE2 b/media/codec2/components/amr_nb_wb/MODULE_LICENSE_APACHE2 similarity index 100% copy from media/common_time/MODULE_LICENSE_APACHE2 copy to media/codec2/components/amr_nb_wb/MODULE_LICENSE_APACHE2
diff --git a/media/common_time/NOTICE b/media/codec2/components/amr_nb_wb/NOTICE similarity index 100% copy from media/common_time/NOTICE copy to media/codec2/components/amr_nb_wb/NOTICE
diff --git a/media/codec2/components/amr_nb_wb/patent_disclaimer.txt b/media/codec2/components/amr_nb_wb/patent_disclaimer.txt new file mode 100644 index 0000000..b4bf11d --- /dev/null +++ b/media/codec2/components/amr_nb_wb/patent_disclaimer.txt
@@ -0,0 +1,9 @@ + +THIS IS NOT A GRANT OF PATENT RIGHTS. + +Google makes no representation or warranty that the codecs for which +source code is made available hereunder are unencumbered by +third-party patents. Those intending to use this source code in +hardware or software products are advised that implementations of +these codecs, including in open source software or shareware, may +require patent licenses from the relevant patent holders.
diff --git a/media/codec2/components/aom/Android.bp b/media/codec2/components/aom/Android.bp new file mode 100644 index 0000000..0fabf5c --- /dev/null +++ b/media/codec2/components/aom/Android.bp
@@ -0,0 +1,14 @@ +cc_library_shared { + name: "libcodec2_soft_av1dec", + defaults: [ + "libcodec2_soft-defaults", + "libcodec2_soft_sanitize_all-defaults", + ], + + srcs: ["C2SoftAomDec.cpp"], + static_libs: ["libaom"], + + include_dirs: [ + "external/libaom/", + ], +}
diff --git a/media/codec2/components/aom/C2SoftAomDec.cpp b/media/codec2/components/aom/C2SoftAomDec.cpp new file mode 100644 index 0000000..769895c --- /dev/null +++ b/media/codec2/components/aom/C2SoftAomDec.cpp
@@ -0,0 +1,810 @@ +/* + * 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 "C2SoftAomDec" +#include <log/log.h> + +#include <media/stagefright/foundation/AUtils.h> +#include <media/stagefright/foundation/MediaDefs.h> + +#include <C2Debug.h> +#include <C2PlatformSupport.h> +#include <SimpleC2Interface.h> + +#include "C2SoftAomDec.h" + +namespace android { + +constexpr char COMPONENT_NAME[] = "c2.android.av1.decoder"; + +class C2SoftAomDec::IntfImpl : public SimpleInterface<void>::BaseParams { + public: + explicit IntfImpl(const std::shared_ptr<C2ReflectorHelper>& helper) + : SimpleInterface<void>::BaseParams( + helper, COMPONENT_NAME, C2Component::KIND_DECODER, + C2Component::DOMAIN_VIDEO, MEDIA_MIMETYPE_VIDEO_AV1) { + noPrivateBuffers(); // TODO: account for our buffers here + noInputReferences(); + noOutputReferences(); + noInputLatency(); + noTimeStretch(); + + addParameter(DefineParam(mAttrib, C2_PARAMKEY_COMPONENT_ATTRIBUTES) + .withConstValue(new C2ComponentAttributesSetting( + C2Component::ATTRIB_IS_TEMPORAL)) + .build()); + + addParameter( + DefineParam(mSize, C2_PARAMKEY_PICTURE_SIZE) + .withDefault(new C2StreamPictureSizeInfo::output(0u, 320, 240)) + .withFields({ + C2F(mSize, width).inRange(2, 2048, 2), + C2F(mSize, height).inRange(2, 2048, 2), + }) + .withSetter(SizeSetter) + .build()); + + addParameter( + DefineParam(mProfileLevel, C2_PARAMKEY_PROFILE_LEVEL) + .withDefault(new C2StreamProfileLevelInfo::input(0u, + C2Config::PROFILE_AV1_0, C2Config::LEVEL_AV1_2_1)) + .withFields({ + C2F(mProfileLevel, profile).oneOf({ + C2Config::PROFILE_AV1_0, + C2Config::PROFILE_AV1_1}), + C2F(mProfileLevel, level).oneOf({ + C2Config::LEVEL_AV1_2, + C2Config::LEVEL_AV1_2_1, + C2Config::LEVEL_AV1_2_2, + C2Config::LEVEL_AV1_3, + C2Config::LEVEL_AV1_3_1, + C2Config::LEVEL_AV1_3_2, + }) + }) + .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()); + + addParameter(DefineParam(mMaxSize, C2_PARAMKEY_MAX_PICTURE_SIZE) + .withDefault(new C2StreamMaxPictureSizeTuning::output( + 0u, 320, 240)) + .withFields({ + C2F(mSize, width).inRange(2, 2048, 2), + C2F(mSize, height).inRange(2, 2048, 2), + }) + .withSetter(MaxPictureSizeSetter, mSize) + .build()); + + addParameter( + DefineParam(mMaxInputSize, C2_PARAMKEY_INPUT_MAX_BUFFER_SIZE) + .withDefault( + new C2StreamMaxBufferSizeInfo::input(0u, 320 * 240 * 3 / 4)) + .withFields({ + C2F(mMaxInputSize, value).any(), + }) + .calculatedAs(MaxInputSizeSetter, mMaxSize) + .build()); + + C2ChromaOffsetStruct locations[1] = { + C2ChromaOffsetStruct::ITU_YUV_420_0()}; + std::shared_ptr<C2StreamColorInfo::output> defaultColorInfo = + C2StreamColorInfo::output::AllocShared(1u, 0u, 8u /* bitDepth */, + C2Color::YUV_420); + memcpy(defaultColorInfo->m.locations, locations, sizeof(locations)); + + defaultColorInfo = C2StreamColorInfo::output::AllocShared( + {C2ChromaOffsetStruct::ITU_YUV_420_0()}, 0u, 8u /* bitDepth */, + C2Color::YUV_420); + helper->addStructDescriptors<C2ChromaOffsetStruct>(); + + addParameter(DefineParam(mColorInfo, C2_PARAMKEY_CODED_COLOR_INFO) + .withConstValue(defaultColorInfo) + .build()); + + addParameter( + DefineParam(mDefaultColorAspects, C2_PARAMKEY_DEFAULT_COLOR_ASPECTS) + .withDefault(new C2StreamColorAspectsTuning::output( + 0u, C2Color::RANGE_UNSPECIFIED, C2Color::PRIMARIES_UNSPECIFIED, + C2Color::TRANSFER_UNSPECIFIED, C2Color::MATRIX_UNSPECIFIED)) + .withFields({ + C2F(mDefaultColorAspects, range).inRange( + C2Color::RANGE_UNSPECIFIED, C2Color::RANGE_OTHER), + C2F(mDefaultColorAspects, primaries).inRange( + C2Color::PRIMARIES_UNSPECIFIED, C2Color::PRIMARIES_OTHER), + C2F(mDefaultColorAspects, transfer).inRange( + C2Color::TRANSFER_UNSPECIFIED, C2Color::TRANSFER_OTHER), + C2F(mDefaultColorAspects, matrix).inRange( + C2Color::MATRIX_UNSPECIFIED, C2Color::MATRIX_OTHER) + }) + .withSetter(DefaultColorAspectsSetter) + .build()); + + // TODO: support more formats? + addParameter(DefineParam(mPixelFormat, C2_PARAMKEY_PIXEL_FORMAT) + .withConstValue(new C2StreamPixelFormatInfo::output( + 0u, HAL_PIXEL_FORMAT_YCBCR_420_888)) + .build()); + } + + static C2R SizeSetter(bool mayBlock, + const C2P<C2StreamPictureSizeInfo::output>& oldMe, + C2P<C2StreamPictureSizeInfo::output>& me) { + (void)mayBlock; + C2R res = C2R::Ok(); + if (!me.F(me.v.width).supportsAtAll(me.v.width)) { + res = res.plus(C2SettingResultBuilder::BadValue(me.F(me.v.width))); + me.set().width = oldMe.v.width; + } + if (!me.F(me.v.height).supportsAtAll(me.v.height)) { + res = res.plus(C2SettingResultBuilder::BadValue(me.F(me.v.height))); + me.set().height = oldMe.v.height; + } + return res; + } + + static C2R MaxPictureSizeSetter( + bool mayBlock, C2P<C2StreamMaxPictureSizeTuning::output>& me, + const C2P<C2StreamPictureSizeInfo::output>& size) { + (void)mayBlock; + // TODO: get max width/height from the size's field helpers vs. + // hardcoding + me.set().width = c2_min(c2_max(me.v.width, size.v.width), 4096u); + me.set().height = c2_min(c2_max(me.v.height, size.v.height), 4096u); + return C2R::Ok(); + } + + static C2R MaxInputSizeSetter( + bool mayBlock, C2P<C2StreamMaxBufferSizeInfo::input>& me, + const C2P<C2StreamMaxPictureSizeTuning::output>& maxSize) { + (void)mayBlock; + // assume compression ratio of 2 + me.set().value = (((maxSize.v.width + 63) / 64) * + ((maxSize.v.height + 63) / 64) * 3072); + return C2R::Ok(); + } + static C2R DefaultColorAspectsSetter(bool mayBlock, C2P<C2StreamColorAspectsTuning::output> &me) { + (void)mayBlock; + if (me.v.range > C2Color::RANGE_OTHER) { + me.set().range = C2Color::RANGE_OTHER; + } + if (me.v.primaries > C2Color::PRIMARIES_OTHER) { + me.set().primaries = C2Color::PRIMARIES_OTHER; + } + if (me.v.transfer > C2Color::TRANSFER_OTHER) { + me.set().transfer = C2Color::TRANSFER_OTHER; + } + if (me.v.matrix > C2Color::MATRIX_OTHER) { + me.set().matrix = C2Color::MATRIX_OTHER; + } + return C2R::Ok(); + } + + static C2R ProfileLevelSetter(bool mayBlock, C2P<C2StreamProfileLevelInfo::input> &me, + const C2P<C2StreamPictureSizeInfo::output> &size) { + (void)mayBlock; + (void)size; + (void)me; // TODO: validate + return C2R::Ok(); + } + std::shared_ptr<C2StreamColorAspectsTuning::output> getDefaultColorAspects_l() { + return mDefaultColorAspects; + } + + 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; + std::shared_ptr<C2StreamMaxPictureSizeTuning::output> mMaxSize; + std::shared_ptr<C2StreamMaxBufferSizeInfo::input> mMaxInputSize; + std::shared_ptr<C2StreamColorInfo::output> mColorInfo; + std::shared_ptr<C2StreamPixelFormatInfo::output> mPixelFormat; + std::shared_ptr<C2StreamColorAspectsTuning::output> mDefaultColorAspects; + std::shared_ptr<C2StreamHdr10PlusInfo::input> mHdr10PlusInfoInput; + std::shared_ptr<C2StreamHdr10PlusInfo::output> mHdr10PlusInfoOutput; +}; + +C2SoftAomDec::C2SoftAomDec(const char* name, c2_node_id_t id, + const std::shared_ptr<IntfImpl>& intfImpl) + : SimpleC2Component( + std::make_shared<SimpleInterface<IntfImpl>>(name, id, intfImpl)), + mIntf(intfImpl), + mCodecCtx(nullptr){ + + GENERATE_FILE_NAMES(); + CREATE_DUMP_FILE(mInFile); + CREATE_DUMP_FILE(mOutFile); + + gettimeofday(&mTimeStart, nullptr); + gettimeofday(&mTimeEnd, nullptr); +} + +C2SoftAomDec::~C2SoftAomDec() { + onRelease(); +} + +c2_status_t C2SoftAomDec::onInit() { + status_t err = initDecoder(); + return err == OK ? C2_OK : C2_CORRUPTED; +} + +c2_status_t C2SoftAomDec::onStop() { + mSignalledError = false; + mSignalledOutputEos = false; + return C2_OK; +} + +void C2SoftAomDec::onReset() { + (void)onStop(); + c2_status_t err = onFlush_sm(); + if (err != C2_OK) { + ALOGW("Failed to flush decoder. Try to hard reset decoder."); + destroyDecoder(); + (void)initDecoder(); + } +} + +void C2SoftAomDec::onRelease() { + destroyDecoder(); +} + +c2_status_t C2SoftAomDec::onFlush_sm() { + if (aom_codec_decode(mCodecCtx, nullptr, 0, nullptr)) { + ALOGE("Failed to flush av1 decoder."); + return C2_CORRUPTED; + } + + aom_codec_iter_t iter = nullptr; + while (aom_codec_get_frame(mCodecCtx, &iter)) { + } + + mSignalledError = false; + mSignalledOutputEos = false; + + return C2_OK; +} + +static int GetCPUCoreCount() { + int cpuCoreCount = 1; +#if defined(_SC_NPROCESSORS_ONLN) + cpuCoreCount = sysconf(_SC_NPROCESSORS_ONLN); +#else + // _SC_NPROC_ONLN must be defined... + cpuCoreCount = sysconf(_SC_NPROC_ONLN); +#endif + CHECK(cpuCoreCount >= 1); + ALOGV("Number of CPU cores: %d", cpuCoreCount); + return cpuCoreCount; +} + +status_t C2SoftAomDec::initDecoder() { + mSignalledError = false; + mSignalledOutputEos = false; + if (!mCodecCtx) { + mCodecCtx = new aom_codec_ctx_t; + } + + if (!mCodecCtx) { + ALOGE("mCodecCtx is null"); + return NO_MEMORY; + } + + aom_codec_dec_cfg_t cfg; + memset(&cfg, 0, sizeof(aom_codec_dec_cfg_t)); + cfg.threads = GetCPUCoreCount(); + cfg.allow_lowbitdepth = 1; + + aom_codec_flags_t flags; + memset(&flags, 0, sizeof(aom_codec_flags_t)); + + aom_codec_err_t err; + if ((err = aom_codec_dec_init(mCodecCtx, aom_codec_av1_dx(), &cfg, 0))) { + ALOGE("av1 decoder failed to initialize. (%d)", err); + return UNKNOWN_ERROR; + } + + return OK; +} + +status_t C2SoftAomDec::destroyDecoder() { + if (mCodecCtx) { + aom_codec_destroy(mCodecCtx); + delete mCodecCtx; + mCodecCtx = nullptr; + } + return OK; +} + +void fillEmptyWork(const std::unique_ptr<C2Work>& work) { + uint32_t flags = 0; + if (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) { + flags |= C2FrameData::FLAG_END_OF_STREAM; + ALOGV("signalling eos"); + } + work->worklets.front()->output.flags = (C2FrameData::flags_t)flags; + work->worklets.front()->output.buffers.clear(); + work->worklets.front()->output.ordinal = work->input.ordinal; + work->workletsProcessed = 1u; +} + +void C2SoftAomDec::finishWork(uint64_t index, + const std::unique_ptr<C2Work>& work, + const std::shared_ptr<C2GraphicBlock>& block) { + std::shared_ptr<C2Buffer> buffer = + createGraphicBuffer(block, C2Rect(mWidth, mHeight)); + 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)) { + flags |= C2FrameData::FLAG_END_OF_STREAM; + ALOGV("signalling eos"); + } + work->worklets.front()->output.flags = (C2FrameData::flags_t)flags; + work->worklets.front()->output.buffers.clear(); + 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); + } else { + finish(index, fillWork); + } +} + +void C2SoftAomDec::process(const std::unique_ptr<C2Work>& work, + const std::shared_ptr<C2BlockPool>& pool) { + 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; + } + + size_t inOffset = 0u; + size_t inSize = 0u; + C2ReadView rView = mDummyReadView; + if (!work->input.buffers.empty()) { + rView = + work->input.buffers[0]->data().linearBlocks().front().map().get(); + inSize = rView.capacity(); + if (inSize && rView.error()) { + ALOGE("read view map failed %d", rView.error()); + work->result = C2_CORRUPTED; + return; + } + } + + bool codecConfig = + ((work->input.flags & C2FrameData::FLAG_CODEC_CONFIG) != 0); + bool eos = ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0); + + ALOGV("in buffer attr. size %zu timestamp %d frameindex %d, flags %x", + inSize, (int)work->input.ordinal.timestamp.peeku(), + (int)work->input.ordinal.frameIndex.peeku(), work->input.flags); + + if (codecConfig) { + fillEmptyWork(work); + return; + } + + int64_t frameIndex = work->input.ordinal.frameIndex.peekll(); + if (inSize) { + uint8_t* bitstream = const_cast<uint8_t*>(rView.data() + inOffset); + int32_t decodeTime = 0; + int32_t delay = 0; + + DUMP_TO_FILE(mOutFile, bitstream, inSize); + GETTIME(&mTimeStart, nullptr); + TIME_DIFF(mTimeEnd, mTimeStart, delay); + + aom_codec_err_t err = + aom_codec_decode(mCodecCtx, bitstream, inSize, &frameIndex); + + GETTIME(&mTimeEnd, nullptr); + TIME_DIFF(mTimeStart, mTimeEnd, decodeTime); + ALOGV("decodeTime=%4d delay=%4d\n", decodeTime, delay); + + if (err != AOM_CODEC_OK) { + ALOGE("av1 decoder failed to decode frame err: %d", err); + work->result = C2_CORRUPTED; + work->workletsProcessed = 1u; + mSignalledError = true; + return; + } + + } else { + if (aom_codec_decode(mCodecCtx, nullptr, 0, nullptr)) { + ALOGE("Failed to flush av1 decoder."); + work->result = C2_CORRUPTED; + work->workletsProcessed = 1u; + mSignalledError = true; + return; + } + } + + (void)outputBuffer(pool, work); + + if (eos) { + drainInternal(DRAIN_COMPONENT_WITH_EOS, pool, work); + mSignalledOutputEos = true; + } else if (!inSize) { + fillEmptyWork(work); + } +} + +static void copyOutputBufferToYuvPlanarFrame( + uint8_t *dst, const uint8_t *srcY, const uint8_t *srcU, const uint8_t *srcV, + size_t srcYStride, size_t srcUStride, size_t srcVStride, + size_t dstYStride, size_t dstUVStride, + uint32_t width, uint32_t height) { + uint8_t* dstStart = dst; + + for (size_t i = 0; i < height; ++i) { + memcpy(dst, srcY, width); + srcY += srcYStride; + dst += dstYStride; + } + + dst = dstStart + dstYStride * height; + for (size_t i = 0; i < height / 2; ++i) { + memcpy(dst, srcV, width / 2); + srcV += srcVStride; + dst += dstUVStride; + } + + dst = dstStart + (dstYStride * height) + (dstUVStride * height / 2); + for (size_t i = 0; i < height / 2; ++i) { + memcpy(dst, srcU, width / 2); + srcU += srcUStride; + dst += dstUVStride; + } +} + +static void convertYUV420Planar16ToY410(uint32_t *dst, + const uint16_t *srcY, const uint16_t *srcU, const uint16_t *srcV, + size_t srcYStride, size_t srcUStride, size_t srcVStride, + size_t dstStride, size_t width, size_t height) { + + // Converting two lines at a time, slightly faster + for (size_t y = 0; y < height; y += 2) { + uint32_t *dstTop = (uint32_t *) dst; + uint32_t *dstBot = (uint32_t *) (dst + dstStride); + uint16_t *ySrcTop = (uint16_t*) srcY; + uint16_t *ySrcBot = (uint16_t*) (srcY + srcYStride); + uint16_t *uSrc = (uint16_t*) srcU; + uint16_t *vSrc = (uint16_t*) srcV; + + uint32_t u01, v01, y01, y23, y45, y67, uv0, uv1; + size_t x = 0; + for (; x < width - 3; x += 4) { + + u01 = *((uint32_t*)uSrc); uSrc += 2; + v01 = *((uint32_t*)vSrc); vSrc += 2; + + y01 = *((uint32_t*)ySrcTop); ySrcTop += 2; + y23 = *((uint32_t*)ySrcTop); ySrcTop += 2; + y45 = *((uint32_t*)ySrcBot); ySrcBot += 2; + y67 = *((uint32_t*)ySrcBot); ySrcBot += 2; + + uv0 = (u01 & 0x3FF) | ((v01 & 0x3FF) << 20); + uv1 = (u01 >> 16) | ((v01 >> 16) << 20); + + *dstTop++ = 3 << 30 | ((y01 & 0x3FF) << 10) | uv0; + *dstTop++ = 3 << 30 | ((y01 >> 16) << 10) | uv0; + *dstTop++ = 3 << 30 | ((y23 & 0x3FF) << 10) | uv1; + *dstTop++ = 3 << 30 | ((y23 >> 16) << 10) | uv1; + + *dstBot++ = 3 << 30 | ((y45 & 0x3FF) << 10) | uv0; + *dstBot++ = 3 << 30 | ((y45 >> 16) << 10) | uv0; + *dstBot++ = 3 << 30 | ((y67 & 0x3FF) << 10) | uv1; + *dstBot++ = 3 << 30 | ((y67 >> 16) << 10) | uv1; + } + + // There should be at most 2 more pixels to process. Note that we don't + // need to consider odd case as the buffer is always aligned to even. + if (x < width) { + u01 = *uSrc; + v01 = *vSrc; + y01 = *((uint32_t*)ySrcTop); + y45 = *((uint32_t*)ySrcBot); + uv0 = (u01 & 0x3FF) | ((v01 & 0x3FF) << 20); + *dstTop++ = ((y01 & 0x3FF) << 10) | uv0; + *dstTop++ = ((y01 >> 16) << 10) | uv0; + *dstBot++ = ((y45 & 0x3FF) << 10) | uv0; + *dstBot++ = ((y45 >> 16) << 10) | uv0; + } + + srcY += srcYStride * 2; + srcU += srcUStride; + srcV += srcVStride; + dst += dstStride * 2; + } + + return; +} + +static void convertYUV420Planar16ToYUV420Planar(uint8_t *dst, + const uint16_t *srcY, const uint16_t *srcU, const uint16_t *srcV, + size_t srcYStride, size_t srcUStride, size_t srcVStride, + size_t dstYStride, size_t dstUVStride, size_t width, size_t height) { + + uint8_t *dstY = (uint8_t *)dst; + size_t dstYSize = dstYStride * height; + size_t dstUVSize = dstUVStride * height / 2; + uint8_t *dstV = dstY + dstYSize; + uint8_t *dstU = dstV + dstUVSize; + + for (size_t y = 0; y < height; ++y) { + for (size_t x = 0; x < width; ++x) { + dstY[x] = (uint8_t)(srcY[x] >> 2); + } + + srcY += srcYStride; + dstY += dstYStride; + } + + for (size_t y = 0; y < (height + 1) / 2; ++y) { + for (size_t x = 0; x < (width + 1) / 2; ++x) { + dstU[x] = (uint8_t)(srcU[x] >> 2); + dstV[x] = (uint8_t)(srcV[x] >> 2); + } + + srcU += srcUStride; + srcV += srcVStride; + dstU += dstUVStride; + dstV += dstUVStride; + } + return; +} +bool C2SoftAomDec::outputBuffer( + const std::shared_ptr<C2BlockPool> &pool, + const std::unique_ptr<C2Work> &work) +{ + if (!(work && pool)) return false; + + aom_codec_iter_t iter = nullptr; + aom_image_t* img = aom_codec_get_frame(mCodecCtx, &iter); + + if (!img) return false; + + if (img->d_w != mWidth || img->d_h != mHeight) { + mWidth = img->d_w; + mHeight = img->d_h; + + C2StreamPictureSizeInfo::output size(0u, mWidth, mHeight); + std::vector<std::unique_ptr<C2SettingResult>> failures; + c2_status_t err = mIntf->config({&size}, C2_MAY_BLOCK, &failures); + if (err == C2_OK) { + work->worklets.front()->output.configUpdate.push_back( + C2Param::Copy(size)); + } else { + ALOGE("Config update size failed"); + mSignalledError = true; + work->result = C2_CORRUPTED; + work->workletsProcessed = 1u; + return false; + } + } + + CHECK(img->fmt == AOM_IMG_FMT_I420 || img->fmt == AOM_IMG_FMT_I42016); + + std::shared_ptr<C2GraphicBlock> block; + uint32_t format = HAL_PIXEL_FORMAT_YV12; + if (img->fmt == AOM_IMG_FMT_I42016) { + IntfImpl::Lock lock = mIntf->lock(); + std::shared_ptr<C2StreamColorAspectsTuning::output> defaultColorAspects = mIntf->getDefaultColorAspects_l(); + + if (defaultColorAspects->primaries == C2Color::PRIMARIES_BT2020 && + defaultColorAspects->matrix == C2Color::MATRIX_BT2020 && + defaultColorAspects->transfer == C2Color::TRANSFER_ST2084) { + format = HAL_PIXEL_FORMAT_RGBA_1010102; + } + } + C2MemoryUsage usage = {C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE}; + + c2_status_t err = pool->fetchGraphicBlock(align(mWidth, 16), mHeight, + format, usage, &block); + + if (err != C2_OK) { + ALOGE("fetchGraphicBlock for Output failed with status %d", err); + work->result = err; + return false; + } + + C2GraphicView wView = block->map().get(); + + if (wView.error()) { + ALOGE("graphic view map failed %d", wView.error()); + work->result = C2_CORRUPTED; + return false; + } + + ALOGV("provided (%dx%d) required (%dx%d), out frameindex %d", + block->width(), block->height(), mWidth, mHeight, + (int)*(int64_t*)img->user_priv); + + uint8_t* dst = const_cast<uint8_t*>(wView.data()[C2PlanarLayout::PLANE_Y]); + size_t srcYStride = img->stride[AOM_PLANE_Y]; + size_t srcUStride = img->stride[AOM_PLANE_U]; + size_t srcVStride = img->stride[AOM_PLANE_V]; + C2PlanarLayout layout = wView.layout(); + size_t dstYStride = layout.planes[C2PlanarLayout::PLANE_Y].rowInc; + size_t dstUVStride = layout.planes[C2PlanarLayout::PLANE_U].rowInc; + + if (img->fmt == AOM_IMG_FMT_I42016) { + const uint16_t *srcY = (const uint16_t *)img->planes[AOM_PLANE_Y]; + const uint16_t *srcU = (const uint16_t *)img->planes[AOM_PLANE_U]; + const uint16_t *srcV = (const uint16_t *)img->planes[AOM_PLANE_V]; + + if (format == HAL_PIXEL_FORMAT_RGBA_1010102) { + convertYUV420Planar16ToY410((uint32_t *)dst, srcY, srcU, srcV, srcYStride / 2, + srcUStride / 2, srcVStride / 2, + dstYStride / sizeof(uint32_t), + mWidth, mHeight); + } else { + convertYUV420Planar16ToYUV420Planar(dst, srcY, srcU, srcV, srcYStride / 2, + srcUStride / 2, srcVStride / 2, + dstYStride, dstUVStride, + mWidth, mHeight); + } + } else { + const uint8_t *srcY = (const uint8_t *)img->planes[AOM_PLANE_Y]; + const uint8_t *srcU = (const uint8_t *)img->planes[AOM_PLANE_U]; + const uint8_t *srcV = (const uint8_t *)img->planes[AOM_PLANE_V]; + copyOutputBufferToYuvPlanarFrame( + dst, srcY, srcU, srcV, + srcYStride, srcUStride, srcVStride, + dstYStride, dstUVStride, + mWidth, mHeight); + } + finishWork(*(int64_t*)img->user_priv, work, std::move(block)); + block = nullptr; + return true; +} + +c2_status_t C2SoftAomDec::drainInternal( + uint32_t drainMode, const std::shared_ptr<C2BlockPool>& pool, + const std::unique_ptr<C2Work>& work) { + if (drainMode == NO_DRAIN) { + ALOGW("drain with NO_DRAIN: no-op"); + return C2_OK; + } + if (drainMode == DRAIN_CHAIN) { + ALOGW("DRAIN_CHAIN not supported"); + return C2_OMITTED; + } + + if (aom_codec_decode(mCodecCtx, nullptr, 0, nullptr)) { + ALOGE("Failed to flush av1 decoder."); + return C2_CORRUPTED; + } + + while ((outputBuffer(pool, work))) { + } + + if (drainMode == DRAIN_COMPONENT_WITH_EOS && work && + work->workletsProcessed == 0u) { + fillEmptyWork(work); + } + + return C2_OK; +} + +c2_status_t C2SoftAomDec::drain(uint32_t drainMode, + const std::shared_ptr<C2BlockPool>& pool) { + return drainInternal(drainMode, pool, nullptr); +} + +class C2SoftAomFactory : public C2ComponentFactory { + public: + C2SoftAomFactory() + : mHelper(std::static_pointer_cast<C2ReflectorHelper>( + GetCodec2PlatformComponentStore()->getParamReflector())) {} + + virtual c2_status_t createComponent( + c2_node_id_t id, std::shared_ptr<C2Component>* const component, + std::function<void(C2Component*)> deleter) override { + *component = std::shared_ptr<C2Component>( + new C2SoftAomDec(COMPONENT_NAME, id, + std::make_shared<C2SoftAomDec::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual c2_status_t createInterface( + c2_node_id_t id, std::shared_ptr<C2ComponentInterface>* const interface, + std::function<void(C2ComponentInterface*)> deleter) override { + *interface = std::shared_ptr<C2ComponentInterface>( + new SimpleInterface<C2SoftAomDec::IntfImpl>( + COMPONENT_NAME, id, + std::make_shared<C2SoftAomDec::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual ~C2SoftAomFactory() override = default; + + private: + std::shared_ptr<C2ReflectorHelper> mHelper; +}; + +} // namespace android + +extern "C" ::C2ComponentFactory* CreateCodec2Factory() { + ALOGV("in %s", __func__); + return new ::android::C2SoftAomFactory(); +} + +extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) { + ALOGV("in %s", __func__); + delete factory; +}
diff --git a/media/codec2/components/aom/C2SoftAomDec.h b/media/codec2/components/aom/C2SoftAomDec.h new file mode 100644 index 0000000..4c82647 --- /dev/null +++ b/media/codec2/components/aom/C2SoftAomDec.h
@@ -0,0 +1,137 @@ +/* + * 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_C2_SOFT_AV1_DEC_H_ +#define ANDROID_C2_SOFT_AV1_DEC_H_ + +#include <SimpleC2Component.h> +#include "aom/aom_decoder.h" +#include "aom/aomdx.h" + +#define GETTIME(a, b) gettimeofday(a, b); +#define TIME_DIFF(start, end, diff) \ + diff = (((end).tv_sec - (start).tv_sec) * 1000000) + \ + ((end).tv_usec - (start).tv_usec); + +namespace android { + +struct C2SoftAomDec : public SimpleC2Component { + class IntfImpl; + + C2SoftAomDec(const char* name, c2_node_id_t id, + const std::shared_ptr<IntfImpl>& intfImpl); + virtual ~C2SoftAomDec(); + + // From SimpleC2Component + c2_status_t onInit() override; + c2_status_t onStop() override; + void onReset() override; + void onRelease() override; + c2_status_t onFlush_sm() override; + void process(const std::unique_ptr<C2Work>& work, + const std::shared_ptr<C2BlockPool>& pool) override; + c2_status_t drain(uint32_t drainMode, + const std::shared_ptr<C2BlockPool>& pool) override; + + private: + std::shared_ptr<IntfImpl> mIntf; + aom_codec_ctx_t* mCodecCtx; + + uint32_t mWidth; + uint32_t mHeight; + bool mSignalledOutputEos; + bool mSignalledError; + + #ifdef FILE_DUMP_ENABLE + char mInFile[200]; + char mOutFile[200]; + #endif /* FILE_DUMP_ENABLE */ + + struct timeval mTimeStart; // Time at the start of decode() + struct timeval mTimeEnd; // Time at the end of decode() + + status_t initDecoder(); + status_t destroyDecoder(); + void finishWork(uint64_t index, const std::unique_ptr<C2Work>& work, + const std::shared_ptr<C2GraphicBlock>& block); + bool outputBuffer(const std::shared_ptr<C2BlockPool>& pool, + const std::unique_ptr<C2Work>& work); + + c2_status_t drainInternal(uint32_t drainMode, + const std::shared_ptr<C2BlockPool>& pool, + const std::unique_ptr<C2Work>& work); + + C2_DO_NOT_COPY(C2SoftAomDec); +}; + +#ifdef FILE_DUMP_ENABLE + +#define INPUT_DUMP_PATH "/data/local/tmp/temp/av1" +#define INPUT_DUMP_EXT "webm" +#define OUTPUT_DUMP_PATH "/data/local/tmp/temp/av1" +#define OUTPUT_DUMP_EXT "av1" +#define GENERATE_FILE_NAMES() \ + { \ + GETTIME(&mTimeStart, NULL); \ + strcpy(mInFile, ""); \ + ALOGD("GENERATE_FILE_NAMES"); \ + sprintf(mInFile, "%s_%ld.%ld.%s", INPUT_DUMP_PATH, mTimeStart.tv_sec, \ + mTimeStart.tv_usec, INPUT_DUMP_EXT); \ + strcpy(mOutFile, ""); \ + sprintf(mOutFile, "%s_%ld.%ld.%s", OUTPUT_DUMP_PATH, \ + mTimeStart.tv_sec, mTimeStart.tv_usec, OUTPUT_DUMP_EXT); \ + } + +#define CREATE_DUMP_FILE(m_filename) \ + { \ + FILE* fp = fopen(m_filename, "wb"); \ + if (fp != NULL) { \ + ALOGD("Opened file %s", m_filename); \ + fclose(fp); \ + } else { \ + ALOGD("Could not open file %s", m_filename); \ + } \ + } +#define DUMP_TO_FILE(m_filename, m_buf, m_size) \ + { \ + FILE* fp = fopen(m_filename, "ab"); \ + if (fp != NULL && m_buf != NULL) { \ + int i; \ + ALOGD("Dump to file!"); \ + i = fwrite(m_buf, 1, m_size, fp); \ + if (i != (int)m_size) { \ + ALOGD("Error in fwrite, returned %d", i); \ + perror("Error in write to file"); \ + } \ + fclose(fp); \ + } else { \ + ALOGD("Could not write to file %s", m_filename); \ + if (fp != NULL) fclose(fp); \ + } \ + } +#else /* FILE_DUMP_ENABLE */ +#define INPUT_DUMP_PATH +#define INPUT_DUMP_EXT +#define OUTPUT_DUMP_PATH +#define OUTPUT_DUMP_EXT +#define GENERATE_FILE_NAMES() +#define CREATE_DUMP_FILE(m_filename) +#define DUMP_TO_FILE(m_filename, m_buf, m_size) +#endif /* FILE_DUMP_ENABLE */ + +} // namespace android + +#endif // ANDROID_C2_SOFT_AV1_DEC_H_
diff --git a/media/codec2/components/avc/Android.bp b/media/codec2/components/avc/Android.bp new file mode 100644 index 0000000..4021444 --- /dev/null +++ b/media/codec2/components/avc/Android.bp
@@ -0,0 +1,37 @@ +cc_library_shared { + name: "libcodec2_soft_avcdec", + defaults: [ + "libcodec2_soft-defaults", + "libcodec2_soft_sanitize_signed-defaults", + ], + + static_libs: ["libavcdec"], + + srcs: ["C2SoftAvcDec.cpp"], + + include_dirs: [ + "external/libavc/decoder", + "external/libavc/common", + ], +} + +cc_library_shared { + name: "libcodec2_soft_avcenc", + defaults: [ + "libcodec2_soft-defaults", + "libcodec2_soft_sanitize_signed-defaults", + ], + + static_libs: ["libavcenc"], + + srcs: ["C2SoftAvcEnc.cpp"], + + include_dirs: [ + "external/libavc/encoder", + "external/libavc/common", + ], + + cflags: [ + "-Wno-unused-variable", + ], +}
diff --git a/media/codec2/components/avc/C2SoftAvcDec.cpp b/media/codec2/components/avc/C2SoftAvcDec.cpp new file mode 100644 index 0000000..3f015b4 --- /dev/null +++ b/media/codec2/components/avc/C2SoftAvcDec.cpp
@@ -0,0 +1,1033 @@ +/* + * Copyright 2017 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 "C2SoftAvcDec" +#include <log/log.h> + +#include <media/stagefright/foundation/MediaDefs.h> + +#include <C2Debug.h> +#include <C2PlatformSupport.h> +#include <Codec2Mapper.h> +#include <SimpleC2Interface.h> + +#include "C2SoftAvcDec.h" +#include "ih264d.h" + +namespace android { + +namespace { + +constexpr char COMPONENT_NAME[] = "c2.android.avc.decoder"; + +} // namespace + +class C2SoftAvcDec::IntfImpl : public SimpleInterface<void>::BaseParams { +public: + explicit IntfImpl(const std::shared_ptr<C2ReflectorHelper> &helper) + : SimpleInterface<void>::BaseParams( + helper, + COMPONENT_NAME, + C2Component::KIND_DECODER, + C2Component::DOMAIN_VIDEO, + MEDIA_MIMETYPE_VIDEO_AVC) { + noPrivateBuffers(); // TODO: account for our buffers here + noInputReferences(); + noOutputReferences(); + noInputLatency(); + noTimeStretch(); + + // TODO: Proper support for reorder depth. + addParameter( + DefineParam(mActualOutputDelay, C2_PARAMKEY_OUTPUT_DELAY) + .withConstValue(new C2PortActualDelayTuning::output(8u)) + .build()); + + // TODO: output latency and reordering + + addParameter( + DefineParam(mAttrib, C2_PARAMKEY_COMPONENT_ATTRIBUTES) + .withConstValue(new C2ComponentAttributesSetting(C2Component::ATTRIB_IS_TEMPORAL)) + .build()); + + // coded and output picture size is the same for this codec + addParameter( + DefineParam(mSize, C2_PARAMKEY_PICTURE_SIZE) + .withDefault(new C2StreamPictureSizeInfo::output(0u, 320, 240)) + .withFields({ + C2F(mSize, width).inRange(2, 4080, 2), + C2F(mSize, height).inRange(2, 4080, 2), + }) + .withSetter(SizeSetter) + .build()); + + addParameter( + DefineParam(mMaxSize, C2_PARAMKEY_MAX_PICTURE_SIZE) + .withDefault(new C2StreamMaxPictureSizeTuning::output(0u, 320, 240)) + .withFields({ + C2F(mSize, width).inRange(2, 4080, 2), + C2F(mSize, height).inRange(2, 4080, 2), + }) + .withSetter(MaxPictureSizeSetter, mSize) + .build()); + + addParameter( + DefineParam(mProfileLevel, C2_PARAMKEY_PROFILE_LEVEL) + .withDefault(new C2StreamProfileLevelInfo::input(0u, + C2Config::PROFILE_AVC_CONSTRAINED_BASELINE, C2Config::LEVEL_AVC_5_2)) + .withFields({ + C2F(mProfileLevel, profile).oneOf({ + C2Config::PROFILE_AVC_CONSTRAINED_BASELINE, + C2Config::PROFILE_AVC_BASELINE, + C2Config::PROFILE_AVC_MAIN, + C2Config::PROFILE_AVC_CONSTRAINED_HIGH, + C2Config::PROFILE_AVC_PROGRESSIVE_HIGH, + C2Config::PROFILE_AVC_HIGH}), + C2F(mProfileLevel, level).oneOf({ + C2Config::LEVEL_AVC_1, C2Config::LEVEL_AVC_1B, C2Config::LEVEL_AVC_1_1, + C2Config::LEVEL_AVC_1_2, C2Config::LEVEL_AVC_1_3, + C2Config::LEVEL_AVC_2, C2Config::LEVEL_AVC_2_1, C2Config::LEVEL_AVC_2_2, + C2Config::LEVEL_AVC_3, C2Config::LEVEL_AVC_3_1, C2Config::LEVEL_AVC_3_2, + C2Config::LEVEL_AVC_4, C2Config::LEVEL_AVC_4_1, C2Config::LEVEL_AVC_4_2, + C2Config::LEVEL_AVC_5, C2Config::LEVEL_AVC_5_1, C2Config::LEVEL_AVC_5_2 + }) + }) + .withSetter(ProfileLevelSetter, mSize) + .build()); + + addParameter( + DefineParam(mMaxInputSize, C2_PARAMKEY_INPUT_MAX_BUFFER_SIZE) + .withDefault(new C2StreamMaxBufferSizeInfo::input(0u, 320 * 240 * 3 / 4)) + .withFields({ + C2F(mMaxInputSize, value).any(), + }) + .calculatedAs(MaxInputSizeSetter, mMaxSize) + .build()); + + C2ChromaOffsetStruct locations[1] = { C2ChromaOffsetStruct::ITU_YUV_420_0() }; + std::shared_ptr<C2StreamColorInfo::output> defaultColorInfo = + C2StreamColorInfo::output::AllocShared( + 1u, 0u, 8u /* bitDepth */, C2Color::YUV_420); + memcpy(defaultColorInfo->m.locations, locations, sizeof(locations)); + + defaultColorInfo = + C2StreamColorInfo::output::AllocShared( + { C2ChromaOffsetStruct::ITU_YUV_420_0() }, + 0u, 8u /* bitDepth */, C2Color::YUV_420); + helper->addStructDescriptors<C2ChromaOffsetStruct>(); + + addParameter( + DefineParam(mColorInfo, C2_PARAMKEY_CODED_COLOR_INFO) + .withConstValue(defaultColorInfo) + .build()); + + addParameter( + DefineParam(mDefaultColorAspects, C2_PARAMKEY_DEFAULT_COLOR_ASPECTS) + .withDefault(new C2StreamColorAspectsTuning::output( + 0u, C2Color::RANGE_UNSPECIFIED, C2Color::PRIMARIES_UNSPECIFIED, + C2Color::TRANSFER_UNSPECIFIED, C2Color::MATRIX_UNSPECIFIED)) + .withFields({ + C2F(mDefaultColorAspects, range).inRange( + C2Color::RANGE_UNSPECIFIED, C2Color::RANGE_OTHER), + C2F(mDefaultColorAspects, primaries).inRange( + C2Color::PRIMARIES_UNSPECIFIED, C2Color::PRIMARIES_OTHER), + C2F(mDefaultColorAspects, transfer).inRange( + C2Color::TRANSFER_UNSPECIFIED, C2Color::TRANSFER_OTHER), + C2F(mDefaultColorAspects, matrix).inRange( + C2Color::MATRIX_UNSPECIFIED, C2Color::MATRIX_OTHER) + }) + .withSetter(DefaultColorAspectsSetter) + .build()); + + addParameter( + DefineParam(mCodedColorAspects, C2_PARAMKEY_VUI_COLOR_ASPECTS) + .withDefault(new C2StreamColorAspectsInfo::input( + 0u, C2Color::RANGE_LIMITED, C2Color::PRIMARIES_UNSPECIFIED, + C2Color::TRANSFER_UNSPECIFIED, C2Color::MATRIX_UNSPECIFIED)) + .withFields({ + C2F(mCodedColorAspects, range).inRange( + C2Color::RANGE_UNSPECIFIED, C2Color::RANGE_OTHER), + C2F(mCodedColorAspects, primaries).inRange( + C2Color::PRIMARIES_UNSPECIFIED, C2Color::PRIMARIES_OTHER), + C2F(mCodedColorAspects, transfer).inRange( + C2Color::TRANSFER_UNSPECIFIED, C2Color::TRANSFER_OTHER), + C2F(mCodedColorAspects, matrix).inRange( + C2Color::MATRIX_UNSPECIFIED, C2Color::MATRIX_OTHER) + }) + .withSetter(CodedColorAspectsSetter) + .build()); + + addParameter( + DefineParam(mColorAspects, C2_PARAMKEY_COLOR_ASPECTS) + .withDefault(new C2StreamColorAspectsInfo::output( + 0u, C2Color::RANGE_UNSPECIFIED, C2Color::PRIMARIES_UNSPECIFIED, + C2Color::TRANSFER_UNSPECIFIED, C2Color::MATRIX_UNSPECIFIED)) + .withFields({ + C2F(mColorAspects, range).inRange( + C2Color::RANGE_UNSPECIFIED, C2Color::RANGE_OTHER), + C2F(mColorAspects, primaries).inRange( + C2Color::PRIMARIES_UNSPECIFIED, C2Color::PRIMARIES_OTHER), + C2F(mColorAspects, transfer).inRange( + C2Color::TRANSFER_UNSPECIFIED, C2Color::TRANSFER_OTHER), + C2F(mColorAspects, matrix).inRange( + C2Color::MATRIX_UNSPECIFIED, C2Color::MATRIX_OTHER) + }) + .withSetter(ColorAspectsSetter, mDefaultColorAspects, mCodedColorAspects) + .build()); + + // TODO: support more formats? + addParameter( + DefineParam(mPixelFormat, C2_PARAMKEY_PIXEL_FORMAT) + .withConstValue(new C2StreamPixelFormatInfo::output( + 0u, HAL_PIXEL_FORMAT_YCBCR_420_888)) + .build()); + } + + static C2R SizeSetter(bool mayBlock, const C2P<C2StreamPictureSizeInfo::output> &oldMe, + C2P<C2StreamPictureSizeInfo::output> &me) { + (void)mayBlock; + C2R res = C2R::Ok(); + if (!me.F(me.v.width).supportsAtAll(me.v.width)) { + res = res.plus(C2SettingResultBuilder::BadValue(me.F(me.v.width))); + me.set().width = oldMe.v.width; + } + if (!me.F(me.v.height).supportsAtAll(me.v.height)) { + res = res.plus(C2SettingResultBuilder::BadValue(me.F(me.v.height))); + me.set().height = oldMe.v.height; + } + return res; + } + + static C2R MaxPictureSizeSetter(bool mayBlock, C2P<C2StreamMaxPictureSizeTuning::output> &me, + const C2P<C2StreamPictureSizeInfo::output> &size) { + (void)mayBlock; + // TODO: get max width/height from the size's field helpers vs. hardcoding + me.set().width = c2_min(c2_max(me.v.width, size.v.width), 4080u); + me.set().height = c2_min(c2_max(me.v.height, size.v.height), 4080u); + return C2R::Ok(); + } + + static C2R MaxInputSizeSetter(bool mayBlock, C2P<C2StreamMaxBufferSizeInfo::input> &me, + const C2P<C2StreamMaxPictureSizeTuning::output> &maxSize) { + (void)mayBlock; + // assume compression ratio of 2 + me.set().value = (((maxSize.v.width + 15) / 16) * ((maxSize.v.height + 15) / 16) * 192); + return C2R::Ok(); + } + + static C2R ProfileLevelSetter(bool mayBlock, C2P<C2StreamProfileLevelInfo::input> &me, + const C2P<C2StreamPictureSizeInfo::output> &size) { + (void)mayBlock; + (void)size; + (void)me; // TODO: validate + return C2R::Ok(); + } + + static C2R DefaultColorAspectsSetter(bool mayBlock, C2P<C2StreamColorAspectsTuning::output> &me) { + (void)mayBlock; + if (me.v.range > C2Color::RANGE_OTHER) { + me.set().range = C2Color::RANGE_OTHER; + } + if (me.v.primaries > C2Color::PRIMARIES_OTHER) { + me.set().primaries = C2Color::PRIMARIES_OTHER; + } + if (me.v.transfer > C2Color::TRANSFER_OTHER) { + me.set().transfer = C2Color::TRANSFER_OTHER; + } + if (me.v.matrix > C2Color::MATRIX_OTHER) { + me.set().matrix = C2Color::MATRIX_OTHER; + } + return C2R::Ok(); + } + + static C2R CodedColorAspectsSetter(bool mayBlock, C2P<C2StreamColorAspectsInfo::input> &me) { + (void)mayBlock; + if (me.v.range > C2Color::RANGE_OTHER) { + me.set().range = C2Color::RANGE_OTHER; + } + if (me.v.primaries > C2Color::PRIMARIES_OTHER) { + me.set().primaries = C2Color::PRIMARIES_OTHER; + } + if (me.v.transfer > C2Color::TRANSFER_OTHER) { + me.set().transfer = C2Color::TRANSFER_OTHER; + } + if (me.v.matrix > C2Color::MATRIX_OTHER) { + me.set().matrix = C2Color::MATRIX_OTHER; + } + return C2R::Ok(); + } + + static C2R ColorAspectsSetter(bool mayBlock, C2P<C2StreamColorAspectsInfo::output> &me, + const C2P<C2StreamColorAspectsTuning::output> &def, + const C2P<C2StreamColorAspectsInfo::input> &coded) { + (void)mayBlock; + // take default values for all unspecified fields, and coded values for specified ones + me.set().range = coded.v.range == RANGE_UNSPECIFIED ? def.v.range : coded.v.range; + me.set().primaries = coded.v.primaries == PRIMARIES_UNSPECIFIED + ? def.v.primaries : coded.v.primaries; + me.set().transfer = coded.v.transfer == TRANSFER_UNSPECIFIED + ? def.v.transfer : coded.v.transfer; + me.set().matrix = coded.v.matrix == MATRIX_UNSPECIFIED ? def.v.matrix : coded.v.matrix; + return C2R::Ok(); + } + + std::shared_ptr<C2StreamColorAspectsInfo::output> getColorAspects_l() { + return mColorAspects; + } + +private: + std::shared_ptr<C2StreamProfileLevelInfo::input> mProfileLevel; + std::shared_ptr<C2StreamPictureSizeInfo::output> mSize; + std::shared_ptr<C2StreamMaxPictureSizeTuning::output> mMaxSize; + std::shared_ptr<C2StreamMaxBufferSizeInfo::input> mMaxInputSize; + std::shared_ptr<C2StreamColorInfo::output> mColorInfo; + std::shared_ptr<C2StreamColorAspectsInfo::input> mCodedColorAspects; + std::shared_ptr<C2StreamColorAspectsTuning::output> mDefaultColorAspects; + std::shared_ptr<C2StreamColorAspectsInfo::output> mColorAspects; + std::shared_ptr<C2StreamPixelFormatInfo::output> mPixelFormat; +}; + +static size_t getCpuCoreCount() { + long cpuCoreCount = 1; +#if defined(_SC_NPROCESSORS_ONLN) + cpuCoreCount = sysconf(_SC_NPROCESSORS_ONLN); +#else + // _SC_NPROC_ONLN must be defined... + cpuCoreCount = sysconf(_SC_NPROC_ONLN); +#endif + CHECK(cpuCoreCount >= 1); + ALOGV("Number of CPU cores: %ld", cpuCoreCount); + return (size_t)cpuCoreCount; +} + +static void *ivd_aligned_malloc(void *ctxt, WORD32 alignment, WORD32 size) { + (void) ctxt; + return memalign(alignment, size); +} + +static void ivd_aligned_free(void *ctxt, void *mem) { + (void) ctxt; + free(mem); +} + +C2SoftAvcDec::C2SoftAvcDec( + const char *name, + c2_node_id_t id, + const std::shared_ptr<IntfImpl> &intfImpl) + : SimpleC2Component(std::make_shared<SimpleInterface<IntfImpl>>(name, id, intfImpl)), + mIntf(intfImpl), + mDecHandle(nullptr), + mOutBufferFlush(nullptr), + mIvColorFormat(IV_YUV_420P), + mWidth(320), + mHeight(240), + mHeaderDecoded(false), + mOutIndex(0u) { + GENERATE_FILE_NAMES(); + CREATE_DUMP_FILE(mInFile); +} + +C2SoftAvcDec::~C2SoftAvcDec() { + onRelease(); +} + +c2_status_t C2SoftAvcDec::onInit() { + status_t err = initDecoder(); + return err == OK ? C2_OK : C2_CORRUPTED; +} + +c2_status_t C2SoftAvcDec::onStop() { + if (OK != resetDecoder()) return C2_CORRUPTED; + resetPlugin(); + return C2_OK; +} + +void C2SoftAvcDec::onReset() { + (void) onStop(); +} + +void C2SoftAvcDec::onRelease() { + (void) deleteDecoder(); + if (mOutBufferFlush) { + ivd_aligned_free(nullptr, mOutBufferFlush); + mOutBufferFlush = nullptr; + } + if (mOutBlock) { + mOutBlock.reset(); + } +} + +c2_status_t C2SoftAvcDec::onFlush_sm() { + if (OK != setFlushMode()) return C2_CORRUPTED; + + uint32_t bufferSize = mStride * mHeight * 3 / 2; + mOutBufferFlush = (uint8_t *)ivd_aligned_malloc(nullptr, 128, bufferSize); + if (!mOutBufferFlush) { + ALOGE("could not allocate tmp output buffer (for flush) of size %u ", bufferSize); + return C2_NO_MEMORY; + } + + while (true) { + ivd_video_decode_ip_t s_decode_ip; + ivd_video_decode_op_t s_decode_op; + + setDecodeArgs(&s_decode_ip, &s_decode_op, nullptr, nullptr, 0, 0, 0); + (void) ivdec_api_function(mDecHandle, &s_decode_ip, &s_decode_op); + if (0 == s_decode_op.u4_output_present) { + resetPlugin(); + break; + } + } + + if (mOutBufferFlush) { + ivd_aligned_free(nullptr, mOutBufferFlush); + mOutBufferFlush = nullptr; + } + + return C2_OK; +} + +status_t C2SoftAvcDec::createDecoder() { + ivdext_create_ip_t s_create_ip; + ivdext_create_op_t s_create_op; + + s_create_ip.s_ivd_create_ip_t.u4_size = sizeof(ivdext_create_ip_t); + s_create_ip.s_ivd_create_ip_t.e_cmd = IVD_CMD_CREATE; + s_create_ip.s_ivd_create_ip_t.u4_share_disp_buf = 0; + s_create_ip.s_ivd_create_ip_t.e_output_format = mIvColorFormat; + s_create_ip.s_ivd_create_ip_t.pf_aligned_alloc = ivd_aligned_malloc; + s_create_ip.s_ivd_create_ip_t.pf_aligned_free = ivd_aligned_free; + s_create_ip.s_ivd_create_ip_t.pv_mem_ctxt = nullptr; + s_create_op.s_ivd_create_op_t.u4_size = sizeof(ivdext_create_op_t); + IV_API_CALL_STATUS_T status = ivdec_api_function(nullptr, + &s_create_ip, + &s_create_op); + if (status != IV_SUCCESS) { + ALOGE("error in %s: 0x%x", __func__, + s_create_op.s_ivd_create_op_t.u4_error_code); + return UNKNOWN_ERROR; + } + mDecHandle = (iv_obj_t*)s_create_op.s_ivd_create_op_t.pv_handle; + mDecHandle->pv_fxns = (void *)ivdec_api_function; + mDecHandle->u4_size = sizeof(iv_obj_t); + + return OK; +} + +status_t C2SoftAvcDec::setNumCores() { + ivdext_ctl_set_num_cores_ip_t s_set_num_cores_ip; + ivdext_ctl_set_num_cores_op_t s_set_num_cores_op; + + s_set_num_cores_ip.u4_size = sizeof(ivdext_ctl_set_num_cores_ip_t); + s_set_num_cores_ip.e_cmd = IVD_CMD_VIDEO_CTL; + s_set_num_cores_ip.e_sub_cmd = IVDEXT_CMD_CTL_SET_NUM_CORES; + s_set_num_cores_ip.u4_num_cores = mNumCores; + s_set_num_cores_op.u4_size = sizeof(ivdext_ctl_set_num_cores_op_t); + IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle, + &s_set_num_cores_ip, + &s_set_num_cores_op); + if (IV_SUCCESS != status) { + ALOGD("error in %s: 0x%x", __func__, s_set_num_cores_op.u4_error_code); + return UNKNOWN_ERROR; + } + + return OK; +} + +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; + + s_set_dyn_params_ip.u4_size = sizeof(ivd_ctl_set_config_ip_t); + s_set_dyn_params_ip.e_cmd = IVD_CMD_VIDEO_CTL; + s_set_dyn_params_ip.e_sub_cmd = IVD_CMD_CTL_SETPARAMS; + 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 = 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, + &s_set_dyn_params_op); + if (status != IV_SUCCESS) { + ALOGE("error in %s: 0x%x", __func__, s_set_dyn_params_op.u4_error_code); + return UNKNOWN_ERROR; + } + + return OK; +} + +void C2SoftAvcDec::getVersion() { + ivd_ctl_getversioninfo_ip_t s_get_versioninfo_ip; + ivd_ctl_getversioninfo_op_t s_get_versioninfo_op; + UWORD8 au1_buf[512]; + + s_get_versioninfo_ip.u4_size = sizeof(ivd_ctl_getversioninfo_ip_t); + s_get_versioninfo_ip.e_cmd = IVD_CMD_VIDEO_CTL; + s_get_versioninfo_ip.e_sub_cmd = IVD_CMD_CTL_GETVERSION; + s_get_versioninfo_ip.pv_version_buffer = au1_buf; + s_get_versioninfo_ip.u4_version_buffer_size = sizeof(au1_buf); + s_get_versioninfo_op.u4_size = sizeof(ivd_ctl_getversioninfo_op_t); + IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle, + &s_get_versioninfo_ip, + &s_get_versioninfo_op); + if (status != IV_SUCCESS) { + ALOGD("error in %s: 0x%x", __func__, + s_get_versioninfo_op.u4_error_code); + } else { + ALOGV("ittiam decoder version number: %s", + (char *) s_get_versioninfo_ip.pv_version_buffer); + } +} + +status_t C2SoftAvcDec::initDecoder() { + if (OK != createDecoder()) return UNKNOWN_ERROR; + mNumCores = MIN(getCpuCoreCount(), MAX_NUM_CORES); + mStride = ALIGN64(mWidth); + mSignalledError = false; + resetPlugin(); + (void) setNumCores(); + if (OK != setParams(mStride, IVD_DECODE_FRAME)) return UNKNOWN_ERROR; + (void) getVersion(); + + return OK; +} + +bool C2SoftAvcDec::setDecodeArgs(ivd_video_decode_ip_t *ps_decode_ip, + ivd_video_decode_op_t *ps_decode_op, + C2ReadView *inBuffer, + C2GraphicView *outBuffer, + size_t inOffset, + size_t inSize, + uint32_t tsMarker) { + uint32_t displayStride = mStride; + uint32_t displayHeight = mHeight; + size_t lumaSize = displayStride * displayHeight; + size_t chromaSize = lumaSize >> 2; + + ps_decode_ip->u4_size = sizeof(ivd_video_decode_ip_t); + ps_decode_ip->e_cmd = IVD_CMD_VIDEO_DECODE; + if (inBuffer) { + ps_decode_ip->u4_ts = tsMarker; + ps_decode_ip->pv_stream_buffer = const_cast<uint8_t *>(inBuffer->data() + inOffset); + ps_decode_ip->u4_num_Bytes = inSize; + } else { + ps_decode_ip->u4_ts = 0; + ps_decode_ip->pv_stream_buffer = nullptr; + ps_decode_ip->u4_num_Bytes = 0; + } + ps_decode_ip->s_out_buffer.u4_min_out_buf_size[0] = lumaSize; + ps_decode_ip->s_out_buffer.u4_min_out_buf_size[1] = chromaSize; + ps_decode_ip->s_out_buffer.u4_min_out_buf_size[2] = chromaSize; + if (outBuffer) { + if (outBuffer->width() < displayStride || outBuffer->height() < displayHeight) { + ALOGE("Output buffer too small: provided (%dx%d) required (%ux%u)", + outBuffer->width(), outBuffer->height(), displayStride, displayHeight); + return false; + } + ps_decode_ip->s_out_buffer.pu1_bufs[0] = outBuffer->data()[C2PlanarLayout::PLANE_Y]; + ps_decode_ip->s_out_buffer.pu1_bufs[1] = outBuffer->data()[C2PlanarLayout::PLANE_U]; + ps_decode_ip->s_out_buffer.pu1_bufs[2] = outBuffer->data()[C2PlanarLayout::PLANE_V]; + } else { + ps_decode_ip->s_out_buffer.pu1_bufs[0] = mOutBufferFlush; + ps_decode_ip->s_out_buffer.pu1_bufs[1] = mOutBufferFlush + lumaSize; + ps_decode_ip->s_out_buffer.pu1_bufs[2] = mOutBufferFlush + lumaSize + chromaSize; + } + ps_decode_ip->s_out_buffer.u4_num_bufs = 3; + ps_decode_op->u4_size = sizeof(ivd_video_decode_op_t); + + return true; +} + +bool C2SoftAvcDec::getVuiParams() { + ivdext_ctl_get_vui_params_ip_t s_get_vui_params_ip; + ivdext_ctl_get_vui_params_op_t s_get_vui_params_op; + + s_get_vui_params_ip.u4_size = sizeof(ivdext_ctl_get_vui_params_ip_t); + s_get_vui_params_ip.e_cmd = IVD_CMD_VIDEO_CTL; + s_get_vui_params_ip.e_sub_cmd = + (IVD_CONTROL_API_COMMAND_TYPE_T) IH264D_CMD_CTL_GET_VUI_PARAMS; + s_get_vui_params_op.u4_size = sizeof(ivdext_ctl_get_vui_params_op_t); + IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle, + &s_get_vui_params_ip, + &s_get_vui_params_op); + if (status != IV_SUCCESS) { + ALOGD("error in %s: 0x%x", __func__, s_get_vui_params_op.u4_error_code); + return false; + } + + VuiColorAspects vuiColorAspects; + vuiColorAspects.primaries = s_get_vui_params_op.u1_colour_primaries; + vuiColorAspects.transfer = s_get_vui_params_op.u1_tfr_chars; + vuiColorAspects.coeffs = s_get_vui_params_op.u1_matrix_coeffs; + vuiColorAspects.fullRange = s_get_vui_params_op.u1_video_full_range_flag; + + // convert vui aspects to C2 values if changed + if (!(vuiColorAspects == mBitstreamColorAspects)) { + mBitstreamColorAspects = vuiColorAspects; + ColorAspects sfAspects; + C2StreamColorAspectsInfo::input codedAspects = { 0u }; + ColorUtils::convertIsoColorAspectsToCodecAspects( + vuiColorAspects.primaries, vuiColorAspects.transfer, vuiColorAspects.coeffs, + vuiColorAspects.fullRange, sfAspects); + if (!C2Mapper::map(sfAspects.mPrimaries, &codedAspects.primaries)) { + codedAspects.primaries = C2Color::PRIMARIES_UNSPECIFIED; + } + if (!C2Mapper::map(sfAspects.mRange, &codedAspects.range)) { + codedAspects.range = C2Color::RANGE_UNSPECIFIED; + } + if (!C2Mapper::map(sfAspects.mMatrixCoeffs, &codedAspects.matrix)) { + codedAspects.matrix = C2Color::MATRIX_UNSPECIFIED; + } + if (!C2Mapper::map(sfAspects.mTransfer, &codedAspects.transfer)) { + codedAspects.transfer = C2Color::TRANSFER_UNSPECIFIED; + } + std::vector<std::unique_ptr<C2SettingResult>> failures; + (void)mIntf->config({&codedAspects}, C2_MAY_BLOCK, &failures); + } + return true; +} + +status_t C2SoftAvcDec::setFlushMode() { + ivd_ctl_flush_ip_t s_set_flush_ip; + ivd_ctl_flush_op_t s_set_flush_op; + + s_set_flush_ip.u4_size = sizeof(ivd_ctl_flush_ip_t); + s_set_flush_ip.e_cmd = IVD_CMD_VIDEO_CTL; + s_set_flush_ip.e_sub_cmd = IVD_CMD_CTL_FLUSH; + s_set_flush_op.u4_size = sizeof(ivd_ctl_flush_op_t); + IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle, + &s_set_flush_ip, + &s_set_flush_op); + if (status != IV_SUCCESS) { + ALOGE("error in %s: 0x%x", __func__, s_set_flush_op.u4_error_code); + return UNKNOWN_ERROR; + } + + return OK; +} + +status_t C2SoftAvcDec::resetDecoder() { + ivd_ctl_reset_ip_t s_reset_ip; + ivd_ctl_reset_op_t s_reset_op; + + s_reset_ip.u4_size = sizeof(ivd_ctl_reset_ip_t); + s_reset_ip.e_cmd = IVD_CMD_VIDEO_CTL; + s_reset_ip.e_sub_cmd = IVD_CMD_CTL_RESET; + s_reset_op.u4_size = sizeof(ivd_ctl_reset_op_t); + IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle, + &s_reset_ip, + &s_reset_op); + if (IV_SUCCESS != status) { + ALOGE("error in %s: 0x%x", __func__, s_reset_op.u4_error_code); + return UNKNOWN_ERROR; + } + mStride = 0; + (void) setNumCores(); + mSignalledError = false; + mHeaderDecoded = false; + + return OK; +} + +void C2SoftAvcDec::resetPlugin() { + mSignalledOutputEos = false; + gettimeofday(&mTimeStart, nullptr); + gettimeofday(&mTimeEnd, nullptr); +} + +status_t C2SoftAvcDec::deleteDecoder() { + if (mDecHandle) { + ivdext_delete_ip_t s_delete_ip; + ivdext_delete_op_t s_delete_op; + + s_delete_ip.s_ivd_delete_ip_t.u4_size = sizeof(ivdext_delete_ip_t); + s_delete_ip.s_ivd_delete_ip_t.e_cmd = IVD_CMD_DELETE; + s_delete_op.s_ivd_delete_op_t.u4_size = sizeof(ivdext_delete_op_t); + IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle, + &s_delete_ip, + &s_delete_op); + if (status != IV_SUCCESS) { + ALOGE("error in %s: 0x%x", __func__, + s_delete_op.s_ivd_delete_op_t.u4_error_code); + return UNKNOWN_ERROR; + } + mDecHandle = nullptr; + } + + return OK; +} + +static void fillEmptyWork(const std::unique_ptr<C2Work> &work) { + uint32_t flags = 0; + if (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) { + flags |= C2FrameData::FLAG_END_OF_STREAM; + ALOGV("signalling eos"); + } + work->worklets.front()->output.flags = (C2FrameData::flags_t)flags; + work->worklets.front()->output.buffers.clear(); + work->worklets.front()->output.ordinal = work->input.ordinal; + work->workletsProcessed = 1u; +} + +void C2SoftAvcDec::finishWork(uint64_t index, const std::unique_ptr<C2Work> &work) { + std::shared_ptr<C2Buffer> buffer = createGraphicBuffer(std::move(mOutBlock), + C2Rect(mWidth, mHeight)); + mOutBlock = nullptr; + { + IntfImpl::Lock lock = mIntf->lock(); + buffer->setInfo(mIntf->getColorAspects_l()); + } + + class FillWork { + public: + FillWork(uint32_t flags, C2WorkOrdinalStruct ordinal, + const std::shared_ptr<C2Buffer>& buffer) + : mFlags(flags), mOrdinal(ordinal), mBuffer(buffer) {} + ~FillWork() = default; + + void operator()(const std::unique_ptr<C2Work>& work) { + work->worklets.front()->output.flags = (C2FrameData::flags_t)mFlags; + work->worklets.front()->output.buffers.clear(); + work->worklets.front()->output.ordinal = mOrdinal; + work->workletsProcessed = 1u; + work->result = C2_OK; + if (mBuffer) { + work->worklets.front()->output.buffers.push_back(mBuffer); + } + ALOGV("timestamp = %lld, index = %lld, w/%s buffer", + mOrdinal.timestamp.peekll(), mOrdinal.frameIndex.peekll(), + mBuffer ? "" : "o"); + } + + private: + const uint32_t mFlags; + const C2WorkOrdinalStruct mOrdinal; + const std::shared_ptr<C2Buffer> mBuffer; + }; + + 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; + work->workletsProcessed = 1u; + }; + if (work && c2_cntr64_t(index) == work->input.ordinal.frameIndex) { + bool eos = ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0); + // TODO: Check if cloneAndSend can be avoided by tracking number of frames remaining + if (eos) { + if (buffer) { + mOutIndex = index; + C2WorkOrdinalStruct outOrdinal = work->input.ordinal; + cloneAndSend( + mOutIndex, work, + FillWork(C2FrameData::FLAG_INCOMPLETE, outOrdinal, buffer)); + buffer.reset(); + } + } else { + fillWork(work); + } + } else { + finish(index, fillWork); + } +} + +c2_status_t C2SoftAvcDec::ensureDecoderState(const std::shared_ptr<C2BlockPool> &pool) { + if (!mDecHandle) { + ALOGE("not supposed to be here, invalid decoder context"); + return C2_CORRUPTED; + } + if (mStride != ALIGN64(mWidth)) { + mStride = ALIGN64(mWidth); + if (OK != setParams(mStride, IVD_DECODE_FRAME)) return C2_CORRUPTED; + } + if (mOutBlock && + (mOutBlock->width() != mStride || mOutBlock->height() != mHeight)) { + mOutBlock.reset(); + } + if (!mOutBlock) { + uint32_t format = HAL_PIXEL_FORMAT_YV12; + C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE }; + c2_status_t err = pool->fetchGraphicBlock(mStride, mHeight, format, usage, &mOutBlock); + if (err != C2_OK) { + ALOGE("fetchGraphicBlock for Output failed with status %d", err); + return err; + } + ALOGV("provided (%dx%d) required (%dx%d)", + mOutBlock->width(), mOutBlock->height(), mStride, mHeight); + } + + return C2_OK; +} + +// 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 +// TODO: pass coloraspects information to surface +// TODO: test support for dynamic change in resolution +// TODO: verify if the decoder sent back all frames +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; + } + + size_t inOffset = 0u; + size_t inSize = 0u; + uint32_t workIndex = work->input.ordinal.frameIndex.peeku() & 0xFFFFFFFF; + C2ReadView rView = mDummyReadView; + if (!work->input.buffers.empty()) { + rView = work->input.buffers[0]->data().linearBlocks().front().map().get(); + inSize = rView.capacity(); + if (inSize && rView.error()) { + ALOGE("read view map failed %d", rView.error()); + work->result = rView.error(); + return; + } + } + bool eos = ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0); + bool hasPicture = false; + + ALOGV("in buffer attr. size %zu timestamp %d frameindex %d, flags %x", + inSize, (int)work->input.ordinal.timestamp.peeku(), + (int)work->input.ordinal.frameIndex.peeku(), work->input.flags); + size_t inPos = 0; + while (inPos < inSize) { + if (C2_OK != ensureDecoderState(pool)) { + mSignalledError = true; + work->workletsProcessed = 1u; + work->result = C2_CORRUPTED; + return; + } + + ivd_video_decode_ip_t s_decode_ip; + ivd_video_decode_op_t s_decode_op; + { + C2GraphicView wView = mOutBlock->map().get(); + if (wView.error()) { + ALOGE("graphic view map failed %d", wView.error()); + work->result = wView.error(); + return; + } + 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, nullptr); + TIME_DIFF(mTimeEnd, mTimeStart, delay); + (void) ivdec_api_function(mDecHandle, &s_decode_ip, &s_decode_op); + WORD32 decodeTime; + GETTIME(&mTimeEnd, nullptr); + TIME_DIFF(mTimeStart, mTimeEnd, decodeTime); + ALOGV("decodeTime=%6d delay=%6d numBytes=%6d", decodeTime, delay, + s_decode_op.u4_num_bytes_consumed); + } + if (IVD_MEM_ALLOC_FAILED == (s_decode_op.u4_error_code & IVD_ERROR_MASK)) { + ALOGE("allocation failure in decoder"); + mSignalledError = true; + work->workletsProcessed = 1u; + work->result = C2_CORRUPTED; + return; + } else if (IVD_STREAM_WIDTH_HEIGHT_NOT_SUPPORTED == (s_decode_op.u4_error_code & IVD_ERROR_MASK)) { + ALOGE("unsupported resolution : %dx%d", mWidth, mHeight); + mSignalledError = true; + work->workletsProcessed = 1u; + work->result = C2_CORRUPTED; + return; + } else if (IVD_RES_CHANGED == (s_decode_op.u4_error_code & IVD_ERROR_MASK)) { + ALOGV("resolution changed"); + drainInternal(DRAIN_COMPONENT_NO_EOS, pool, work); + resetDecoder(); + resetPlugin(); + 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); + } else if (IS_IVD_FATAL_ERROR(s_decode_op.u4_error_code)) { + ALOGE("Fatal error in decoder 0x%x", s_decode_op.u4_error_code); + mSignalledError = true; + work->workletsProcessed = 1u; + work->result = C2_CORRUPTED; + return; + } + 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; + CHECK_EQ(0u, s_decode_op.u4_output_present); + + C2StreamPictureSizeInfo::output size(0u, mWidth, mHeight); + std::vector<std::unique_ptr<C2SettingResult>> failures; + 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; + } + } + (void)getVuiParams(); + hasPicture |= (1 == s_decode_op.u4_frame_decoded_flag); + 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", + (int)inSize - (int)inPos); + break; + } + } + if (eos) { + drainInternal(DRAIN_COMPONENT_WITH_EOS, pool, work); + mSignalledOutputEos = true; + } else if (!hasPicture) { + fillEmptyWork(work); + } + + work->input.buffers.clear(); +} + +c2_status_t C2SoftAvcDec::drainInternal( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool, + const std::unique_ptr<C2Work> &work) { + if (drainMode == NO_DRAIN) { + ALOGW("drain with NO_DRAIN: no-op"); + return C2_OK; + } + if (drainMode == DRAIN_CHAIN) { + ALOGW("DRAIN_CHAIN not supported"); + return C2_OMITTED; + } + + if (OK != setFlushMode()) return C2_CORRUPTED; + while (true) { + if (C2_OK != ensureDecoderState(pool)) { + mSignalledError = true; + work->workletsProcessed = 1u; + work->result = C2_CORRUPTED; + return C2_CORRUPTED; + } + C2GraphicView wView = mOutBlock->map().get(); + if (wView.error()) { + ALOGE("graphic view map failed %d", wView.error()); + return C2_CORRUPTED; + } + ivd_video_decode_ip_t s_decode_ip; + 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; + } + } + + return C2_OK; +} + +c2_status_t C2SoftAvcDec::drain( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool) { + return drainInternal(drainMode, pool, nullptr); +} + +class C2SoftAvcDecFactory : public C2ComponentFactory { +public: + C2SoftAvcDecFactory() : mHelper(std::static_pointer_cast<C2ReflectorHelper>( + GetCodec2PlatformComponentStore()->getParamReflector())) { + } + + virtual c2_status_t createComponent( + c2_node_id_t id, + std::shared_ptr<C2Component>* const component, + std::function<void(C2Component*)> deleter) override { + *component = std::shared_ptr<C2Component>( + new C2SoftAvcDec(COMPONENT_NAME, + id, + std::make_shared<C2SoftAvcDec::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual c2_status_t createInterface( + c2_node_id_t id, + std::shared_ptr<C2ComponentInterface>* const interface, + std::function<void(C2ComponentInterface*)> deleter) override { + *interface = std::shared_ptr<C2ComponentInterface>( + new SimpleInterface<C2SoftAvcDec::IntfImpl>( + COMPONENT_NAME, id, std::make_shared<C2SoftAvcDec::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual ~C2SoftAvcDecFactory() override = default; + +private: + std::shared_ptr<C2ReflectorHelper> mHelper; +}; + +} // namespace android + +extern "C" ::C2ComponentFactory* CreateCodec2Factory() { + ALOGV("in %s", __func__); + return new ::android::C2SoftAvcDecFactory(); +} + +extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) { + ALOGV("in %s", __func__); + delete factory; +}
diff --git a/media/codec2/components/avc/C2SoftAvcDec.h b/media/codec2/components/avc/C2SoftAvcDec.h new file mode 100644 index 0000000..72ee583 --- /dev/null +++ b/media/codec2/components/avc/C2SoftAvcDec.h
@@ -0,0 +1,198 @@ +/* + * Copyright 2017 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_C2_SOFT_AVC_DEC_H_ +#define ANDROID_C2_SOFT_AVC_DEC_H_ + +#include <sys/time.h> + +#include <media/stagefright/foundation/ColorUtils.h> + +#include <atomic> +#include <SimpleC2Component.h> + +#include "ih264_typedefs.h" +#include "iv.h" +#include "ivd.h" + +namespace android { + +#define ivdec_api_function ih264d_api_function +#define ivdext_create_ip_t ih264d_create_ip_t +#define ivdext_create_op_t ih264d_create_op_t +#define ivdext_delete_ip_t ih264d_delete_ip_t +#define ivdext_delete_op_t ih264d_delete_op_t +#define ivdext_ctl_set_num_cores_ip_t ih264d_ctl_set_num_cores_ip_t +#define ivdext_ctl_set_num_cores_op_t ih264d_ctl_set_num_cores_op_t +#define ivdext_ctl_get_vui_params_ip_t ih264d_ctl_get_vui_params_ip_t +#define ivdext_ctl_get_vui_params_op_t ih264d_ctl_get_vui_params_op_t +#define ALIGN64(x) ((((x) + 63) >> 6) << 6) +#define MAX_NUM_CORES 4 +#define IVDEXT_CMD_CTL_SET_NUM_CORES \ + (IVD_CONTROL_API_COMMAND_TYPE_T)IH264D_CMD_CTL_SET_NUM_CORES +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#define GETTIME(a, b) gettimeofday(a, b); +#define TIME_DIFF(start, end, diff) \ + diff = (((end).tv_sec - (start).tv_sec) * 1000000) + \ + ((end).tv_usec - (start).tv_usec); + +#ifdef FILE_DUMP_ENABLE + #define INPUT_DUMP_PATH "/sdcard/clips/avcd_input" + #define INPUT_DUMP_EXT "h264" + #define GENERATE_FILE_NAMES() { \ + GETTIME(&mTimeStart, NULL); \ + strcpy(mInFile, ""); \ + sprintf(mInFile, "%s_%ld.%ld.%s", INPUT_DUMP_PATH, \ + mTimeStart.tv_sec, mTimeStart.tv_usec, \ + INPUT_DUMP_EXT); \ + } + #define CREATE_DUMP_FILE(m_filename) { \ + FILE *fp = fopen(m_filename, "wb"); \ + if (fp != NULL) { \ + fclose(fp); \ + } else { \ + ALOGD("Could not open file %s", m_filename); \ + } \ + } + #define DUMP_TO_FILE(m_filename, m_buf, m_size, m_offset)\ + { \ + FILE *fp = fopen(m_filename, "ab"); \ + if (fp != NULL && m_buf != NULL && m_offset == 0) { \ + int i; \ + i = fwrite(m_buf, 1, m_size, fp); \ + ALOGD("fwrite ret %d to write %d", i, m_size); \ + if (i != (int) m_size) { \ + ALOGD("Error in fwrite, returned %d", i); \ + perror("Error in write to file"); \ + } \ + } else if (fp == NULL) { \ + ALOGD("Could not write to file %s", m_filename);\ + } \ + if (fp) { \ + fclose(fp); \ + } \ + } +#else /* FILE_DUMP_ENABLE */ + #define INPUT_DUMP_PATH + #define INPUT_DUMP_EXT + #define OUTPUT_DUMP_PATH + #define OUTPUT_DUMP_EXT + #define GENERATE_FILE_NAMES() + #define CREATE_DUMP_FILE(m_filename) + #define DUMP_TO_FILE(m_filename, m_buf, m_size, m_offset) +#endif /* FILE_DUMP_ENABLE */ + + +class C2SoftAvcDec : public SimpleC2Component { +public: + class IntfImpl; + C2SoftAvcDec(const char *name, c2_node_id_t id, const std::shared_ptr<IntfImpl> &intfImpl); + virtual ~C2SoftAvcDec(); + + // From SimpleC2Component + c2_status_t onInit() override; + c2_status_t onStop() override; + void onReset() override; + void onRelease() override; + c2_status_t onFlush_sm() override; + void process( + const std::unique_ptr<C2Work> &work, + const std::shared_ptr<C2BlockPool> &pool) override; + c2_status_t drain( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool) override; + +private: + status_t createDecoder(); + status_t setNumCores(); + 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, + ivd_video_decode_op_t *ps_decode_op, + C2ReadView *inBuffer, + C2GraphicView *outBuffer, + size_t inOffset, + size_t inSize, + uint32_t tsMarker); + bool getVuiParams(); + c2_status_t ensureDecoderState(const std::shared_ptr<C2BlockPool> &pool); + void finishWork(uint64_t index, const std::unique_ptr<C2Work> &work); + status_t setFlushMode(); + c2_status_t drainInternal( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool, + const std::unique_ptr<C2Work> &work); + status_t resetDecoder(); + void resetPlugin(); + status_t deleteDecoder(); + + std::shared_ptr<IntfImpl> mIntf; + + // 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 + enum { + kNotSupported, + kPreferBitstream, + kPreferContainer, + }; + + iv_obj_t *mDecHandle; + std::shared_ptr<C2GraphicBlock> mOutBlock; + uint8_t *mOutBufferFlush; + + size_t mNumCores; + IV_COLOR_FORMAT_T mIvColorFormat; + + uint32_t mWidth; + uint32_t mHeight; + uint32_t mStride; + bool mSignalledOutputEos; + bool mSignalledError; + bool mHeaderDecoded; + std::atomic_uint64_t mOutIndex; + // 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 { + uint8_t primaries; + uint8_t transfer; + uint8_t coeffs; + uint8_t fullRange; + + // default color aspects + VuiColorAspects() + : primaries(2), transfer(2), coeffs(2), fullRange(0) { } + + bool operator==(const VuiColorAspects &o) { + return primaries == o.primaries && transfer == o.transfer && coeffs == o.coeffs + && fullRange == o.fullRange; + } + } mBitstreamColorAspects; + + // profile + struct timeval mTimeStart; + struct timeval mTimeEnd; +#ifdef FILE_DUMP_ENABLE + char mInFile[200]; +#endif /* FILE_DUMP_ENABLE */ + + C2_DO_NOT_COPY(C2SoftAvcDec); +}; + +} // namespace android + +#endif // ANDROID_C2_SOFT_AVC_DEC_H_
diff --git a/media/codec2/components/avc/C2SoftAvcEnc.cpp b/media/codec2/components/avc/C2SoftAvcEnc.cpp new file mode 100644 index 0000000..b41c271 --- /dev/null +++ b/media/codec2/components/avc/C2SoftAvcEnc.cpp
@@ -0,0 +1,1765 @@ +/* + * Copyright 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 "C2SoftAvcEnc" +#include <log/log.h> +#include <utils/misc.h> + +#include <media/hardware/VideoAPI.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/foundation/AUtils.h> + +#include <C2Debug.h> +#include <C2PlatformSupport.h> +#include <Codec2BufferUtils.h> +#include <SimpleC2Interface.h> +#include <util/C2InterfaceHelper.h> + +#include "C2SoftAvcEnc.h" +#include "ih264e.h" +#include "ih264e_error.h" + +namespace android { + +namespace { + +constexpr char COMPONENT_NAME[] = "c2.android.avc.encoder"; + +void ParseGop( + const C2StreamGopTuning::output &gop, + uint32_t *syncInterval, uint32_t *iInterval, uint32_t *maxBframes) { + uint32_t syncInt = 1; + uint32_t iInt = 1; + for (size_t i = 0; i < gop.flexCount(); ++i) { + const C2GopLayerStruct &layer = gop.m.values[i]; + if (layer.count == UINT32_MAX) { + syncInt = 0; + } else if (syncInt <= UINT32_MAX / (layer.count + 1)) { + syncInt *= (layer.count + 1); + } + if ((layer.type_ & I_FRAME) == 0) { + if (layer.count == UINT32_MAX) { + iInt = 0; + } else if (iInt <= UINT32_MAX / (layer.count + 1)) { + iInt *= (layer.count + 1); + } + } + if (layer.type_ == C2Config::picture_type_t(P_FRAME | B_FRAME) && maxBframes) { + *maxBframes = layer.count; + } + } + if (syncInterval) { + *syncInterval = syncInt; + } + if (iInterval) { + *iInterval = iInt; + } +} + +} // namespace + +class C2SoftAvcEnc::IntfImpl : public SimpleInterface<void>::BaseParams { +public: + explicit IntfImpl(const std::shared_ptr<C2ReflectorHelper> &helper) + : SimpleInterface<void>::BaseParams( + helper, + COMPONENT_NAME, + C2Component::KIND_ENCODER, + C2Component::DOMAIN_VIDEO, + MEDIA_MIMETYPE_VIDEO_AVC) { + noPrivateBuffers(); // TODO: account for our buffers here + noInputReferences(); + noOutputReferences(); + noTimeStretch(); + setDerivedInstance(this); + + addParameter( + DefineParam(mUsage, C2_PARAMKEY_INPUT_STREAM_USAGE) + .withConstValue(new C2StreamUsageTuning::input( + 0u, (uint64_t)C2MemoryUsage::CPU_READ)) + .build()); + + addParameter( + DefineParam(mAttrib, C2_PARAMKEY_COMPONENT_ATTRIBUTES) + .withConstValue(new C2ComponentAttributesSetting( + C2Component::ATTRIB_IS_TEMPORAL)) + .build()); + + addParameter( + DefineParam(mSize, C2_PARAMKEY_PICTURE_SIZE) + .withDefault(new C2StreamPictureSizeInfo::input(0u, 320, 240)) + .withFields({ + C2F(mSize, width).inRange(2, 2560, 2), + C2F(mSize, height).inRange(2, 2560, 2), + }) + .withSetter(SizeSetter) + .build()); + + addParameter( + DefineParam(mGop, C2_PARAMKEY_GOP) + .withDefault(C2StreamGopTuning::output::AllocShared( + 0 /* flexCount */, 0u /* stream */)) + .withFields({C2F(mGop, m.values[0].type_).any(), + C2F(mGop, m.values[0].count).any()}) + .withSetter(GopSetter) + .build()); + + addParameter( + DefineParam(mActualInputDelay, C2_PARAMKEY_INPUT_DELAY) + .withDefault(new C2PortActualDelayTuning::input(DEFAULT_B_FRAMES)) + .withFields({C2F(mActualInputDelay, value).inRange(0, MAX_B_FRAMES)}) + .calculatedAs(InputDelaySetter, mGop) + .build()); + + addParameter( + DefineParam(mFrameRate, C2_PARAMKEY_FRAME_RATE) + .withDefault(new C2StreamFrameRateInfo::output(0u, 30.)) + // TODO: More restriction? + .withFields({C2F(mFrameRate, value).greaterThan(0.)}) + .withSetter(Setter<decltype(*mFrameRate)>::StrictValueWithNoDeps) + .build()); + + addParameter( + DefineParam(mBitrate, C2_PARAMKEY_BITRATE) + .withDefault(new C2StreamBitrateInfo::output(0u, 64000)) + .withFields({C2F(mBitrate, value).inRange(4096, 12000000)}) + .withSetter(BitrateSetter) + .build()); + + addParameter( + DefineParam(mIntraRefresh, C2_PARAMKEY_INTRA_REFRESH) + .withDefault(new C2StreamIntraRefreshTuning::output( + 0u, C2Config::INTRA_REFRESH_DISABLED, 0.)) + .withFields({ + C2F(mIntraRefresh, mode).oneOf({ + C2Config::INTRA_REFRESH_DISABLED, C2Config::INTRA_REFRESH_ARBITRARY }), + C2F(mIntraRefresh, period).any() + }) + .withSetter(IntraRefreshSetter) + .build()); + + addParameter( + DefineParam(mProfileLevel, C2_PARAMKEY_PROFILE_LEVEL) + .withDefault(new C2StreamProfileLevelInfo::output( + 0u, PROFILE_AVC_CONSTRAINED_BASELINE, LEVEL_AVC_4_1)) + .withFields({ + C2F(mProfileLevel, profile).oneOf({ + PROFILE_AVC_BASELINE, + PROFILE_AVC_CONSTRAINED_BASELINE, + PROFILE_AVC_MAIN, + }), + C2F(mProfileLevel, level).oneOf({ + LEVEL_AVC_1, + LEVEL_AVC_1B, + LEVEL_AVC_1_1, + LEVEL_AVC_1_2, + LEVEL_AVC_1_3, + LEVEL_AVC_2, + LEVEL_AVC_2_1, + LEVEL_AVC_2_2, + LEVEL_AVC_3, + LEVEL_AVC_3_1, + LEVEL_AVC_3_2, + LEVEL_AVC_4, + LEVEL_AVC_4_1, + LEVEL_AVC_4_2, + LEVEL_AVC_5, + }), + }) + .withSetter(ProfileLevelSetter, mSize, mFrameRate, mBitrate) + .build()); + + addParameter( + DefineParam(mRequestSync, C2_PARAMKEY_REQUEST_SYNC_FRAME) + .withDefault(new C2StreamRequestSyncFrameTuning::output(0u, C2_FALSE)) + .withFields({C2F(mRequestSync, value).oneOf({ C2_FALSE, C2_TRUE }) }) + .withSetter(Setter<decltype(*mRequestSync)>::NonStrictValueWithNoDeps) + .build()); + + addParameter( + DefineParam(mSyncFramePeriod, C2_PARAMKEY_SYNC_FRAME_INTERVAL) + .withDefault(new C2StreamSyncFrameIntervalTuning::output(0u, 1000000)) + .withFields({C2F(mSyncFramePeriod, value).any()}) + .withSetter(Setter<decltype(*mSyncFramePeriod)>::StrictValueWithNoDeps) + .build()); + } + + static C2R InputDelaySetter( + bool mayBlock, + C2P<C2PortActualDelayTuning::input> &me, + const C2P<C2StreamGopTuning::output> &gop) { + (void)mayBlock; + uint32_t maxBframes = 0; + ParseGop(gop.v, nullptr, nullptr, &maxBframes); + me.set().value = maxBframes; + return C2R::Ok(); + } + + static C2R BitrateSetter(bool mayBlock, C2P<C2StreamBitrateInfo::output> &me) { + (void)mayBlock; + C2R res = C2R::Ok(); + if (me.v.value <= 4096) { + me.set().value = 4096; + } + return res; + } + + static C2R SizeSetter(bool mayBlock, const C2P<C2StreamPictureSizeInfo::input> &oldMe, + C2P<C2StreamPictureSizeInfo::input> &me) { + (void)mayBlock; + C2R res = C2R::Ok(); + if (!me.F(me.v.width).supportsAtAll(me.v.width)) { + res = res.plus(C2SettingResultBuilder::BadValue(me.F(me.v.width))); + me.set().width = oldMe.v.width; + } + if (!me.F(me.v.height).supportsAtAll(me.v.height)) { + res = res.plus(C2SettingResultBuilder::BadValue(me.F(me.v.height))); + me.set().height = oldMe.v.height; + } + return res; + } + + static C2R ProfileLevelSetter( + bool mayBlock, + C2P<C2StreamProfileLevelInfo::output> &me, + const C2P<C2StreamPictureSizeInfo::input> &size, + const C2P<C2StreamFrameRateInfo::output> &frameRate, + const C2P<C2StreamBitrateInfo::output> &bitrate) { + (void)mayBlock; + if (!me.F(me.v.profile).supportsAtAll(me.v.profile)) { + me.set().profile = PROFILE_AVC_CONSTRAINED_BASELINE; + } + + struct LevelLimits { + C2Config::level_t level; + float mbsPerSec; + uint64_t mbs; + uint32_t bitrate; + }; + constexpr LevelLimits kLimits[] = { + { LEVEL_AVC_1, 1485, 99, 64000 }, + // Decoder does not properly handle level 1b. + // { LEVEL_AVC_1B, 1485, 99, 128000 }, + { LEVEL_AVC_1_1, 3000, 396, 192000 }, + { LEVEL_AVC_1_2, 6000, 396, 384000 }, + { LEVEL_AVC_1_3, 11880, 396, 768000 }, + { LEVEL_AVC_2, 11880, 396, 2000000 }, + { LEVEL_AVC_2_1, 19800, 792, 4000000 }, + { LEVEL_AVC_2_2, 20250, 1620, 4000000 }, + { LEVEL_AVC_3, 40500, 1620, 10000000 }, + { LEVEL_AVC_3_1, 108000, 3600, 14000000 }, + { LEVEL_AVC_3_2, 216000, 5120, 20000000 }, + { LEVEL_AVC_4, 245760, 8192, 20000000 }, + { LEVEL_AVC_4_1, 245760, 8192, 50000000 }, + { LEVEL_AVC_4_2, 522240, 8704, 50000000 }, + { LEVEL_AVC_5, 589824, 22080, 135000000 }, + }; + + uint64_t mbs = uint64_t((size.v.width + 15) / 16) * ((size.v.height + 15) / 16); + 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. + + bool found = false; + // By default needsUpdate = false in case the supplied level does meet + // the requirements. For Level 1b, we want to update the level anyway, + // so we set it to true in that case. + bool needsUpdate = (me.v.level == LEVEL_AVC_1B); + for (const LevelLimits &limit : kLimits) { + if (mbs <= limit.mbs && mbsPerSec <= limit.mbsPerSec && + bitrate.v.value <= limit.bitrate) { + // This is the lowest level that meets the requirements, and if + // we haven't seen the supplied level yet, that means we don't + // need the update. + if (needsUpdate) { + ALOGD("Given level %x does not cover current configuration: " + "adjusting to %x", me.v.level, limit.level); + me.set().level = limit.level; + } + found = true; + break; + } + if (me.v.level == limit.level) { + // We break out of the loop when the lowest feasible level is + // found. The fact that we're here means that our level doesn't + // meet the requirement and needs to be updated. + needsUpdate = true; + } + } + if (!found) { + // We set to the highest supported level. + me.set().level = LEVEL_AVC_5; + } + + return C2R::Ok(); + } + + static C2R IntraRefreshSetter(bool mayBlock, C2P<C2StreamIntraRefreshTuning::output> &me) { + (void)mayBlock; + C2R res = C2R::Ok(); + if (me.v.period < 1) { + me.set().mode = C2Config::INTRA_REFRESH_DISABLED; + me.set().period = 0; + } else { + // only support arbitrary mode (cyclic in our case) + me.set().mode = C2Config::INTRA_REFRESH_ARBITRARY; + } + return res; + } + + static C2R GopSetter(bool mayBlock, C2P<C2StreamGopTuning::output> &me) { + (void)mayBlock; + for (size_t i = 0; i < me.v.flexCount(); ++i) { + const C2GopLayerStruct &layer = me.v.m.values[0]; + if (layer.type_ == C2Config::picture_type_t(P_FRAME | B_FRAME) + && layer.count > MAX_B_FRAMES) { + me.set().m.values[i].count = MAX_B_FRAMES; + } + } + return C2R::Ok(); + } + + IV_PROFILE_T getProfile_l() const { + switch (mProfileLevel->profile) { + case PROFILE_AVC_CONSTRAINED_BASELINE: [[fallthrough]]; + case PROFILE_AVC_BASELINE: return IV_PROFILE_BASE; + case PROFILE_AVC_MAIN: return IV_PROFILE_MAIN; + default: + ALOGD("Unrecognized profile: %x", mProfileLevel->profile); + return IV_PROFILE_DEFAULT; + } + } + + UWORD32 getLevel_l() const { + struct Level { + C2Config::level_t c2Level; + UWORD32 avcLevel; + }; + constexpr Level levels[] = { + { LEVEL_AVC_1, 10 }, + { LEVEL_AVC_1B, 9 }, + { LEVEL_AVC_1_1, 11 }, + { LEVEL_AVC_1_2, 12 }, + { LEVEL_AVC_1_3, 13 }, + { LEVEL_AVC_2, 20 }, + { LEVEL_AVC_2_1, 21 }, + { LEVEL_AVC_2_2, 22 }, + { LEVEL_AVC_3, 30 }, + { LEVEL_AVC_3_1, 31 }, + { LEVEL_AVC_3_2, 32 }, + { LEVEL_AVC_4, 40 }, + { LEVEL_AVC_4_1, 41 }, + { LEVEL_AVC_4_2, 42 }, + { LEVEL_AVC_5, 50 }, + }; + for (const Level &level : levels) { + if (mProfileLevel->level == level.c2Level) { + return level.avcLevel; + } + } + ALOGD("Unrecognized level: %x", mProfileLevel->level); + return 41; + } + + uint32_t getSyncFramePeriod_l() const { + if (mSyncFramePeriod->value < 0 || mSyncFramePeriod->value == INT64_MAX) { + return 0; + } + double period = mSyncFramePeriod->value / 1e6 * mFrameRate->value; + return (uint32_t)c2_max(c2_min(period + 0.5, double(UINT32_MAX)), 1.); + } + + // unsafe getters + std::shared_ptr<C2StreamPictureSizeInfo::input> getSize_l() const { return mSize; } + std::shared_ptr<C2StreamIntraRefreshTuning::output> getIntraRefresh_l() const { return mIntraRefresh; } + std::shared_ptr<C2StreamFrameRateInfo::output> getFrameRate_l() const { return mFrameRate; } + std::shared_ptr<C2StreamBitrateInfo::output> getBitrate_l() const { return mBitrate; } + std::shared_ptr<C2StreamRequestSyncFrameTuning::output> getRequestSync_l() const { return mRequestSync; } + std::shared_ptr<C2StreamGopTuning::output> getGop_l() const { return mGop; } + +private: + std::shared_ptr<C2StreamUsageTuning::input> mUsage; + std::shared_ptr<C2StreamPictureSizeInfo::input> mSize; + std::shared_ptr<C2StreamFrameRateInfo::output> mFrameRate; + std::shared_ptr<C2StreamRequestSyncFrameTuning::output> mRequestSync; + std::shared_ptr<C2StreamIntraRefreshTuning::output> mIntraRefresh; + std::shared_ptr<C2StreamBitrateInfo::output> mBitrate; + std::shared_ptr<C2StreamProfileLevelInfo::output> mProfileLevel; + std::shared_ptr<C2StreamSyncFrameIntervalTuning::output> mSyncFramePeriod; + std::shared_ptr<C2StreamGopTuning::output> mGop; +}; + +#define ive_api_function ih264e_api_function + +namespace { + +// From external/libavc/encoder/ih264e_bitstream.h +constexpr uint32_t MIN_STREAM_SIZE = 0x800; + +static size_t GetCPUCoreCount() { + long cpuCoreCount = 1; +#if defined(_SC_NPROCESSORS_ONLN) + cpuCoreCount = sysconf(_SC_NPROCESSORS_ONLN); +#else + // _SC_NPROC_ONLN must be defined... + cpuCoreCount = sysconf(_SC_NPROC_ONLN); +#endif + CHECK(cpuCoreCount >= 1); + ALOGV("Number of CPU cores: %ld", cpuCoreCount); + return (size_t)cpuCoreCount; +} + +} // namespace + +C2SoftAvcEnc::C2SoftAvcEnc( + const char *name, c2_node_id_t id, const std::shared_ptr<IntfImpl> &intfImpl) + : SimpleC2Component(std::make_shared<SimpleInterface<IntfImpl>>(name, id, intfImpl)), + mIntf(intfImpl), + mIvVideoColorFormat(IV_YUV_420P), + mAVCEncProfile(IV_PROFILE_BASE), + mAVCEncLevel(41), + mStarted(false), + mSawInputEOS(false), + mSignalledError(false), + mCodecCtx(nullptr), + mOutBlock(nullptr), + // TODO: output buffer size + mOutBufferSize(524288) { + + // If dump is enabled, then open create an empty file + GENERATE_FILE_NAMES(); + CREATE_DUMP_FILE(mInFile); + CREATE_DUMP_FILE(mOutFile); + + initEncParams(); +} + +C2SoftAvcEnc::~C2SoftAvcEnc() { + onRelease(); +} + +c2_status_t C2SoftAvcEnc::onInit() { + return C2_OK; +} + +c2_status_t C2SoftAvcEnc::onStop() { + return C2_OK; +} + +void C2SoftAvcEnc::onReset() { + // TODO: use IVE_CMD_CTL_RESET? + releaseEncoder(); + if (mOutBlock) { + mOutBlock.reset(); + } + initEncParams(); +} + +void C2SoftAvcEnc::onRelease() { + releaseEncoder(); + if (mOutBlock) { + mOutBlock.reset(); + } +} + +c2_status_t C2SoftAvcEnc::onFlush_sm() { + // TODO: use IVE_CMD_CTL_FLUSH? + return C2_OK; +} + +void C2SoftAvcEnc::initEncParams() { + mCodecCtx = nullptr; + mMemRecords = nullptr; + mNumMemRecords = DEFAULT_MEM_REC_CNT; + mHeaderGenerated = 0; + mNumCores = GetCPUCoreCount(); + mArch = DEFAULT_ARCH; + mSliceMode = DEFAULT_SLICE_MODE; + mSliceParam = DEFAULT_SLICE_PARAM; + mHalfPelEnable = DEFAULT_HPEL; + mIInterval = DEFAULT_I_INTERVAL; + mIDRInterval = DEFAULT_IDR_INTERVAL; + mDisableDeblkLevel = DEFAULT_DISABLE_DEBLK_LEVEL; + mEnableFastSad = DEFAULT_ENABLE_FAST_SAD; + mEnableAltRef = DEFAULT_ENABLE_ALT_REF; + mEncSpeed = DEFAULT_ENC_SPEED; + mIntra4x4 = DEFAULT_INTRA4x4; + mConstrainedIntraFlag = DEFAULT_CONSTRAINED_INTRA; + mPSNREnable = DEFAULT_PSNR_ENABLE; + mReconEnable = DEFAULT_RECON_ENABLE; + mEntropyMode = DEFAULT_ENTROPY_MODE; + mBframes = DEFAULT_B_FRAMES; + + gettimeofday(&mTimeStart, nullptr); + gettimeofday(&mTimeEnd, nullptr); +} + +c2_status_t C2SoftAvcEnc::setDimensions() { + ive_ctl_set_dimensions_ip_t s_dimensions_ip; + ive_ctl_set_dimensions_op_t s_dimensions_op; + IV_STATUS_T status; + + s_dimensions_ip.e_cmd = IVE_CMD_VIDEO_CTL; + s_dimensions_ip.e_sub_cmd = IVE_CMD_CTL_SET_DIMENSIONS; + s_dimensions_ip.u4_ht = mSize->height; + s_dimensions_ip.u4_wd = mSize->width; + + s_dimensions_ip.u4_timestamp_high = -1; + s_dimensions_ip.u4_timestamp_low = -1; + + s_dimensions_ip.u4_size = sizeof(ive_ctl_set_dimensions_ip_t); + s_dimensions_op.u4_size = sizeof(ive_ctl_set_dimensions_op_t); + + status = ive_api_function(mCodecCtx, &s_dimensions_ip, &s_dimensions_op); + if (status != IV_SUCCESS) { + ALOGE("Unable to set frame dimensions = 0x%x\n", + s_dimensions_op.u4_error_code); + return C2_CORRUPTED; + } + return C2_OK; +} + +c2_status_t C2SoftAvcEnc::setNumCores() { + IV_STATUS_T status; + ive_ctl_set_num_cores_ip_t s_num_cores_ip; + ive_ctl_set_num_cores_op_t s_num_cores_op; + s_num_cores_ip.e_cmd = IVE_CMD_VIDEO_CTL; + s_num_cores_ip.e_sub_cmd = IVE_CMD_CTL_SET_NUM_CORES; + s_num_cores_ip.u4_num_cores = MIN(mNumCores, CODEC_MAX_CORES); + s_num_cores_ip.u4_timestamp_high = -1; + s_num_cores_ip.u4_timestamp_low = -1; + s_num_cores_ip.u4_size = sizeof(ive_ctl_set_num_cores_ip_t); + + s_num_cores_op.u4_size = sizeof(ive_ctl_set_num_cores_op_t); + + status = ive_api_function( + mCodecCtx, (void *) &s_num_cores_ip, (void *) &s_num_cores_op); + if (status != IV_SUCCESS) { + ALOGE("Unable to set processor params = 0x%x\n", + s_num_cores_op.u4_error_code); + return C2_CORRUPTED; + } + return C2_OK; +} + +c2_status_t C2SoftAvcEnc::setFrameRate() { + ive_ctl_set_frame_rate_ip_t s_frame_rate_ip; + ive_ctl_set_frame_rate_op_t s_frame_rate_op; + IV_STATUS_T status; + + s_frame_rate_ip.e_cmd = IVE_CMD_VIDEO_CTL; + s_frame_rate_ip.e_sub_cmd = IVE_CMD_CTL_SET_FRAMERATE; + + s_frame_rate_ip.u4_src_frame_rate = mFrameRate->value + 0.5; + s_frame_rate_ip.u4_tgt_frame_rate = mFrameRate->value + 0.5; + + s_frame_rate_ip.u4_timestamp_high = -1; + s_frame_rate_ip.u4_timestamp_low = -1; + + s_frame_rate_ip.u4_size = sizeof(ive_ctl_set_frame_rate_ip_t); + s_frame_rate_op.u4_size = sizeof(ive_ctl_set_frame_rate_op_t); + + status = ive_api_function(mCodecCtx, &s_frame_rate_ip, &s_frame_rate_op); + if (status != IV_SUCCESS) { + ALOGE("Unable to set frame rate = 0x%x\n", + s_frame_rate_op.u4_error_code); + return C2_CORRUPTED; + } + return C2_OK; +} + +c2_status_t C2SoftAvcEnc::setIpeParams() { + ive_ctl_set_ipe_params_ip_t s_ipe_params_ip; + ive_ctl_set_ipe_params_op_t s_ipe_params_op; + IV_STATUS_T status; + + s_ipe_params_ip.e_cmd = IVE_CMD_VIDEO_CTL; + s_ipe_params_ip.e_sub_cmd = IVE_CMD_CTL_SET_IPE_PARAMS; + + s_ipe_params_ip.u4_enable_intra_4x4 = mIntra4x4; + s_ipe_params_ip.u4_enc_speed_preset = mEncSpeed; + s_ipe_params_ip.u4_constrained_intra_pred = mConstrainedIntraFlag; + + s_ipe_params_ip.u4_timestamp_high = -1; + s_ipe_params_ip.u4_timestamp_low = -1; + + s_ipe_params_ip.u4_size = sizeof(ive_ctl_set_ipe_params_ip_t); + s_ipe_params_op.u4_size = sizeof(ive_ctl_set_ipe_params_op_t); + + status = ive_api_function(mCodecCtx, &s_ipe_params_ip, &s_ipe_params_op); + if (status != IV_SUCCESS) { + ALOGE("Unable to set ipe params = 0x%x\n", + s_ipe_params_op.u4_error_code); + return C2_CORRUPTED; + } + return C2_OK; +} + +c2_status_t C2SoftAvcEnc::setBitRate() { + ive_ctl_set_bitrate_ip_t s_bitrate_ip; + ive_ctl_set_bitrate_op_t s_bitrate_op; + IV_STATUS_T status; + + s_bitrate_ip.e_cmd = IVE_CMD_VIDEO_CTL; + s_bitrate_ip.e_sub_cmd = IVE_CMD_CTL_SET_BITRATE; + + s_bitrate_ip.u4_target_bitrate = mBitrate->value; + + s_bitrate_ip.u4_timestamp_high = -1; + s_bitrate_ip.u4_timestamp_low = -1; + + s_bitrate_ip.u4_size = sizeof(ive_ctl_set_bitrate_ip_t); + s_bitrate_op.u4_size = sizeof(ive_ctl_set_bitrate_op_t); + + status = ive_api_function(mCodecCtx, &s_bitrate_ip, &s_bitrate_op); + if (status != IV_SUCCESS) { + ALOGE("Unable to set bit rate = 0x%x\n", s_bitrate_op.u4_error_code); + return C2_CORRUPTED; + } + return C2_OK; +} + +c2_status_t C2SoftAvcEnc::setFrameType(IV_PICTURE_CODING_TYPE_T e_frame_type) { + ive_ctl_set_frame_type_ip_t s_frame_type_ip; + ive_ctl_set_frame_type_op_t s_frame_type_op; + IV_STATUS_T status; + s_frame_type_ip.e_cmd = IVE_CMD_VIDEO_CTL; + s_frame_type_ip.e_sub_cmd = IVE_CMD_CTL_SET_FRAMETYPE; + + s_frame_type_ip.e_frame_type = e_frame_type; + + s_frame_type_ip.u4_timestamp_high = -1; + s_frame_type_ip.u4_timestamp_low = -1; + + s_frame_type_ip.u4_size = sizeof(ive_ctl_set_frame_type_ip_t); + s_frame_type_op.u4_size = sizeof(ive_ctl_set_frame_type_op_t); + + status = ive_api_function(mCodecCtx, &s_frame_type_ip, &s_frame_type_op); + if (status != IV_SUCCESS) { + ALOGE("Unable to set frame type = 0x%x\n", + s_frame_type_op.u4_error_code); + return C2_CORRUPTED; + } + return C2_OK; +} + +c2_status_t C2SoftAvcEnc::setQp() { + ive_ctl_set_qp_ip_t s_qp_ip; + ive_ctl_set_qp_op_t s_qp_op; + IV_STATUS_T status; + + s_qp_ip.e_cmd = IVE_CMD_VIDEO_CTL; + s_qp_ip.e_sub_cmd = IVE_CMD_CTL_SET_QP; + + s_qp_ip.u4_i_qp = DEFAULT_I_QP; + s_qp_ip.u4_i_qp_max = DEFAULT_QP_MAX; + s_qp_ip.u4_i_qp_min = DEFAULT_QP_MIN; + + s_qp_ip.u4_p_qp = DEFAULT_P_QP; + s_qp_ip.u4_p_qp_max = DEFAULT_QP_MAX; + s_qp_ip.u4_p_qp_min = DEFAULT_QP_MIN; + + s_qp_ip.u4_b_qp = DEFAULT_P_QP; + s_qp_ip.u4_b_qp_max = DEFAULT_QP_MAX; + s_qp_ip.u4_b_qp_min = DEFAULT_QP_MIN; + + s_qp_ip.u4_timestamp_high = -1; + s_qp_ip.u4_timestamp_low = -1; + + s_qp_ip.u4_size = sizeof(ive_ctl_set_qp_ip_t); + s_qp_op.u4_size = sizeof(ive_ctl_set_qp_op_t); + + status = ive_api_function(mCodecCtx, &s_qp_ip, &s_qp_op); + if (status != IV_SUCCESS) { + ALOGE("Unable to set qp 0x%x\n", s_qp_op.u4_error_code); + return C2_CORRUPTED; + } + return C2_OK; +} + +c2_status_t C2SoftAvcEnc::setEncMode(IVE_ENC_MODE_T e_enc_mode) { + IV_STATUS_T status; + ive_ctl_set_enc_mode_ip_t s_enc_mode_ip; + ive_ctl_set_enc_mode_op_t s_enc_mode_op; + + s_enc_mode_ip.e_cmd = IVE_CMD_VIDEO_CTL; + s_enc_mode_ip.e_sub_cmd = IVE_CMD_CTL_SET_ENC_MODE; + + s_enc_mode_ip.e_enc_mode = e_enc_mode; + + s_enc_mode_ip.u4_timestamp_high = -1; + s_enc_mode_ip.u4_timestamp_low = -1; + + s_enc_mode_ip.u4_size = sizeof(ive_ctl_set_enc_mode_ip_t); + s_enc_mode_op.u4_size = sizeof(ive_ctl_set_enc_mode_op_t); + + status = ive_api_function(mCodecCtx, &s_enc_mode_ip, &s_enc_mode_op); + if (status != IV_SUCCESS) { + ALOGE("Unable to set in header encode mode = 0x%x\n", + s_enc_mode_op.u4_error_code); + return C2_CORRUPTED; + } + return C2_OK; +} + +c2_status_t C2SoftAvcEnc::setVbvParams() { + ive_ctl_set_vbv_params_ip_t s_vbv_ip; + ive_ctl_set_vbv_params_op_t s_vbv_op; + IV_STATUS_T status; + + s_vbv_ip.e_cmd = IVE_CMD_VIDEO_CTL; + s_vbv_ip.e_sub_cmd = IVE_CMD_CTL_SET_VBV_PARAMS; + + s_vbv_ip.u4_vbv_buf_size = 0; + s_vbv_ip.u4_vbv_buffer_delay = 1000; + + s_vbv_ip.u4_timestamp_high = -1; + s_vbv_ip.u4_timestamp_low = -1; + + s_vbv_ip.u4_size = sizeof(ive_ctl_set_vbv_params_ip_t); + s_vbv_op.u4_size = sizeof(ive_ctl_set_vbv_params_op_t); + + status = ive_api_function(mCodecCtx, &s_vbv_ip, &s_vbv_op); + if (status != IV_SUCCESS) { + ALOGE("Unable to set VBV params = 0x%x\n", s_vbv_op.u4_error_code); + return C2_CORRUPTED; + } + return C2_OK; +} + +c2_status_t C2SoftAvcEnc::setAirParams() { + ive_ctl_set_air_params_ip_t s_air_ip; + ive_ctl_set_air_params_op_t s_air_op; + IV_STATUS_T status; + + s_air_ip.e_cmd = IVE_CMD_VIDEO_CTL; + s_air_ip.e_sub_cmd = IVE_CMD_CTL_SET_AIR_PARAMS; + + s_air_ip.e_air_mode = + (mIntraRefresh->mode == C2Config::INTRA_REFRESH_DISABLED || mIntraRefresh->period < 1) + ? IVE_AIR_MODE_NONE : IVE_AIR_MODE_CYCLIC; + s_air_ip.u4_air_refresh_period = mIntraRefresh->period; + + s_air_ip.u4_timestamp_high = -1; + s_air_ip.u4_timestamp_low = -1; + + s_air_ip.u4_size = sizeof(ive_ctl_set_air_params_ip_t); + s_air_op.u4_size = sizeof(ive_ctl_set_air_params_op_t); + + status = ive_api_function(mCodecCtx, &s_air_ip, &s_air_op); + if (status != IV_SUCCESS) { + ALOGE("Unable to set air params = 0x%x\n", s_air_op.u4_error_code); + return C2_CORRUPTED; + } + return C2_OK; +} + +c2_status_t C2SoftAvcEnc::setMeParams() { + IV_STATUS_T status; + ive_ctl_set_me_params_ip_t s_me_params_ip; + ive_ctl_set_me_params_op_t s_me_params_op; + + s_me_params_ip.e_cmd = IVE_CMD_VIDEO_CTL; + s_me_params_ip.e_sub_cmd = IVE_CMD_CTL_SET_ME_PARAMS; + + s_me_params_ip.u4_enable_fast_sad = mEnableFastSad; + s_me_params_ip.u4_enable_alt_ref = mEnableAltRef; + + s_me_params_ip.u4_enable_hpel = mHalfPelEnable; + s_me_params_ip.u4_enable_qpel = DEFAULT_QPEL; + s_me_params_ip.u4_me_speed_preset = DEFAULT_ME_SPEED; + s_me_params_ip.u4_srch_rng_x = DEFAULT_SRCH_RNG_X; + s_me_params_ip.u4_srch_rng_y = DEFAULT_SRCH_RNG_Y; + + s_me_params_ip.u4_timestamp_high = -1; + s_me_params_ip.u4_timestamp_low = -1; + + s_me_params_ip.u4_size = sizeof(ive_ctl_set_me_params_ip_t); + s_me_params_op.u4_size = sizeof(ive_ctl_set_me_params_op_t); + + status = ive_api_function(mCodecCtx, &s_me_params_ip, &s_me_params_op); + if (status != IV_SUCCESS) { + ALOGE("Unable to set me params = 0x%x\n", s_me_params_op.u4_error_code); + return C2_CORRUPTED; + } + return C2_OK; +} + +c2_status_t C2SoftAvcEnc::setGopParams() { + IV_STATUS_T status; + ive_ctl_set_gop_params_ip_t s_gop_params_ip; + ive_ctl_set_gop_params_op_t s_gop_params_op; + + s_gop_params_ip.e_cmd = IVE_CMD_VIDEO_CTL; + s_gop_params_ip.e_sub_cmd = IVE_CMD_CTL_SET_GOP_PARAMS; + + s_gop_params_ip.u4_i_frm_interval = mIInterval; + s_gop_params_ip.u4_idr_frm_interval = mIDRInterval; + + s_gop_params_ip.u4_timestamp_high = -1; + s_gop_params_ip.u4_timestamp_low = -1; + + s_gop_params_ip.u4_size = sizeof(ive_ctl_set_gop_params_ip_t); + s_gop_params_op.u4_size = sizeof(ive_ctl_set_gop_params_op_t); + + status = ive_api_function(mCodecCtx, &s_gop_params_ip, &s_gop_params_op); + if (status != IV_SUCCESS) { + ALOGE("Unable to set GOP params = 0x%x\n", + s_gop_params_op.u4_error_code); + return C2_CORRUPTED; + } + return C2_OK; +} + +c2_status_t C2SoftAvcEnc::setProfileParams() { + IntfImpl::Lock lock = mIntf->lock(); + + IV_STATUS_T status; + ive_ctl_set_profile_params_ip_t s_profile_params_ip; + ive_ctl_set_profile_params_op_t s_profile_params_op; + + s_profile_params_ip.e_cmd = IVE_CMD_VIDEO_CTL; + s_profile_params_ip.e_sub_cmd = IVE_CMD_CTL_SET_PROFILE_PARAMS; + + s_profile_params_ip.e_profile = mIntf->getProfile_l(); + if (s_profile_params_ip.e_profile == IV_PROFILE_BASE) { + s_profile_params_ip.u4_entropy_coding_mode = 0; + } else { + s_profile_params_ip.u4_entropy_coding_mode = 1; + } + s_profile_params_ip.u4_timestamp_high = -1; + s_profile_params_ip.u4_timestamp_low = -1; + + s_profile_params_ip.u4_size = sizeof(ive_ctl_set_profile_params_ip_t); + s_profile_params_op.u4_size = sizeof(ive_ctl_set_profile_params_op_t); + lock.unlock(); + + status = ive_api_function(mCodecCtx, &s_profile_params_ip, &s_profile_params_op); + if (status != IV_SUCCESS) { + ALOGE("Unable to set profile params = 0x%x\n", + s_profile_params_op.u4_error_code); + return C2_CORRUPTED; + } + return C2_OK; +} + +c2_status_t C2SoftAvcEnc::setDeblockParams() { + IV_STATUS_T status; + ive_ctl_set_deblock_params_ip_t s_deblock_params_ip; + ive_ctl_set_deblock_params_op_t s_deblock_params_op; + + s_deblock_params_ip.e_cmd = IVE_CMD_VIDEO_CTL; + s_deblock_params_ip.e_sub_cmd = IVE_CMD_CTL_SET_DEBLOCK_PARAMS; + + s_deblock_params_ip.u4_disable_deblock_level = mDisableDeblkLevel; + + s_deblock_params_ip.u4_timestamp_high = -1; + s_deblock_params_ip.u4_timestamp_low = -1; + + s_deblock_params_ip.u4_size = sizeof(ive_ctl_set_deblock_params_ip_t); + s_deblock_params_op.u4_size = sizeof(ive_ctl_set_deblock_params_op_t); + + status = ive_api_function(mCodecCtx, &s_deblock_params_ip, &s_deblock_params_op); + if (status != IV_SUCCESS) { + ALOGE("Unable to enable/disable deblock params = 0x%x\n", + s_deblock_params_op.u4_error_code); + return C2_CORRUPTED; + } + return C2_OK; +} + +void C2SoftAvcEnc::logVersion() { + ive_ctl_getversioninfo_ip_t s_ctl_ip; + ive_ctl_getversioninfo_op_t s_ctl_op; + UWORD8 au1_buf[512]; + IV_STATUS_T status; + + s_ctl_ip.e_cmd = IVE_CMD_VIDEO_CTL; + s_ctl_ip.e_sub_cmd = IVE_CMD_CTL_GETVERSION; + s_ctl_ip.u4_size = sizeof(ive_ctl_getversioninfo_ip_t); + s_ctl_op.u4_size = sizeof(ive_ctl_getversioninfo_op_t); + s_ctl_ip.pu1_version = au1_buf; + s_ctl_ip.u4_version_bufsize = sizeof(au1_buf); + + status = ive_api_function(mCodecCtx, (void *) &s_ctl_ip, (void *) &s_ctl_op); + + if (status != IV_SUCCESS) { + ALOGE("Error in getting version: 0x%x", s_ctl_op.u4_error_code); + } else { + ALOGV("Ittiam encoder version: %s", (char *)s_ctl_ip.pu1_version); + } + return; +} + +c2_status_t C2SoftAvcEnc::initEncoder() { + IV_STATUS_T status; + WORD32 level; + + CHECK(!mStarted); + + c2_status_t errType = C2_OK; + + std::shared_ptr<C2StreamGopTuning::output> gop; + { + IntfImpl::Lock lock = mIntf->lock(); + mSize = mIntf->getSize_l(); + mBitrate = mIntf->getBitrate_l(); + mFrameRate = mIntf->getFrameRate_l(); + mIntraRefresh = mIntf->getIntraRefresh_l(); + mAVCEncLevel = mIntf->getLevel_l(); + mIInterval = mIntf->getSyncFramePeriod_l(); + mIDRInterval = mIntf->getSyncFramePeriod_l(); + gop = mIntf->getGop_l(); + } + if (gop && gop->flexCount() > 0) { + uint32_t syncInterval = 1; + uint32_t iInterval = 1; + uint32_t maxBframes = 0; + ParseGop(*gop, &syncInterval, &iInterval, &maxBframes); + if (syncInterval > 0) { + ALOGD("Updating IDR interval from GOP: old %u new %u", mIDRInterval, syncInterval); + mIDRInterval = syncInterval; + } + if (iInterval > 0) { + ALOGD("Updating I interval from GOP: old %u new %u", mIInterval, iInterval); + mIInterval = iInterval; + } + if (mBframes != maxBframes) { + ALOGD("Updating max B frames from GOP: old %u new %u", mBframes, maxBframes); + mBframes = maxBframes; + } + } + uint32_t width = mSize->width; + uint32_t height = mSize->height; + + mStride = width; + + // TODO + mIvVideoColorFormat = IV_YUV_420P; + + ALOGD("Params width %d height %d level %d colorFormat %d bframes %d", width, + height, mAVCEncLevel, mIvVideoColorFormat, mBframes); + + /* Getting Number of MemRecords */ + { + iv_num_mem_rec_ip_t s_num_mem_rec_ip; + iv_num_mem_rec_op_t s_num_mem_rec_op; + + s_num_mem_rec_ip.u4_size = sizeof(iv_num_mem_rec_ip_t); + s_num_mem_rec_op.u4_size = sizeof(iv_num_mem_rec_op_t); + + s_num_mem_rec_ip.e_cmd = IV_CMD_GET_NUM_MEM_REC; + + 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", + s_num_mem_rec_op.u4_error_code); + return C2_CORRUPTED; + } + + mNumMemRecords = s_num_mem_rec_op.u4_num_mem_rec; + } + + /* Allocate array to hold memory records */ + if (mNumMemRecords > SIZE_MAX / sizeof(iv_mem_rec_t)) { + ALOGE("requested memory size is too big."); + return C2_CORRUPTED; + } + mMemRecords = (iv_mem_rec_t *)malloc(mNumMemRecords * sizeof(iv_mem_rec_t)); + if (nullptr == mMemRecords) { + ALOGE("Unable to allocate memory for hold memory records: Size %zu", + mNumMemRecords * sizeof(iv_mem_rec_t)); + mSignalledError = true; + return C2_CORRUPTED; + } + + { + iv_mem_rec_t *ps_mem_rec; + 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 = 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; + + ps_mem_rec++; + } + } + + /* Getting MemRecords Attributes */ + { + iv_fill_mem_rec_ip_t s_fill_mem_rec_ip; + iv_fill_mem_rec_op_t s_fill_mem_rec_op; + + s_fill_mem_rec_ip.u4_size = sizeof(iv_fill_mem_rec_ip_t); + s_fill_mem_rec_op.u4_size = sizeof(iv_fill_mem_rec_op_t); + + s_fill_mem_rec_ip.e_cmd = IV_CMD_FILL_NUM_MEM_REC; + s_fill_mem_rec_ip.ps_mem_rec = mMemRecords; + s_fill_mem_rec_ip.u4_num_mem_rec = mNumMemRecords; + s_fill_mem_rec_ip.u4_max_wd = width; + s_fill_mem_rec_ip.u4_max_ht = height; + s_fill_mem_rec_ip.u4_max_level = mAVCEncLevel; + s_fill_mem_rec_ip.e_color_format = DEFAULT_INP_COLOR_FORMAT; + s_fill_mem_rec_ip.u4_max_ref_cnt = DEFAULT_MAX_REF_FRM; + s_fill_mem_rec_ip.u4_max_reorder_cnt = DEFAULT_MAX_REORDER_FRM; + 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(nullptr, &s_fill_mem_rec_ip, &s_fill_mem_rec_op); + + if (status != IV_SUCCESS) { + ALOGE("Fill memory records failed = 0x%x\n", + s_fill_mem_rec_op.u4_error_code); + return C2_CORRUPTED; + } + } + + /* Allocating Memory for Mem Records */ + { + WORD32 total_size; + iv_mem_rec_t *ps_mem_rec; + total_size = 0; + ps_mem_rec = mMemRecords; + + 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 == nullptr) { + ALOGE("Allocation failure for mem record id %zu size %u\n", i, + ps_mem_rec->u4_mem_size); + return C2_CORRUPTED; + + } + total_size += ps_mem_rec->u4_mem_size; + + ps_mem_rec++; + } + } + + /* Codec Instance Creation */ + { + ive_init_ip_t s_init_ip; + ive_init_op_t s_init_op; + + mCodecCtx = (iv_obj_t *)mMemRecords[0].pv_base; + mCodecCtx->u4_size = sizeof(iv_obj_t); + mCodecCtx->pv_fxns = (void *)ive_api_function; + + s_init_ip.u4_size = sizeof(ive_init_ip_t); + s_init_op.u4_size = sizeof(ive_init_op_t); + + s_init_ip.e_cmd = IV_CMD_INIT; + s_init_ip.u4_num_mem_rec = mNumMemRecords; + s_init_ip.ps_mem_rec = mMemRecords; + s_init_ip.u4_max_wd = width; + s_init_ip.u4_max_ht = height; + s_init_ip.u4_max_ref_cnt = DEFAULT_MAX_REF_FRM; + s_init_ip.u4_max_reorder_cnt = DEFAULT_MAX_REORDER_FRM; + s_init_ip.u4_max_level = mAVCEncLevel; + s_init_ip.e_inp_color_fmt = mIvVideoColorFormat; + + if (mReconEnable || mPSNREnable) { + s_init_ip.u4_enable_recon = 1; + } else { + s_init_ip.u4_enable_recon = 0; + } + s_init_ip.e_recon_color_fmt = DEFAULT_RECON_COLOR_FORMAT; + s_init_ip.e_rc_mode = DEFAULT_RC_MODE; + s_init_ip.u4_max_framerate = DEFAULT_MAX_FRAMERATE; + s_init_ip.u4_max_bitrate = DEFAULT_MAX_BITRATE; + s_init_ip.u4_num_bframes = mBframes; + s_init_ip.e_content_type = IV_PROGRESSIVE; + s_init_ip.u4_max_srch_rng_x = DEFAULT_MAX_SRCH_RANGE_X; + s_init_ip.u4_max_srch_rng_y = DEFAULT_MAX_SRCH_RANGE_Y; + s_init_ip.e_slice_mode = mSliceMode; + s_init_ip.u4_slice_param = mSliceParam; + s_init_ip.e_arch = mArch; + s_init_ip.e_soc = DEFAULT_SOC; + + status = ive_api_function(mCodecCtx, &s_init_ip, &s_init_op); + + if (status != IV_SUCCESS) { + ALOGE("Init encoder failed = 0x%x\n", s_init_op.u4_error_code); + return C2_CORRUPTED; + } + } + + /* Get Codec Version */ + logVersion(); + + /* set processor details */ + setNumCores(); + + /* Video control Set Frame dimensions */ + setDimensions(); + + /* Video control Set Frame rates */ + setFrameRate(); + + /* Video control Set IPE Params */ + setIpeParams(); + + /* Video control Set Bitrate */ + setBitRate(); + + /* Video control Set QP */ + setQp(); + + /* Video control Set AIR params */ + setAirParams(); + + /* Video control Set VBV params */ + setVbvParams(); + + /* Video control Set Motion estimation params */ + setMeParams(); + + /* Video control Set GOP params */ + setGopParams(); + + /* Video control Set Deblock params */ + setDeblockParams(); + + /* Video control Set Profile params */ + setProfileParams(); + + /* Video control Set in Encode header mode */ + setEncMode(IVE_ENC_MODE_HEADER); + + ALOGV("init_codec successfull"); + + mSpsPpsHeaderReceived = false; + mStarted = true; + + return C2_OK; +} + +c2_status_t C2SoftAvcEnc::releaseEncoder() { + IV_STATUS_T status = IV_SUCCESS; + iv_retrieve_mem_rec_ip_t s_retrieve_mem_ip; + iv_retrieve_mem_rec_op_t s_retrieve_mem_op; + iv_mem_rec_t *ps_mem_rec; + + if (!mStarted) { + return C2_OK; + } + + s_retrieve_mem_ip.u4_size = sizeof(iv_retrieve_mem_rec_ip_t); + s_retrieve_mem_op.u4_size = sizeof(iv_retrieve_mem_rec_op_t); + s_retrieve_mem_ip.e_cmd = IV_CMD_RETRIEVE_MEMREC; + s_retrieve_mem_ip.ps_mem_rec = mMemRecords; + + status = ive_api_function(mCodecCtx, &s_retrieve_mem_ip, &s_retrieve_mem_op); + + if (status != IV_SUCCESS) { + ALOGE("Unable to retrieve memory records = 0x%x\n", + s_retrieve_mem_op.u4_error_code); + return C2_CORRUPTED; + } + + /* Free memory records */ + ps_mem_rec = mMemRecords; + for (size_t i = 0; i < s_retrieve_mem_op.u4_num_mem_rec_filled; i++) { + if (ps_mem_rec) ive_aligned_free(ps_mem_rec->pv_base); + else { + ALOGE("memory record is null."); + return C2_CORRUPTED; + } + ps_mem_rec++; + } + + if (mMemRecords) free(mMemRecords); + + // clear other pointers into the space being free()d + mCodecCtx = nullptr; + + mStarted = false; + + return C2_OK; +} + +c2_status_t C2SoftAvcEnc::setEncodeArgs( + ive_video_encode_ip_t *ps_encode_ip, + ive_video_encode_op_t *ps_encode_op, + const C2GraphicView *const input, + uint8_t *base, + uint32_t capacity, + uint64_t workIndex) { + iv_raw_buf_t *ps_inp_raw_buf; + memset(ps_encode_ip, 0, sizeof(*ps_encode_ip)); + memset(ps_encode_op, 0, sizeof(*ps_encode_op)); + + ps_inp_raw_buf = &ps_encode_ip->s_inp_buf; + ps_encode_ip->s_out_buf.pv_buf = base; + ps_encode_ip->s_out_buf.u4_bytes = 0; + ps_encode_ip->s_out_buf.u4_bufsize = capacity; + ps_encode_ip->u4_size = sizeof(ive_video_encode_ip_t); + 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 = 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 = workIndex >> 32; + ps_encode_ip->u4_timestamp_low = workIndex & 0xFFFFFFFF; + ps_encode_op->s_out_buf.pv_buf = nullptr; + + /* Initialize color formats */ + memset(ps_inp_raw_buf, 0, sizeof(iv_raw_buf_t)); + ps_inp_raw_buf->u4_size = sizeof(iv_raw_buf_t); + ps_inp_raw_buf->e_color_fmt = mIvVideoColorFormat; + if (input == nullptr) { + if (mSawInputEOS) { + ps_encode_ip->u4_is_last = 1; + } + return C2_OK; + } + + if (input->width() < mSize->width || + input->height() < mSize->height) { + /* Expect width height to be configured */ + ALOGW("unexpected Capacity Aspect %d(%d) x %d(%d)", input->width(), + mSize->width, input->height(), mSize->height); + return C2_BAD_VALUE; + } + ALOGV("width = %d, height = %d", input->width(), input->height()); + const C2PlanarLayout &layout = input->layout(); + uint8_t *yPlane = const_cast<uint8_t *>(input->data()[C2PlanarLayout::PLANE_Y]); + uint8_t *uPlane = const_cast<uint8_t *>(input->data()[C2PlanarLayout::PLANE_U]); + uint8_t *vPlane = const_cast<uint8_t *>(input->data()[C2PlanarLayout::PLANE_V]); + int32_t yStride = layout.planes[C2PlanarLayout::PLANE_Y].rowInc; + int32_t uStride = layout.planes[C2PlanarLayout::PLANE_U].rowInc; + int32_t vStride = layout.planes[C2PlanarLayout::PLANE_V].rowInc; + + uint32_t width = mSize->width; + uint32_t height = mSize->height; + // width and height are always even (as block size is 16x16) + CHECK_EQ((width & 1u), 0u); + CHECK_EQ((height & 1u), 0u); + size_t yPlaneSize = width * height; + + switch (layout.type) { + case C2PlanarLayout::TYPE_RGB: + [[fallthrough]]; + case C2PlanarLayout::TYPE_RGBA: { + ALOGV("yPlaneSize = %zu", yPlaneSize); + MemoryBlock conversionBuffer = mConversionBuffers.fetch(yPlaneSize * 3 / 2); + mConversionBuffersInUse.emplace(conversionBuffer.data(), conversionBuffer); + yPlane = conversionBuffer.data(); + uPlane = yPlane + yPlaneSize; + vPlane = uPlane + yPlaneSize / 4; + yStride = width; + uStride = vStride = yStride / 2; + ConvertRGBToPlanarYUV(yPlane, yStride, height, conversionBuffer.size(), *input); + break; + } + case C2PlanarLayout::TYPE_YUV: { + if (!IsYUV420(*input)) { + ALOGE("input is not YUV420"); + return C2_BAD_VALUE; + } + + if (layout.planes[layout.PLANE_Y].colInc == 1 + && layout.planes[layout.PLANE_U].colInc == 1 + && layout.planes[layout.PLANE_V].colInc == 1 + && uStride == vStride + && yStride == 2 * vStride) { + // I420 compatible - already set up above + break; + } + + // copy to I420 + yStride = width; + uStride = vStride = yStride / 2; + MemoryBlock conversionBuffer = mConversionBuffers.fetch(yPlaneSize * 3 / 2); + mConversionBuffersInUse.emplace(conversionBuffer.data(), conversionBuffer); + MediaImage2 img = CreateYUV420PlanarMediaImage2(width, height, yStride, height); + status_t err = ImageCopy(conversionBuffer.data(), &img, *input); + if (err != OK) { + ALOGE("Buffer conversion failed: %d", err); + return C2_BAD_VALUE; + } + yPlane = conversionBuffer.data(); + uPlane = yPlane + yPlaneSize; + vPlane = uPlane + yPlaneSize / 4; + break; + + } + + case C2PlanarLayout::TYPE_YUVA: + ALOGE("YUVA plane type is not supported"); + return C2_BAD_VALUE; + + default: + ALOGE("Unrecognized plane type: %d", layout.type); + return C2_BAD_VALUE; + } + + switch (mIvVideoColorFormat) { + case IV_YUV_420P: + { + // input buffer is supposed to be const but Ittiam API wants bare pointer. + ps_inp_raw_buf->apv_bufs[0] = yPlane; + ps_inp_raw_buf->apv_bufs[1] = uPlane; + ps_inp_raw_buf->apv_bufs[2] = vPlane; + + ps_inp_raw_buf->au4_wd[0] = input->width(); + ps_inp_raw_buf->au4_wd[1] = input->width() / 2; + ps_inp_raw_buf->au4_wd[2] = input->width() / 2; + + ps_inp_raw_buf->au4_ht[0] = input->height(); + ps_inp_raw_buf->au4_ht[1] = input->height() / 2; + ps_inp_raw_buf->au4_ht[2] = input->height() / 2; + + ps_inp_raw_buf->au4_strd[0] = yStride; + ps_inp_raw_buf->au4_strd[1] = uStride; + ps_inp_raw_buf->au4_strd[2] = vStride; + break; + } + + case IV_YUV_422ILE: + { + // TODO + // ps_inp_raw_buf->apv_bufs[0] = pu1_buf; + // ps_inp_raw_buf->au4_wd[0] = mWidth * 2; + // ps_inp_raw_buf->au4_ht[0] = mHeight; + // ps_inp_raw_buf->au4_strd[0] = mStride * 2; + break; + } + + case IV_YUV_420SP_UV: + case IV_YUV_420SP_VU: + default: + { + ps_inp_raw_buf->apv_bufs[0] = yPlane; + ps_inp_raw_buf->apv_bufs[1] = uPlane; + + ps_inp_raw_buf->au4_wd[0] = input->width(); + ps_inp_raw_buf->au4_wd[1] = input->width(); + + ps_inp_raw_buf->au4_ht[0] = input->height(); + ps_inp_raw_buf->au4_ht[1] = input->height() / 2; + + ps_inp_raw_buf->au4_strd[0] = yStride; + ps_inp_raw_buf->au4_strd[1] = uStride; + break; + } + } + return C2_OK; +} + +void C2SoftAvcEnc::finishWork(uint64_t workIndex, const std::unique_ptr<C2Work> &work, + ive_video_encode_op_t *ps_encode_op) { + std::shared_ptr<C2Buffer> buffer = + createLinearBuffer(mOutBlock, 0, ps_encode_op->s_out_buf.u4_bytes); + if (IV_IDR_FRAME == ps_encode_op->u4_encoded_frame_type) { + ALOGV("IDR frame produced"); + buffer->setInfo(std::make_shared<C2StreamPictureTypeMaskInfo::output>( + 0u /* stream id */, C2Config::SYNC_FRAME)); + } + mOutBlock = nullptr; + + 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; + work->workletsProcessed = 1u; + }; + if (work && c2_cntr64_t(workIndex) == work->input.ordinal.frameIndex) { + fillWork(work); + if (mSawInputEOS) { + work->worklets.front()->output.flags = C2FrameData::FLAG_END_OF_STREAM; + } + } else { + finish(workIndex, fillWork); + } +} + +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->worklets.front()->output.flags = work->input.flags; + + IV_STATUS_T status; + WORD32 timeDelay = 0; + WORD32 timeTaken = 0; + uint64_t workIndex = work->input.ordinal.frameIndex.peekull(); + + // Initialize encoder if not already initialized + if (mCodecCtx == nullptr) { + if (C2_OK != initEncoder()) { + ALOGE("Failed to initialize encoder"); + mSignalledError = true; + work->result = C2_CORRUPTED; + work->workletsProcessed = 1u; + return; + } + } + if (mSignalledError) { + return; + } + // while (!mSawOutputEOS && !outQueue.empty()) { + c2_status_t error; + ive_video_encode_ip_t s_encode_ip; + ive_video_encode_op_t s_encode_op; + memset(&s_encode_op, 0, sizeof(s_encode_op)); + + if (!mSpsPpsHeaderReceived) { + constexpr uint32_t kHeaderLength = MIN_STREAM_SIZE; + uint8_t header[kHeaderLength]; + error = setEncodeArgs( + &s_encode_ip, &s_encode_op, nullptr, header, kHeaderLength, workIndex); + if (error != C2_OK) { + ALOGE("setEncodeArgs failed: %d", error); + mSignalledError = true; + work->result = C2_CORRUPTED; + work->workletsProcessed = 1u; + return; + } + status = ive_api_function(mCodecCtx, &s_encode_ip, &s_encode_op); + + if (IV_SUCCESS != status) { + ALOGE("Encode header failed = 0x%x\n", + s_encode_op.u4_error_code); + work->workletsProcessed = 1u; + return; + } else { + ALOGV("Bytes Generated in header %d\n", + s_encode_op.s_out_buf.u4_bytes); + } + + mSpsPpsHeaderReceived = true; + + std::unique_ptr<C2StreamInitDataInfo::output> csd = + C2StreamInitDataInfo::output::AllocUnique(s_encode_op.s_out_buf.u4_bytes, 0u); + if (!csd) { + ALOGE("CSD allocation failed"); + mSignalledError = true; + work->result = C2_NO_MEMORY; + work->workletsProcessed = 1u; + return; + } + memcpy(csd->m.value, header, s_encode_op.s_out_buf.u4_bytes); + work->worklets.front()->output.configUpdate.push_back(std::move(csd)); + + DUMP_TO_FILE( + mOutFile, csd->m.value, csd->flexCount()); + if (work->input.buffers.empty()) { + work->workletsProcessed = 1u; + return; + } + } + + // handle dynamic config parameters + { + IntfImpl::Lock lock = mIntf->lock(); + std::shared_ptr<C2StreamIntraRefreshTuning::output> intraRefresh = mIntf->getIntraRefresh_l(); + std::shared_ptr<C2StreamBitrateInfo::output> bitrate = mIntf->getBitrate_l(); + std::shared_ptr<C2StreamRequestSyncFrameTuning::output> requestSync = mIntf->getRequestSync_l(); + lock.unlock(); + + if (bitrate != mBitrate) { + mBitrate = bitrate; + setBitRate(); + } + + if (intraRefresh != mIntraRefresh) { + mIntraRefresh = intraRefresh; + setAirParams(); + } + + if (requestSync != mRequestSync) { + // we can handle IDR immediately + if (requestSync->value) { + // unset request + C2StreamRequestSyncFrameTuning::output clearSync(0u, C2_FALSE); + std::vector<std::unique_ptr<C2SettingResult>> failures; + mIntf->config({ &clearSync }, C2_MAY_BLOCK, &failures); + ALOGV("Got sync request"); + setFrameType(IV_IDR_FRAME); + } + mRequestSync = requestSync; + } + } + + if (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) { + mSawInputEOS = true; + } + + /* In normal mode, store inputBufferInfo and this will be returned + when encoder consumes this input */ + // if (!mInputDataIsMeta && (inputBufferInfo != NULL)) { + // for (size_t i = 0; i < MAX_INPUT_BUFFER_HEADERS; i++) { + // if (NULL == mInputBufferInfo[i]) { + // mInputBufferInfo[i] = inputBufferInfo; + // break; + // } + // } + // } + std::shared_ptr<const C2GraphicView> view; + std::shared_ptr<C2Buffer> inputBuffer; + if (!work->input.buffers.empty()) { + inputBuffer = work->input.buffers[0]; + view = std::make_shared<const C2GraphicView>( + inputBuffer->data().graphicBlocks().front().map().get()); + if (view->error() != C2_OK) { + ALOGE("graphic view map err = %d", view->error()); + work->workletsProcessed = 1u; + return; + } + } + + do { + if (mSawInputEOS && work->input.buffers.empty()) break; + if (!mOutBlock) { + C2MemoryUsage usage = {C2MemoryUsage::CPU_READ, + C2MemoryUsage::CPU_WRITE}; + // TODO: error handling, proper usage, etc. + c2_status_t err = + pool->fetchLinearBlock(mOutBufferSize, usage, &mOutBlock); + if (err != C2_OK) { + ALOGE("fetch linear block err = %d", err); + work->result = err; + work->workletsProcessed = 1u; + return; + } + } + C2WriteView wView = mOutBlock->map().get(); + if (wView.error() != C2_OK) { + ALOGE("write view map err = %d", wView.error()); + work->result = wView.error(); + work->workletsProcessed = 1u; + return; + } + + error = setEncodeArgs( + &s_encode_ip, &s_encode_op, view.get(), wView.base(), wView.capacity(), workIndex); + if (error != C2_OK) { + ALOGE("setEncodeArgs failed : %d", error); + mSignalledError = true; + work->result = error; + work->workletsProcessed = 1u; + return; + } + + // DUMP_TO_FILE( + // mInFile, s_encode_ip.s_inp_buf.apv_bufs[0], + // (mHeight * mStride * 3 / 2)); + + GETTIME(&mTimeStart, nullptr); + /* Compute time elapsed between end of previous decode() + * to start of current decode() */ + TIME_DIFF(mTimeEnd, mTimeStart, timeDelay); + status = ive_api_function(mCodecCtx, &s_encode_ip, &s_encode_op); + + if (IV_SUCCESS != status) { + if ((s_encode_op.u4_error_code & 0xFF) == IH264E_BITSTREAM_BUFFER_OVERFLOW) { + // TODO: use IVE_CMD_CTL_GETBUFINFO for proper max input size? + mOutBufferSize *= 2; + mOutBlock.reset(); + continue; + } + ALOGE("Encode Frame failed = 0x%x\n", + s_encode_op.u4_error_code); + mSignalledError = true; + work->result = C2_CORRUPTED; + work->workletsProcessed = 1u; + return; + } + } while (IV_SUCCESS != status); + + // Hold input buffer reference + if (inputBuffer) { + mBuffers[s_encode_ip.s_inp_buf.apv_bufs[0]] = inputBuffer; + } + + GETTIME(&mTimeEnd, nullptr); + /* Compute time taken for decode() */ + TIME_DIFF(mTimeStart, mTimeEnd, timeTaken); + + ALOGV("timeTaken=%6d delay=%6d numBytes=%6d", timeTaken, timeDelay, + s_encode_op.s_out_buf.u4_bytes); + + void *freed = s_encode_op.s_inp_buf.apv_bufs[0]; + /* If encoder frees up an input buffer, mark it as free */ + if (freed != nullptr) { + if (mBuffers.count(freed) == 0u) { + ALOGD("buffer not tracked"); + } else { + // Release input buffer reference + mBuffers.erase(freed); + mConversionBuffersInUse.erase(freed); + } + } + + if (s_encode_op.output_present) { + if (!s_encode_op.s_out_buf.u4_bytes) { + ALOGE("Error: Output present but bytes generated is zero"); + mSignalledError = true; + work->result = C2_CORRUPTED; + work->workletsProcessed = 1u; + return; + } + uint64_t workId = ((uint64_t)s_encode_op.u4_timestamp_high << 32) | + s_encode_op.u4_timestamp_low; + finishWork(workId, work, &s_encode_op); + } + if (mSawInputEOS) { + drainInternal(DRAIN_COMPONENT_WITH_EOS, pool, work); + } +} + +c2_status_t C2SoftAvcEnc::drainInternal( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool, + const std::unique_ptr<C2Work> &work) { + + if (drainMode == NO_DRAIN) { + ALOGW("drain with NO_DRAIN: no-op"); + return C2_OK; + } + if (drainMode == DRAIN_CHAIN) { + ALOGW("DRAIN_CHAIN not supported"); + return C2_OMITTED; + } + + while (true) { + if (!mOutBlock) { + C2MemoryUsage usage = {C2MemoryUsage::CPU_READ, + C2MemoryUsage::CPU_WRITE}; + // TODO: error handling, proper usage, etc. + c2_status_t err = + pool->fetchLinearBlock(mOutBufferSize, usage, &mOutBlock); + if (err != C2_OK) { + ALOGE("fetch linear block err = %d", err); + work->result = err; + work->workletsProcessed = 1u; + return err; + } + } + C2WriteView wView = mOutBlock->map().get(); + if (wView.error()) { + ALOGE("graphic view map failed %d", wView.error()); + return C2_CORRUPTED; + } + ive_video_encode_ip_t s_encode_ip; + ive_video_encode_op_t s_encode_op; + if (C2_OK != setEncodeArgs(&s_encode_ip, &s_encode_op, nullptr, + wView.base(), wView.capacity(), 0)) { + ALOGE("setEncodeArgs failed for drainInternal"); + mSignalledError = true; + work->result = C2_CORRUPTED; + work->workletsProcessed = 1u; + return C2_CORRUPTED; + } + (void)ive_api_function(mCodecCtx, &s_encode_ip, &s_encode_op); + + void *freed = s_encode_op.s_inp_buf.apv_bufs[0]; + /* If encoder frees up an input buffer, mark it as free */ + if (freed != nullptr) { + if (mBuffers.count(freed) == 0u) { + ALOGD("buffer not tracked"); + } else { + // Release input buffer reference + mBuffers.erase(freed); + mConversionBuffersInUse.erase(freed); + } + } + + if (s_encode_op.output_present) { + uint64_t workId = ((uint64_t)s_encode_op.u4_timestamp_high << 32) | + s_encode_op.u4_timestamp_low; + finishWork(workId, work, &s_encode_op); + } else { + if (work->workletsProcessed != 1u) { + 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; + } + break; + } + } + + return C2_OK; +} + +c2_status_t C2SoftAvcEnc::drain( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool) { + return drainInternal(drainMode, pool, nullptr); +} + +class C2SoftAvcEncFactory : public C2ComponentFactory { +public: + C2SoftAvcEncFactory() : mHelper(std::static_pointer_cast<C2ReflectorHelper>( + GetCodec2PlatformComponentStore()->getParamReflector())) { + } + + virtual c2_status_t createComponent( + c2_node_id_t id, + std::shared_ptr<C2Component>* const component, + std::function<void(C2Component*)> deleter) override { + *component = std::shared_ptr<C2Component>( + new C2SoftAvcEnc(COMPONENT_NAME, + id, + std::make_shared<C2SoftAvcEnc::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual c2_status_t createInterface( + c2_node_id_t id, + std::shared_ptr<C2ComponentInterface>* const interface, + std::function<void(C2ComponentInterface*)> deleter) override { + *interface = std::shared_ptr<C2ComponentInterface>( + new SimpleInterface<C2SoftAvcEnc::IntfImpl>( + COMPONENT_NAME, id, std::make_shared<C2SoftAvcEnc::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual ~C2SoftAvcEncFactory() override = default; + +private: + std::shared_ptr<C2ReflectorHelper> mHelper; +}; + +} // namespace android + +extern "C" ::C2ComponentFactory* CreateCodec2Factory() { + ALOGV("in %s", __func__); + return new ::android::C2SoftAvcEncFactory(); +} + +extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) { + ALOGV("in %s", __func__); + delete factory; +}
diff --git a/media/codec2/components/avc/C2SoftAvcEnc.h b/media/codec2/components/avc/C2SoftAvcEnc.h new file mode 100644 index 0000000..555055b --- /dev/null +++ b/media/codec2/components/avc/C2SoftAvcEnc.h
@@ -0,0 +1,304 @@ +/* + * Copyright 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_C2_SOFT_AVC_ENC_H__ +#define ANDROID_C2_SOFT_AVC_ENC_H__ + +#include <map> + +#include <utils/Vector.h> + +#include <SimpleC2Component.h> + +#include "ih264_typedefs.h" +#include "iv2.h" +#include "ive2.h" + +namespace android { + +#define CODEC_MAX_CORES 4 +#define LEN_STATUS_BUFFER (10 * 1024) +#define MAX_VBV_BUFF_SIZE (120 * 16384) +#define MAX_NUM_IO_BUFS 3 +#define MAX_B_FRAMES 1 + +#define DEFAULT_MAX_REF_FRM 2 +#define DEFAULT_MAX_REORDER_FRM 0 +#define DEFAULT_QP_MIN 10 +#define DEFAULT_QP_MAX 40 +#define DEFAULT_MAX_BITRATE 240000000 +#define DEFAULT_MAX_SRCH_RANGE_X 256 +#define DEFAULT_MAX_SRCH_RANGE_Y 256 +#define DEFAULT_MAX_FRAMERATE 120000 +#define DEFAULT_NUM_CORES 1 +#define DEFAULT_NUM_CORES_PRE_ENC 0 +#define DEFAULT_FPS 30 +#define DEFAULT_ENC_SPEED IVE_NORMAL + +#define DEFAULT_MEM_REC_CNT 0 +#define DEFAULT_RECON_ENABLE 0 +#define DEFAULT_CHKSUM_ENABLE 0 +#define DEFAULT_START_FRM 0 +#define DEFAULT_NUM_FRMS 0xFFFFFFFF +#define DEFAULT_INP_COLOR_FORMAT IV_YUV_420SP_VU +#define DEFAULT_RECON_COLOR_FORMAT IV_YUV_420P +#define DEFAULT_LOOPBACK 0 +#define DEFAULT_SRC_FRAME_RATE 30 +#define DEFAULT_TGT_FRAME_RATE 30 +#define DEFAULT_MAX_WD 1920 +#define DEFAULT_MAX_HT 1920 +#define DEFAULT_MAX_LEVEL 41 +#define DEFAULT_STRIDE 0 +#define DEFAULT_WD 1280 +#define DEFAULT_HT 720 +#define DEFAULT_PSNR_ENABLE 0 +#define DEFAULT_ME_SPEED 100 +#define DEFAULT_ENABLE_FAST_SAD 0 +#define DEFAULT_ENABLE_ALT_REF 0 +#define DEFAULT_RC_MODE IVE_RC_STORAGE +#define DEFAULT_BITRATE 6000000 +#define DEFAULT_I_QP 22 +#define DEFAULT_I_QP_MAX DEFAULT_QP_MAX +#define DEFAULT_I_QP_MIN DEFAULT_QP_MIN +#define DEFAULT_P_QP 28 +#define DEFAULT_P_QP_MAX DEFAULT_QP_MAX +#define DEFAULT_P_QP_MIN DEFAULT_QP_MIN +#define DEFAULT_B_QP 22 +#define DEFAULT_B_QP_MAX DEFAULT_QP_MAX +#define DEFAULT_B_QP_MIN DEFAULT_QP_MIN +#define DEFAULT_AIR IVE_AIR_MODE_NONE +#define DEFAULT_AIR_REFRESH_PERIOD 30 +#define DEFAULT_SRCH_RNG_X 64 +#define DEFAULT_SRCH_RNG_Y 48 +#define DEFAULT_I_INTERVAL 30 +#define DEFAULT_IDR_INTERVAL 1000 +#define DEFAULT_B_FRAMES 0 +#define DEFAULT_DISABLE_DEBLK_LEVEL 0 +#define DEFAULT_HPEL 1 +#define DEFAULT_QPEL 1 +#define DEFAULT_I4 1 +#define DEFAULT_EPROFILE IV_PROFILE_BASE +#define DEFAULT_ENTROPY_MODE 0 +#define DEFAULT_SLICE_MODE IVE_SLICE_MODE_NONE +#define DEFAULT_SLICE_PARAM 256 +#define DEFAULT_ARCH ARCH_ARM_A9Q +#define DEFAULT_SOC SOC_GENERIC +#define DEFAULT_INTRA4x4 0 +#define STRLENGTH 500 +#define DEFAULT_CONSTRAINED_INTRA 0 + +#define MIN(a, b) ((a) < (b))? (a) : (b) +#define MAX(a, b) ((a) > (b))? (a) : (b) +#define ALIGN16(x) ((((x) + 15) >> 4) << 4) +#define ALIGN128(x) ((((x) + 127) >> 7) << 7) +#define ALIGN4096(x) ((((x) + 4095) >> 12) << 12) + +/** Used to remove warnings about unused parameters */ +#define UNUSED(x) ((void)(x)) + +/** Get time */ +#define GETTIME(a, b) gettimeofday(a, b); + +/** Compute difference between start and end */ +#define TIME_DIFF(start, end, diff) \ + diff = (((end).tv_sec - (start).tv_sec) * 1000000) + \ + ((end).tv_usec - (start).tv_usec); + +#define ive_aligned_malloc(alignment, size) memalign(alignment, size) +#define ive_aligned_free(buf) free(buf) + +struct C2SoftAvcEnc : public SimpleC2Component { + class IntfImpl; + + C2SoftAvcEnc(const char *name, c2_node_id_t id, const std::shared_ptr<IntfImpl> &intfImpl); + + // From SimpleC2Component + c2_status_t onInit() override; + c2_status_t onStop() override; + void onReset() override; + void onRelease() override; + c2_status_t onFlush_sm() override; + void process( + const std::unique_ptr<C2Work> &work, + const std::shared_ptr<C2BlockPool> &pool) override; + c2_status_t drain( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool) override; + +protected: + virtual ~C2SoftAvcEnc(); + +private: + // OMX input buffer's timestamp and flags + typedef struct { + int64_t mTimeUs; + int32_t mFlags; + } InputBufferInfo; + + std::shared_ptr<IntfImpl> mIntf; + + int32_t mStride; + + struct timeval mTimeStart; // Time at the start of decode() + struct timeval mTimeEnd; // Time at the end of decode() + +#ifdef FILE_DUMP_ENABLE + char mInFile[200]; + char mOutFile[200]; +#endif /* FILE_DUMP_ENABLE */ + + IV_COLOR_FORMAT_T mIvVideoColorFormat; + + IV_PROFILE_T mAVCEncProfile __unused; + WORD32 mAVCEncLevel; + bool mStarted; + bool mSpsPpsHeaderReceived; + + bool mSawInputEOS; + bool mSignalledError; + bool mIntra4x4; + bool mEnableFastSad; + bool mEnableAltRef; + bool mReconEnable; + bool mPSNREnable; + bool mEntropyMode; + bool mConstrainedIntraFlag; + IVE_SPEED_CONFIG mEncSpeed; + + iv_obj_t *mCodecCtx; // Codec context + iv_mem_rec_t *mMemRecords; // Memory records requested by the codec + size_t mNumMemRecords; // Number of memory records requested by codec + size_t mNumCores; // Number of cores used by the codec + + std::shared_ptr<C2LinearBlock> mOutBlock; + + // configurations used by component in process + // (TODO: keep this in intf but make them internal only) + std::shared_ptr<C2StreamPictureSizeInfo::input> mSize; + std::shared_ptr<C2StreamIntraRefreshTuning::output> mIntraRefresh; + std::shared_ptr<C2StreamFrameRateInfo::output> mFrameRate; + std::shared_ptr<C2StreamBitrateInfo::output> mBitrate; + std::shared_ptr<C2StreamRequestSyncFrameTuning::output> mRequestSync; + + uint32_t mOutBufferSize; + UWORD32 mHeaderGenerated; + UWORD32 mBframes; + IV_ARCH_T mArch; + IVE_SLICE_MODE_T mSliceMode; + UWORD32 mSliceParam; + bool mHalfPelEnable; + UWORD32 mIInterval; + UWORD32 mIDRInterval; + UWORD32 mDisableDeblkLevel; + std::map<const void *, std::shared_ptr<C2Buffer>> mBuffers; + MemoryBlockPool mConversionBuffers; + std::map<const void *, MemoryBlock> mConversionBuffersInUse; + + void initEncParams(); + c2_status_t initEncoder(); + c2_status_t releaseEncoder(); + + c2_status_t setFrameType(IV_PICTURE_CODING_TYPE_T e_frame_type); + c2_status_t setQp(); + c2_status_t setEncMode(IVE_ENC_MODE_T e_enc_mode); + c2_status_t setDimensions(); + c2_status_t setNumCores(); + c2_status_t setFrameRate(); + c2_status_t setIpeParams(); + c2_status_t setBitRate(); + c2_status_t setAirParams(); + c2_status_t setMeParams(); + c2_status_t setGopParams(); + c2_status_t setProfileParams(); + c2_status_t setDeblockParams(); + c2_status_t setVbvParams(); + void logVersion(); + c2_status_t setEncodeArgs( + ive_video_encode_ip_t *ps_encode_ip, + ive_video_encode_op_t *ps_encode_op, + const C2GraphicView *const input, + uint8_t *base, + uint32_t capacity, + uint64_t workIndex); + void finishWork(uint64_t workIndex, + const std::unique_ptr<C2Work> &work, + ive_video_encode_op_t *ps_encode_op); + c2_status_t drainInternal(uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool, + const std::unique_ptr<C2Work> &work); + + C2_DO_NOT_COPY(C2SoftAvcEnc); +}; + +#ifdef FILE_DUMP_ENABLE + +#define INPUT_DUMP_PATH "/sdcard/media/avce_input" +#define INPUT_DUMP_EXT "yuv" +#define OUTPUT_DUMP_PATH "/sdcard/media/avce_output" +#define OUTPUT_DUMP_EXT "h264" + +#define GENERATE_FILE_NAMES() { \ + GETTIME(&mTimeStart, NULL); \ + strcpy(mInFile, ""); \ + sprintf(mInFile, "%s_%ld.%ld.%s", INPUT_DUMP_PATH, \ + mTimeStart.tv_sec, mTimeStart.tv_usec, \ + INPUT_DUMP_EXT); \ + strcpy(mOutFile, ""); \ + sprintf(mOutFile, "%s_%ld.%ld.%s", OUTPUT_DUMP_PATH,\ + mTimeStart.tv_sec, mTimeStart.tv_usec, \ + OUTPUT_DUMP_EXT); \ +} + +#define CREATE_DUMP_FILE(m_filename) { \ + FILE *fp = fopen(m_filename, "wb"); \ + if (fp != NULL) { \ + ALOGD("Opened file %s", m_filename); \ + fclose(fp); \ + } else { \ + ALOGD("Could not open file %s", m_filename); \ + } \ +} +#define DUMP_TO_FILE(m_filename, m_buf, m_size) \ +{ \ + FILE *fp = fopen(m_filename, "ab"); \ + if (fp != NULL && m_buf != NULL) { \ + int i; \ + i = fwrite(m_buf, 1, m_size, fp); \ + ALOGD("fwrite ret %d to write %d", i, m_size); \ + if (i != (int)m_size) { \ + ALOGD("Error in fwrite, returned %d", i); \ + perror("Error in write to file"); \ + } \ + fclose(fp); \ + } else { \ + ALOGD("Could not write to file %s", m_filename);\ + if (fp != NULL) \ + fclose(fp); \ + } \ +} +#else /* FILE_DUMP_ENABLE */ +#define INPUT_DUMP_PATH +#define INPUT_DUMP_EXT +#define OUTPUT_DUMP_PATH +#define OUTPUT_DUMP_EXT +#define GENERATE_FILE_NAMES() +#define CREATE_DUMP_FILE(m_filename) +#define DUMP_TO_FILE(m_filename, m_buf, m_size) +#endif /* FILE_DUMP_ENABLE */ + +} // namespace android + +#endif // ANDROID_C2_SOFT_AVC_ENC_H__
diff --git a/media/codec2/components/base/Android.bp b/media/codec2/components/base/Android.bp new file mode 100644 index 0000000..f10835f --- /dev/null +++ b/media/codec2/components/base/Android.bp
@@ -0,0 +1,134 @@ +// DO NOT DEPEND ON THIS DIRECTLY +// use libcodec2_soft-defaults instead +cc_library_shared { + name: "libcodec2_soft_common", + defaults: ["libcodec2-impl-defaults"], + vendor_available: true, + + srcs: [ + "SimpleC2Component.cpp", + "SimpleC2Interface.cpp", + ], + + export_include_dirs: [ + "include", + ], + + export_shared_lib_headers: [ + "libsfplugin_ccodec_utils", + ], + + shared_libs: [ + "libcutils", // for properties + "liblog", // for ALOG + "libsfplugin_ccodec_utils", // for ImageCopy + "libstagefright_foundation", // for Mutexed + ], + + sanitize: { + misc_undefined: [ + "unsigned-integer-overflow", + "signed-integer-overflow", + ], + cfi: true, + }, + + ldflags: ["-Wl,-Bsymbolic"], +} + +filegroup { + name: "codec2_soft_exports", + srcs: [ "exports.lds" ], +} + +// public dependency for software codec implementation +// to be used by code under media/codecs/* only as its stability is not guaranteed +cc_defaults { + name: "libcodec2_soft-defaults", + defaults: ["libcodec2-impl-defaults"], + vendor_available: true, + version_script: ":codec2_soft_exports", + export_shared_lib_headers: [ + "libsfplugin_ccodec_utils", + ], + + shared_libs: [ + "libcodec2_soft_common", + "libcutils", // for properties + "liblog", // for ALOG + "libsfplugin_ccodec_utils", // for ImageCopy + "libstagefright_foundation", // for ColorUtils and MIME + ], + + cflags: [ + "-Wall", + "-Werror", + ], + + ldflags: ["-Wl,-Bsymbolic"], +} + +// public dependency for software codec implementation +// to be used by code under media/codecs/* only +cc_defaults { + name: "libcodec2_soft_sanitize_all-defaults", + + sanitize: { + misc_undefined: [ + "unsigned-integer-overflow", + "signed-integer-overflow", + ], + cfi: true, + }, +} + +// public dependency for software codec implementation +// to be used by code under media/codecs/* only +cc_defaults { + name: "libcodec2_soft_sanitize_signed-defaults", + + sanitize: { + misc_undefined: [ + "signed-integer-overflow", + ], + cfi: true, + }, +} + +// TEMP: used by cheets2 project - remove when no longer used +cc_library_shared { + name: "libcodec2_simple_component", + vendor_available: true, + + srcs: [ + "SimpleC2Interface.cpp", + ], + + local_include_dirs: [ + "include", + ], + + export_include_dirs: [ + "include", + ], + + shared_libs: [ + "libcodec2", + "libcodec2_vndk", + "libcutils", + "liblog", + "libstagefright_foundation", + "libutils", + ], + + sanitize: { + misc_undefined: [ + "unsigned-integer-overflow", + "signed-integer-overflow", + ], + cfi: true, + }, + + ldflags: ["-Wl,-Bsymbolic"], +} +
diff --git a/media/codec2/components/base/SimpleC2Component.cpp b/media/codec2/components/base/SimpleC2Component.cpp new file mode 100644 index 0000000..fb3fbd0 --- /dev/null +++ b/media/codec2/components/base/SimpleC2Component.cpp
@@ -0,0 +1,611 @@ +/* + * Copyright (C) 2017 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 "SimpleC2Component" +#include <log/log.h> + +#include <cutils/properties.h> +#include <media/stagefright/foundation/AMessage.h> + +#include <inttypes.h> + +#include <C2Config.h> +#include <C2Debug.h> +#include <C2PlatformSupport.h> +#include <SimpleC2Component.h> + +namespace android { + +std::unique_ptr<C2Work> SimpleC2Component::WorkQueue::pop_front() { + std::unique_ptr<C2Work> work = std::move(mQueue.front().work); + mQueue.pop_front(); + return work; +} + +void SimpleC2Component::WorkQueue::push_back(std::unique_ptr<C2Work> work) { + mQueue.push_back({ std::move(work), NO_DRAIN }); +} + +bool SimpleC2Component::WorkQueue::empty() const { + return mQueue.empty(); +} + +void SimpleC2Component::WorkQueue::clear() { + mQueue.clear(); +} + +uint32_t SimpleC2Component::WorkQueue::drainMode() const { + return mQueue.front().drainMode; +} + +void SimpleC2Component::WorkQueue::markDrain(uint32_t drainMode) { + mQueue.push_back({ nullptr, drainMode }); +} + +//////////////////////////////////////////////////////////////////////////////// + +SimpleC2Component::WorkHandler::WorkHandler() : mRunning(false) {} + +void SimpleC2Component::WorkHandler::setComponent( + const std::shared_ptr<SimpleC2Component> &thiz) { + mThiz = thiz; +} + +static void Reply(const sp<AMessage> &msg, int32_t *err = nullptr) { + sp<AReplyToken> replyId; + CHECK(msg->senderAwaitsResponse(&replyId)); + sp<AMessage> reply = new AMessage; + if (err) { + reply->setInt32("err", *err); + } + reply->postReply(replyId); +} + +void SimpleC2Component::WorkHandler::onMessageReceived(const sp<AMessage> &msg) { + std::shared_ptr<SimpleC2Component> thiz = mThiz.lock(); + if (!thiz) { + ALOGD("component not yet set; msg = %s", msg->debugString().c_str()); + sp<AReplyToken> replyId; + if (msg->senderAwaitsResponse(&replyId)) { + sp<AMessage> reply = new AMessage; + reply->setInt32("err", C2_CORRUPTED); + reply->postReply(replyId); + } + return; + } + + switch (msg->what()) { + case kWhatProcess: { + if (mRunning) { + if (thiz->processQueue()) { + (new AMessage(kWhatProcess, this))->post(); + } + } else { + ALOGV("Ignore process message as we're not running"); + } + break; + } + case kWhatInit: { + int32_t err = thiz->onInit(); + Reply(msg, &err); + [[fallthrough]]; + } + case kWhatStart: { + mRunning = true; + break; + } + case kWhatStop: { + int32_t err = thiz->onStop(); + Reply(msg, &err); + break; + } + case kWhatReset: { + thiz->onReset(); + mRunning = false; + Reply(msg); + break; + } + case kWhatRelease: { + thiz->onRelease(); + mRunning = false; + Reply(msg); + break; + } + default: { + ALOGD("Unrecognized msg: %d", msg->what()); + break; + } + } +} + +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_BLOCKING); + 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_BLOCKING); + 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_BLOCKING); + return status; + } + +private: + std::shared_ptr<C2BlockPool> mBase; +}; + +//////////////////////////////////////////////////////////////////////////////// + +namespace { + +struct DummyReadView : public C2ReadView { + DummyReadView() : C2ReadView(C2_NO_INIT) {} +}; + +} // namespace + +SimpleC2Component::SimpleC2Component( + const std::shared_ptr<C2ComponentInterface> &intf) + : mDummyReadView(DummyReadView()), + mIntf(intf), + mLooper(new ALooper), + mHandler(new WorkHandler) { + mLooper->setName(intf->getName().c_str()); + (void)mLooper->registerHandler(mHandler); + mLooper->start(false, false, ANDROID_PRIORITY_VIDEO); +} + +SimpleC2Component::~SimpleC2Component() { + mLooper->unregisterHandler(mHandler->id()); + (void)mLooper->stop(); +} + +c2_status_t SimpleC2Component::setListener_vb( + const std::shared_ptr<C2Component::Listener> &listener, c2_blocking_t mayBlock) { + mHandler->setComponent(shared_from_this()); + + Mutexed<ExecState>::Locked state(mExecState); + if (state->mState == RUNNING) { + if (listener) { + return C2_BAD_STATE; + } else if (!mayBlock) { + return C2_BLOCKING; + } + } + state->mListener = listener; + // TODO: wait for listener change to have taken place before returning + // (e.g. if there is an ongoing listener callback) + return C2_OK; +} + +c2_status_t SimpleC2Component::queue_nb(std::list<std::unique_ptr<C2Work>> * const items) { + { + Mutexed<ExecState>::Locked state(mExecState); + if (state->mState != RUNNING) { + return C2_BAD_STATE; + } + } + bool queueWasEmpty = false; + { + Mutexed<WorkQueue>::Locked queue(mWorkQueue); + queueWasEmpty = queue->empty(); + while (!items->empty()) { + queue->push_back(std::move(items->front())); + items->pop_front(); + } + } + if (queueWasEmpty) { + (new AMessage(WorkHandler::kWhatProcess, mHandler))->post(); + } + return C2_OK; +} + +c2_status_t SimpleC2Component::announce_nb(const std::vector<C2WorkOutline> &items) { + (void)items; + return C2_OMITTED; +} + +c2_status_t SimpleC2Component::flush_sm( + flush_mode_t flushMode, std::list<std::unique_ptr<C2Work>>* const flushedWork) { + (void)flushMode; + { + Mutexed<ExecState>::Locked state(mExecState); + if (state->mState != RUNNING) { + return C2_BAD_STATE; + } + } + { + Mutexed<WorkQueue>::Locked queue(mWorkQueue); + queue->incGeneration(); + // TODO: queue->splicedBy(flushedWork, flushedWork->end()); + while (!queue->empty()) { + std::unique_ptr<C2Work> work = queue->pop_front(); + if (work) { + flushedWork->push_back(std::move(work)); + } + } + while (!queue->pending().empty()) { + flushedWork->push_back(std::move(queue->pending().begin()->second)); + queue->pending().erase(queue->pending().begin()); + } + } + + return C2_OK; +} + +c2_status_t SimpleC2Component::drain_nb(drain_mode_t drainMode) { + if (drainMode == DRAIN_CHAIN) { + return C2_OMITTED; + } + { + Mutexed<ExecState>::Locked state(mExecState); + if (state->mState != RUNNING) { + return C2_BAD_STATE; + } + } + bool queueWasEmpty = false; + { + Mutexed<WorkQueue>::Locked queue(mWorkQueue); + queueWasEmpty = queue->empty(); + queue->markDrain(drainMode); + } + if (queueWasEmpty) { + (new AMessage(WorkHandler::kWhatProcess, mHandler))->post(); + } + + return C2_OK; +} + +c2_status_t SimpleC2Component::start() { + Mutexed<ExecState>::Locked state(mExecState); + if (state->mState == RUNNING) { + return C2_BAD_STATE; + } + bool needsInit = (state->mState == UNINITIALIZED); + state.unlock(); + if (needsInit) { + sp<AMessage> reply; + (new AMessage(WorkHandler::kWhatInit, mHandler))->postAndAwaitResponse(&reply); + int32_t err; + CHECK(reply->findInt32("err", &err)); + if (err != C2_OK) { + return (c2_status_t)err; + } + } else { + (new AMessage(WorkHandler::kWhatStart, mHandler))->post(); + } + state.lock(); + state->mState = RUNNING; + return C2_OK; +} + +c2_status_t SimpleC2Component::stop() { + ALOGV("stop"); + { + Mutexed<ExecState>::Locked state(mExecState); + if (state->mState != RUNNING) { + return C2_BAD_STATE; + } + state->mState = STOPPED; + } + { + Mutexed<WorkQueue>::Locked queue(mWorkQueue); + queue->clear(); + queue->pending().clear(); + } + sp<AMessage> reply; + (new AMessage(WorkHandler::kWhatStop, mHandler))->postAndAwaitResponse(&reply); + int32_t err; + CHECK(reply->findInt32("err", &err)); + if (err != C2_OK) { + return (c2_status_t)err; + } + return C2_OK; +} + +c2_status_t SimpleC2Component::reset() { + ALOGV("reset"); + { + Mutexed<ExecState>::Locked state(mExecState); + state->mState = UNINITIALIZED; + } + { + Mutexed<WorkQueue>::Locked queue(mWorkQueue); + queue->clear(); + queue->pending().clear(); + } + sp<AMessage> reply; + (new AMessage(WorkHandler::kWhatReset, mHandler))->postAndAwaitResponse(&reply); + return C2_OK; +} + +c2_status_t SimpleC2Component::release() { + ALOGV("release"); + sp<AMessage> reply; + (new AMessage(WorkHandler::kWhatRelease, mHandler))->postAndAwaitResponse(&reply); + return C2_OK; +} + +std::shared_ptr<C2ComponentInterface> SimpleC2Component::intf() { + return mIntf; +} + +namespace { + +std::list<std::unique_ptr<C2Work>> vec(std::unique_ptr<C2Work> &work) { + std::list<std::unique_ptr<C2Work>> ret; + ret.push_back(std::move(work)); + return ret; +} + +} // namespace + +void SimpleC2Component::finish( + uint64_t frameIndex, std::function<void(const std::unique_ptr<C2Work> &)> fillWork) { + std::unique_ptr<C2Work> work; + { + Mutexed<WorkQueue>::Locked queue(mWorkQueue); + if (queue->pending().count(frameIndex) == 0) { + ALOGW("unknown frame index: %" PRIu64, frameIndex); + return; + } + work = std::move(queue->pending().at(frameIndex)); + queue->pending().erase(frameIndex); + } + if (work) { + fillWork(work); + std::shared_ptr<C2Component::Listener> listener = mExecState.lock()->mListener; + listener->onWorkDone_nb(shared_from_this(), vec(work)); + ALOGV("returning pending work"); + } +} + +void SimpleC2Component::cloneAndSend( + uint64_t frameIndex, + const std::unique_ptr<C2Work> ¤tWork, + std::function<void(const std::unique_ptr<C2Work> &)> fillWork) { + std::unique_ptr<C2Work> work(new C2Work); + if (currentWork->input.ordinal.frameIndex == frameIndex) { + work->input.flags = currentWork->input.flags; + work->input.ordinal = currentWork->input.ordinal; + } else { + Mutexed<WorkQueue>::Locked queue(mWorkQueue); + if (queue->pending().count(frameIndex) == 0) { + ALOGW("unknown frame index: %" PRIu64, frameIndex); + return; + } + work->input.flags = queue->pending().at(frameIndex)->input.flags; + work->input.ordinal = queue->pending().at(frameIndex)->input.ordinal; + } + work->worklets.emplace_back(new C2Worklet); + if (work) { + fillWork(work); + std::shared_ptr<C2Component::Listener> listener = mExecState.lock()->mListener; + listener->onWorkDone_nb(shared_from_this(), vec(work)); + ALOGV("cloned and sending work"); + } +} + +bool SimpleC2Component::processQueue() { + std::unique_ptr<C2Work> work; + uint64_t generation; + int32_t drainMode; + bool isFlushPending = false; + bool hasQueuedWork = false; + { + Mutexed<WorkQueue>::Locked queue(mWorkQueue); + if (queue->empty()) { + return false; + } + + generation = queue->generation(); + drainMode = queue->drainMode(); + isFlushPending = queue->popPendingFlush(); + work = queue->pop_front(); + hasQueuedWork = !queue->empty(); + } + if (isFlushPending) { + ALOGV("processing pending flush"); + c2_status_t err = onFlush_sm(); + if (err != C2_OK) { + ALOGD("flush err: %d", err); + // TODO: error + } + } + + if (!mOutputBlockPool) { + c2_status_t err = [this] { + // TODO: don't use query_vb + C2StreamBufferTypeSetting::output outputFormat(0u); + std::vector<std::unique_ptr<C2Param>> params; + c2_status_t err = intf()->query_vb( + { &outputFormat }, + { C2PortBlockPoolsTuning::output::PARAM_TYPE }, + C2_DONT_BLOCK, + ¶ms); + if (err != C2_OK && err != C2_BAD_INDEX) { + ALOGD("query err = %d", err); + return err; + } + C2BlockPool::local_id_t poolId = + outputFormat.value == C2BufferData::GRAPHIC + ? C2BlockPool::BASIC_GRAPHIC + : C2BlockPool::BASIC_LINEAR; + if (params.size()) { + C2PortBlockPoolsTuning::output *outputPools = + C2PortBlockPoolsTuning::output::From(params[0].get()); + if (outputPools && outputPools->flexCount() >= 1) { + poolId = outputPools->m.values[0]; + } + } + + std::shared_ptr<C2BlockPool> blockPool; + err = GetCodec2BlockPool(poolId, shared_from_this(), &blockPool); + ALOGD("Using output block pool with poolID %llu => got %llu - %d", + (unsigned long long)poolId, + (unsigned long long)( + blockPool ? blockPool->getLocalId() : 111000111), + err); + if (err == C2_OK) { + mOutputBlockPool = std::make_shared<BlockingBlockPool>(blockPool); + } + return err; + }(); + if (err != C2_OK) { + Mutexed<ExecState>::Locked state(mExecState); + std::shared_ptr<C2Component::Listener> listener = state->mListener; + state.unlock(); + listener->onError_nb(shared_from_this(), err); + return hasQueuedWork; + } + } + + if (!work) { + c2_status_t err = drain(drainMode, mOutputBlockPool); + if (err != C2_OK) { + Mutexed<ExecState>::Locked state(mExecState); + std::shared_ptr<C2Component::Listener> listener = state->mListener; + state.unlock(); + listener->onError_nb(shared_from_this(), err); + } + return hasQueuedWork; + } + + { + std::vector<C2Param *> updates; + for (const std::unique_ptr<C2Param> ¶m: work->input.configUpdate) { + if (param) { + updates.emplace_back(param.get()); + } + } + if (!updates.empty()) { + std::vector<std::unique_ptr<C2SettingResult>> failures; + c2_status_t err = intf()->config_vb(updates, C2_MAY_BLOCK, &failures); + ALOGD("applied %zu configUpdates => %s (%d)", updates.size(), asString(err), err); + } + } + + 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()); + Mutexed<WorkQueue>::Locked queue(mWorkQueue); + if (queue->generation() != generation) { + ALOGD("work form old generation: was %" PRIu64 " now %" PRIu64, + queue->generation(), generation); + work->result = C2_NOT_FOUND; + queue.unlock(); + + Mutexed<ExecState>::Locked state(mExecState); + std::shared_ptr<C2Component::Listener> listener = state->mListener; + state.unlock(); + listener->onWorkDone_nb(shared_from_this(), vec(work)); + return hasQueuedWork; + } + if (work->workletsProcessed != 0u) { + queue.unlock(); + Mutexed<ExecState>::Locked state(mExecState); + ALOGV("returning this work"); + std::shared_ptr<C2Component::Listener> listener = state->mListener; + state.unlock(); + listener->onWorkDone_nb(shared_from_this(), vec(work)); + } else { + ALOGV("queue pending work"); + work->input.buffers.clear(); + std::unique_ptr<C2Work> unexpected; + + uint64_t frameIndex = work->input.ordinal.frameIndex.peeku(); + if (queue->pending().count(frameIndex) != 0) { + unexpected = std::move(queue->pending().at(frameIndex)); + queue->pending().erase(frameIndex); + } + (void)queue->pending().insert({ frameIndex, std::move(work) }); + + queue.unlock(); + if (unexpected) { + ALOGD("unexpected pending work"); + unexpected->result = C2_CORRUPTED; + Mutexed<ExecState>::Locked state(mExecState); + std::shared_ptr<C2Component::Listener> listener = state->mListener; + state.unlock(); + listener->onWorkDone_nb(shared_from_this(), vec(unexpected)); + } + } + return hasQueuedWork; +} + +std::shared_ptr<C2Buffer> SimpleC2Component::createLinearBuffer( + const std::shared_ptr<C2LinearBlock> &block) { + return createLinearBuffer(block, block->offset(), block->size()); +} + +std::shared_ptr<C2Buffer> SimpleC2Component::createLinearBuffer( + const std::shared_ptr<C2LinearBlock> &block, size_t offset, size_t size) { + return C2Buffer::CreateLinearBuffer(block->share(offset, size, ::C2Fence())); +} + +std::shared_ptr<C2Buffer> SimpleC2Component::createGraphicBuffer( + const std::shared_ptr<C2GraphicBlock> &block) { + return createGraphicBuffer(block, C2Rect(block->width(), block->height())); +} + +std::shared_ptr<C2Buffer> SimpleC2Component::createGraphicBuffer( + const std::shared_ptr<C2GraphicBlock> &block, const C2Rect &crop) { + return C2Buffer::CreateGraphicBuffer(block->share(crop, ::C2Fence())); +} + +} // namespace android
diff --git a/media/codec2/components/base/SimpleC2Interface.cpp b/media/codec2/components/base/SimpleC2Interface.cpp new file mode 100644 index 0000000..c849a4e --- /dev/null +++ b/media/codec2/components/base/SimpleC2Interface.cpp
@@ -0,0 +1,315 @@ +/* + * Copyright (C) 2017 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 "SimpleC2Interface" +#include <utils/Log.h> + +// use MediaDefs here vs. MediaCodecConstants as this is not MediaCodec specific/dependent +#include <media/stagefright/foundation/MediaDefs.h> + +#include <SimpleC2Interface.h> + +namespace android { + +/* SimpleInterface */ + +SimpleInterface<void>::BaseParams::BaseParams( + const std::shared_ptr<C2ReflectorHelper> &reflector, + C2String name, + C2Component::kind_t kind, + C2Component::domain_t domain, + C2String mediaType, + std::vector<C2String> aliases) + : C2InterfaceHelper(reflector) { + setDerivedInstance(this); + + addParameter( + DefineParam(mName, C2_PARAMKEY_COMPONENT_NAME) + .withConstValue(AllocSharedString<C2ComponentNameSetting>(name.c_str())) + .build()); + + if (aliases.size()) { + C2String joined; + for (const C2String &alias : aliases) { + if (joined.length()) { + joined += ","; + } + joined += alias; + } + addParameter( + DefineParam(mAliases, C2_PARAMKEY_COMPONENT_ALIASES) + .withConstValue(AllocSharedString<C2ComponentAliasesSetting>(joined.c_str())) + .build()); + } + + addParameter( + DefineParam(mKind, C2_PARAMKEY_COMPONENT_KIND) + .withConstValue(new C2ComponentKindSetting(kind)) + .build()); + + addParameter( + DefineParam(mDomain, C2_PARAMKEY_COMPONENT_DOMAIN) + .withConstValue(new C2ComponentDomainSetting(domain)) + .build()); + + // simple interfaces have single streams + addParameter( + DefineParam(mInputStreamCount, C2_PARAMKEY_INPUT_STREAM_COUNT) + .withConstValue(new C2PortStreamCountTuning::input(1)) + .build()); + + addParameter( + DefineParam(mOutputStreamCount, C2_PARAMKEY_OUTPUT_STREAM_COUNT) + .withConstValue(new C2PortStreamCountTuning::output(1)) + .build()); + + // set up buffer formats and allocators + + // default to linear buffers and no media type + C2BufferData::type_t rawBufferType = C2BufferData::LINEAR; + C2String rawMediaType; + C2Allocator::id_t rawAllocator = C2AllocatorStore::DEFAULT_LINEAR; + C2BlockPool::local_id_t rawPoolId = C2BlockPool::BASIC_LINEAR; + C2BufferData::type_t codedBufferType = C2BufferData::LINEAR; + C2Allocator::id_t codedAllocator = C2AllocatorStore::DEFAULT_LINEAR; + C2BlockPool::local_id_t codedPoolId = C2BlockPool::BASIC_LINEAR; + + switch (domain) { + case C2Component::DOMAIN_IMAGE: + case C2Component::DOMAIN_VIDEO: + // TODO: should we define raw image? The only difference is timestamp handling + rawBufferType = C2BufferData::GRAPHIC; + rawMediaType = MEDIA_MIMETYPE_VIDEO_RAW; + rawAllocator = C2AllocatorStore::DEFAULT_GRAPHIC; + rawPoolId = C2BlockPool::BASIC_GRAPHIC; + break; + case C2Component::DOMAIN_AUDIO: + rawBufferType = C2BufferData::LINEAR; + rawMediaType = MEDIA_MIMETYPE_AUDIO_RAW; + rawAllocator = C2AllocatorStore::DEFAULT_LINEAR; + rawPoolId = C2BlockPool::BASIC_LINEAR; + break; + default: + break; + } + bool isEncoder = kind == C2Component::KIND_ENCODER; + + // handle raw decoders + if (mediaType == rawMediaType) { + codedBufferType = rawBufferType; + codedAllocator = rawAllocator; + codedPoolId = rawPoolId; + } + + addParameter( + DefineParam(mInputFormat, C2_PARAMKEY_INPUT_STREAM_BUFFER_TYPE) + .withConstValue(new C2StreamBufferTypeSetting::input( + 0u, isEncoder ? rawBufferType : codedBufferType)) + .build()); + + addParameter( + DefineParam(mInputMediaType, C2_PARAMKEY_INPUT_MEDIA_TYPE) + .withConstValue(AllocSharedString<C2PortMediaTypeSetting::input>( + isEncoder ? rawMediaType : mediaType)) + .build()); + + addParameter( + DefineParam(mOutputFormat, C2_PARAMKEY_OUTPUT_STREAM_BUFFER_TYPE) + .withConstValue(new C2StreamBufferTypeSetting::output( + 0u, isEncoder ? codedBufferType : rawBufferType)) + .build()); + + addParameter( + DefineParam(mOutputMediaType, C2_PARAMKEY_OUTPUT_MEDIA_TYPE) + .withConstValue(AllocSharedString<C2PortMediaTypeSetting::output>( + isEncoder ? mediaType : rawMediaType)) + .build()); + + C2Allocator::id_t inputAllocators[1] = { isEncoder ? rawAllocator : codedAllocator }; + C2Allocator::id_t outputAllocators[1] = { isEncoder ? codedAllocator : rawAllocator }; + C2BlockPool::local_id_t outputPoolIds[1] = { isEncoder ? codedPoolId : rawPoolId }; + + addParameter( + DefineParam(mInputAllocators, C2_PARAMKEY_INPUT_ALLOCATORS) + .withDefault(C2PortAllocatorsTuning::input::AllocShared(inputAllocators)) + .withFields({ C2F(mInputAllocators, m.values[0]).any(), + C2F(mInputAllocators, m.values).inRange(0, 1) }) + .withSetter(Setter<C2PortAllocatorsTuning::input>::NonStrictValuesWithNoDeps) + .build()); + + addParameter( + DefineParam(mOutputAllocators, C2_PARAMKEY_OUTPUT_ALLOCATORS) + .withDefault(C2PortAllocatorsTuning::output::AllocShared(outputAllocators)) + .withFields({ C2F(mOutputAllocators, m.values[0]).any(), + C2F(mOutputAllocators, m.values).inRange(0, 1) }) + .withSetter(Setter<C2PortAllocatorsTuning::output>::NonStrictValuesWithNoDeps) + .build()); + + addParameter( + DefineParam(mOutputPoolIds, C2_PARAMKEY_OUTPUT_BLOCK_POOLS) + .withDefault(C2PortBlockPoolsTuning::output::AllocShared(outputPoolIds)) + .withFields({ C2F(mOutputPoolIds, m.values[0]).any(), + C2F(mOutputPoolIds, m.values).inRange(0, 1) }) + .withSetter(Setter<C2PortBlockPoolsTuning::output>::NonStrictValuesWithNoDeps) + .build()); + + // add stateless params + addParameter( + DefineParam(mSubscribedParamIndices, C2_PARAMKEY_SUBSCRIBED_PARAM_INDICES) + .withDefault(C2SubscribedParamIndicesTuning::AllocShared(0u)) + .withFields({ C2F(mSubscribedParamIndices, m.values[0]).any(), + C2F(mSubscribedParamIndices, m.values).any() }) + .withSetter(Setter<C2SubscribedParamIndicesTuning>::NonStrictValuesWithNoDeps) + .build()); + + /* TODO + + addParameter( + DefineParam(mCurrentWorkOrdinal, C2_PARAMKEY_CURRENT_WORK) + .withDefault(new C2CurrentWorkTuning()) + .withFields({ C2F(mCurrentWorkOrdinal, m.timeStamp).any(), + C2F(mCurrentWorkOrdinal, m.frameIndex).any(), + C2F(mCurrentWorkOrdinal, m.customOrdinal).any() }) + .withSetter(Setter<C2CurrentWorkTuning>::NonStrictValuesWithNoDeps) + .build()); + + addParameter( + DefineParam(mLastInputQueuedWorkOrdinal, C2_PARAMKEY_LAST_INPUT_QUEUED) + .withDefault(new C2LastWorkQueuedTuning::input()) + .withFields({ C2F(mLastInputQueuedWorkOrdinal, m.timeStamp).any(), + C2F(mLastInputQueuedWorkOrdinal, m.frameIndex).any(), + C2F(mLastInputQueuedWorkOrdinal, m.customOrdinal).any() }) + .withSetter(Setter<C2LastWorkQueuedTuning::input>::NonStrictValuesWithNoDeps) + .build()); + + addParameter( + DefineParam(mLastOutputQueuedWorkOrdinal, C2_PARAMKEY_LAST_OUTPUT_QUEUED) + .withDefault(new C2LastWorkQueuedTuning::output()) + .withFields({ C2F(mLastOutputQueuedWorkOrdinal, m.timeStamp).any(), + C2F(mLastOutputQueuedWorkOrdinal, m.frameIndex).any(), + C2F(mLastOutputQueuedWorkOrdinal, m.customOrdinal).any() }) + .withSetter(Setter<C2LastWorkQueuedTuning::output>::NonStrictValuesWithNoDeps) + .build()); + + std::shared_ptr<C2OutOfMemoryTuning> mOutOfMemory; + + std::shared_ptr<C2PortConfigCounterTuning::input> mInputConfigCounter; + std::shared_ptr<C2PortConfigCounterTuning::output> mOutputConfigCounter; + std::shared_ptr<C2ConfigCounterTuning> mDirectConfigCounter; + + */ +} + +void SimpleInterface<void>::BaseParams::noInputLatency() { + addParameter( + DefineParam(mRequestedInputDelay, C2_PARAMKEY_INPUT_DELAY_REQUEST) + .withConstValue(new C2PortRequestedDelayTuning::input(0u)) + .build()); + + addParameter( + DefineParam(mActualInputDelay, C2_PARAMKEY_INPUT_DELAY) + .withConstValue(new C2PortActualDelayTuning::input(0u)) + .build()); +} + +void SimpleInterface<void>::BaseParams::noOutputLatency() { + addParameter( + DefineParam(mRequestedOutputDelay, C2_PARAMKEY_OUTPUT_DELAY_REQUEST) + .withConstValue(new C2PortRequestedDelayTuning::output(0u)) + .build()); + + addParameter( + DefineParam(mActualOutputDelay, C2_PARAMKEY_OUTPUT_DELAY) + .withConstValue(new C2PortActualDelayTuning::output(0u)) + .build()); +} + +void SimpleInterface<void>::BaseParams::noPipelineLatency() { + addParameter( + DefineParam(mRequestedPipelineDelay, C2_PARAMKEY_PIPELINE_DELAY_REQUEST) + .withConstValue(new C2RequestedPipelineDelayTuning(0u)) + .build()); + + addParameter( + DefineParam(mActualPipelineDelay, C2_PARAMKEY_PIPELINE_DELAY) + .withConstValue(new C2ActualPipelineDelayTuning(0u)) + .build()); +} + +void SimpleInterface<void>::BaseParams::noPrivateBuffers() { + addParameter( + DefineParam(mPrivateAllocators, C2_PARAMKEY_PRIVATE_ALLOCATORS) + .withConstValue(C2PrivateAllocatorsTuning::AllocShared(0u)) + .build()); + + addParameter( + DefineParam(mMaxPrivateBufferCount, C2_PARAMKEY_MAX_PRIVATE_BUFFER_COUNT) + .withConstValue(C2MaxPrivateBufferCountTuning::AllocShared(0u)) + .build()); + + addParameter( + DefineParam(mPrivatePoolIds, C2_PARAMKEY_PRIVATE_BLOCK_POOLS) + .withConstValue(C2PrivateBlockPoolsTuning::AllocShared(0u)) + .build()); +} + +void SimpleInterface<void>::BaseParams::noInputReferences() { + addParameter( + DefineParam(mMaxInputReferenceAge, C2_PARAMKEY_INPUT_MAX_REFERENCE_AGE) + .withConstValue(new C2StreamMaxReferenceAgeTuning::input(0u)) + .build()); + + addParameter( + DefineParam(mMaxInputReferenceCount, C2_PARAMKEY_INPUT_MAX_REFERENCE_COUNT) + .withConstValue(new C2StreamMaxReferenceCountTuning::input(0u)) + .build()); +} + +void SimpleInterface<void>::BaseParams::noOutputReferences() { + addParameter( + DefineParam(mMaxOutputReferenceAge, C2_PARAMKEY_OUTPUT_MAX_REFERENCE_AGE) + .withConstValue(new C2StreamMaxReferenceAgeTuning::output(0u)) + .build()); + + addParameter( + DefineParam(mMaxOutputReferenceCount, C2_PARAMKEY_OUTPUT_MAX_REFERENCE_COUNT) + .withConstValue(new C2StreamMaxReferenceCountTuning::output(0u)) + .build()); +} + +void SimpleInterface<void>::BaseParams::noTimeStretch() { + addParameter( + DefineParam(mTimeStretch, C2_PARAMKEY_TIME_STRETCH) + .withConstValue(new C2ComponentTimeStretchTuning(1.f)) + .build()); +} + +/* + Clients need to handle the following base params due to custom dependency. + + std::shared_ptr<C2ApiLevelSetting> mApiLevel; + std::shared_ptr<C2ApiFeaturesSetting> mApiFeatures; + std::shared_ptr<C2ComponentAttributesSetting> mAttrib; + + std::shared_ptr<C2PortSuggestedBufferCountTuning::input> mSuggestedInputBufferCount; + std::shared_ptr<C2PortSuggestedBufferCountTuning::output> mSuggestedOutputBufferCount; + + std::shared_ptr<C2TrippedTuning> mTripped; + +*/ + +} // namespace android
diff --git a/media/codec2/components/base/exports.lds b/media/codec2/components/base/exports.lds new file mode 100644 index 0000000..641bae8 --- /dev/null +++ b/media/codec2/components/base/exports.lds
@@ -0,0 +1,7 @@ +{ + global: + CreateCodec2Factory; + DestroyCodec2Factory; + local: *; +}; +
diff --git a/media/codec2/components/base/include/SimpleC2Component.h b/media/codec2/components/base/include/SimpleC2Component.h new file mode 100644 index 0000000..22d5714 --- /dev/null +++ b/media/codec2/components/base/include/SimpleC2Component.h
@@ -0,0 +1,246 @@ +/* + * Copyright (C) 2017 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_C2_COMPONENT_H_ +#define SIMPLE_C2_COMPONENT_H_ + +#include <list> +#include <unordered_map> + +#include <C2Component.h> + +#include <media/stagefright/foundation/AHandler.h> +#include <media/stagefright/foundation/ALooper.h> +#include <media/stagefright/foundation/Mutexed.h> + +namespace android { + +class SimpleC2Component + : public C2Component, public std::enable_shared_from_this<SimpleC2Component> { +public: + explicit SimpleC2Component( + const std::shared_ptr<C2ComponentInterface> &intf); + virtual ~SimpleC2Component(); + + // C2Component + // From C2Component + virtual c2_status_t setListener_vb( + const std::shared_ptr<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; + + // for handler + bool processQueue(); + +protected: + /** + * Initialize internal states of the component according to the config set + * in the interface. + * + * This method is called during start(), but only at the first invocation or + * after reset(). + */ + virtual c2_status_t onInit() = 0; + + /** + * Stop the component. + */ + virtual c2_status_t onStop() = 0; + + /** + * Reset the component. + */ + virtual void onReset() = 0; + + /** + * Release the component. + */ + virtual void onRelease() = 0; + + /** + * Flush the component. + */ + virtual c2_status_t onFlush_sm() = 0; + + /** + * Process the given work and finish pending work using finish(). + * + * \param[in,out] work the work to process + * \param[in] pool the pool to use for allocating output blocks. + */ + virtual void process( + const std::unique_ptr<C2Work> &work, + const std::shared_ptr<C2BlockPool> &pool) = 0; + + /** + * Drain the component and finish pending work using finish(). + * + * \param[in] drainMode mode of drain. + * \param[in] pool the pool to use for allocating output blocks. + * + * \retval C2_OK The component has drained all pending output + * work. + * \retval C2_OMITTED Unsupported mode (e.g. DRAIN_CHAIN) + */ + virtual c2_status_t drain( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool) = 0; + + // for derived classes + /** + * Finish pending work. + * + * This method will retrieve the pending work according to |frameIndex| and + * feed the work into |fillWork| function. |fillWork| must be + * "non-blocking". Once |fillWork| returns the filled work will be returned + * to the client. + * + * \param[in] frameIndex the index of the pending work + * \param[in] fillWork the function to fill the retrieved work. + */ + void finish(uint64_t frameIndex, std::function<void(const std::unique_ptr<C2Work> &)> fillWork); + + /** + * Clone pending or current work and send the work back to client. + * + * This method will retrieve and clone the pending or current work according + * to |frameIndex| and feed the work into |fillWork| function. |fillWork| + * must be "non-blocking". Once |fillWork| returns the filled work will be + * returned to the client. + * + * \param[in] frameIndex the index of the work + * \param[in] currentWork the current work under processing + * \param[in] fillWork the function to fill the retrieved work. + */ + void cloneAndSend( + uint64_t frameIndex, + const std::unique_ptr<C2Work> ¤tWork, + std::function<void(const std::unique_ptr<C2Work> &)> fillWork); + + + std::shared_ptr<C2Buffer> createLinearBuffer( + const std::shared_ptr<C2LinearBlock> &block); + + std::shared_ptr<C2Buffer> createLinearBuffer( + const std::shared_ptr<C2LinearBlock> &block, size_t offset, size_t size); + + std::shared_ptr<C2Buffer> createGraphicBuffer( + const std::shared_ptr<C2GraphicBlock> &block); + + std::shared_ptr<C2Buffer> createGraphicBuffer( + const std::shared_ptr<C2GraphicBlock> &block, + const C2Rect &crop); + + static constexpr uint32_t NO_DRAIN = ~0u; + + C2ReadView mDummyReadView; + +private: + const std::shared_ptr<C2ComponentInterface> mIntf; + + class WorkHandler : public AHandler { + public: + enum { + kWhatProcess, + kWhatInit, + kWhatStart, + kWhatStop, + kWhatReset, + kWhatRelease, + }; + + WorkHandler(); + ~WorkHandler() override = default; + + void setComponent(const std::shared_ptr<SimpleC2Component> &thiz); + + protected: + void onMessageReceived(const sp<AMessage> &msg) override; + + private: + std::weak_ptr<SimpleC2Component> mThiz; + bool mRunning; + }; + + enum { + UNINITIALIZED, + STOPPED, + RUNNING, + }; + + struct ExecState { + ExecState() : mState(UNINITIALIZED) {} + + int mState; + std::shared_ptr<C2Component::Listener> mListener; + }; + Mutexed<ExecState> mExecState; + + sp<ALooper> mLooper; + sp<WorkHandler> mHandler; + + class WorkQueue { + public: + typedef std::unordered_map<uint64_t, std::unique_ptr<C2Work>> PendingWork; + + inline WorkQueue() : mFlush(false), mGeneration(0ul) {} + + inline uint64_t generation() const { return mGeneration; } + inline void incGeneration() { ++mGeneration; mFlush = true; } + + std::unique_ptr<C2Work> pop_front(); + void push_back(std::unique_ptr<C2Work> work); + bool empty() const; + uint32_t drainMode() const; + void markDrain(uint32_t drainMode); + inline bool popPendingFlush() { + bool flush = mFlush; + mFlush = false; + return flush; + } + void clear(); + PendingWork &pending() { return mPendingWork; } + + private: + struct Entry { + std::unique_ptr<C2Work> work; + uint32_t drainMode; + }; + + bool mFlush; + uint64_t mGeneration; + std::list<Entry> mQueue; + PendingWork mPendingWork; + }; + Mutexed<WorkQueue> mWorkQueue; + + class BlockingBlockPool; + std::shared_ptr<BlockingBlockPool> mOutputBlockPool; + + SimpleC2Component() = delete; +}; + +} // namespace android + +#endif // SIMPLE_C2_COMPONENT_H_
diff --git a/media/codec2/components/base/include/SimpleC2Interface.h b/media/codec2/components/base/include/SimpleC2Interface.h new file mode 100644 index 0000000..2051d3d0 --- /dev/null +++ b/media/codec2/components/base/include/SimpleC2Interface.h
@@ -0,0 +1,236 @@ +/* + * 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_SIMPLE_C2_INTERFACE_H_ +#define ANDROID_SIMPLE_C2_INTERFACE_H_ + +#include <C2Component.h> +#include <C2Config.h> +#include <util/C2InterfaceHelper.h> + +namespace android { + +/** + * Wrap a common interface object (such as Codec2Client::Interface, or C2InterfaceHelper into + * a C2ComponentInterface. + * + * \param T common interface type + */ +template <typename T> +class SimpleC2Interface : public C2ComponentInterface { +public: + SimpleC2Interface(const char *name, c2_node_id_t id, const std::shared_ptr<T> &impl) + : mName(name), + mId(id), + mImpl(impl) { + } + + ~SimpleC2Interface() override = default; + + // From C2ComponentInterface + C2String getName() const override { return mName; } + c2_node_id_t getId() const override { return mId; } + c2_status_t query_vb( + const std::vector<C2Param*> &stackParams, + const std::vector<C2Param::Index> &heapParamIndices, + c2_blocking_t mayBlock, + std::vector<std::unique_ptr<C2Param>>* const heapParams) const override { + return mImpl->query(stackParams, heapParamIndices, mayBlock, heapParams); + } + c2_status_t config_vb( + const std::vector<C2Param*> ¶ms, + c2_blocking_t mayBlock, + std::vector<std::unique_ptr<C2SettingResult>>* const failures) override { + return mImpl->config(params, mayBlock, failures); + } + c2_status_t createTunnel_sm(c2_node_id_t) override { return C2_OMITTED; } + c2_status_t releaseTunnel_sm(c2_node_id_t) override { return C2_OMITTED; } + c2_status_t querySupportedParams_nb( + std::vector<std::shared_ptr<C2ParamDescriptor>> * const params) const override { + return mImpl->querySupportedParams(params); + } + c2_status_t querySupportedValues_vb( + std::vector<C2FieldSupportedValuesQuery> &fields, + c2_blocking_t mayBlock) const override { + return mImpl->querySupportedValues(fields, mayBlock); + } + +private: + C2String mName; + const c2_node_id_t mId; + const std::shared_ptr<T> mImpl; +}; + +/** + * Utility classes for common interfaces. + */ +template<> +class SimpleC2Interface<void> { +public: + /** + * Base Codec 2.0 parameters required for all components. + */ + struct BaseParams : C2InterfaceHelper { + explicit BaseParams( + const std::shared_ptr<C2ReflectorHelper> &helper, + C2String name, + C2Component::kind_t kind, + C2Component::domain_t domain, + C2String mediaType, + std::vector<C2String> aliases = std::vector<C2String>()); + + /// Marks that this component has no input latency. Otherwise, component must + /// add support for C2PortRequestedDelayTuning::input and C2PortActualDelayTuning::input. + void noInputLatency(); + + /// Marks that this component has no output latency. Otherwise, component must + /// add support for C2PortRequestedDelayTuning::output and C2PortActualDelayTuning::output. + void noOutputLatency(); + + /// Marks that this component has no pipeline latency. Otherwise, component must + /// add support for C2RequestedPipelineDelayTuning and C2ActualPipelineDelayTuning. + void noPipelineLatency(); + + /// Marks that this component has no need for private buffers. Otherwise, component must + /// add support for C2MaxPrivateBufferCountTuning, C2PrivateAllocatorsTuning and + /// C2PrivateBlockPoolsTuning. + void noPrivateBuffers(); + + /// Marks that this component holds no references to input buffers. Otherwise, component + /// must add support for C2StreamMaxReferenceAgeTuning::input and + /// C2StreamMaxReferenceCountTuning::input. + void noInputReferences(); + + /// Marks that this component holds no references to output buffers. Otherwise, component + /// must add support for C2StreamMaxReferenceAgeTuning::output and + /// C2StreamMaxReferenceCountTuning::output. + void noOutputReferences(); + + /// Marks that this component does not stretch time. Otherwise, component + /// must add support for C2ComponentTimeStretchTuning. + void noTimeStretch(); + + std::shared_ptr<C2ApiLevelSetting> mApiLevel; + std::shared_ptr<C2ApiFeaturesSetting> mApiFeatures; + + std::shared_ptr<C2PlatformLevelSetting> mPlatformLevel; + std::shared_ptr<C2PlatformFeaturesSetting> mPlatformFeatures; + + std::shared_ptr<C2ComponentNameSetting> mName; + std::shared_ptr<C2ComponentAliasesSetting> mAliases; + std::shared_ptr<C2ComponentKindSetting> mKind; + std::shared_ptr<C2ComponentDomainSetting> mDomain; + std::shared_ptr<C2ComponentAttributesSetting> mAttrib; + std::shared_ptr<C2ComponentTimeStretchTuning> mTimeStretch; + + std::shared_ptr<C2PortMediaTypeSetting::input> mInputMediaType; + std::shared_ptr<C2PortMediaTypeSetting::output> mOutputMediaType; + std::shared_ptr<C2StreamBufferTypeSetting::input> mInputFormat; + std::shared_ptr<C2StreamBufferTypeSetting::output> mOutputFormat; + + std::shared_ptr<C2PortRequestedDelayTuning::input> mRequestedInputDelay; + std::shared_ptr<C2PortRequestedDelayTuning::output> mRequestedOutputDelay; + std::shared_ptr<C2RequestedPipelineDelayTuning> mRequestedPipelineDelay; + + std::shared_ptr<C2PortActualDelayTuning::input> mActualInputDelay; + std::shared_ptr<C2PortActualDelayTuning::output> mActualOutputDelay; + std::shared_ptr<C2ActualPipelineDelayTuning> mActualPipelineDelay; + + std::shared_ptr<C2StreamMaxReferenceAgeTuning::input> mMaxInputReferenceAge; + std::shared_ptr<C2StreamMaxReferenceCountTuning::input> mMaxInputReferenceCount; + std::shared_ptr<C2StreamMaxReferenceAgeTuning::output> mMaxOutputReferenceAge; + std::shared_ptr<C2StreamMaxReferenceCountTuning::output> mMaxOutputReferenceCount; + std::shared_ptr<C2MaxPrivateBufferCountTuning> mMaxPrivateBufferCount; + + std::shared_ptr<C2PortStreamCountTuning::input> mInputStreamCount; + std::shared_ptr<C2PortStreamCountTuning::output> mOutputStreamCount; + + std::shared_ptr<C2SubscribedParamIndicesTuning> mSubscribedParamIndices; + std::shared_ptr<C2PortSuggestedBufferCountTuning::input> mSuggestedInputBufferCount; + std::shared_ptr<C2PortSuggestedBufferCountTuning::output> mSuggestedOutputBufferCount; + + std::shared_ptr<C2CurrentWorkTuning> mCurrentWorkOrdinal; + std::shared_ptr<C2LastWorkQueuedTuning::input> mLastInputQueuedWorkOrdinal; + std::shared_ptr<C2LastWorkQueuedTuning::output> mLastOutputQueuedWorkOrdinal; + + std::shared_ptr<C2PortAllocatorsTuning::input> mInputAllocators; + std::shared_ptr<C2PortAllocatorsTuning::output> mOutputAllocators; + std::shared_ptr<C2PrivateAllocatorsTuning> mPrivateAllocators; + std::shared_ptr<C2PortBlockPoolsTuning::output> mOutputPoolIds; + std::shared_ptr<C2PrivateBlockPoolsTuning> mPrivatePoolIds; + + std::shared_ptr<C2TrippedTuning> mTripped; + std::shared_ptr<C2OutOfMemoryTuning> mOutOfMemory; + + std::shared_ptr<C2PortConfigCounterTuning::input> mInputConfigCounter; + std::shared_ptr<C2PortConfigCounterTuning::output> mOutputConfigCounter; + std::shared_ptr<C2ConfigCounterTuning> mDirectConfigCounter; + }; +}; + +template<typename T> +using SimpleInterface = SimpleC2Interface<T>; + +template<typename T, typename ...Args> +std::shared_ptr<T> AllocSharedString(const Args(&... args), const char *str) { + size_t len = strlen(str) + 1; + std::shared_ptr<T> ret = T::AllocShared(len, args...); + strcpy(ret->m.value, str); + return ret; +} + +template<typename T, typename ...Args> +std::shared_ptr<T> AllocSharedString(const Args(&... args), const std::string &str) { + std::shared_ptr<T> ret = T::AllocShared(str.length() + 1, args...); + strcpy(ret->m.value, str.c_str()); + return ret; +} + +template <typename T> +struct Setter { + typedef typename std::remove_reference<T>::type type; + + static C2R NonStrictValueWithNoDeps( + bool mayBlock, C2InterfaceHelper::C2P<type> &me) { + (void)mayBlock; + return me.F(me.v.value).validatePossible(me.v.value); + } + + static C2R NonStrictValuesWithNoDeps( + bool mayBlock, C2InterfaceHelper::C2P<type> &me) { + (void)mayBlock; + C2R res = C2R::Ok(); + for (size_t ix = 0; ix < me.v.flexCount(); ++ix) { + res.plus(me.F(me.v.m.values[ix]).validatePossible(me.v.m.values[ix])); + } + return res; + } + + static C2R StrictValueWithNoDeps( + bool mayBlock, + const C2InterfaceHelper::C2P<type> &old, + C2InterfaceHelper::C2P<type> &me) { + (void)mayBlock; + if (!me.F(me.v.value).supportsNow(me.v.value)) { + me.set().value = old.v.value; + } + return me.F(me.v.value).validatePossible(me.v.value); + } +}; + +} // namespace android + +#endif // ANDROID_SIMPLE_C2_INTERFACE_H_
diff --git a/media/codec2/components/cmds/Android.bp b/media/codec2/components/cmds/Android.bp new file mode 100644 index 0000000..35f689e --- /dev/null +++ b/media/codec2/components/cmds/Android.bp
@@ -0,0 +1,36 @@ +cc_binary { + name: "codec2play", + defaults: ["libcodec2-impl-defaults"], + + srcs: [ + "codec2.cpp", + ], + + include_dirs: [ + ], + + shared_libs: [ + "libbase", + "libbinder", + "libcutils", + "libgui", + "liblog", + "libstagefright", + "libstagefright_foundation", + "libui", + "libutils", + ], + + cflags: [ + "-Werror", + "-Wall", + ], + + sanitize: { + cfi: true, + misc_undefined: [ + "unsigned-integer-overflow", + "signed-integer-overflow", + ], + }, +}
diff --git a/media/codec2/components/cmds/codec2.cpp b/media/codec2/components/cmds/codec2.cpp new file mode 100644 index 0000000..f2cf545 --- /dev/null +++ b/media/codec2/components/cmds/codec2.cpp
@@ -0,0 +1,483 @@ +/* + * Copyright (C) 2017 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 <inttypes.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include <thread> + +//#define LOG_NDEBUG 0 +#define LOG_TAG "codec2" +#include <log/log.h> + +#include <binder/IServiceManager.h> +#include <binder/ProcessState.h> +#include <media/DataSource.h> +#include <media/ICrypto.h> +#include <media/IMediaHTTPService.h> +#include <media/MediaSource.h> +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ALooper.h> +#include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/foundation/AUtils.h> +#include <media/stagefright/DataSourceFactory.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/MediaExtractorFactory.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/Utils.h> + +#include <gui/GLConsumer.h> +#include <gui/IProducerListener.h> +#include <gui/Surface.h> +#include <gui/SurfaceComposerClient.h> + +#include <C2AllocatorGralloc.h> +#include <C2Buffer.h> +#include <C2BufferPriv.h> +#include <C2Component.h> +#include <C2Config.h> +#include <C2Debug.h> +#include <C2PlatformSupport.h> +#include <C2Work.h> + +using namespace android; +using namespace std::chrono_literals; + +namespace { + +class LinearBuffer : public C2Buffer { +public: + explicit LinearBuffer(const std::shared_ptr<C2LinearBlock> &block) + : C2Buffer({ block->share(block->offset(), block->size(), ::C2Fence()) }) {} +}; + +class Listener; + +class SimplePlayer { +public: + SimplePlayer(); + ~SimplePlayer(); + + void onWorkDone(std::weak_ptr<C2Component> component, + std::list<std::unique_ptr<C2Work>> workItems); + void onTripped(std::weak_ptr<C2Component> component, + std::vector<std::shared_ptr<C2SettingResult>> settingResult); + void onError(std::weak_ptr<C2Component> component, uint32_t errorCode); + + void play(const sp<IMediaSource> &source); + +private: + typedef std::unique_lock<std::mutex> ULock; + + std::shared_ptr<Listener> mListener; + std::shared_ptr<C2Component> mComponent; + + sp<IProducerListener> mProducerListener; + + std::atomic_int mLinearPoolId; + + std::shared_ptr<C2Allocator> mAllocIon; + std::shared_ptr<C2BlockPool> mLinearPool; + + std::mutex mQueueLock; + std::condition_variable mQueueCondition; + std::list<std::unique_ptr<C2Work>> mWorkQueue; + + std::mutex mProcessedLock; + std::condition_variable mProcessedCondition; + std::list<std::unique_ptr<C2Work>> mProcessedWork; + + sp<Surface> mSurface; + sp<SurfaceComposerClient> mComposerClient; + sp<SurfaceControl> mControl; +}; + +class Listener : public C2Component::Listener { +public: + explicit Listener(SimplePlayer *thiz) : mThis(thiz) {} + virtual ~Listener() = default; + + virtual void onWorkDone_nb(std::weak_ptr<C2Component> component, + std::list<std::unique_ptr<C2Work>> workItems) override { + mThis->onWorkDone(component, std::move(workItems)); + } + + virtual void onTripped_nb(std::weak_ptr<C2Component> component, + std::vector<std::shared_ptr<C2SettingResult>> settingResult) override { + mThis->onTripped(component, settingResult); + } + + virtual void onError_nb(std::weak_ptr<C2Component> component, + uint32_t errorCode) override { + mThis->onError(component, errorCode); + } + +private: + SimplePlayer * const mThis; +}; + + +SimplePlayer::SimplePlayer() + : mListener(new Listener(this)), + mProducerListener(new DummyProducerListener), + mLinearPoolId(C2BlockPool::PLATFORM_START), + mComposerClient(new SurfaceComposerClient) { + CHECK_EQ(mComposerClient->initCheck(), (status_t)OK); + + std::shared_ptr<C2AllocatorStore> store = GetCodec2PlatformAllocatorStore(); + CHECK_EQ(store->fetchAllocator(C2AllocatorStore::DEFAULT_LINEAR, &mAllocIon), C2_OK); + mLinearPool = std::make_shared<C2PooledBlockPool>(mAllocIon, mLinearPoolId++); + + mControl = mComposerClient->createSurface( + String8("A Surface"), + 1280, + 800, + HAL_PIXEL_FORMAT_YV12); + //PIXEL_FORMAT_RGB_565); + + CHECK(mControl != nullptr); + CHECK(mControl->isValid()); + + SurfaceComposerClient::Transaction{} + .setLayer(mControl, INT_MAX) + .show(mControl) + .apply(); + + mSurface = mControl->getSurface(); + CHECK(mSurface != nullptr); + mSurface->connect(NATIVE_WINDOW_API_CPU, mProducerListener); +} + +SimplePlayer::~SimplePlayer() { + mComposerClient->dispose(); +} + +void SimplePlayer::onWorkDone( + std::weak_ptr<C2Component> component, std::list<std::unique_ptr<C2Work>> workItems) { + ALOGV("SimplePlayer::onWorkDone"); + (void) component; + ULock l(mProcessedLock); + for (auto & item : workItems) { + mProcessedWork.push_back(std::move(item)); + } + mProcessedCondition.notify_all(); +} + +void SimplePlayer::onTripped( + std::weak_ptr<C2Component> component, + std::vector<std::shared_ptr<C2SettingResult>> settingResult) { + (void) component; + (void) settingResult; + // TODO +} + +void SimplePlayer::onError(std::weak_ptr<C2Component> component, uint32_t errorCode) { + (void) component; + (void) errorCode; + // TODO +} + +void SimplePlayer::play(const sp<IMediaSource> &source) { + ALOGV("SimplePlayer::play"); + sp<AMessage> format; + (void) convertMetaDataToMessage(source->getFormat(), &format); + + sp<ABuffer> csd0, csd1; + format->findBuffer("csd-0", &csd0); + format->findBuffer("csd-1", &csd1); + + status_t err = source->start(); + + if (err != OK) { + fprintf(stderr, "source returned error %d (0x%08x)\n", err, err); + return; + } + + std::shared_ptr<C2ComponentStore> store = GetCodec2PlatformComponentStore(); + std::shared_ptr<C2Component> component; + (void)store->createComponent("c2.android.avc.decoder", &component); + + (void)component->setListener_vb(mListener, C2_DONT_BLOCK); + std::unique_ptr<C2PortBlockPoolsTuning::output> pools = + C2PortBlockPoolsTuning::output::AllocUnique({ (uint64_t)C2BlockPool::BASIC_GRAPHIC }); + std::vector<std::unique_ptr<C2SettingResult>> result; + (void)component->intf()->config_vb({pools.get()}, C2_DONT_BLOCK, &result); + component->start(); + + for (int i = 0; i < 8; ++i) { + mWorkQueue.emplace_back(new C2Work); + } + + std::atomic_bool running(true); + std::thread surfaceThread([this, &running]() { + const sp<IGraphicBufferProducer> &igbp = mSurface->getIGraphicBufferProducer(); + while (running) { + std::unique_ptr<C2Work> work; + { + ULock l(mProcessedLock); + if (mProcessedWork.empty()) { + mProcessedCondition.wait_for(l, 100ms); + if (mProcessedWork.empty()) { + continue; + } + } + work.swap(mProcessedWork.front()); + mProcessedWork.pop_front(); + } + int slot; + sp<Fence> fence; + ALOGV("Render: Frame #%lld", work->worklets.front()->output.ordinal.frameIndex.peekll()); + const std::shared_ptr<C2Buffer> &output = work->worklets.front()->output.buffers[0]; + if (output) { + const C2ConstGraphicBlock block = output->data().graphicBlocks().front(); + native_handle_t *grallocHandle = UnwrapNativeCodec2GrallocHandle(block.handle()); + sp<GraphicBuffer> buffer(new GraphicBuffer( + grallocHandle, + GraphicBuffer::CLONE_HANDLE, + block.width(), + block.height(), + HAL_PIXEL_FORMAT_YV12, + 1, + (uint64_t)GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN, + block.width())); + native_handle_delete(grallocHandle); + + status_t err = igbp->attachBuffer(&slot, buffer); + + IGraphicBufferProducer::QueueBufferInput qbi( + (work->worklets.front()->output.ordinal.timestamp * 1000ll).peekll(), + false, + HAL_DATASPACE_UNKNOWN, + Rect(block.width(), block.height()), + NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW, + 0, + Fence::NO_FENCE, + 0); + IGraphicBufferProducer::QueueBufferOutput qbo; + err = igbp->queueBuffer(slot, qbi, &qbo); + } + + work->input.buffers.clear(); + work->worklets.clear(); + + ULock l(mQueueLock); + mWorkQueue.push_back(std::move(work)); + mQueueCondition.notify_all(); + } + ALOGV("render loop finished"); + }); + + long numFrames = 0; + mLinearPool.reset(new C2PooledBlockPool(mAllocIon, mLinearPoolId++)); + + for (;;) { + size_t size = 0u; + void *data = nullptr; + int64_t timestamp = 0u; + MediaBufferBase *buffer = nullptr; + sp<ABuffer> csd; + if (csd0 != nullptr) { + csd = csd0; + csd0 = nullptr; + } else if (csd1 != nullptr) { + csd = csd1; + csd1 = nullptr; + } else { + status_t err = source->read(&buffer); + if (err != OK) { + CHECK(buffer == nullptr); + + if (err == INFO_FORMAT_CHANGED) { + continue; + } + + break; + } + MetaDataBase &meta = buffer->meta_data(); + CHECK(meta.findInt64(kKeyTime, ×tamp)); + + size = buffer->size(); + data = buffer->data(); + } + + if (csd != nullptr) { + size = csd->size(); + data = csd->data(); + } + + // Prepare C2Work + + std::unique_ptr<C2Work> work; + while (!work) { + ULock l(mQueueLock); + if (!mWorkQueue.empty()) { + work.swap(mWorkQueue.front()); + mWorkQueue.pop_front(); + } else { + mQueueCondition.wait_for(l, 100ms); + } + } + work->input.flags = (C2FrameData::flags_t)0; + work->input.ordinal.timestamp = timestamp; + work->input.ordinal.frameIndex = numFrames; + + std::shared_ptr<C2LinearBlock> block; + mLinearPool->fetchLinearBlock( + size, + { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE }, + &block); + C2WriteView view = block->map().get(); + if (view.error() != C2_OK) { + fprintf(stderr, "C2LinearBlock::map() failed : %d", view.error()); + break; + } + memcpy(view.base(), data, size); + + work->input.buffers.clear(); + work->input.buffers.emplace_back(new LinearBuffer(block)); + work->worklets.clear(); + work->worklets.emplace_back(new C2Worklet); + + std::list<std::unique_ptr<C2Work>> items; + items.push_back(std::move(work)); + + ALOGV("Frame #%ld size = %zu", numFrames, size); + // DO THE DECODING + component->queue_nb(&items); + + if (buffer) { + buffer->release(); + buffer = nullptr; + } + + ++numFrames; + } + ALOGV("main loop finished"); + source->stop(); + running.store(false); + surfaceThread.join(); + + component->release(); + printf("\n"); +} + +} // namespace + +static void usage(const char *me) { + fprintf(stderr, "usage: %s [options] [input_filename]\n", me); + fprintf(stderr, " -h(elp)\n"); +} + +int main(int argc, char **argv) { + android::ProcessState::self()->startThreadPool(); + + int res; + while ((res = getopt(argc, argv, "h")) >= 0) { + switch (res) { + case 'h': + default: + { + usage(argv[0]); + exit(1); + break; + } + } + } + + argc -= optind; + argv += optind; + + if (argc < 1) { + fprintf(stderr, "No input file specified\n"); + return 1; + } + + status_t err = OK; + SimplePlayer player; + + for (int k = 0; k < argc && err == OK; ++k) { + const char *filename = argv[k]; + + sp<DataSource> dataSource = + DataSourceFactory::CreateFromURI(nullptr /* httpService */, filename); + + if (strncasecmp(filename, "sine:", 5) && dataSource == nullptr) { + fprintf(stderr, "Unable to create data source.\n"); + return 1; + } + + Vector<sp<IMediaSource> > mediaSources; + sp<IMediaSource> mediaSource; + + sp<IMediaExtractor> extractor = MediaExtractorFactory::Create(dataSource); + + if (extractor == nullptr) { + fprintf(stderr, "could not create extractor.\n"); + return -1; + } + + sp<MetaData> meta = extractor->getMetaData(); + + if (meta != nullptr) { + const char *mime; + if (!meta->findCString(kKeyMIMEType, &mime)) { + fprintf(stderr, "extractor did not provide MIME type.\n"); + return -1; + } + } + + size_t numTracks = extractor->countTracks(); + + size_t i; + for (i = 0; i < numTracks; ++i) { + meta = extractor->getTrackMetaData(i, 0); + + if (meta == nullptr) { + break; + } + const char *mime; + meta->findCString(kKeyMIMEType, &mime); + + // TODO: allowing AVC only for the time being + if (!strncasecmp(mime, "video/avc", 9)) { + break; + } + + meta = nullptr; + } + + if (meta == nullptr) { + fprintf(stderr, "No AVC track found.\n"); + return -1; + } + + mediaSource = extractor->getTrack(i); + if (mediaSource == nullptr) { + fprintf(stderr, "skip NULL track %zu, total tracks %zu.\n", i, numTracks); + return -1; + } + + player.play(mediaSource); + } + + return 0; +}
diff --git a/media/codec2/components/flac/Android.bp b/media/codec2/components/flac/Android.bp new file mode 100644 index 0000000..e5eb51d --- /dev/null +++ b/media/codec2/components/flac/Android.bp
@@ -0,0 +1,30 @@ +cc_library_shared { + name: "libcodec2_soft_flacdec", + defaults: [ + "libcodec2_soft-defaults", + "libcodec2_soft_sanitize_all-defaults", + ], + + header_libs: ["libFLAC-headers"], + + srcs: ["C2SoftFlacDec.cpp"], + + shared_libs: [ + "libstagefright_flacdec", + ], +} + +cc_library_shared { + name: "libcodec2_soft_flacenc", + defaults: [ + "libcodec2_soft-defaults", + "libcodec2_soft_sanitize_all-defaults", + ], + + srcs: ["C2SoftFlacEnc.cpp"], + + static_libs: [ + "libaudioutils", + "libFLAC", + ], +}
diff --git a/media/codec2/components/flac/C2SoftFlacDec.cpp b/media/codec2/components/flac/C2SoftFlacDec.cpp new file mode 100644 index 0000000..4039b9b --- /dev/null +++ b/media/codec2/components/flac/C2SoftFlacDec.cpp
@@ -0,0 +1,378 @@ +/* + * 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 "C2SoftFlacDec" +#include <log/log.h> + +#include <media/stagefright/foundation/MediaDefs.h> + +#include <C2PlatformSupport.h> +#include <SimpleC2Interface.h> + +#include "C2SoftFlacDec.h" + +namespace android { + +namespace { + +constexpr char COMPONENT_NAME[] = "c2.android.flac.decoder"; + +} // namespace + +class C2SoftFlacDec::IntfImpl : public SimpleInterface<void>::BaseParams { +public: + explicit IntfImpl(const std::shared_ptr<C2ReflectorHelper> &helper) + : SimpleInterface<void>::BaseParams( + helper, + COMPONENT_NAME, + C2Component::KIND_DECODER, + C2Component::DOMAIN_AUDIO, + MEDIA_MIMETYPE_AUDIO_FLAC) { + noPrivateBuffers(); + noInputReferences(); + noOutputReferences(); + noInputLatency(); + noTimeStretch(); + setDerivedInstance(this); + + addParameter( + DefineParam(mAttrib, C2_PARAMKEY_COMPONENT_ATTRIBUTES) + .withConstValue(new C2ComponentAttributesSetting( + C2Component::ATTRIB_IS_TEMPORAL)) + .build()); + + addParameter( + DefineParam(mSampleRate, C2_PARAMKEY_SAMPLE_RATE) + .withDefault(new C2StreamSampleRateInfo::output(0u, 44100)) + .withFields({C2F(mSampleRate, value).inRange(1, 655350)}) + .withSetter((Setter<decltype(*mSampleRate)>::StrictValueWithNoDeps)) + .build()); + + addParameter( + DefineParam(mChannelCount, C2_PARAMKEY_CHANNEL_COUNT) + .withDefault(new C2StreamChannelCountInfo::output(0u, 1)) + .withFields({C2F(mChannelCount, value).inRange(1, 8)}) + .withSetter(Setter<decltype(*mChannelCount)>::StrictValueWithNoDeps) + .build()); + + addParameter( + DefineParam(mBitrate, C2_PARAMKEY_BITRATE) + .withDefault(new C2StreamBitrateInfo::input(0u, 768000)) + .withFields({C2F(mBitrate, value).inRange(1, 21000000)}) + .withSetter(Setter<decltype(*mBitrate)>::NonStrictValueWithNoDeps) + .build()); + + addParameter( + DefineParam(mInputMaxBufSize, C2_PARAMKEY_INPUT_MAX_BUFFER_SIZE) + .withConstValue(new C2StreamMaxBufferSizeInfo::input(0u, 32768)) + .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()); + } + + int32_t getPcmEncodingInfo() const { return mPcmEncodingInfo->value; } + +private: + std::shared_ptr<C2StreamSampleRateInfo::output> mSampleRate; + std::shared_ptr<C2StreamChannelCountInfo::output> mChannelCount; + std::shared_ptr<C2StreamBitrateInfo::input> mBitrate; + std::shared_ptr<C2StreamMaxBufferSizeInfo::input> mInputMaxBufSize; + std::shared_ptr<C2StreamPcmEncodingInfo::output> mPcmEncodingInfo; +}; + +C2SoftFlacDec::C2SoftFlacDec( + const char *name, + c2_node_id_t id, + const std::shared_ptr<IntfImpl> &intfImpl) + : SimpleC2Component(std::make_shared<SimpleInterface<IntfImpl>>(name, id, intfImpl)), + mIntf(intfImpl), + mFLACDecoder(nullptr) { +} + +C2SoftFlacDec::~C2SoftFlacDec() { + onRelease(); +} + +c2_status_t C2SoftFlacDec::onInit() { + status_t err = initDecoder(); + return err == OK ? C2_OK : C2_NO_MEMORY; +} + +c2_status_t C2SoftFlacDec::onStop() { + if (mFLACDecoder) mFLACDecoder->flush(); + memset(&mStreamInfo, 0, sizeof(mStreamInfo)); + mHasStreamInfo = false; + mSignalledError = false; + mSignalledOutputEos = false; + return C2_OK; +} + +void C2SoftFlacDec::onReset() { + mInputBufferCount = 0; + (void)onStop(); +} + +void C2SoftFlacDec::onRelease() { + mInputBufferCount = 0; + if (mFLACDecoder) delete mFLACDecoder; + mFLACDecoder = nullptr; +} + +c2_status_t C2SoftFlacDec::onFlush_sm() { + return onStop(); +} + +status_t C2SoftFlacDec::initDecoder() { + if (mFLACDecoder) { + delete mFLACDecoder; + } + mFLACDecoder = FLACDecoder::Create(); + if (!mFLACDecoder) { + ALOGE("initDecoder: failed to create FLACDecoder"); + mSignalledError = true; + return NO_MEMORY; + } + + memset(&mStreamInfo, 0, sizeof(mStreamInfo)); + mHasStreamInfo = false; + mSignalledError = false; + mSignalledOutputEos = false; + mInputBufferCount = 0; + + return OK; +} + +static void fillEmptyWork(const std::unique_ptr<C2Work> &work) { + 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; +} + +// (TODO) add multiframe support, in plugin and FLACDecoder.cpp +void C2SoftFlacDec::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.configUpdate.clear(); + work->worklets.front()->output.flags = work->input.flags; + + if (mSignalledError || mSignalledOutputEos) { + work->result = C2_BAD_VALUE; + return; + } + + C2ReadView rView = mDummyReadView; + size_t inOffset = 0u; + size_t inSize = 0u; + if (!work->input.buffers.empty()) { + rView = work->input.buffers[0]->data().linearBlocks().front().map().get(); + inSize = rView.capacity(); + if (inSize && rView.error()) { + ALOGE("read view map failed %d", rView.error()); + work->result = C2_CORRUPTED; + return; + } + } + bool eos = (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0; + bool codecConfig = (work->input.flags & C2FrameData::FLAG_CODEC_CONFIG) != 0; + + ALOGV("in buffer attr. size %zu timestamp %d frameindex %d", inSize, + (int)work->input.ordinal.timestamp.peeku(), (int)work->input.ordinal.frameIndex.peeku()); + + if (inSize == 0) { + fillEmptyWork(work); + if (eos) { + mSignalledOutputEos = true; + ALOGV("signalled EOS"); + } + return; + } + + if (mInputBufferCount == 0 && !codecConfig) { + ALOGV("First frame has to include configuration, forcing config"); + codecConfig = true; + } + + uint8_t *input = const_cast<uint8_t *>(rView.data() + inOffset); + if (codecConfig) { + status_t decoderErr = mFLACDecoder->parseMetadata(input, inSize); + if (decoderErr != OK && decoderErr != WOULD_BLOCK) { + ALOGE("process: FLACDecoder parseMetaData returns error %d", decoderErr); + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } + + mInputBufferCount++; + fillEmptyWork(work); + if (eos) { + mSignalledOutputEos = true; + ALOGV("signalled EOS"); + } + + if (decoderErr == WOULD_BLOCK) { + ALOGV("process: parseMetadata is Blocking, Continue %d", decoderErr); + } else { + mStreamInfo = mFLACDecoder->getStreamInfo(); + if (mStreamInfo.sample_rate && mStreamInfo.max_blocksize && + mStreamInfo.channels) { + mHasStreamInfo = true; + C2StreamSampleRateInfo::output sampleRateInfo( + 0u, mStreamInfo.sample_rate); + C2StreamChannelCountInfo::output channelCountInfo( + 0u, mStreamInfo.channels); + std::vector<std::unique_ptr<C2SettingResult>> failures; + c2_status_t err = + mIntf->config({&sampleRateInfo, &channelCountInfo}, + C2_MAY_BLOCK, &failures); + if (err == OK) { + work->worklets.front()->output.configUpdate.push_back( + C2Param::Copy(sampleRateInfo)); + work->worklets.front()->output.configUpdate.push_back( + C2Param::Copy(channelCountInfo)); + } else { + ALOGE("Config Update failed"); + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } + } + ALOGD("process: decoder configuration : %d Hz, %d channels, %d samples," + " %d block size", mStreamInfo.sample_rate, mStreamInfo.channels, + (int)mStreamInfo.total_samples, mStreamInfo.max_blocksize); + } + return; + } + + const bool outputFloat = mIntf->getPcmEncodingInfo() == C2Config::PCM_FLOAT; + const size_t sampleSize = outputFloat ? sizeof(float) : sizeof(short); + size_t outSize = mHasStreamInfo ? + mStreamInfo.max_blocksize * mStreamInfo.channels * sampleSize + : kMaxBlockSize * FLACDecoder::kMaxChannels * sampleSize; + + std::shared_ptr<C2LinearBlock> block; + C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE }; + c2_status_t err = pool->fetchLinearBlock(outSize, usage, &block); + if (err != C2_OK) { + ALOGE("fetchLinearBlock for Output failed with status %d", err); + work->result = C2_NO_MEMORY; + return; + } + C2WriteView wView = block->map().get(); + if (wView.error()) { + ALOGE("write view map failed %d", wView.error()); + work->result = C2_CORRUPTED; + return; + } + + status_t decoderErr = mFLACDecoder->decodeOneFrame( + input, inSize, wView.data(), &outSize, outputFloat); + if (decoderErr != OK) { + ALOGE("process: FLACDecoder decodeOneFrame returns error %d", decoderErr); + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } + + mInputBufferCount++; + ALOGV("out buffer attr. size %zu", outSize); + work->worklets.front()->output.flags = work->input.flags; + 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; + if (eos) { + mSignalledOutputEos = true; + ALOGV("signalled EOS"); + } +} + +c2_status_t C2SoftFlacDec::drain( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool) { + (void) pool; + if (drainMode == NO_DRAIN) { + ALOGW("drain with NO_DRAIN: no-op"); + return C2_OK; + } + if (drainMode == DRAIN_CHAIN) { + ALOGW("DRAIN_CHAIN not supported"); + return C2_OMITTED; + } + + if (mFLACDecoder) mFLACDecoder->flush(); + + return C2_OK; +} + +class C2SoftFlacDecFactory : public C2ComponentFactory { +public: + C2SoftFlacDecFactory() : mHelper(std::static_pointer_cast<C2ReflectorHelper>( + GetCodec2PlatformComponentStore()->getParamReflector())) { + } + + virtual c2_status_t createComponent( + c2_node_id_t id, + std::shared_ptr<C2Component>* const component, + std::function<void(C2Component*)> deleter) override { + *component = std::shared_ptr<C2Component>( + new C2SoftFlacDec(COMPONENT_NAME, + id, + std::make_shared<C2SoftFlacDec::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual c2_status_t createInterface( + c2_node_id_t id, + std::shared_ptr<C2ComponentInterface>* const interface, + std::function<void(C2ComponentInterface*)> deleter) override { + *interface = std::shared_ptr<C2ComponentInterface>( + new SimpleInterface<C2SoftFlacDec::IntfImpl>( + COMPONENT_NAME, id, std::make_shared<C2SoftFlacDec::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual ~C2SoftFlacDecFactory() override = default; + +private: + std::shared_ptr<C2ReflectorHelper> mHelper; +}; + +} // namespace android + +extern "C" ::C2ComponentFactory* CreateCodec2Factory() { + ALOGV("in %s", __func__); + return new ::android::C2SoftFlacDecFactory(); +} + +extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) { + ALOGV("in %s", __func__); + delete factory; +}
diff --git a/media/codec2/components/flac/C2SoftFlacDec.h b/media/codec2/components/flac/C2SoftFlacDec.h new file mode 100644 index 0000000..b491bfd --- /dev/null +++ b/media/codec2/components/flac/C2SoftFlacDec.h
@@ -0,0 +1,66 @@ +/* + * 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_C2_SOFT_FLAC_DEC_H_ +#define ANDROID_C2_SOFT_FLAC_DEC_H_ + +#include <SimpleC2Component.h> + +#include "FLACDecoder.h" + +namespace android { + +struct C2SoftFlacDec : public SimpleC2Component { + class IntfImpl; + + C2SoftFlacDec(const char *name, c2_node_id_t id, + const std::shared_ptr<IntfImpl> &intfImpl); + virtual ~C2SoftFlacDec(); + + // From SimpleC2Component + c2_status_t onInit() override; + c2_status_t onStop() override; + void onReset() override; + void onRelease() override; + c2_status_t onFlush_sm() override; + void process( + const std::unique_ptr<C2Work> &work, + const std::shared_ptr<C2BlockPool> &pool) override; + c2_status_t drain( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool) override; + +private: + enum { + kMaxBlockSize = 4096 + }; + + std::shared_ptr<IntfImpl> mIntf; + FLACDecoder *mFLACDecoder; + FLAC__StreamMetadata_StreamInfo mStreamInfo; + bool mSignalledError; + bool mSignalledOutputEos; + bool mHasStreamInfo; + size_t mInputBufferCount; + + status_t initDecoder(); + + C2_DO_NOT_COPY(C2SoftFlacDec); +}; + +} // namespace android + +#endif // ANDROID_C2_SOFT_FLAC_DEC_H_
diff --git a/media/codec2/components/flac/C2SoftFlacEnc.cpp b/media/codec2/components/flac/C2SoftFlacEnc.cpp new file mode 100644 index 0000000..408db7e --- /dev/null +++ b/media/codec2/components/flac/C2SoftFlacEnc.cpp
@@ -0,0 +1,492 @@ +/* + * 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 "C2SoftFlacEnc" +#include <log/log.h> + +#include <audio_utils/primitives.h> +#include <media/stagefright/foundation/MediaDefs.h> + +#include <C2PlatformSupport.h> +#include <SimpleC2Interface.h> + +#include "C2SoftFlacEnc.h" + +namespace android { + +namespace { + +constexpr char COMPONENT_NAME[] = "c2.android.flac.encoder"; + +} // namespace + +class C2SoftFlacEnc::IntfImpl : public SimpleInterface<void>::BaseParams { +public: + explicit IntfImpl(const std::shared_ptr<C2ReflectorHelper> &helper) + : SimpleInterface<void>::BaseParams( + helper, + COMPONENT_NAME, + C2Component::KIND_ENCODER, + C2Component::DOMAIN_AUDIO, + MEDIA_MIMETYPE_AUDIO_FLAC) { + noPrivateBuffers(); + noInputReferences(); + noOutputReferences(); + noInputLatency(); + noTimeStretch(); + setDerivedInstance(this); + + addParameter( + DefineParam(mAttrib, C2_PARAMKEY_COMPONENT_ATTRIBUTES) + .withConstValue(new C2ComponentAttributesSetting( + C2Component::ATTRIB_IS_TEMPORAL)) + .build()); + addParameter( + DefineParam(mSampleRate, C2_PARAMKEY_SAMPLE_RATE) + .withDefault(new C2StreamSampleRateInfo::input(0u, 44100)) + .withFields({C2F(mSampleRate, value).inRange(1, 655350)}) + .withSetter((Setter<decltype(*mSampleRate)>::StrictValueWithNoDeps)) + .build()); + addParameter( + DefineParam(mChannelCount, C2_PARAMKEY_CHANNEL_COUNT) + .withDefault(new C2StreamChannelCountInfo::input(0u, 1)) + .withFields({C2F(mChannelCount, value).inRange(1, 2)}) + .withSetter(Setter<decltype(*mChannelCount)>::StrictValueWithNoDeps) + .build()); + addParameter( + DefineParam(mBitrate, C2_PARAMKEY_BITRATE) + .withDefault(new C2StreamBitrateInfo::output(0u, 768000)) + .withFields({C2F(mBitrate, value).inRange(1, 21000000)}) + .withSetter(Setter<decltype(*mBitrate)>::NonStrictValueWithNoDeps) + .build()); + addParameter( + DefineParam(mComplexity, C2_PARAMKEY_COMPLEXITY) + .withDefault(new C2StreamComplexityTuning::output(0u, + FLAC_COMPRESSION_LEVEL_DEFAULT)) + .withFields({C2F(mComplexity, value).inRange( + FLAC_COMPRESSION_LEVEL_MIN, FLAC_COMPRESSION_LEVEL_MAX)}) + .withSetter(Setter<decltype(*mComplexity)>::NonStrictValueWithNoDeps) + .build()); + addParameter( + DefineParam(mInputMaxBufSize, C2_PARAMKEY_INPUT_MAX_BUFFER_SIZE) + .withConstValue(new C2StreamMaxBufferSizeInfo::input(0u, 4608)) + .build()); + + addParameter( + DefineParam(mPcmEncodingInfo, C2_PARAMKEY_PCM_ENCODING) + .withDefault(new C2StreamPcmEncodingInfo::input(0u, C2Config::PCM_16)) + .withFields({C2F(mPcmEncodingInfo, value).oneOf({ + C2Config::PCM_16, + // C2Config::PCM_8, + C2Config::PCM_FLOAT}) + }) + .withSetter((Setter<decltype(*mPcmEncodingInfo)>::StrictValueWithNoDeps)) + .build()); + } + + uint32_t getSampleRate() const { return mSampleRate->value; } + uint32_t getChannelCount() const { return mChannelCount->value; } + uint32_t getBitrate() const { return mBitrate->value; } + uint32_t getComplexity() const { return mComplexity->value; } + int32_t getPcmEncodingInfo() const { return mPcmEncodingInfo->value; } + +private: + std::shared_ptr<C2StreamSampleRateInfo::input> mSampleRate; + std::shared_ptr<C2StreamChannelCountInfo::input> mChannelCount; + std::shared_ptr<C2StreamBitrateInfo::output> mBitrate; + std::shared_ptr<C2StreamComplexityTuning::output> mComplexity; + std::shared_ptr<C2StreamMaxBufferSizeInfo::input> mInputMaxBufSize; + std::shared_ptr<C2StreamPcmEncodingInfo::input> mPcmEncodingInfo; +}; + +C2SoftFlacEnc::C2SoftFlacEnc( + const char *name, + c2_node_id_t id, + const std::shared_ptr<IntfImpl> &intfImpl) + : SimpleC2Component(std::make_shared<SimpleInterface<IntfImpl>>(name, id, intfImpl)), + mIntf(intfImpl), + mFlacStreamEncoder(nullptr), + mInputBufferPcm32(nullptr) { +} + +C2SoftFlacEnc::~C2SoftFlacEnc() { + onRelease(); +} + +c2_status_t C2SoftFlacEnc::onInit() { + mFlacStreamEncoder = FLAC__stream_encoder_new(); + if (!mFlacStreamEncoder) return C2_CORRUPTED; + + mInputBufferPcm32 = (FLAC__int32*) malloc( + kInBlockSize * kMaxNumChannels * sizeof(FLAC__int32)); + if (!mInputBufferPcm32) return C2_NO_MEMORY; + + mSignalledError = false; + mSignalledOutputEos = false; + mIsFirstFrame = true; + mAnchorTimeStamp = 0ull; + mProcessedSamples = 0u; + mEncoderWriteData = false; + mEncoderReturnedNbBytes = 0; + mHeaderOffset = 0; + mWroteHeader = false; + + status_t err = configureEncoder(); + return err == OK ? C2_OK : C2_CORRUPTED; +} + +void C2SoftFlacEnc::onRelease() { + if (mFlacStreamEncoder) { + FLAC__stream_encoder_delete(mFlacStreamEncoder); + mFlacStreamEncoder = nullptr; + } + + if (mInputBufferPcm32) { + free(mInputBufferPcm32); + mInputBufferPcm32 = nullptr; + } +} + +void C2SoftFlacEnc::onReset() { + (void) onStop(); +} + +c2_status_t C2SoftFlacEnc::onStop() { + mSignalledError = false; + mSignalledOutputEos = false; + mIsFirstFrame = true; + mAnchorTimeStamp = 0ull; + mProcessedSamples = 0u; + mEncoderWriteData = false; + mEncoderReturnedNbBytes = 0; + mHeaderOffset = 0; + mWroteHeader = false; + + c2_status_t status = drain(DRAIN_COMPONENT_NO_EOS, nullptr); + if (C2_OK != status) return status; + + status_t err = configureEncoder(); + if (err != OK) mSignalledError = true; + return C2_OK; +} + +c2_status_t C2SoftFlacEnc::onFlush_sm() { + return onStop(); +} + +static void fillEmptyWork(const std::unique_ptr<C2Work> &work) { + work->worklets.front()->output.flags = work->input.flags; + work->worklets.front()->output.buffers.clear(); + work->worklets.front()->output.ordinal = work->input.ordinal; +} + +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; + } + + bool eos = ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0); + C2ReadView rView = mDummyReadView; + size_t inOffset = 0u; + size_t inSize = 0u; + if (!work->input.buffers.empty()) { + rView = work->input.buffers[0]->data().linearBlocks().front().map().get(); + inSize = rView.capacity(); + if (inSize && rView.error()) { + ALOGE("read view map failed %d", rView.error()); + work->result = C2_CORRUPTED; + return; + } + } + + ALOGV("in buffer attr. size %zu timestamp %d frameindex %d, flags %x", + inSize, (int)work->input.ordinal.timestamp.peeku(), + (int)work->input.ordinal.frameIndex.peeku(), work->input.flags); + if (mIsFirstFrame && inSize) { + mAnchorTimeStamp = work->input.ordinal.timestamp.peekull(); + mIsFirstFrame = false; + } + + if (!mWroteHeader) { + std::unique_ptr<C2StreamInitDataInfo::output> csd = + C2StreamInitDataInfo::output::AllocUnique(mHeaderOffset, 0u); + 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); + + work->worklets.front()->output.configUpdate.push_back(std::move(csd)); + mWroteHeader = true; + } + + const uint32_t sampleRate = mIntf->getSampleRate(); + const uint32_t channelCount = mIntf->getChannelCount(); + const bool inputFloat = mIntf->getPcmEncodingInfo() == C2Config::PCM_FLOAT; + const unsigned sampleSize = inputFloat ? sizeof(float) : sizeof(int16_t); + const unsigned frameSize = channelCount * sampleSize; + const uint64_t outTimeStamp = mProcessedSamples * 1000000ll / sampleRate; + + size_t outCapacity = inSize; + outCapacity += mBlockSize * frameSize; + + C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE }; + c2_status_t err = pool->fetchLinearBlock(outCapacity, usage, &mOutputBlock); + if (err != C2_OK) { + ALOGE("fetchLinearBlock for Output failed with status %d", err); + work->result = C2_NO_MEMORY; + return; + } + C2WriteView wView = mOutputBlock->map().get(); + if (wView.error()) { + ALOGE("write view map failed %d", wView.error()); + work->result = C2_CORRUPTED; + return; + } + + mEncoderWriteData = true; + mEncoderReturnedNbBytes = 0; + size_t inPos = 0; + while (inPos < inSize) { + const uint8_t *inPtr = rView.data() + inOffset; + const size_t processSize = MIN(kInBlockSize * frameSize, (inSize - inPos)); + const unsigned nbInputFrames = processSize / frameSize; + const unsigned nbInputSamples = processSize / sampleSize; + + ALOGV("about to encode %zu bytes", processSize); + if (inputFloat) { + const float * const pcmFloat = reinterpret_cast<const float *>(inPtr + inPos); + memcpy_to_q8_23_from_float_with_clamp(mInputBufferPcm32, pcmFloat, nbInputSamples); + } else { + const int16_t * const pcm16 = reinterpret_cast<const int16_t *>(inPtr + inPos); + for (unsigned i = 0; i < nbInputSamples; i++) { + mInputBufferPcm32[i] = (FLAC__int32) pcm16[i]; + } + } + + FLAC__bool ok = FLAC__stream_encoder_process_interleaved( + mFlacStreamEncoder, mInputBufferPcm32, nbInputFrames); + if (!ok) { + ALOGE("error encountered during encoding"); + mSignalledError = true; + work->result = C2_CORRUPTED; + mOutputBlock.reset(); + return; + } + inPos += processSize; + } + if (eos && (C2_OK != drain(DRAIN_COMPONENT_WITH_EOS, pool))) { + ALOGE("error encountered during encoding"); + mSignalledError = true; + work->result = C2_CORRUPTED; + mOutputBlock.reset(); + return; + } + fillEmptyWork(work); + if (mEncoderReturnedNbBytes != 0) { + std::shared_ptr<C2Buffer> buffer = createLinearBuffer(std::move(mOutputBlock), 0, mEncoderReturnedNbBytes); + work->worklets.front()->output.buffers.push_back(buffer); + work->worklets.front()->output.ordinal.timestamp = mAnchorTimeStamp + outTimeStamp; + } else { + ALOGV("encoder process_interleaved returned without data to write"); + } + mOutputBlock = nullptr; + if (eos) { + mSignalledOutputEos = true; + ALOGV("signalled EOS"); + } + mEncoderWriteData = false; + mEncoderReturnedNbBytes = 0; +} + +FLAC__StreamEncoderWriteStatus C2SoftFlacEnc::onEncodedFlacAvailable( + const FLAC__byte buffer[], size_t bytes, unsigned samples, + unsigned current_frame) { + (void) current_frame; + ALOGV("%s (bytes=%zu, samples=%u, curr_frame=%u)", __func__, bytes, samples, + current_frame); + + if (samples == 0) { + ALOGI("saving %zu bytes of header", bytes); + memcpy(mHeader + mHeaderOffset, buffer, bytes); + mHeaderOffset += bytes;// will contain header size when finished receiving header + return FLAC__STREAM_ENCODER_WRITE_STATUS_OK; + } + + if ((samples == 0) || !mEncoderWriteData) { + // called by the encoder because there's header data to save, but it's not the role + // of this component (unless WRITE_FLAC_HEADER_IN_FIRST_BUFFER is defined) + ALOGV("ignoring %zu bytes of header data (samples=%d)", bytes, samples); + return FLAC__STREAM_ENCODER_WRITE_STATUS_OK; + } + + // write encoded data + C2WriteView wView = mOutputBlock->map().get(); + uint8_t* outData = wView.data(); + ALOGV("writing %zu bytes of encoded data on output", bytes); + // increment mProcessedSamples to maintain audio synchronization during + // play back + mProcessedSamples += samples; + if (bytes + mEncoderReturnedNbBytes > mOutputBlock->capacity()) { + ALOGE("not enough space left to write encoded data, dropping %zu bytes", bytes); + // a fatal error would stop the encoding + return FLAC__STREAM_ENCODER_WRITE_STATUS_OK; + } + memcpy(outData + mEncoderReturnedNbBytes, buffer, bytes); + mEncoderReturnedNbBytes += bytes; + return FLAC__STREAM_ENCODER_WRITE_STATUS_OK; +} + + +status_t C2SoftFlacEnc::configureEncoder() { + ALOGV("%s numChannel=%d, sampleRate=%d", __func__, mIntf->getChannelCount(), mIntf->getSampleRate()); + + if (mSignalledError || !mFlacStreamEncoder) { + ALOGE("can't configure encoder: no encoder or invalid state"); + return UNKNOWN_ERROR; + } + + const bool inputFloat = mIntf->getPcmEncodingInfo() == C2Config::PCM_FLOAT; + const int bitsPerSample = inputFloat ? 24 : 16; + FLAC__bool ok = true; + ok = ok && FLAC__stream_encoder_set_channels(mFlacStreamEncoder, mIntf->getChannelCount()); + ok = ok && FLAC__stream_encoder_set_sample_rate(mFlacStreamEncoder, mIntf->getSampleRate()); + ok = ok && FLAC__stream_encoder_set_bits_per_sample(mFlacStreamEncoder, bitsPerSample); + ok = ok && FLAC__stream_encoder_set_compression_level(mFlacStreamEncoder, + mIntf->getComplexity()); + ok = ok && FLAC__stream_encoder_set_verify(mFlacStreamEncoder, false); + if (!ok) { + ALOGE("unknown error when configuring encoder"); + return UNKNOWN_ERROR; + } + + ok &= FLAC__STREAM_ENCODER_INIT_STATUS_OK == + FLAC__stream_encoder_init_stream(mFlacStreamEncoder, + flacEncoderWriteCallback /*write_callback*/, + nullptr /*seek_callback*/, + nullptr /*tell_callback*/, + nullptr /*metadata_callback*/, + (void *) this /*client_data*/); + + if (!ok) { + ALOGE("unknown error when configuring encoder"); + return UNKNOWN_ERROR; + } + + mBlockSize = FLAC__stream_encoder_get_blocksize(mFlacStreamEncoder); + + ALOGV("encoder successfully configured"); + return OK; +} + +FLAC__StreamEncoderWriteStatus C2SoftFlacEnc::flacEncoderWriteCallback( + const FLAC__StreamEncoder *, + const FLAC__byte buffer[], + size_t bytes, + unsigned samples, + unsigned current_frame, + void *client_data) { + return ((C2SoftFlacEnc*) client_data)->onEncodedFlacAvailable( + buffer, bytes, samples, current_frame); +} + +c2_status_t C2SoftFlacEnc::drain( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool) { + (void) pool; + switch (drainMode) { + case NO_DRAIN: + ALOGW("drain with NO_DRAIN: no-op"); + return C2_OK; + case DRAIN_CHAIN: + ALOGW("DRAIN_CHAIN not supported"); + return C2_OMITTED; + case DRAIN_COMPONENT_WITH_EOS: + // TODO: This flag is not being sent back to the client + // because there are no items in PendingWork queue as all the + // inputs are being sent back with emptywork or valid encoded data + // mSignalledOutputEos = true; + case DRAIN_COMPONENT_NO_EOS: + break; + default: + return C2_BAD_VALUE; + } + FLAC__bool ok = FLAC__stream_encoder_finish(mFlacStreamEncoder); + if (!ok) return C2_CORRUPTED; + mIsFirstFrame = true; + mAnchorTimeStamp = 0ull; + mProcessedSamples = 0u; + + return C2_OK; +} + +class C2SoftFlacEncFactory : public C2ComponentFactory { +public: + C2SoftFlacEncFactory() : mHelper(std::static_pointer_cast<C2ReflectorHelper>( + GetCodec2PlatformComponentStore()->getParamReflector())) { + } + + virtual c2_status_t createComponent( + c2_node_id_t id, + std::shared_ptr<C2Component>* const component, + std::function<void(C2Component*)> deleter) override { + *component = std::shared_ptr<C2Component>( + new C2SoftFlacEnc(COMPONENT_NAME, + id, + std::make_shared<C2SoftFlacEnc::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual c2_status_t createInterface( + c2_node_id_t id, + std::shared_ptr<C2ComponentInterface>* const interface, + std::function<void(C2ComponentInterface*)> deleter) override { + *interface = std::shared_ptr<C2ComponentInterface>( + new SimpleInterface<C2SoftFlacEnc::IntfImpl>( + COMPONENT_NAME, id, std::make_shared<C2SoftFlacEnc::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual ~C2SoftFlacEncFactory() override = default; +private: + std::shared_ptr<C2ReflectorHelper> mHelper; +}; + +} // namespace android + +extern "C" ::C2ComponentFactory* CreateCodec2Factory() { + ALOGV("in %s", __func__); + return new ::android::C2SoftFlacEncFactory(); +} + +extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) { + ALOGV("in %s", __func__); + delete factory; +}
diff --git a/media/codec2/components/flac/C2SoftFlacEnc.h b/media/codec2/components/flac/C2SoftFlacEnc.h new file mode 100644 index 0000000..b3f01d5 --- /dev/null +++ b/media/codec2/components/flac/C2SoftFlacEnc.h
@@ -0,0 +1,88 @@ +/* + * 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_C2_SOFT_FLAC_ENC_H_ +#define ANDROID_C2_SOFT_FLAC_ENC_H_ + +#include <SimpleC2Component.h> + +#include "FLAC/stream_encoder.h" + +#define FLAC_COMPRESSION_LEVEL_MIN 0 +#define FLAC_COMPRESSION_LEVEL_DEFAULT 5 +#define FLAC_COMPRESSION_LEVEL_MAX 8 + +#define FLAC_HEADER_SIZE 128 + +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) + +namespace android { + +class C2SoftFlacEnc : public SimpleC2Component { +public: + class IntfImpl; + + C2SoftFlacEnc(const char *name, c2_node_id_t id, const std::shared_ptr<IntfImpl> &intfImpl); + virtual ~C2SoftFlacEnc(); + + // From SimpleC2Component + c2_status_t onInit() override; + c2_status_t onStop() override; + void onReset() override; + void onRelease() override; + c2_status_t onFlush_sm() override; + void process( + const std::unique_ptr<C2Work> &work, + const std::shared_ptr<C2BlockPool> &pool) override; + c2_status_t drain( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool) override; + +private: + status_t configureEncoder(); + static FLAC__StreamEncoderWriteStatus flacEncoderWriteCallback( + const FLAC__StreamEncoder *encoder, const FLAC__byte buffer[], + size_t bytes, unsigned samples, unsigned current_frame, + void *client_data); + FLAC__StreamEncoderWriteStatus onEncodedFlacAvailable( + const FLAC__byte buffer[], size_t bytes, unsigned samples, + unsigned current_frame); + + std::shared_ptr<IntfImpl> mIntf; + const unsigned int kInBlockSize = 1152; + const unsigned int kMaxNumChannels = 2; + FLAC__StreamEncoder* mFlacStreamEncoder; + FLAC__int32* mInputBufferPcm32; + std::shared_ptr<C2LinearBlock> mOutputBlock; + bool mSignalledError; + bool mSignalledOutputEos; + uint32_t mBlockSize; + bool mIsFirstFrame; + uint64_t mAnchorTimeStamp; + uint64_t mProcessedSamples; + // should the data received by the callback be written to the output port + bool mEncoderWriteData; + size_t mEncoderReturnedNbBytes; + unsigned mHeaderOffset; + bool mWroteHeader; + char mHeader[FLAC_HEADER_SIZE]; + + C2_DO_NOT_COPY(C2SoftFlacEnc); +}; + +} // namespace android + +#endif // ANDROID_C2_SOFT_FLAC_ENC_H_
diff --git a/media/common_time/MODULE_LICENSE_APACHE2 b/media/codec2/components/flac/MODULE_LICENSE_APACHE2 similarity index 100% copy from media/common_time/MODULE_LICENSE_APACHE2 copy to media/codec2/components/flac/MODULE_LICENSE_APACHE2
diff --git a/media/common_time/NOTICE b/media/codec2/components/flac/NOTICE similarity index 100% copy from media/common_time/NOTICE copy to media/codec2/components/flac/NOTICE
diff --git a/media/codec2/components/g711/Android.bp b/media/codec2/components/g711/Android.bp new file mode 100644 index 0000000..3ede68c --- /dev/null +++ b/media/codec2/components/g711/Android.bp
@@ -0,0 +1,23 @@ +cc_library_shared { + name: "libcodec2_soft_g711alawdec", + defaults: [ + "libcodec2_soft-defaults", + "libcodec2_soft_sanitize_all-defaults", + ], + + srcs: ["C2SoftG711Dec.cpp"], + + cflags: [ + "-DALAW", + ], +} + +cc_library_shared { + name: "libcodec2_soft_g711mlawdec", + defaults: [ + "libcodec2_soft-defaults", + "libcodec2_soft_sanitize_all-defaults", + ], + + srcs: ["C2SoftG711Dec.cpp"], +}
diff --git a/media/codec2/components/g711/C2SoftG711Dec.cpp b/media/codec2/components/g711/C2SoftG711Dec.cpp new file mode 100644 index 0000000..43b843a --- /dev/null +++ b/media/codec2/components/g711/C2SoftG711Dec.cpp
@@ -0,0 +1,317 @@ +/* + * 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 "C2SoftG711Dec" +#include <log/log.h> + +#include <media/stagefright/foundation/MediaDefs.h> + +#include <C2PlatformSupport.h> +#include <SimpleC2Interface.h> + +#include "C2SoftG711Dec.h" + +namespace android { + +namespace { + +#ifdef ALAW +constexpr char COMPONENT_NAME[] = "c2.android.g711.alaw.decoder"; +#else +constexpr char COMPONENT_NAME[] = "c2.android.g711.mlaw.decoder"; +#endif + +} // namespace + +class C2SoftG711Dec::IntfImpl : public SimpleInterface<void>::BaseParams { +public: + explicit IntfImpl(const std::shared_ptr<C2ReflectorHelper> &helper) + : SimpleInterface<void>::BaseParams( + helper, + COMPONENT_NAME, + C2Component::KIND_DECODER, + C2Component::DOMAIN_AUDIO, +#ifdef ALAW + MEDIA_MIMETYPE_AUDIO_G711_ALAW +#else + MEDIA_MIMETYPE_AUDIO_G711_MLAW +#endif + ) { + noPrivateBuffers(); + noInputReferences(); + noOutputReferences(); + noInputLatency(); + noTimeStretch(); + setDerivedInstance(this); + + addParameter( + DefineParam(mAttrib, C2_PARAMKEY_COMPONENT_ATTRIBUTES) + .withConstValue(new C2ComponentAttributesSetting( + C2Component::ATTRIB_IS_TEMPORAL)) + .build()); + + addParameter( + DefineParam(mSampleRate, C2_PARAMKEY_SAMPLE_RATE) + .withDefault(new C2StreamSampleRateInfo::output(0u, 8000)) + .withFields({C2F(mSampleRate, value).inRange(8000, 48000)}) + .withSetter((Setter<decltype(*mSampleRate)>::StrictValueWithNoDeps)) + .build()); + + addParameter( + DefineParam(mChannelCount, C2_PARAMKEY_CHANNEL_COUNT) + .withDefault(new C2StreamChannelCountInfo::output(0u, 1)) + .withFields({C2F(mChannelCount, value).equalTo(1)}) + .withSetter(Setter<decltype(*mChannelCount)>::StrictValueWithNoDeps) + .build()); + + addParameter( + DefineParam(mBitrate, C2_PARAMKEY_BITRATE) + .withDefault(new C2StreamBitrateInfo::input(0u, 64000)) + .withFields({C2F(mBitrate, value).equalTo(64000)}) + .withSetter(Setter<decltype(*mBitrate)>::NonStrictValueWithNoDeps) + .build()); + + addParameter( + DefineParam(mInputMaxBufSize, C2_PARAMKEY_INPUT_MAX_BUFFER_SIZE) + .withConstValue(new C2StreamMaxBufferSizeInfo::input(0u, 8192)) + .build()); + } + +private: + std::shared_ptr<C2StreamSampleRateInfo::output> mSampleRate; + std::shared_ptr<C2StreamChannelCountInfo::output> mChannelCount; + std::shared_ptr<C2StreamBitrateInfo::input> mBitrate; + std::shared_ptr<C2StreamMaxBufferSizeInfo::input> mInputMaxBufSize; +}; + +C2SoftG711Dec::C2SoftG711Dec( + const char *name, + c2_node_id_t id, + const std::shared_ptr<IntfImpl> &intfImpl) + : SimpleC2Component(std::make_shared<SimpleInterface<IntfImpl>>(name, id, intfImpl)), + mIntf(intfImpl) { +} + +C2SoftG711Dec::~C2SoftG711Dec() { + onRelease(); +} + +c2_status_t C2SoftG711Dec::onInit() { + mSignalledOutputEos = false; + return C2_OK; +} + +c2_status_t C2SoftG711Dec::onStop() { + mSignalledOutputEos = false; + return C2_OK; +} + +void C2SoftG711Dec::onReset() { + (void)onStop(); +} + +void C2SoftG711Dec::onRelease() { +} + +c2_status_t C2SoftG711Dec::onFlush_sm() { + return onStop(); +} + +void C2SoftG711Dec::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 (mSignalledOutputEos) { + work->result = C2_BAD_VALUE; + return; + } + + C2ReadView rView = mDummyReadView; + size_t inOffset = 0u; + size_t inSize = 0u; + if (!work->input.buffers.empty()) { + rView = work->input.buffers[0]->data().linearBlocks().front().map().get(); + inSize = rView.capacity(); + if (inSize && rView.error()) { + ALOGE("read view map failed %d", rView.error()); + work->result = C2_CORRUPTED; + return; + } + } + bool eos = (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0; + int outSize = inSize * sizeof(int16_t); + + ALOGV("in buffer attr. size %zu timestamp %d frameindex %d", inSize, + (int)work->input.ordinal.timestamp.peeku(), (int)work->input.ordinal.frameIndex.peeku()); + + if (inSize == 0) { + work->worklets.front()->output.flags = work->input.flags; + work->worklets.front()->output.buffers.clear(); + work->worklets.front()->output.ordinal = work->input.ordinal; + if (eos) { + mSignalledOutputEos = true; + ALOGV("signalled EOS"); + } + return; + } + + uint8_t *inputptr = const_cast<uint8_t *>(rView.data() + inOffset); + + std::shared_ptr<C2LinearBlock> block; + C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE }; + c2_status_t err = pool->fetchLinearBlock(outSize, usage, &block); + if (err != C2_OK) { + ALOGE("fetchLinearBlock for Output failed with status %d", err); + work->result = C2_NO_MEMORY; + return; + } + C2WriteView wView = block->map().get(); + if (wView.error()) { + ALOGE("write view map failed %d", wView.error()); + work->result = C2_CORRUPTED; + return; + } + int16_t *outputptr = reinterpret_cast<int16_t *>(wView.data()); + +#ifdef ALAW + DecodeALaw(outputptr, inputptr, inSize); +#else + DecodeMLaw(outputptr, inputptr, inSize); +#endif + + work->worklets.front()->output.flags = work->input.flags; + work->worklets.front()->output.buffers.clear(); + work->worklets.front()->output.buffers.push_back(createLinearBuffer(block)); + work->worklets.front()->output.ordinal = work->input.ordinal; + + if (eos) { + mSignalledOutputEos = true; + ALOGV("signalled EOS"); + } +} + +c2_status_t C2SoftG711Dec::drain( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool) { + (void) pool; + if (drainMode == NO_DRAIN) { + ALOGW("drain with NO_DRAIN: no-op"); + return C2_OK; + } + if (drainMode == DRAIN_CHAIN) { + ALOGW("DRAIN_CHAIN not supported"); + return C2_OMITTED; + } + + return C2_OK; +} + +#ifdef ALAW +void C2SoftG711Dec::DecodeALaw( + int16_t *out, const uint8_t *in, size_t inSize) { + while (inSize > 0) { + inSize--; + int32_t x = *in++; + + int32_t ix = x ^ 0x55; + ix &= 0x7f; + + int32_t iexp = ix >> 4; + int32_t mant = ix & 0x0f; + + if (iexp > 0) { + mant += 16; + } + + mant = (mant << 4) + 8; + + if (iexp > 1) { + mant = mant << (iexp - 1); + } + + *out++ = (x > 127) ? mant : -mant; + } +} +#else +void C2SoftG711Dec::DecodeMLaw( + int16_t *out, const uint8_t *in, size_t inSize) { + while (inSize > 0) { + inSize--; + int32_t x = *in++; + + int32_t mantissa = ~x; + int32_t exponent = (mantissa >> 4) & 7; + int32_t segment = exponent + 1; + mantissa &= 0x0f; + + int32_t step = 4 << segment; + + int32_t abs = (0x80l << exponent) + step * mantissa + step / 2 - 4 * 33; + + *out++ = (x < 0x80) ? -abs : abs; + } +} +#endif + +class C2SoftG711DecFactory : public C2ComponentFactory { +public: + C2SoftG711DecFactory() : mHelper(std::static_pointer_cast<C2ReflectorHelper>( + GetCodec2PlatformComponentStore()->getParamReflector())) { + } + + virtual c2_status_t createComponent( + c2_node_id_t id, + std::shared_ptr<C2Component>* const component, + std::function<void(C2Component*)> deleter) override { + *component = std::shared_ptr<C2Component>( + new C2SoftG711Dec(COMPONENT_NAME, id, + std::make_shared<C2SoftG711Dec::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual c2_status_t createInterface( + c2_node_id_t id, std::shared_ptr<C2ComponentInterface>* const interface, + std::function<void(C2ComponentInterface*)> deleter) override { + *interface = std::shared_ptr<C2ComponentInterface>( + new SimpleInterface<C2SoftG711Dec::IntfImpl>( + COMPONENT_NAME, id, std::make_shared<C2SoftG711Dec::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual ~C2SoftG711DecFactory() override = default; + +private: + std::shared_ptr<C2ReflectorHelper> mHelper; +}; + +} // namespace android + +extern "C" ::C2ComponentFactory* CreateCodec2Factory() { + ALOGV("in %s", __func__); + return new ::android::C2SoftG711DecFactory(); +} + +extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) { + ALOGV("in %s", __func__); + delete factory; +}
diff --git a/media/codec2/components/g711/C2SoftG711Dec.h b/media/codec2/components/g711/C2SoftG711Dec.h new file mode 100644 index 0000000..23e8ffc --- /dev/null +++ b/media/codec2/components/g711/C2SoftG711Dec.h
@@ -0,0 +1,59 @@ +/* + * 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_C2_SOFT_G711_DEC_H_ +#define ANDROID_C2_SOFT_G711_DEC_H_ + +#include <SimpleC2Component.h> + + +namespace android { + +struct C2SoftG711Dec : public SimpleC2Component { + class IntfImpl; + + C2SoftG711Dec(const char *name, c2_node_id_t id, + const std::shared_ptr<IntfImpl> &intfImpl); + virtual ~C2SoftG711Dec(); + + // From SimpleC2Component + c2_status_t onInit() override; + c2_status_t onStop() override; + void onReset() override; + void onRelease() override; + c2_status_t onFlush_sm() override; + void process( + const std::unique_ptr<C2Work> &work, + const std::shared_ptr<C2BlockPool> &pool) override; + c2_status_t drain( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool) override; +private: + std::shared_ptr<IntfImpl> mIntf; + bool mSignalledOutputEos; + +#ifdef ALAW + void DecodeALaw(int16_t *out, const uint8_t *in, size_t inSize); +#else + void DecodeMLaw(int16_t *out, const uint8_t *in, size_t inSize); +#endif + + C2_DO_NOT_COPY(C2SoftG711Dec); +}; + +} // namespace android + +#endif // ANDROID_C2_SOFT_G711_DEC_H_
diff --git a/media/common_time/MODULE_LICENSE_APACHE2 b/media/codec2/components/g711/MODULE_LICENSE_APACHE2 similarity index 100% copy from media/common_time/MODULE_LICENSE_APACHE2 copy to media/codec2/components/g711/MODULE_LICENSE_APACHE2
diff --git a/media/common_time/NOTICE b/media/codec2/components/g711/NOTICE similarity index 100% copy from media/common_time/NOTICE copy to media/codec2/components/g711/NOTICE
diff --git a/media/codec2/components/gsm/Android.bp b/media/codec2/components/gsm/Android.bp new file mode 100644 index 0000000..9330c01 --- /dev/null +++ b/media/codec2/components/gsm/Android.bp
@@ -0,0 +1,11 @@ +cc_library_shared { + name: "libcodec2_soft_gsmdec", + defaults: [ + "libcodec2_soft-defaults", + "libcodec2_soft_sanitize_all-defaults", + ], + + srcs: ["C2SoftGsmDec.cpp"], + + static_libs: ["libgsm"], +}
diff --git a/media/codec2/components/gsm/C2SoftGsmDec.cpp b/media/codec2/components/gsm/C2SoftGsmDec.cpp new file mode 100644 index 0000000..287cfc6 --- /dev/null +++ b/media/codec2/components/gsm/C2SoftGsmDec.cpp
@@ -0,0 +1,305 @@ +/* + * 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 "C2SoftGsmDec" +#include <log/log.h> + +#include <media/stagefright/foundation/MediaDefs.h> + +#include <C2PlatformSupport.h> +#include <SimpleC2Interface.h> + +#include "C2SoftGsmDec.h" + +namespace android { + +namespace { + +constexpr char COMPONENT_NAME[] = "c2.android.gsm.decoder"; + +} // namespace + +class C2SoftGsmDec::IntfImpl : public SimpleInterface<void>::BaseParams { +public: + explicit IntfImpl(const std::shared_ptr<C2ReflectorHelper> &helper) + : SimpleInterface<void>::BaseParams( + helper, + COMPONENT_NAME, + C2Component::KIND_DECODER, + C2Component::DOMAIN_AUDIO, + MEDIA_MIMETYPE_AUDIO_MSGSM) { + noPrivateBuffers(); + noInputReferences(); + noOutputReferences(); + noInputLatency(); + noTimeStretch(); + setDerivedInstance(this); + + addParameter( + DefineParam(mAttrib, C2_PARAMKEY_COMPONENT_ATTRIBUTES) + .withConstValue(new C2ComponentAttributesSetting( + C2Component::ATTRIB_IS_TEMPORAL)) + .build()); + + addParameter( + DefineParam(mSampleRate, C2_PARAMKEY_SAMPLE_RATE) + .withDefault(new C2StreamSampleRateInfo::output(0u, 8000)) + .withFields({C2F(mSampleRate, value).equalTo(8000)}) + .withSetter((Setter<decltype(*mSampleRate)>::StrictValueWithNoDeps)) + .build()); + + addParameter( + DefineParam(mChannelCount, C2_PARAMKEY_CHANNEL_COUNT) + .withDefault(new C2StreamChannelCountInfo::output(0u, 1)) + .withFields({C2F(mChannelCount, value).equalTo(1)}) + .withSetter(Setter<decltype(*mChannelCount)>::StrictValueWithNoDeps) + .build()); + + addParameter( + DefineParam(mBitrate, C2_PARAMKEY_BITRATE) + .withDefault(new C2StreamBitrateInfo::input(0u, 13200)) + .withFields({C2F(mBitrate, value).equalTo(13200)}) + .withSetter(Setter<decltype(*mBitrate)>::NonStrictValueWithNoDeps) + .build()); + + addParameter( + DefineParam(mInputMaxBufSize, C2_PARAMKEY_INPUT_MAX_BUFFER_SIZE) + .withConstValue(new C2StreamMaxBufferSizeInfo::input(0u, 1024 / MSGSM_IN_FRM_SZ * MSGSM_IN_FRM_SZ)) + .build()); + } + + private: + std::shared_ptr<C2StreamSampleRateInfo::output> mSampleRate; + std::shared_ptr<C2StreamChannelCountInfo::output> mChannelCount; + std::shared_ptr<C2StreamBitrateInfo::input> mBitrate; + std::shared_ptr<C2StreamMaxBufferSizeInfo::input> mInputMaxBufSize; +}; + +C2SoftGsmDec::C2SoftGsmDec(const char *name, c2_node_id_t id, + const std::shared_ptr<IntfImpl>& intfImpl) + : SimpleC2Component( + std::make_shared<SimpleInterface<IntfImpl>>(name, id, intfImpl)), + mIntf(intfImpl), + mGsm(nullptr) { +} + +C2SoftGsmDec::~C2SoftGsmDec() { + onRelease(); +} + +c2_status_t C2SoftGsmDec::onInit() { + if (!mGsm) mGsm = gsm_create(); + if (!mGsm) return C2_NO_MEMORY; + int msopt = 1; + (void)gsm_option(mGsm, GSM_OPT_WAV49, &msopt); + mSignalledError = false; + mSignalledEos = false; + return C2_OK; +} + +c2_status_t C2SoftGsmDec::onStop() { + if (mGsm) { + gsm_destroy(mGsm); + mGsm = nullptr; + } + if (!mGsm) mGsm = gsm_create(); + if (!mGsm) return C2_NO_MEMORY; + int msopt = 1; + (void)gsm_option(mGsm, GSM_OPT_WAV49, &msopt); + mSignalledError = false; + mSignalledEos = false; + return C2_OK; +} + +void C2SoftGsmDec::onReset() { + (void)onStop(); +} + +void C2SoftGsmDec::onRelease() { + if (mGsm) { + gsm_destroy(mGsm); + mGsm = nullptr; + } +} + +c2_status_t C2SoftGsmDec::onFlush_sm() { + return onStop(); +} + +static size_t decodeGSM(gsm handle, int16_t *out, size_t outCapacity, + uint8_t *in, size_t inSize) { + size_t outSize = 0; + + if (inSize % MSGSM_IN_FRM_SZ == 0 + && (inSize / MSGSM_IN_FRM_SZ * MSGSM_OUT_FRM_SZ * sizeof(*out) + <= outCapacity)) { + while (inSize > 0) { + gsm_decode(handle, in, out); + in += FRGSM_IN_FRM_SZ; + inSize -= FRGSM_IN_FRM_SZ; + out += FRGSM_OUT_FRM_SZ; + outSize += FRGSM_OUT_FRM_SZ; + + gsm_decode(handle, in, out); + in += FRGSM_IN_FRM_SZ_MINUS_1; + inSize -= FRGSM_IN_FRM_SZ_MINUS_1; + out += FRGSM_OUT_FRM_SZ; + outSize += FRGSM_OUT_FRM_SZ; + } + } + + return outSize * sizeof(int16_t); +} + +void C2SoftGsmDec::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 || mSignalledEos) { + work->result = C2_BAD_VALUE; + return; + } + + bool eos = ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0); + C2ReadView rView = mDummyReadView; + size_t inOffset = 0u; + size_t inSize = 0u; + if (!work->input.buffers.empty()) { + rView = work->input.buffers[0]->data().linearBlocks().front().map().get(); + inSize = rView.capacity(); + if (inSize && rView.error()) { + ALOGE("read view map failed %d", rView.error()); + work->result = rView.error(); + return; + } + } + + if (inSize == 0) { + work->worklets.front()->output.flags = work->input.flags; + work->worklets.front()->output.buffers.clear(); + work->worklets.front()->output.ordinal = work->input.ordinal; + if (eos) { + mSignalledEos = true; + ALOGV("signalled EOS"); + } + return; + } + ALOGV("in buffer attr. size %zu timestamp %d frameindex %d", inSize, + (int)work->input.ordinal.timestamp.peeku(), (int)work->input.ordinal.frameIndex.peeku()); + + size_t outCapacity = (inSize / MSGSM_IN_FRM_SZ ) * MSGSM_OUT_FRM_SZ * sizeof(int16_t); + std::shared_ptr<C2LinearBlock> block; + C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE }; + c2_status_t err = pool->fetchLinearBlock(outCapacity, usage, &block); + if (err != C2_OK) { + ALOGE("fetchLinearBlock for Output failed with status %d", err); + work->result = C2_NO_MEMORY; + return; + } + C2WriteView wView = block->map().get(); + if (wView.error()) { + ALOGE("write view map failed %d", wView.error()); + work->result = wView.error(); + return; + } + + int16_t *output = reinterpret_cast<int16_t *>(wView.data()); + uint8_t *input = const_cast<uint8_t *>(rView.data() + inOffset); + size_t outSize = decodeGSM(mGsm, output, outCapacity, input, inSize); + if (!outSize) { + ALOGE("encountered improper insize or outsize"); + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } + ALOGV("out buffer attr. size %zu", outSize); + work->worklets.front()->output.flags = work->input.flags; + 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; + if (eos) { + mSignalledEos = true; + ALOGV("signalled EOS"); + } +} + +c2_status_t C2SoftGsmDec::drain( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool) { + (void) pool; + if (drainMode == NO_DRAIN) { + ALOGW("drain with NO_DRAIN: no-op"); + return C2_OK; + } + if (drainMode == DRAIN_CHAIN) { + ALOGW("DRAIN_CHAIN not supported"); + return C2_OMITTED; + } + + return C2_OK; +} + +class C2SoftGSMDecFactory : public C2ComponentFactory { +public: + C2SoftGSMDecFactory() : mHelper(std::static_pointer_cast<C2ReflectorHelper>( + GetCodec2PlatformComponentStore()->getParamReflector())) { + } + + virtual c2_status_t createComponent( + c2_node_id_t id, + std::shared_ptr<C2Component>* const component, + std::function<void(C2Component*)> deleter) override { + *component = std::shared_ptr<C2Component>( + new C2SoftGsmDec(COMPONENT_NAME, + id, + std::make_shared<C2SoftGsmDec::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual c2_status_t createInterface( + c2_node_id_t id, + std::shared_ptr<C2ComponentInterface>* const interface, + std::function<void(C2ComponentInterface*)> deleter) override { + *interface = std::shared_ptr<C2ComponentInterface>( + new SimpleInterface<C2SoftGsmDec::IntfImpl>( + COMPONENT_NAME, id, std::make_shared<C2SoftGsmDec::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual ~C2SoftGSMDecFactory() override = default; + +private: + std::shared_ptr<C2ReflectorHelper> mHelper; +}; + +} // namespace android + +extern "C" ::C2ComponentFactory* CreateCodec2Factory() { + ALOGV("in %s", __func__); + return new ::android::C2SoftGSMDecFactory(); +} + +extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) { + ALOGV("in %s", __func__); + delete factory; +}
diff --git a/media/codec2/components/gsm/C2SoftGsmDec.h b/media/codec2/components/gsm/C2SoftGsmDec.h new file mode 100644 index 0000000..2b209fe --- /dev/null +++ b/media/codec2/components/gsm/C2SoftGsmDec.h
@@ -0,0 +1,65 @@ +/* + * 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_C2_SOFT_GSM_DEC_H_ +#define ANDROID_C2_SOFT_GSM_DEC_H_ + +#include <SimpleC2Component.h> + + +extern "C" { + #include "gsm.h" +} + +namespace android { + +#define FRGSM_IN_FRM_SZ 33 +#define FRGSM_IN_FRM_SZ_MINUS_1 32 +#define FRGSM_OUT_FRM_SZ 160 +#define MSGSM_IN_FRM_SZ (FRGSM_IN_FRM_SZ + FRGSM_IN_FRM_SZ_MINUS_1) +#define MSGSM_OUT_FRM_SZ (FRGSM_OUT_FRM_SZ * 2) + +struct C2SoftGsmDec : public SimpleC2Component { + class IntfImpl; + + C2SoftGsmDec(const char *name, c2_node_id_t id, + const std::shared_ptr<IntfImpl> &intfImpl); + virtual ~C2SoftGsmDec(); + + // From SimpleC2Component + c2_status_t onInit() override; + c2_status_t onStop() override; + void onReset() override; + void onRelease() override; + c2_status_t onFlush_sm() override; + void process( + const std::unique_ptr<C2Work> &work, + const std::shared_ptr<C2BlockPool> &pool) override; + c2_status_t drain( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool) override; + private: + std::shared_ptr<IntfImpl> mIntf; + gsm mGsm; + bool mSignalledError; + bool mSignalledEos; + + C2_DO_NOT_COPY(C2SoftGsmDec); +}; + +} // namespace android + +#endif // ANDROID_C2_SOFT_GSM_DEC_H_
diff --git a/media/common_time/MODULE_LICENSE_APACHE2 b/media/codec2/components/gsm/MODULE_LICENSE_APACHE2 similarity index 100% copy from media/common_time/MODULE_LICENSE_APACHE2 copy to media/codec2/components/gsm/MODULE_LICENSE_APACHE2
diff --git a/media/common_time/NOTICE b/media/codec2/components/gsm/NOTICE similarity index 100% copy from media/common_time/NOTICE copy to media/codec2/components/gsm/NOTICE
diff --git a/media/codec2/components/hevc/Android.bp b/media/codec2/components/hevc/Android.bp new file mode 100644 index 0000000..369bd78 --- /dev/null +++ b/media/codec2/components/hevc/Android.bp
@@ -0,0 +1,25 @@ +cc_library_shared { + name: "libcodec2_soft_hevcdec", + defaults: [ + "libcodec2_soft-defaults", + "libcodec2_soft_sanitize_signed-defaults", + ], + + srcs: ["C2SoftHevcDec.cpp"], + + static_libs: ["libhevcdec"], + +} + +cc_library_shared { + name: "libcodec2_soft_hevcenc", + defaults: [ + "libcodec2_soft-defaults", + "libcodec2_soft_sanitize_signed-defaults", + ], + + srcs: ["C2SoftHevcEnc.cpp"], + + static_libs: ["libhevcenc"], + +}
diff --git a/media/codec2/components/hevc/C2SoftHevcDec.cpp b/media/codec2/components/hevc/C2SoftHevcDec.cpp new file mode 100644 index 0000000..7232572 --- /dev/null +++ b/media/codec2/components/hevc/C2SoftHevcDec.cpp
@@ -0,0 +1,1028 @@ +/* + * 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 "C2SoftHevcDec" +#include <log/log.h> + +#include <media/stagefright/foundation/MediaDefs.h> + +#include <C2Debug.h> +#include <C2PlatformSupport.h> +#include <Codec2Mapper.h> +#include <SimpleC2Interface.h> + +#include "C2SoftHevcDec.h" +#include "ihevcd_cxa.h" + +namespace android { + +namespace { + +constexpr char COMPONENT_NAME[] = "c2.android.hevc.decoder"; + +} // namespace + +class C2SoftHevcDec::IntfImpl : public SimpleInterface<void>::BaseParams { +public: + explicit IntfImpl(const std::shared_ptr<C2ReflectorHelper> &helper) + : SimpleInterface<void>::BaseParams( + helper, + COMPONENT_NAME, + C2Component::KIND_DECODER, + C2Component::DOMAIN_VIDEO, + MEDIA_MIMETYPE_VIDEO_HEVC) { + noPrivateBuffers(); // TODO: account for our buffers here + noInputReferences(); + noOutputReferences(); + noInputLatency(); + noTimeStretch(); + + // TODO: Proper support for reorder depth. + addParameter( + DefineParam(mActualOutputDelay, C2_PARAMKEY_OUTPUT_DELAY) + .withConstValue(new C2PortActualDelayTuning::output(8u)) + .build()); + + addParameter( + DefineParam(mAttrib, C2_PARAMKEY_COMPONENT_ATTRIBUTES) + .withConstValue(new C2ComponentAttributesSetting(C2Component::ATTRIB_IS_TEMPORAL)) + .build()); + + addParameter( + DefineParam(mSize, C2_PARAMKEY_PICTURE_SIZE) + .withDefault(new C2StreamPictureSizeInfo::output(0u, 320, 240)) + .withFields({ + C2F(mSize, width).inRange(2, 4096, 2), + C2F(mSize, height).inRange(2, 4096, 2), + }) + .withSetter(SizeSetter) + .build()); + + addParameter( + DefineParam(mProfileLevel, C2_PARAMKEY_PROFILE_LEVEL) + .withDefault(new C2StreamProfileLevelInfo::input(0u, + C2Config::PROFILE_HEVC_MAIN, C2Config::LEVEL_HEVC_MAIN_5_1)) + .withFields({ + C2F(mProfileLevel, profile).oneOf({ + C2Config::PROFILE_HEVC_MAIN, + C2Config::PROFILE_HEVC_MAIN_STILL}), + C2F(mProfileLevel, level).oneOf({ + C2Config::LEVEL_HEVC_MAIN_1, + C2Config::LEVEL_HEVC_MAIN_2, C2Config::LEVEL_HEVC_MAIN_2_1, + C2Config::LEVEL_HEVC_MAIN_3, C2Config::LEVEL_HEVC_MAIN_3_1, + C2Config::LEVEL_HEVC_MAIN_4, C2Config::LEVEL_HEVC_MAIN_4_1, + C2Config::LEVEL_HEVC_MAIN_5, C2Config::LEVEL_HEVC_MAIN_5_1, + C2Config::LEVEL_HEVC_MAIN_5_2, C2Config::LEVEL_HEVC_HIGH_4, + C2Config::LEVEL_HEVC_HIGH_4_1, C2Config::LEVEL_HEVC_HIGH_5, + C2Config::LEVEL_HEVC_HIGH_5_1, C2Config::LEVEL_HEVC_HIGH_5_2 + }) + }) + .withSetter(ProfileLevelSetter, mSize) + .build()); + + addParameter( + DefineParam(mMaxSize, C2_PARAMKEY_MAX_PICTURE_SIZE) + .withDefault(new C2StreamMaxPictureSizeTuning::output(0u, 320, 240)) + .withFields({ + C2F(mSize, width).inRange(2, 4096, 2), + C2F(mSize, height).inRange(2, 4096, 2), + }) + .withSetter(MaxPictureSizeSetter, mSize) + .build()); + + addParameter( + DefineParam(mMaxInputSize, C2_PARAMKEY_INPUT_MAX_BUFFER_SIZE) + .withDefault(new C2StreamMaxBufferSizeInfo::input(0u, 320 * 240 * 3 / 4)) + .withFields({ + C2F(mMaxInputSize, value).any(), + }) + .calculatedAs(MaxInputSizeSetter, mMaxSize) + .build()); + + C2ChromaOffsetStruct locations[1] = { C2ChromaOffsetStruct::ITU_YUV_420_0() }; + std::shared_ptr<C2StreamColorInfo::output> defaultColorInfo = + C2StreamColorInfo::output::AllocShared( + 1u, 0u, 8u /* bitDepth */, C2Color::YUV_420); + memcpy(defaultColorInfo->m.locations, locations, sizeof(locations)); + + defaultColorInfo = C2StreamColorInfo::output::AllocShared( + {C2ChromaOffsetStruct::ITU_YUV_420_0()}, 0u, 8u /* bitDepth */, + C2Color::YUV_420); + helper->addStructDescriptors<C2ChromaOffsetStruct>(); + + addParameter( + DefineParam(mColorInfo, C2_PARAMKEY_CODED_COLOR_INFO) + .withConstValue(defaultColorInfo) + .build()); + + addParameter( + DefineParam(mDefaultColorAspects, C2_PARAMKEY_DEFAULT_COLOR_ASPECTS) + .withDefault(new C2StreamColorAspectsTuning::output( + 0u, C2Color::RANGE_UNSPECIFIED, C2Color::PRIMARIES_UNSPECIFIED, + C2Color::TRANSFER_UNSPECIFIED, C2Color::MATRIX_UNSPECIFIED)) + .withFields({ + C2F(mDefaultColorAspects, range).inRange( + C2Color::RANGE_UNSPECIFIED, C2Color::RANGE_OTHER), + C2F(mDefaultColorAspects, primaries).inRange( + C2Color::PRIMARIES_UNSPECIFIED, C2Color::PRIMARIES_OTHER), + C2F(mDefaultColorAspects, transfer).inRange( + C2Color::TRANSFER_UNSPECIFIED, C2Color::TRANSFER_OTHER), + C2F(mDefaultColorAspects, matrix).inRange( + C2Color::MATRIX_UNSPECIFIED, C2Color::MATRIX_OTHER) + }) + .withSetter(DefaultColorAspectsSetter) + .build()); + + addParameter( + DefineParam(mCodedColorAspects, C2_PARAMKEY_VUI_COLOR_ASPECTS) + .withDefault(new C2StreamColorAspectsInfo::input( + 0u, C2Color::RANGE_LIMITED, C2Color::PRIMARIES_UNSPECIFIED, + C2Color::TRANSFER_UNSPECIFIED, C2Color::MATRIX_UNSPECIFIED)) + .withFields({ + C2F(mCodedColorAspects, range).inRange( + C2Color::RANGE_UNSPECIFIED, C2Color::RANGE_OTHER), + C2F(mCodedColorAspects, primaries).inRange( + C2Color::PRIMARIES_UNSPECIFIED, C2Color::PRIMARIES_OTHER), + C2F(mCodedColorAspects, transfer).inRange( + C2Color::TRANSFER_UNSPECIFIED, C2Color::TRANSFER_OTHER), + C2F(mCodedColorAspects, matrix).inRange( + C2Color::MATRIX_UNSPECIFIED, C2Color::MATRIX_OTHER) + }) + .withSetter(CodedColorAspectsSetter) + .build()); + + addParameter( + DefineParam(mColorAspects, C2_PARAMKEY_COLOR_ASPECTS) + .withDefault(new C2StreamColorAspectsInfo::output( + 0u, C2Color::RANGE_UNSPECIFIED, C2Color::PRIMARIES_UNSPECIFIED, + C2Color::TRANSFER_UNSPECIFIED, C2Color::MATRIX_UNSPECIFIED)) + .withFields({ + C2F(mColorAspects, range).inRange( + C2Color::RANGE_UNSPECIFIED, C2Color::RANGE_OTHER), + C2F(mColorAspects, primaries).inRange( + C2Color::PRIMARIES_UNSPECIFIED, C2Color::PRIMARIES_OTHER), + C2F(mColorAspects, transfer).inRange( + C2Color::TRANSFER_UNSPECIFIED, C2Color::TRANSFER_OTHER), + C2F(mColorAspects, matrix).inRange( + C2Color::MATRIX_UNSPECIFIED, C2Color::MATRIX_OTHER) + }) + .withSetter(ColorAspectsSetter, mDefaultColorAspects, mCodedColorAspects) + .build()); + + // TODO: support more formats? + addParameter( + DefineParam(mPixelFormat, C2_PARAMKEY_PIXEL_FORMAT) + .withConstValue(new C2StreamPixelFormatInfo::output( + 0u, HAL_PIXEL_FORMAT_YCBCR_420_888)) + .build()); + } + + static C2R SizeSetter(bool mayBlock, const C2P<C2StreamPictureSizeInfo::output> &oldMe, + C2P<C2StreamPictureSizeInfo::output> &me) { + (void)mayBlock; + C2R res = C2R::Ok(); + if (!me.F(me.v.width).supportsAtAll(me.v.width)) { + res = res.plus(C2SettingResultBuilder::BadValue(me.F(me.v.width))); + me.set().width = oldMe.v.width; + } + if (!me.F(me.v.height).supportsAtAll(me.v.height)) { + res = res.plus(C2SettingResultBuilder::BadValue(me.F(me.v.height))); + me.set().height = oldMe.v.height; + } + return res; + } + + static C2R MaxPictureSizeSetter(bool mayBlock, C2P<C2StreamMaxPictureSizeTuning::output> &me, + const C2P<C2StreamPictureSizeInfo::output> &size) { + (void)mayBlock; + // TODO: get max width/height from the size's field helpers vs. hardcoding + me.set().width = c2_min(c2_max(me.v.width, size.v.width), 4096u); + me.set().height = c2_min(c2_max(me.v.height, size.v.height), 4096u); + return C2R::Ok(); + } + + static C2R MaxInputSizeSetter(bool mayBlock, C2P<C2StreamMaxBufferSizeInfo::input> &me, + const C2P<C2StreamMaxPictureSizeTuning::output> &maxSize) { + (void)mayBlock; + // assume compression ratio of 2 + me.set().value = (((maxSize.v.width + 63) / 64) * ((maxSize.v.height + 63) / 64) * 3072); + return C2R::Ok(); + } + + static C2R ProfileLevelSetter(bool mayBlock, C2P<C2StreamProfileLevelInfo::input> &me, + const C2P<C2StreamPictureSizeInfo::output> &size) { + (void)mayBlock; + (void)size; + (void)me; // TODO: validate + return C2R::Ok(); + } + + static C2R DefaultColorAspectsSetter(bool mayBlock, C2P<C2StreamColorAspectsTuning::output> &me) { + (void)mayBlock; + if (me.v.range > C2Color::RANGE_OTHER) { + me.set().range = C2Color::RANGE_OTHER; + } + if (me.v.primaries > C2Color::PRIMARIES_OTHER) { + me.set().primaries = C2Color::PRIMARIES_OTHER; + } + if (me.v.transfer > C2Color::TRANSFER_OTHER) { + me.set().transfer = C2Color::TRANSFER_OTHER; + } + if (me.v.matrix > C2Color::MATRIX_OTHER) { + me.set().matrix = C2Color::MATRIX_OTHER; + } + return C2R::Ok(); + } + + static C2R CodedColorAspectsSetter(bool mayBlock, C2P<C2StreamColorAspectsInfo::input> &me) { + (void)mayBlock; + if (me.v.range > C2Color::RANGE_OTHER) { + me.set().range = C2Color::RANGE_OTHER; + } + if (me.v.primaries > C2Color::PRIMARIES_OTHER) { + me.set().primaries = C2Color::PRIMARIES_OTHER; + } + if (me.v.transfer > C2Color::TRANSFER_OTHER) { + me.set().transfer = C2Color::TRANSFER_OTHER; + } + if (me.v.matrix > C2Color::MATRIX_OTHER) { + me.set().matrix = C2Color::MATRIX_OTHER; + } + return C2R::Ok(); + } + + static C2R ColorAspectsSetter(bool mayBlock, C2P<C2StreamColorAspectsInfo::output> &me, + const C2P<C2StreamColorAspectsTuning::output> &def, + const C2P<C2StreamColorAspectsInfo::input> &coded) { + (void)mayBlock; + // take default values for all unspecified fields, and coded values for specified ones + me.set().range = coded.v.range == RANGE_UNSPECIFIED ? def.v.range : coded.v.range; + me.set().primaries = coded.v.primaries == PRIMARIES_UNSPECIFIED + ? def.v.primaries : coded.v.primaries; + me.set().transfer = coded.v.transfer == TRANSFER_UNSPECIFIED + ? def.v.transfer : coded.v.transfer; + me.set().matrix = coded.v.matrix == MATRIX_UNSPECIFIED ? def.v.matrix : coded.v.matrix; + return C2R::Ok(); + } + + std::shared_ptr<C2StreamColorAspectsInfo::output> getColorAspects_l() { + return mColorAspects; + } + +private: + std::shared_ptr<C2StreamProfileLevelInfo::input> mProfileLevel; + std::shared_ptr<C2StreamPictureSizeInfo::output> mSize; + std::shared_ptr<C2StreamMaxPictureSizeTuning::output> mMaxSize; + std::shared_ptr<C2StreamMaxBufferSizeInfo::input> mMaxInputSize; + std::shared_ptr<C2StreamColorInfo::output> mColorInfo; + std::shared_ptr<C2StreamColorAspectsInfo::input> mCodedColorAspects; + std::shared_ptr<C2StreamColorAspectsTuning::output> mDefaultColorAspects; + std::shared_ptr<C2StreamColorAspectsInfo::output> mColorAspects; + std::shared_ptr<C2StreamPixelFormatInfo::output> mPixelFormat; +}; + +static size_t getCpuCoreCount() { + long cpuCoreCount = 1; +#if defined(_SC_NPROCESSORS_ONLN) + cpuCoreCount = sysconf(_SC_NPROCESSORS_ONLN); +#else + // _SC_NPROC_ONLN must be defined... + cpuCoreCount = sysconf(_SC_NPROC_ONLN); +#endif + CHECK(cpuCoreCount >= 1); + ALOGV("Number of CPU cores: %ld", cpuCoreCount); + return (size_t)cpuCoreCount; +} + +static void *ivd_aligned_malloc(void *ctxt, WORD32 alignment, WORD32 size) { + (void) ctxt; + return memalign(alignment, size); +} + +static void ivd_aligned_free(void *ctxt, void *mem) { + (void) ctxt; + free(mem); +} + +C2SoftHevcDec::C2SoftHevcDec( + const char *name, + c2_node_id_t id, + const std::shared_ptr<IntfImpl> &intfImpl) + : SimpleC2Component(std::make_shared<SimpleInterface<IntfImpl>>(name, id, intfImpl)), + mIntf(intfImpl), + mDecHandle(nullptr), + mOutBufferFlush(nullptr), + mIvColorformat(IV_YUV_420P), + mWidth(320), + mHeight(240), + mHeaderDecoded(false), + mOutIndex(0u) { +} + +C2SoftHevcDec::~C2SoftHevcDec() { + onRelease(); +} + +c2_status_t C2SoftHevcDec::onInit() { + status_t err = initDecoder(); + return err == OK ? C2_OK : C2_CORRUPTED; +} + +c2_status_t C2SoftHevcDec::onStop() { + if (OK != resetDecoder()) return C2_CORRUPTED; + resetPlugin(); + return C2_OK; +} + +void C2SoftHevcDec::onReset() { + (void) onStop(); +} + +void C2SoftHevcDec::onRelease() { + (void) deleteDecoder(); + if (mOutBufferFlush) { + ivd_aligned_free(nullptr, mOutBufferFlush); + mOutBufferFlush = nullptr; + } + if (mOutBlock) { + mOutBlock.reset(); + } +} + +c2_status_t C2SoftHevcDec::onFlush_sm() { + if (OK != setFlushMode()) return C2_CORRUPTED; + + uint32_t displayStride = mStride; + uint32_t displayHeight = mHeight; + uint32_t bufferSize = displayStride * displayHeight * 3 / 2; + mOutBufferFlush = (uint8_t *)ivd_aligned_malloc(nullptr, 128, bufferSize); + if (!mOutBufferFlush) { + ALOGE("could not allocate tmp output buffer (for flush) of size %u ", bufferSize); + return C2_NO_MEMORY; + } + + while (true) { + ivd_video_decode_ip_t s_decode_ip; + ivd_video_decode_op_t s_decode_op; + + setDecodeArgs(&s_decode_ip, &s_decode_op, nullptr, nullptr, 0, 0, 0); + (void) ivdec_api_function(mDecHandle, &s_decode_ip, &s_decode_op); + if (0 == s_decode_op.u4_output_present) { + resetPlugin(); + break; + } + } + + if (mOutBufferFlush) { + ivd_aligned_free(nullptr, mOutBufferFlush); + mOutBufferFlush = nullptr; + } + + return C2_OK; +} + +status_t C2SoftHevcDec::createDecoder() { + ivdext_create_ip_t s_create_ip; + ivdext_create_op_t s_create_op; + + s_create_ip.s_ivd_create_ip_t.u4_size = sizeof(ivdext_create_ip_t); + s_create_ip.s_ivd_create_ip_t.e_cmd = IVD_CMD_CREATE; + s_create_ip.s_ivd_create_ip_t.u4_share_disp_buf = 0; + s_create_ip.s_ivd_create_ip_t.e_output_format = mIvColorformat; + s_create_ip.s_ivd_create_ip_t.pf_aligned_alloc = ivd_aligned_malloc; + s_create_ip.s_ivd_create_ip_t.pf_aligned_free = ivd_aligned_free; + s_create_ip.s_ivd_create_ip_t.pv_mem_ctxt = nullptr; + s_create_op.s_ivd_create_op_t.u4_size = sizeof(ivdext_create_op_t); + IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle, + &s_create_ip, + &s_create_op); + if (status != IV_SUCCESS) { + ALOGE("error in %s: 0x%x", __func__, + s_create_op.s_ivd_create_op_t.u4_error_code); + return UNKNOWN_ERROR; + } + mDecHandle = (iv_obj_t*)s_create_op.s_ivd_create_op_t.pv_handle; + mDecHandle->pv_fxns = (void *)ivdec_api_function; + mDecHandle->u4_size = sizeof(iv_obj_t); + + return OK; +} + +status_t C2SoftHevcDec::setNumCores() { + ivdext_ctl_set_num_cores_ip_t s_set_num_cores_ip; + ivdext_ctl_set_num_cores_op_t s_set_num_cores_op; + + s_set_num_cores_ip.u4_size = sizeof(ivdext_ctl_set_num_cores_ip_t); + s_set_num_cores_ip.e_cmd = IVD_CMD_VIDEO_CTL; + s_set_num_cores_ip.e_sub_cmd = IVDEXT_CMD_CTL_SET_NUM_CORES; + s_set_num_cores_ip.u4_num_cores = mNumCores; + s_set_num_cores_op.u4_size = sizeof(ivdext_ctl_set_num_cores_op_t); + IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle, + &s_set_num_cores_ip, + &s_set_num_cores_op); + if (IV_SUCCESS != status) { + ALOGD("error in %s: 0x%x", __func__, s_set_num_cores_op.u4_error_code); + return UNKNOWN_ERROR; + } + + return OK; +} + +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; + + s_set_dyn_params_ip.u4_size = sizeof(ivd_ctl_set_config_ip_t); + s_set_dyn_params_ip.e_cmd = IVD_CMD_VIDEO_CTL; + s_set_dyn_params_ip.e_sub_cmd = IVD_CMD_CTL_SETPARAMS; + 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 = 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, + &s_set_dyn_params_op); + if (status != IV_SUCCESS) { + ALOGE("error in %s: 0x%x", __func__, s_set_dyn_params_op.u4_error_code); + return UNKNOWN_ERROR; + } + + return OK; +} + +status_t C2SoftHevcDec::getVersion() { + ivd_ctl_getversioninfo_ip_t s_get_versioninfo_ip; + ivd_ctl_getversioninfo_op_t s_get_versioninfo_op; + UWORD8 au1_buf[512]; + + s_get_versioninfo_ip.u4_size = sizeof(ivd_ctl_getversioninfo_ip_t); + s_get_versioninfo_ip.e_cmd = IVD_CMD_VIDEO_CTL; + s_get_versioninfo_ip.e_sub_cmd = IVD_CMD_CTL_GETVERSION; + s_get_versioninfo_ip.pv_version_buffer = au1_buf; + s_get_versioninfo_ip.u4_version_buffer_size = sizeof(au1_buf); + s_get_versioninfo_op.u4_size = sizeof(ivd_ctl_getversioninfo_op_t); + IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle, + &s_get_versioninfo_ip, + &s_get_versioninfo_op); + if (status != IV_SUCCESS) { + ALOGD("error in %s: 0x%x", __func__, + s_get_versioninfo_op.u4_error_code); + } else { + ALOGV("ittiam decoder version number: %s", + (char *) s_get_versioninfo_ip.pv_version_buffer); + } + + return OK; +} + +status_t C2SoftHevcDec::initDecoder() { + if (OK != createDecoder()) return UNKNOWN_ERROR; + mNumCores = MIN(getCpuCoreCount(), MAX_NUM_CORES); + mStride = ALIGN64(mWidth); + mSignalledError = false; + resetPlugin(); + (void) setNumCores(); + if (OK != setParams(mStride, IVD_DECODE_FRAME)) return UNKNOWN_ERROR; + (void) getVersion(); + + return OK; +} + +bool C2SoftHevcDec::setDecodeArgs(ivd_video_decode_ip_t *ps_decode_ip, + ivd_video_decode_op_t *ps_decode_op, + C2ReadView *inBuffer, + C2GraphicView *outBuffer, + size_t inOffset, + size_t inSize, + uint32_t tsMarker) { + uint32_t displayStride = mStride; + uint32_t displayHeight = mHeight; + size_t lumaSize = displayStride * displayHeight; + size_t chromaSize = lumaSize >> 2; + + ps_decode_ip->u4_size = sizeof(ivd_video_decode_ip_t); + ps_decode_ip->e_cmd = IVD_CMD_VIDEO_DECODE; + if (inBuffer) { + ps_decode_ip->u4_ts = tsMarker; + ps_decode_ip->pv_stream_buffer = const_cast<uint8_t *>(inBuffer->data() + inOffset); + ps_decode_ip->u4_num_Bytes = inSize; + } else { + ps_decode_ip->u4_ts = 0; + ps_decode_ip->pv_stream_buffer = nullptr; + ps_decode_ip->u4_num_Bytes = 0; + } + ps_decode_ip->s_out_buffer.u4_min_out_buf_size[0] = lumaSize; + ps_decode_ip->s_out_buffer.u4_min_out_buf_size[1] = chromaSize; + ps_decode_ip->s_out_buffer.u4_min_out_buf_size[2] = chromaSize; + if (outBuffer) { + if (outBuffer->width() < displayStride || outBuffer->height() < displayHeight) { + ALOGE("Output buffer too small: provided (%dx%d) required (%ux%u)", + outBuffer->width(), outBuffer->height(), displayStride, displayHeight); + return false; + } + ps_decode_ip->s_out_buffer.pu1_bufs[0] = outBuffer->data()[C2PlanarLayout::PLANE_Y]; + ps_decode_ip->s_out_buffer.pu1_bufs[1] = outBuffer->data()[C2PlanarLayout::PLANE_U]; + ps_decode_ip->s_out_buffer.pu1_bufs[2] = outBuffer->data()[C2PlanarLayout::PLANE_V]; + } else { + ps_decode_ip->s_out_buffer.pu1_bufs[0] = mOutBufferFlush; + ps_decode_ip->s_out_buffer.pu1_bufs[1] = mOutBufferFlush + lumaSize; + ps_decode_ip->s_out_buffer.pu1_bufs[2] = mOutBufferFlush + lumaSize + chromaSize; + } + ps_decode_ip->s_out_buffer.u4_num_bufs = 3; + ps_decode_op->u4_size = sizeof(ivd_video_decode_op_t); + ps_decode_op->u4_output_present = 0; + + return true; +} + +bool C2SoftHevcDec::getVuiParams() { + ivdext_ctl_get_vui_params_ip_t s_get_vui_params_ip; + ivdext_ctl_get_vui_params_op_t s_get_vui_params_op; + + s_get_vui_params_ip.u4_size = sizeof(ivdext_ctl_get_vui_params_ip_t); + s_get_vui_params_ip.e_cmd = IVD_CMD_VIDEO_CTL; + s_get_vui_params_ip.e_sub_cmd = + (IVD_CONTROL_API_COMMAND_TYPE_T) IHEVCD_CXA_CMD_CTL_GET_VUI_PARAMS; + s_get_vui_params_op.u4_size = sizeof(ivdext_ctl_get_vui_params_op_t); + IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle, + &s_get_vui_params_ip, + &s_get_vui_params_op); + if (status != IV_SUCCESS) { + ALOGD("error in %s: 0x%x", __func__, s_get_vui_params_op.u4_error_code); + return false; + } + + VuiColorAspects vuiColorAspects; + vuiColorAspects.primaries = s_get_vui_params_op.u1_colour_primaries; + vuiColorAspects.transfer = s_get_vui_params_op.u1_transfer_characteristics; + vuiColorAspects.coeffs = s_get_vui_params_op.u1_matrix_coefficients; + vuiColorAspects.fullRange = s_get_vui_params_op.u1_video_full_range_flag; + + // convert vui aspects to C2 values if changed + if (!(vuiColorAspects == mBitstreamColorAspects)) { + mBitstreamColorAspects = vuiColorAspects; + ColorAspects sfAspects; + C2StreamColorAspectsInfo::input codedAspects = { 0u }; + ColorUtils::convertIsoColorAspectsToCodecAspects( + vuiColorAspects.primaries, vuiColorAspects.transfer, vuiColorAspects.coeffs, + vuiColorAspects.fullRange, sfAspects); + if (!C2Mapper::map(sfAspects.mPrimaries, &codedAspects.primaries)) { + codedAspects.primaries = C2Color::PRIMARIES_UNSPECIFIED; + } + if (!C2Mapper::map(sfAspects.mRange, &codedAspects.range)) { + codedAspects.range = C2Color::RANGE_UNSPECIFIED; + } + if (!C2Mapper::map(sfAspects.mMatrixCoeffs, &codedAspects.matrix)) { + codedAspects.matrix = C2Color::MATRIX_UNSPECIFIED; + } + if (!C2Mapper::map(sfAspects.mTransfer, &codedAspects.transfer)) { + codedAspects.transfer = C2Color::TRANSFER_UNSPECIFIED; + } + std::vector<std::unique_ptr<C2SettingResult>> failures; + (void)mIntf->config({&codedAspects}, C2_MAY_BLOCK, &failures); + } + return true; +} + +status_t C2SoftHevcDec::setFlushMode() { + ivd_ctl_flush_ip_t s_set_flush_ip; + ivd_ctl_flush_op_t s_set_flush_op; + + s_set_flush_ip.u4_size = sizeof(ivd_ctl_flush_ip_t); + s_set_flush_ip.e_cmd = IVD_CMD_VIDEO_CTL; + s_set_flush_ip.e_sub_cmd = IVD_CMD_CTL_FLUSH; + s_set_flush_op.u4_size = sizeof(ivd_ctl_flush_op_t); + IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle, + &s_set_flush_ip, + &s_set_flush_op); + if (status != IV_SUCCESS) { + ALOGE("error in %s: 0x%x", __func__, s_set_flush_op.u4_error_code); + return UNKNOWN_ERROR; + } + + return OK; +} + +status_t C2SoftHevcDec::resetDecoder() { + ivd_ctl_reset_ip_t s_reset_ip; + ivd_ctl_reset_op_t s_reset_op; + + s_reset_ip.u4_size = sizeof(ivd_ctl_reset_ip_t); + s_reset_ip.e_cmd = IVD_CMD_VIDEO_CTL; + s_reset_ip.e_sub_cmd = IVD_CMD_CTL_RESET; + s_reset_op.u4_size = sizeof(ivd_ctl_reset_op_t); + IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle, + &s_reset_ip, + &s_reset_op); + if (IV_SUCCESS != status) { + ALOGE("error in %s: 0x%x", __func__, s_reset_op.u4_error_code); + return UNKNOWN_ERROR; + } + mStride = 0; + (void) setNumCores(); + mSignalledError = false; + mHeaderDecoded = false; + return OK; +} + +void C2SoftHevcDec::resetPlugin() { + mSignalledOutputEos = false; + gettimeofday(&mTimeStart, nullptr); + gettimeofday(&mTimeEnd, nullptr); +} + +status_t C2SoftHevcDec::deleteDecoder() { + if (mDecHandle) { + ivdext_delete_ip_t s_delete_ip; + ivdext_delete_op_t s_delete_op; + + s_delete_ip.s_ivd_delete_ip_t.u4_size = sizeof(ivdext_delete_ip_t); + s_delete_ip.s_ivd_delete_ip_t.e_cmd = IVD_CMD_DELETE; + s_delete_op.s_ivd_delete_op_t.u4_size = sizeof(ivdext_delete_op_t); + IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle, + &s_delete_ip, + &s_delete_op); + if (status != IV_SUCCESS) { + ALOGE("error in %s: 0x%x", __func__, + s_delete_op.s_ivd_delete_op_t.u4_error_code); + return UNKNOWN_ERROR; + } + mDecHandle = nullptr; + } + + return OK; +} + +void fillEmptyWork(const std::unique_ptr<C2Work> &work) { + uint32_t flags = 0; + if (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) { + flags |= C2FrameData::FLAG_END_OF_STREAM; + ALOGV("signalling eos"); + } + work->worklets.front()->output.flags = (C2FrameData::flags_t)flags; + work->worklets.front()->output.buffers.clear(); + work->worklets.front()->output.ordinal = work->input.ordinal; + work->workletsProcessed = 1u; +} + +void C2SoftHevcDec::finishWork(uint64_t index, const std::unique_ptr<C2Work> &work) { + std::shared_ptr<C2Buffer> buffer = createGraphicBuffer(std::move(mOutBlock), + C2Rect(mWidth, mHeight)); + mOutBlock = nullptr; + { + IntfImpl::Lock lock = mIntf->lock(); + buffer->setInfo(mIntf->getColorAspects_l()); + } + + class FillWork { + public: + FillWork(uint32_t flags, C2WorkOrdinalStruct ordinal, + const std::shared_ptr<C2Buffer>& buffer) + : mFlags(flags), mOrdinal(ordinal), mBuffer(buffer) {} + ~FillWork() = default; + + void operator()(const std::unique_ptr<C2Work>& work) { + work->worklets.front()->output.flags = (C2FrameData::flags_t)mFlags; + work->worklets.front()->output.buffers.clear(); + work->worklets.front()->output.ordinal = mOrdinal; + work->workletsProcessed = 1u; + work->result = C2_OK; + if (mBuffer) { + work->worklets.front()->output.buffers.push_back(mBuffer); + } + ALOGV("timestamp = %lld, index = %lld, w/%s buffer", + mOrdinal.timestamp.peekll(), mOrdinal.frameIndex.peekll(), + mBuffer ? "" : "o"); + } + + private: + const uint32_t mFlags; + const C2WorkOrdinalStruct mOrdinal; + const std::shared_ptr<C2Buffer> mBuffer; + }; + + 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; + work->workletsProcessed = 1u; + }; + if (work && c2_cntr64_t(index) == work->input.ordinal.frameIndex) { + bool eos = ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0); + // TODO: Check if cloneAndSend can be avoided by tracking number of frames remaining + if (eos) { + if (buffer) { + mOutIndex = index; + C2WorkOrdinalStruct outOrdinal = work->input.ordinal; + cloneAndSend( + mOutIndex, work, + FillWork(C2FrameData::FLAG_INCOMPLETE, outOrdinal, buffer)); + buffer.reset(); + } + } else { + fillWork(work); + } + } else { + finish(index, fillWork); + } +} + +c2_status_t C2SoftHevcDec::ensureDecoderState(const std::shared_ptr<C2BlockPool> &pool) { + if (!mDecHandle) { + ALOGE("not supposed to be here, invalid decoder context"); + return C2_CORRUPTED; + } + if (mStride != ALIGN64(mWidth)) { + mStride = ALIGN64(mWidth); + if (OK != setParams(mStride, IVD_DECODE_FRAME)) return C2_CORRUPTED; + } + if (mOutBlock && + (mOutBlock->width() != mStride || mOutBlock->height() != mHeight)) { + mOutBlock.reset(); + } + if (!mOutBlock) { + uint32_t format = HAL_PIXEL_FORMAT_YV12; + C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE }; + c2_status_t err = pool->fetchGraphicBlock(mStride, mHeight, format, usage, &mOutBlock); + if (err != C2_OK) { + ALOGE("fetchGraphicBlock for Output failed with status %d", err); + return err; + } + ALOGV("provided (%dx%d) required (%dx%d)", + mOutBlock->width(), mOutBlock->height(), mStride, mHeight); + } + + return C2_OK; +} + +// 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 +// TODO: pass coloraspects information to surface +// TODO: test support for dynamic change in resolution +// TODO: verify if the decoder sent back all frames +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; + } + + size_t inOffset = 0u; + size_t inSize = 0u; + uint32_t workIndex = work->input.ordinal.frameIndex.peeku() & 0xFFFFFFFF; + C2ReadView rView = mDummyReadView; + if (!work->input.buffers.empty()) { + rView = work->input.buffers[0]->data().linearBlocks().front().map().get(); + inSize = rView.capacity(); + if (inSize && rView.error()) { + ALOGE("read view map failed %d", rView.error()); + work->result = rView.error(); + return; + } + } + bool eos = ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0); + bool hasPicture = false; + + ALOGV("in buffer attr. size %zu timestamp %d frameindex %d, flags %x", + inSize, (int)work->input.ordinal.timestamp.peeku(), + (int)work->input.ordinal.frameIndex.peeku(), work->input.flags); + size_t inPos = 0; + while (inPos < inSize) { + if (C2_OK != ensureDecoderState(pool)) { + mSignalledError = true; + work->workletsProcessed = 1u; + work->result = C2_CORRUPTED; + return; + } + C2GraphicView wView = mOutBlock->map().get(); + if (wView.error()) { + ALOGE("graphic view map failed %d", wView.error()); + work->result = wView.error(); + return; + } + ivd_video_decode_ip_t s_decode_ip; + ivd_video_decode_op_t s_decode_op; + 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, nullptr); + TIME_DIFF(mTimeEnd, mTimeStart, delay); + (void) ivdec_api_function(mDecHandle, &s_decode_ip, &s_decode_op); + WORD32 decodeTime; + GETTIME(&mTimeEnd, nullptr); + TIME_DIFF(mTimeStart, mTimeEnd, decodeTime); + ALOGV("decodeTime=%6d delay=%6d numBytes=%6d", decodeTime, delay, + s_decode_op.u4_num_bytes_consumed); + if (IVD_MEM_ALLOC_FAILED == (s_decode_op.u4_error_code & IVD_ERROR_MASK)) { + ALOGE("allocation failure in decoder"); + mSignalledError = true; + work->workletsProcessed = 1u; + work->result = C2_CORRUPTED; + return; + } else if (IVD_STREAM_WIDTH_HEIGHT_NOT_SUPPORTED == + (s_decode_op.u4_error_code & IVD_ERROR_MASK)) { + ALOGE("unsupported resolution : %dx%d", mWidth, mHeight); + mSignalledError = true; + work->workletsProcessed = 1u; + work->result = C2_CORRUPTED; + return; + } else if (IVD_RES_CHANGED == (s_decode_op.u4_error_code & IVD_ERROR_MASK)) { + ALOGV("resolution changed"); + drainInternal(DRAIN_COMPONENT_NO_EOS, pool, work); + resetDecoder(); + resetPlugin(); + 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); + } else if (IS_IVD_FATAL_ERROR(s_decode_op.u4_error_code)) { + ALOGE("Fatal error in decoder 0x%x", s_decode_op.u4_error_code); + mSignalledError = true; + work->workletsProcessed = 1u; + work->result = C2_CORRUPTED; + return; + } + 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; + CHECK_EQ(0u, s_decode_op.u4_output_present); + + C2StreamPictureSizeInfo::output size(0u, mWidth, mHeight); + std::vector<std::unique_ptr<C2SettingResult>> failures; + 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; + } + } + (void) getVuiParams(); + hasPicture |= (1 == s_decode_op.u4_frame_decoded_flag); + 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", + (int)inSize - (int)inPos); + break; + } + } + + if (eos) { + drainInternal(DRAIN_COMPONENT_WITH_EOS, pool, work); + mSignalledOutputEos = true; + } else if (!hasPicture) { + fillEmptyWork(work); + } +} + +c2_status_t C2SoftHevcDec::drainInternal( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool, + const std::unique_ptr<C2Work> &work) { + if (drainMode == NO_DRAIN) { + ALOGW("drain with NO_DRAIN: no-op"); + return C2_OK; + } + if (drainMode == DRAIN_CHAIN) { + ALOGW("DRAIN_CHAIN not supported"); + return C2_OMITTED; + } + + if (OK != setFlushMode()) return C2_CORRUPTED; + while (true) { + if (C2_OK != ensureDecoderState(pool)) { + mSignalledError = true; + work->workletsProcessed = 1u; + work->result = C2_CORRUPTED; + return C2_CORRUPTED; + } + C2GraphicView wView = mOutBlock->map().get(); + if (wView.error()) { + ALOGE("graphic view map failed %d", wView.error()); + return C2_CORRUPTED; + } + ivd_video_decode_ip_t s_decode_ip; + 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; + } + } + + return C2_OK; +} + +c2_status_t C2SoftHevcDec::drain( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool) { + return drainInternal(drainMode, pool, nullptr); +} + +class C2SoftHevcDecFactory : public C2ComponentFactory { +public: + C2SoftHevcDecFactory() : mHelper(std::static_pointer_cast<C2ReflectorHelper>( + GetCodec2PlatformComponentStore()->getParamReflector())) { + } + + virtual c2_status_t createComponent( + c2_node_id_t id, + std::shared_ptr<C2Component>* const component, + std::function<void(C2Component*)> deleter) override { + *component = std::shared_ptr<C2Component>( + new C2SoftHevcDec(COMPONENT_NAME, + id, + std::make_shared<C2SoftHevcDec::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual c2_status_t createInterface( + c2_node_id_t id, + std::shared_ptr<C2ComponentInterface>* const interface, + std::function<void(C2ComponentInterface*)> deleter) override { + *interface = std::shared_ptr<C2ComponentInterface>( + new SimpleInterface<C2SoftHevcDec::IntfImpl>( + COMPONENT_NAME, id, std::make_shared<C2SoftHevcDec::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual ~C2SoftHevcDecFactory() override = default; + +private: + std::shared_ptr<C2ReflectorHelper> mHelper; +}; + +} // namespace android + +extern "C" ::C2ComponentFactory* CreateCodec2Factory() { + ALOGV("in %s", __func__); + return new ::android::C2SoftHevcDecFactory(); +} + +extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) { + ALOGV("in %s", __func__); + delete factory; +}
diff --git a/media/codec2/components/hevc/C2SoftHevcDec.h b/media/codec2/components/hevc/C2SoftHevcDec.h new file mode 100644 index 0000000..b7664e6 --- /dev/null +++ b/media/codec2/components/hevc/C2SoftHevcDec.h
@@ -0,0 +1,154 @@ +/* + * 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_C2_SOFT_HEVC_DEC_H_ +#define ANDROID_C2_SOFT_HEVC_DEC_H_ + +#include <media/stagefright/foundation/ColorUtils.h> + +#include <atomic> +#include <SimpleC2Component.h> + +#include "ihevc_typedefs.h" +#include "iv.h" +#include "ivd.h" + +namespace android { + +#define ivdec_api_function ihevcd_cxa_api_function +#define ivdext_create_ip_t ihevcd_cxa_create_ip_t +#define ivdext_create_op_t ihevcd_cxa_create_op_t +#define ivdext_delete_ip_t ihevcd_cxa_delete_ip_t +#define ivdext_delete_op_t ihevcd_cxa_delete_op_t +#define ivdext_ctl_set_num_cores_ip_t ihevcd_cxa_ctl_set_num_cores_ip_t +#define ivdext_ctl_set_num_cores_op_t ihevcd_cxa_ctl_set_num_cores_op_t +#define ivdext_ctl_get_vui_params_ip_t ihevcd_cxa_ctl_get_vui_params_ip_t +#define ivdext_ctl_get_vui_params_op_t ihevcd_cxa_ctl_get_vui_params_op_t +#define ALIGN64(x) ((((x) + 63) >> 6) << 6) +#define MAX_NUM_CORES 4 +#define IVDEXT_CMD_CTL_SET_NUM_CORES \ + (IVD_CONTROL_API_COMMAND_TYPE_T)IHEVCD_CXA_CMD_CTL_SET_NUM_CORES +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#define GETTIME(a, b) gettimeofday(a, b); +#define TIME_DIFF(start, end, diff) \ + diff = (((end).tv_sec - (start).tv_sec) * 1000000) + \ + ((end).tv_usec - (start).tv_usec); + + +struct C2SoftHevcDec : public SimpleC2Component { + class IntfImpl; + + C2SoftHevcDec(const char* name, c2_node_id_t id, + const std::shared_ptr<IntfImpl>& intfImpl); + virtual ~C2SoftHevcDec(); + + // From SimpleC2Component + c2_status_t onInit() override; + c2_status_t onStop() override; + void onReset() override; + void onRelease() override; + c2_status_t onFlush_sm() override; + void process( + const std::unique_ptr<C2Work> &work, + const std::shared_ptr<C2BlockPool> &pool) override; + c2_status_t drain( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool) override; + private: + status_t createDecoder(); + status_t setNumCores(); + 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, + ivd_video_decode_op_t *ps_decode_op, + C2ReadView *inBuffer, + C2GraphicView *outBuffer, + size_t inOffset, + size_t inSize, + uint32_t tsMarker); + bool getVuiParams(); + // TODO:This is not the right place for colorAspects functions. These should + // be part of c2-vndk so that they can be accessed by all video plugins + // until then, make them feel at home + bool colorAspectsDiffer(const ColorAspects &a, const ColorAspects &b); + void updateFinalColorAspects( + const ColorAspects &otherAspects, const ColorAspects &preferredAspects); + status_t handleColorAspectsChange(); + c2_status_t ensureDecoderState(const std::shared_ptr<C2BlockPool> &pool); + void finishWork(uint64_t index, const std::unique_ptr<C2Work> &work); + status_t setFlushMode(); + c2_status_t drainInternal( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool, + const std::unique_ptr<C2Work> &work); + status_t resetDecoder(); + void resetPlugin(); + status_t deleteDecoder(); + + // 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 + enum { + kNotSupported, + kPreferBitstream, + kPreferContainer, + }; + + std::shared_ptr<IntfImpl> mIntf; + iv_obj_t *mDecHandle; + std::shared_ptr<C2GraphicBlock> mOutBlock; + uint8_t *mOutBufferFlush; + + size_t mNumCores; + IV_COLOR_FORMAT_T mIvColorformat; + + uint32_t mWidth; + uint32_t mHeight; + uint32_t mStride; + bool mSignalledOutputEos; + bool mSignalledError; + bool mHeaderDecoded; + std::atomic_uint64_t mOutIndex; + + // 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 { + uint8_t primaries; + uint8_t transfer; + uint8_t coeffs; + uint8_t fullRange; + + // default color aspects + VuiColorAspects() + : primaries(2), transfer(2), coeffs(2), fullRange(0) { } + + bool operator==(const VuiColorAspects &o) { + return primaries == o.primaries && transfer == o.transfer && coeffs == o.coeffs + && fullRange == o.fullRange; + } + } mBitstreamColorAspects; + + // profile + struct timeval mTimeStart; + struct timeval mTimeEnd; + + C2_DO_NOT_COPY(C2SoftHevcDec); +}; + +} // namespace android + +#endif // ANDROID_C2_SOFT_HEVC_DEC_H_
diff --git a/media/codec2/components/hevc/C2SoftHevcEnc.cpp b/media/codec2/components/hevc/C2SoftHevcEnc.cpp new file mode 100644 index 0000000..b129b1b --- /dev/null +++ b/media/codec2/components/hevc/C2SoftHevcEnc.cpp
@@ -0,0 +1,974 @@ +/* + * 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 "C2SoftHevcEnc" +#include <log/log.h> + +#include <media/hardware/VideoAPI.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/foundation/AUtils.h> + +#include <C2Debug.h> +#include <C2PlatformSupport.h> +#include <Codec2BufferUtils.h> +#include <SimpleC2Interface.h> +#include <util/C2InterfaceHelper.h> + +#include "ihevc_typedefs.h" +#include "itt_video_api.h" +#include "ihevce_api.h" +#include "ihevce_plugin.h" +#include "C2SoftHevcEnc.h" + +namespace android { + +namespace { + +constexpr char COMPONENT_NAME[] = "c2.android.hevc.encoder"; + +} // namepsace + +class C2SoftHevcEnc::IntfImpl : public SimpleInterface<void>::BaseParams { + public: + explicit IntfImpl(const std::shared_ptr<C2ReflectorHelper> &helper) + : SimpleInterface<void>::BaseParams( + helper, + COMPONENT_NAME, + C2Component::KIND_ENCODER, + C2Component::DOMAIN_VIDEO, + MEDIA_MIMETYPE_VIDEO_HEVC) { + noPrivateBuffers(); // TODO: account for our buffers here + noInputReferences(); + noOutputReferences(); + noTimeStretch(); + setDerivedInstance(this); + + addParameter( + DefineParam(mActualInputDelay, C2_PARAMKEY_INPUT_DELAY) + .withDefault(new C2PortActualDelayTuning::input( + DEFAULT_B_FRAMES + DEFAULT_RC_LOOKAHEAD)) + .withFields({C2F(mActualInputDelay, value).inRange( + 0, MAX_B_FRAMES + MAX_RC_LOOKAHEAD)}) + .withSetter( + Setter<decltype(*mActualInputDelay)>::StrictValueWithNoDeps) + .build()); + + addParameter( + DefineParam(mAttrib, C2_PARAMKEY_COMPONENT_ATTRIBUTES) + .withConstValue(new C2ComponentAttributesSetting( + C2Component::ATTRIB_IS_TEMPORAL)) + .build()); + + addParameter( + DefineParam(mUsage, C2_PARAMKEY_INPUT_STREAM_USAGE) + .withConstValue(new C2StreamUsageTuning::input( + 0u, (uint64_t)C2MemoryUsage::CPU_READ)) + .build()); + + // matches size limits in codec library + addParameter( + DefineParam(mSize, C2_PARAMKEY_PICTURE_SIZE) + .withDefault(new C2StreamPictureSizeInfo::input(0u, 320, 240)) + .withFields({ + C2F(mSize, width).inRange(2, 1920, 2), + C2F(mSize, height).inRange(2, 1088, 2), + }) + .withSetter(SizeSetter) + .build()); + + addParameter( + DefineParam(mFrameRate, C2_PARAMKEY_FRAME_RATE) + .withDefault(new C2StreamFrameRateInfo::output(0u, 30.)) + .withFields({C2F(mFrameRate, value).greaterThan(0.)}) + .withSetter( + Setter<decltype(*mFrameRate)>::StrictValueWithNoDeps) + .build()); + + // matches limits in codec library + addParameter( + DefineParam(mBitrateMode, C2_PARAMKEY_BITRATE_MODE) + .withDefault(new C2StreamBitrateModeTuning::output( + 0u, C2Config::BITRATE_VARIABLE)) + .withFields({ + C2F(mBitrateMode, value).oneOf({ + C2Config::BITRATE_CONST, + C2Config::BITRATE_VARIABLE, + C2Config::BITRATE_IGNORE}) + }) + .withSetter( + Setter<decltype(*mBitrateMode)>::StrictValueWithNoDeps) + .build()); + + addParameter( + DefineParam(mBitrate, C2_PARAMKEY_BITRATE) + .withDefault(new C2StreamBitrateInfo::output(0u, 64000)) + .withFields({C2F(mBitrate, value).inRange(4096, 12000000)}) + .withSetter(BitrateSetter) + .build()); + + // matches levels allowed within codec library + addParameter( + DefineParam(mComplexity, C2_PARAMKEY_COMPLEXITY) + .withDefault(new C2StreamComplexityTuning::output(0u, 0)) + .withFields({C2F(mComplexity, value).inRange(0, 10)}) + .withSetter(Setter<decltype(*mComplexity)>::NonStrictValueWithNoDeps) + .build()); + + addParameter( + DefineParam(mQuality, C2_PARAMKEY_QUALITY) + .withDefault(new C2StreamQualityTuning::output(0u, 80)) + .withFields({C2F(mQuality, value).inRange(0, 100)}) + .withSetter(Setter<decltype(*mQuality)>::NonStrictValueWithNoDeps) + .build()); + + addParameter( + DefineParam(mProfileLevel, C2_PARAMKEY_PROFILE_LEVEL) + .withDefault(new C2StreamProfileLevelInfo::output( + 0u, PROFILE_HEVC_MAIN, LEVEL_HEVC_MAIN_1)) + .withFields({ + C2F(mProfileLevel, profile) + .oneOf({C2Config::PROFILE_HEVC_MAIN, + C2Config::PROFILE_HEVC_MAIN_STILL}), + C2F(mProfileLevel, level) + .oneOf({LEVEL_HEVC_MAIN_1, LEVEL_HEVC_MAIN_2, + LEVEL_HEVC_MAIN_2_1, LEVEL_HEVC_MAIN_3, + LEVEL_HEVC_MAIN_3_1, LEVEL_HEVC_MAIN_4, + LEVEL_HEVC_MAIN_4_1, LEVEL_HEVC_MAIN_5, + LEVEL_HEVC_MAIN_5_1, LEVEL_HEVC_MAIN_5_2}), + }) + .withSetter(ProfileLevelSetter, mSize, mFrameRate, mBitrate) + .build()); + + addParameter( + DefineParam(mRequestSync, C2_PARAMKEY_REQUEST_SYNC_FRAME) + .withDefault(new C2StreamRequestSyncFrameTuning::output(0u, C2_FALSE)) + .withFields({C2F(mRequestSync, value).oneOf({ C2_FALSE, C2_TRUE }) }) + .withSetter(Setter<decltype(*mRequestSync)>::NonStrictValueWithNoDeps) + .build()); + + addParameter( + DefineParam(mSyncFramePeriod, C2_PARAMKEY_SYNC_FRAME_INTERVAL) + .withDefault( + new C2StreamSyncFrameIntervalTuning::output(0u, 1000000)) + .withFields({C2F(mSyncFramePeriod, value).any()}) + .withSetter( + Setter<decltype(*mSyncFramePeriod)>::StrictValueWithNoDeps) + .build()); + } + + static C2R BitrateSetter(bool mayBlock, + C2P<C2StreamBitrateInfo::output>& me) { + (void)mayBlock; + C2R res = C2R::Ok(); + if (me.v.value < 4096) { + me.set().value = 4096; + } + return res; + } + + static C2R SizeSetter(bool mayBlock, + const C2P<C2StreamPictureSizeInfo::input>& oldMe, + C2P<C2StreamPictureSizeInfo::input>& me) { + (void)mayBlock; + C2R res = C2R::Ok(); + if (!me.F(me.v.width).supportsAtAll(me.v.width)) { + res = res.plus(C2SettingResultBuilder::BadValue(me.F(me.v.width))); + me.set().width = oldMe.v.width; + } + if (!me.F(me.v.height).supportsAtAll(me.v.height)) { + res = res.plus(C2SettingResultBuilder::BadValue(me.F(me.v.height))); + me.set().height = oldMe.v.height; + } + return res; + } + + static C2R ProfileLevelSetter( + bool mayBlock, + C2P<C2StreamProfileLevelInfo::output> &me, + const C2P<C2StreamPictureSizeInfo::input> &size, + const C2P<C2StreamFrameRateInfo::output> &frameRate, + const C2P<C2StreamBitrateInfo::output> &bitrate) { + (void)mayBlock; + if (!me.F(me.v.profile).supportsAtAll(me.v.profile)) { + me.set().profile = PROFILE_HEVC_MAIN; + } + + struct LevelLimits { + C2Config::level_t level; + uint64_t samplesPerSec; + uint64_t samples; + uint32_t bitrate; + }; + + constexpr LevelLimits kLimits[] = { + { LEVEL_HEVC_MAIN_1, 552960, 36864, 128000 }, + { LEVEL_HEVC_MAIN_2, 3686400, 122880, 1500000 }, + { LEVEL_HEVC_MAIN_2_1, 7372800, 245760, 3000000 }, + { LEVEL_HEVC_MAIN_3, 16588800, 552960, 6000000 }, + { LEVEL_HEVC_MAIN_3_1, 33177600, 983040, 10000000 }, + { LEVEL_HEVC_MAIN_4, 66846720, 2228224, 12000000 }, + { LEVEL_HEVC_MAIN_4_1, 133693440, 2228224, 20000000 }, + { LEVEL_HEVC_MAIN_5, 267386880, 8912896, 25000000 }, + { LEVEL_HEVC_MAIN_5_1, 534773760, 8912896, 40000000 }, + { LEVEL_HEVC_MAIN_5_2, 1069547520, 8912896, 60000000 }, + { LEVEL_HEVC_MAIN_6, 1069547520, 35651584, 60000000 }, + { LEVEL_HEVC_MAIN_6_1, 2139095040, 35651584, 120000000 }, + { LEVEL_HEVC_MAIN_6_2, 4278190080, 35651584, 240000000 }, + }; + + uint64_t samples = size.v.width * size.v.height; + uint64_t samplesPerSec = samples * 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. + + bool found = false; + // By default needsUpdate = false in case the supplied level does meet + // the requirements. + bool needsUpdate = false; + for (const LevelLimits &limit : kLimits) { + if (samples <= limit.samples && samplesPerSec <= limit.samplesPerSec && + bitrate.v.value <= limit.bitrate) { + // This is the lowest level that meets the requirements, and if + // we haven't seen the supplied level yet, that means we don't + // need the update. + if (needsUpdate) { + ALOGD("Given level %x does not cover current configuration: " + "adjusting to %x", me.v.level, limit.level); + me.set().level = limit.level; + } + found = true; + break; + } + if (me.v.level == limit.level) { + // We break out of the loop when the lowest feasible level is + // found. The fact that we're here means that our level doesn't + // meet the requirement and needs to be updated. + needsUpdate = true; + } + } + if (!found) { + // We set to the highest supported level. + me.set().level = LEVEL_HEVC_MAIN_5_2; + } + return C2R::Ok(); + } + + UWORD32 getProfile_l() const { + switch (mProfileLevel->profile) { + case PROFILE_HEVC_MAIN: [[fallthrough]]; + case PROFILE_HEVC_MAIN_STILL: return 1; + default: + ALOGD("Unrecognized profile: %x", mProfileLevel->profile); + return 1; + } + } + + UWORD32 getLevel_l() const { + struct Level { + C2Config::level_t c2Level; + UWORD32 hevcLevel; + }; + constexpr Level levels[] = { + { LEVEL_HEVC_MAIN_1, 30 }, + { LEVEL_HEVC_MAIN_2, 60 }, + { LEVEL_HEVC_MAIN_2_1, 63 }, + { LEVEL_HEVC_MAIN_3, 90 }, + { LEVEL_HEVC_MAIN_3_1, 93 }, + { LEVEL_HEVC_MAIN_4, 120 }, + { LEVEL_HEVC_MAIN_4_1, 123 }, + { LEVEL_HEVC_MAIN_5, 150 }, + { LEVEL_HEVC_MAIN_5_1, 153 }, + { LEVEL_HEVC_MAIN_5_2, 156 }, + { LEVEL_HEVC_MAIN_6, 180 }, + { LEVEL_HEVC_MAIN_6_1, 183 }, + { LEVEL_HEVC_MAIN_6_2, 186 }, + }; + for (const Level &level : levels) { + if (mProfileLevel->level == level.c2Level) { + return level.hevcLevel; + } + } + ALOGD("Unrecognized level: %x", mProfileLevel->level); + return 156; + } + uint32_t getSyncFramePeriod_l() const { + if (mSyncFramePeriod->value < 0 || + mSyncFramePeriod->value == INT64_MAX) { + return 0; + } + double period = mSyncFramePeriod->value / 1e6 * mFrameRate->value; + return (uint32_t)c2_max(c2_min(period + 0.5, double(UINT32_MAX)), 1.); + } + + std::shared_ptr<C2StreamPictureSizeInfo::input> getSize_l() const { + return mSize; + } + std::shared_ptr<C2StreamFrameRateInfo::output> getFrameRate_l() const { + return mFrameRate; + } + std::shared_ptr<C2StreamBitrateModeTuning::output> getBitrateMode_l() const { + return mBitrateMode; + } + std::shared_ptr<C2StreamBitrateInfo::output> getBitrate_l() const { + return mBitrate; + } + std::shared_ptr<C2StreamRequestSyncFrameTuning::output> getRequestSync_l() const { + return mRequestSync; + } + std::shared_ptr<C2StreamComplexityTuning::output> getComplexity_l() const { + return mComplexity; + } + std::shared_ptr<C2StreamQualityTuning::output> getQuality_l() const { + return mQuality; + } + + private: + std::shared_ptr<C2StreamUsageTuning::input> mUsage; + std::shared_ptr<C2StreamPictureSizeInfo::input> mSize; + std::shared_ptr<C2StreamFrameRateInfo::output> mFrameRate; + std::shared_ptr<C2StreamRequestSyncFrameTuning::output> mRequestSync; + std::shared_ptr<C2StreamBitrateInfo::output> mBitrate; + std::shared_ptr<C2StreamBitrateModeTuning::output> mBitrateMode; + std::shared_ptr<C2StreamComplexityTuning::output> mComplexity; + std::shared_ptr<C2StreamQualityTuning::output> mQuality; + std::shared_ptr<C2StreamProfileLevelInfo::output> mProfileLevel; + std::shared_ptr<C2StreamSyncFrameIntervalTuning::output> mSyncFramePeriod; +}; + +static size_t GetCPUCoreCount() { + long cpuCoreCount = 0; + +#if defined(_SC_NPROCESSORS_ONLN) + cpuCoreCount = sysconf(_SC_NPROCESSORS_ONLN); +#else + // _SC_NPROC_ONLN must be defined... + cpuCoreCount = sysconf(_SC_NPROC_ONLN); +#endif + + if (cpuCoreCount < 1) + cpuCoreCount = 1; + return (size_t)cpuCoreCount; +} + +C2SoftHevcEnc::C2SoftHevcEnc(const char* name, c2_node_id_t id, + const std::shared_ptr<IntfImpl>& intfImpl) + : SimpleC2Component( + std::make_shared<SimpleInterface<IntfImpl>>(name, id, intfImpl)), + mIntf(intfImpl), + mIvVideoColorFormat(IV_YUV_420P), + mHevcEncProfile(1), + mHevcEncLevel(30), + mStarted(false), + mSpsPpsHeaderReceived(false), + mSignalledEos(false), + mSignalledError(false), + mCodecCtx(nullptr) { + // If dump is enabled, then create an empty file + GENERATE_FILE_NAMES(); + CREATE_DUMP_FILE(mInFile); + CREATE_DUMP_FILE(mOutFile); + + gettimeofday(&mTimeStart, nullptr); + gettimeofday(&mTimeEnd, nullptr); +} + +C2SoftHevcEnc::~C2SoftHevcEnc() { + onRelease(); +} + +c2_status_t C2SoftHevcEnc::onInit() { + return C2_OK; +} + +c2_status_t C2SoftHevcEnc::onStop() { + return C2_OK; +} + +void C2SoftHevcEnc::onReset() { + releaseEncoder(); +} + +void C2SoftHevcEnc::onRelease() { + releaseEncoder(); +} + +c2_status_t C2SoftHevcEnc::onFlush_sm() { + return C2_OK; +} + +static void fillEmptyWork(const std::unique_ptr<C2Work>& work) { + uint32_t flags = 0; + if (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) { + flags |= C2FrameData::FLAG_END_OF_STREAM; + ALOGV("Signalling EOS"); + } + work->worklets.front()->output.flags = (C2FrameData::flags_t)flags; + work->worklets.front()->output.buffers.clear(); + work->worklets.front()->output.ordinal = work->input.ordinal; + work->workletsProcessed = 1u; +} + +static int getQpFromQuality(int quality) { + int qp; +#define MIN_QP 4 +#define MAX_QP 50 + /* Quality: 100 -> Qp : MIN_QP + * Quality: 0 -> Qp : MAX_QP + * Qp = ((MIN_QP - MAX_QP) * quality / 100) + MAX_QP; + */ + qp = ((MIN_QP - MAX_QP) * quality / 100) + MAX_QP; + qp = std::min(qp, MAX_QP); + qp = std::max(qp, MIN_QP); + return qp; +} +c2_status_t C2SoftHevcEnc::initEncParams() { + mCodecCtx = nullptr; + mNumCores = std::min(GetCPUCoreCount(), (size_t) CODEC_MAX_CORES); + memset(&mEncParams, 0, sizeof(ihevce_static_cfg_params_t)); + + // default configuration + IHEVCE_PLUGIN_STATUS_T err = ihevce_set_def_params(&mEncParams); + if (IHEVCE_EOK != err) { + ALOGE("HEVC default init failed : 0x%x", err); + return C2_CORRUPTED; + } + + // update configuration + mEncParams.s_src_prms.i4_width = mSize->width; + mEncParams.s_src_prms.i4_height = mSize->height; + mEncParams.s_src_prms.i4_frm_rate_denom = 1000; + mEncParams.s_src_prms.i4_frm_rate_num = + mFrameRate->value * mEncParams.s_src_prms.i4_frm_rate_denom; + mEncParams.s_tgt_lyr_prms.as_tgt_params[0].i4_quality_preset = IHEVCE_QUALITY_P5; + mEncParams.s_tgt_lyr_prms.as_tgt_params[0].ai4_tgt_bitrate[0] = + mBitrate->value; + mEncParams.s_tgt_lyr_prms.as_tgt_params[0].ai4_peak_bitrate[0] = + mBitrate->value << 1; + mEncParams.s_tgt_lyr_prms.as_tgt_params[0].i4_codec_level = mHevcEncLevel; + mEncParams.s_coding_tools_prms.i4_max_i_open_gop_period = mIDRInterval; + mEncParams.s_coding_tools_prms.i4_max_cra_open_gop_period = mIDRInterval; + mIvVideoColorFormat = IV_YUV_420P; + mEncParams.s_multi_thrd_prms.i4_max_num_cores = mNumCores; + mEncParams.s_out_strm_prms.i4_codec_profile = mHevcEncProfile; + mEncParams.s_lap_prms.i4_rc_look_ahead_pics = DEFAULT_RC_LOOKAHEAD; + mEncParams.s_coding_tools_prms.i4_max_temporal_layers = DEFAULT_B_FRAMES; + + switch (mBitrateMode->value) { + case C2Config::BITRATE_IGNORE: + mEncParams.s_config_prms.i4_rate_control_mode = 3; + mEncParams.s_tgt_lyr_prms.as_tgt_params[0].ai4_frame_qp[0] = + getQpFromQuality(mQuality->value); + break; + case C2Config::BITRATE_CONST: + mEncParams.s_config_prms.i4_rate_control_mode = 5; + break; + case C2Config::BITRATE_VARIABLE: + [[fallthrough]]; + default: + mEncParams.s_config_prms.i4_rate_control_mode = 2; + break; + break; + } + + if (mComplexity->value == 10) { + mEncParams.s_tgt_lyr_prms.as_tgt_params[0].i4_quality_preset = IHEVCE_QUALITY_P0; + } else if (mComplexity->value >= 8) { + mEncParams.s_tgt_lyr_prms.as_tgt_params[0].i4_quality_preset = IHEVCE_QUALITY_P2; + } else if (mComplexity->value >= 7) { + mEncParams.s_tgt_lyr_prms.as_tgt_params[0].i4_quality_preset = IHEVCE_QUALITY_P3; + } else if (mComplexity->value >= 5) { + mEncParams.s_tgt_lyr_prms.as_tgt_params[0].i4_quality_preset = IHEVCE_QUALITY_P4; + } else { + mEncParams.s_tgt_lyr_prms.as_tgt_params[0].i4_quality_preset = IHEVCE_QUALITY_P5; + } + + return C2_OK; +} + +c2_status_t C2SoftHevcEnc::releaseEncoder() { + mSpsPpsHeaderReceived = false; + mSignalledEos = false; + mSignalledError = false; + mStarted = false; + + if (mCodecCtx) { + IHEVCE_PLUGIN_STATUS_T err = ihevce_close(mCodecCtx); + if (IHEVCE_EOK != err) return C2_CORRUPTED; + mCodecCtx = nullptr; + } + return C2_OK; +} + +c2_status_t C2SoftHevcEnc::drain(uint32_t drainMode, + const std::shared_ptr<C2BlockPool>& pool) { + return drainInternal(drainMode, pool, nullptr); +} + +c2_status_t C2SoftHevcEnc::initEncoder() { + CHECK(!mCodecCtx); + { + IntfImpl::Lock lock = mIntf->lock(); + mSize = mIntf->getSize_l(); + mBitrateMode = mIntf->getBitrateMode_l(); + mBitrate = mIntf->getBitrate_l(); + mFrameRate = mIntf->getFrameRate_l(); + mHevcEncProfile = mIntf->getProfile_l(); + mHevcEncLevel = mIntf->getLevel_l(); + mIDRInterval = mIntf->getSyncFramePeriod_l(); + mComplexity = mIntf->getComplexity_l(); + mQuality = mIntf->getQuality_l(); + } + + c2_status_t status = initEncParams(); + + if (C2_OK != status) { + ALOGE("Failed to initialize encoder params : 0x%x", status); + mSignalledError = true; + return status; + } + + IHEVCE_PLUGIN_STATUS_T err = IHEVCE_EOK; + err = ihevce_init(&mEncParams, &mCodecCtx); + if (IHEVCE_EOK != err) { + ALOGE("HEVC encoder init failed : 0x%x", err); + return C2_CORRUPTED; + } + + mStarted = true; + return C2_OK; +} + +c2_status_t C2SoftHevcEnc::setEncodeArgs(ihevce_inp_buf_t* ps_encode_ip, + const C2GraphicView* const input, + uint64_t workIndex) { + ihevce_static_cfg_params_t* params = &mEncParams; + memset(ps_encode_ip, 0, sizeof(*ps_encode_ip)); + + if (!input) { + return C2_OK; + } + + if (input->width() < mSize->width || + input->height() < mSize->height) { + /* Expect width height to be configured */ + ALOGW("unexpected Capacity Aspect %d(%d) x %d(%d)", input->width(), + mSize->width, input->height(), mSize->height); + return C2_BAD_VALUE; + } + + const C2PlanarLayout& layout = input->layout(); + uint8_t* yPlane = + const_cast<uint8_t *>(input->data()[C2PlanarLayout::PLANE_Y]); + uint8_t* uPlane = + const_cast<uint8_t *>(input->data()[C2PlanarLayout::PLANE_U]); + uint8_t* vPlane = + const_cast<uint8_t *>(input->data()[C2PlanarLayout::PLANE_V]); + int32_t yStride = layout.planes[C2PlanarLayout::PLANE_Y].rowInc; + int32_t uStride = layout.planes[C2PlanarLayout::PLANE_U].rowInc; + int32_t vStride = layout.planes[C2PlanarLayout::PLANE_V].rowInc; + + const uint32_t width = mSize->width; + const uint32_t height = mSize->height; + + // width and height must be even + if (width & 1u || height & 1u) { + ALOGW("height(%u) and width(%u) must both be even", height, width); + return C2_BAD_VALUE; + } + + size_t yPlaneSize = width * height; + + switch (layout.type) { + case C2PlanarLayout::TYPE_RGB: + [[fallthrough]]; + case C2PlanarLayout::TYPE_RGBA: { + MemoryBlock conversionBuffer = + mConversionBuffers.fetch(yPlaneSize * 3 / 2); + mConversionBuffersInUse.emplace(conversionBuffer.data(), + conversionBuffer); + yPlane = conversionBuffer.data(); + uPlane = yPlane + yPlaneSize; + vPlane = uPlane + yPlaneSize / 4; + yStride = width; + uStride = vStride = yStride / 2; + ConvertRGBToPlanarYUV(yPlane, yStride, height, + conversionBuffer.size(), *input); + break; + } + case C2PlanarLayout::TYPE_YUV: { + if (!IsYUV420(*input)) { + ALOGE("input is not YUV420"); + return C2_BAD_VALUE; + } + + if (layout.planes[layout.PLANE_Y].colInc == 1 && + layout.planes[layout.PLANE_U].colInc == 1 && + layout.planes[layout.PLANE_V].colInc == 1 && + uStride == vStride && yStride == 2 * vStride) { + // I420 compatible - already set up above + break; + } + + // copy to I420 + yStride = width; + uStride = vStride = yStride / 2; + MemoryBlock conversionBuffer = + mConversionBuffers.fetch(yPlaneSize * 3 / 2); + mConversionBuffersInUse.emplace(conversionBuffer.data(), + conversionBuffer); + MediaImage2 img = + CreateYUV420PlanarMediaImage2(width, height, yStride, height); + status_t err = ImageCopy(conversionBuffer.data(), &img, *input); + if (err != OK) { + ALOGE("Buffer conversion failed: %d", err); + return C2_BAD_VALUE; + } + yPlane = conversionBuffer.data(); + uPlane = yPlane + yPlaneSize; + vPlane = uPlane + yPlaneSize / 4; + break; + } + + case C2PlanarLayout::TYPE_YUVA: + ALOGE("YUVA plane type is not supported"); + return C2_BAD_VALUE; + + default: + ALOGE("Unrecognized plane type: %d", layout.type); + return C2_BAD_VALUE; + } + + switch (mIvVideoColorFormat) { + case IV_YUV_420P: { + // input buffer is supposed to be const but Ittiam API wants bare + // pointer. + ps_encode_ip->apv_inp_planes[0] = yPlane; + ps_encode_ip->apv_inp_planes[1] = uPlane; + ps_encode_ip->apv_inp_planes[2] = vPlane; + + ps_encode_ip->ai4_inp_strd[0] = yStride; + ps_encode_ip->ai4_inp_strd[1] = uStride; + ps_encode_ip->ai4_inp_strd[2] = vStride; + + ps_encode_ip->ai4_inp_size[0] = yStride * height; + ps_encode_ip->ai4_inp_size[1] = uStride * height >> 1; + ps_encode_ip->ai4_inp_size[2] = vStride * height >> 1; + break; + } + + case IV_YUV_422ILE: { + // TODO + break; + } + + case IV_YUV_420SP_UV: + case IV_YUV_420SP_VU: + default: { + ps_encode_ip->apv_inp_planes[0] = yPlane; + ps_encode_ip->apv_inp_planes[1] = uPlane; + ps_encode_ip->apv_inp_planes[2] = nullptr; + + ps_encode_ip->ai4_inp_strd[0] = yStride; + ps_encode_ip->ai4_inp_strd[1] = uStride; + ps_encode_ip->ai4_inp_strd[2] = 0; + + ps_encode_ip->ai4_inp_size[0] = yStride * height; + ps_encode_ip->ai4_inp_size[1] = uStride * height >> 1; + ps_encode_ip->ai4_inp_size[2] = 0; + break; + } + } + + ps_encode_ip->i4_curr_bitrate = + params->s_tgt_lyr_prms.as_tgt_params[0].ai4_tgt_bitrate[0]; + ps_encode_ip->i4_curr_peak_bitrate = + params->s_tgt_lyr_prms.as_tgt_params[0].ai4_peak_bitrate[0]; + ps_encode_ip->i4_curr_rate_factor = params->s_config_prms.i4_rate_factor; + ps_encode_ip->u8_pts = workIndex; + return C2_OK; +} + +void C2SoftHevcEnc::finishWork(uint64_t index, + const std::unique_ptr<C2Work>& work, + const std::shared_ptr<C2BlockPool>& pool, + ihevce_out_buf_t* ps_encode_op) { + std::shared_ptr<C2LinearBlock> block; + C2MemoryUsage usage = {C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE}; + c2_status_t status = + pool->fetchLinearBlock(ps_encode_op->i4_bytes_generated, usage, &block); + if (C2_OK != status) { + ALOGE("fetchLinearBlock for Output failed with status 0x%x", status); + mSignalledError = true; + work->result = status; + work->workletsProcessed = 1u; + return; + } + C2WriteView wView = block->map().get(); + if (C2_OK != wView.error()) { + ALOGE("write view map failed with status 0x%x", wView.error()); + mSignalledError = true; + work->result = wView.error(); + work->workletsProcessed = 1u; + return; + } + memcpy(wView.data(), ps_encode_op->pu1_output_buf, + ps_encode_op->i4_bytes_generated); + + std::shared_ptr<C2Buffer> buffer = + createLinearBuffer(block, 0, ps_encode_op->i4_bytes_generated); + + DUMP_TO_FILE(mOutFile, ps_encode_op->pu1_output_buf, + ps_encode_op->i4_bytes_generated); + + if (ps_encode_op->i4_is_key_frame) { + ALOGV("IDR frame produced"); + buffer->setInfo(std::make_shared<C2StreamPictureTypeMaskInfo::output>( + 0u /* stream id */, C2Config::SYNC_FRAME)); + } + + 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; + work->workletsProcessed = 1u; + }; + if (work && c2_cntr64_t(index) == work->input.ordinal.frameIndex) { + fillWork(work); + if (mSignalledEos) { + work->worklets.front()->output.flags = + C2FrameData::FLAG_END_OF_STREAM; + } + } else { + finish(index, fillWork); + } +} + +c2_status_t C2SoftHevcEnc::drainInternal( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool, + const std::unique_ptr<C2Work> &work) { + + if (drainMode == NO_DRAIN) { + ALOGW("drain with NO_DRAIN: no-op"); + return C2_OK; + } + if (drainMode == DRAIN_CHAIN) { + ALOGW("DRAIN_CHAIN not supported"); + return C2_OMITTED; + } + + while (true) { + ihevce_out_buf_t s_encode_op{}; + memset(&s_encode_op, 0, sizeof(s_encode_op)); + + ihevce_encode(mCodecCtx, nullptr, &s_encode_op); + if (s_encode_op.i4_bytes_generated) { + finishWork(s_encode_op.u8_pts, work, pool, &s_encode_op); + } else { + if (work->workletsProcessed != 1u) fillEmptyWork(work); + break; + } + } + return C2_OK; +} + +void C2SoftHevcEnc::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 || mSignalledEos) { + work->result = C2_BAD_VALUE; + ALOGD("Signalled Error / Signalled Eos"); + return; + } + c2_status_t status = C2_OK; + + // Initialize encoder if not already initialized + if (!mStarted) { + status = initEncoder(); + if (C2_OK != status) { + ALOGE("Failed to initialize encoder : 0x%x", status); + mSignalledError = true; + work->result = status; + work->workletsProcessed = 1u; + return; + } + } + + std::shared_ptr<const C2GraphicView> view; + std::shared_ptr<C2Buffer> inputBuffer = nullptr; + bool eos = ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0); + if (eos) mSignalledEos = true; + + if (!work->input.buffers.empty()) { + inputBuffer = work->input.buffers[0]; + view = std::make_shared<const C2GraphicView>( + inputBuffer->data().graphicBlocks().front().map().get()); + if (view->error() != C2_OK) { + ALOGE("graphic view map err = %d", view->error()); + mSignalledError = true; + work->result = C2_CORRUPTED; + work->workletsProcessed = 1u; + return; + } + } + IHEVCE_PLUGIN_STATUS_T err = IHEVCE_EOK; + + if (!mSpsPpsHeaderReceived) { + ihevce_out_buf_t s_header_op{}; + err = ihevce_encode_header(mCodecCtx, &s_header_op); + if (err == IHEVCE_EOK && s_header_op.i4_bytes_generated) { + std::unique_ptr<C2StreamInitDataInfo::output> csd = + C2StreamInitDataInfo::output::AllocUnique( + s_header_op.i4_bytes_generated, 0u); + if (!csd) { + ALOGE("CSD allocation failed"); + mSignalledError = true; + work->result = C2_NO_MEMORY; + work->workletsProcessed = 1u; + return; + } + memcpy(csd->m.value, s_header_op.pu1_output_buf, + s_header_op.i4_bytes_generated); + DUMP_TO_FILE(mOutFile, csd->m.value, csd->flexCount()); + work->worklets.front()->output.configUpdate.push_back( + std::move(csd)); + mSpsPpsHeaderReceived = true; + } + if (!inputBuffer) { + work->workletsProcessed = 1u; + return; + } + } + + // handle dynamic config parameters + { + IntfImpl::Lock lock = mIntf->lock(); + std::shared_ptr<C2StreamBitrateInfo::output> bitrate = mIntf->getBitrate_l(); + lock.unlock(); + + if (bitrate != mBitrate) { + mBitrate = bitrate; + mEncParams.s_tgt_lyr_prms.as_tgt_params[0].ai4_tgt_bitrate[0] = + mBitrate->value; + mEncParams.s_tgt_lyr_prms.as_tgt_params[0].ai4_peak_bitrate[0] = + mBitrate->value << 1; + } + } + + ihevce_inp_buf_t s_encode_ip{}; + ihevce_out_buf_t s_encode_op{}; + uint64_t workIndex = work->input.ordinal.frameIndex.peekull(); + + status = setEncodeArgs(&s_encode_ip, view.get(), workIndex); + if (C2_OK != status) { + ALOGE("setEncodeArgs failed : 0x%x", status); + mSignalledError = true; + work->result = status; + work->workletsProcessed = 1u; + return; + } + + uint64_t timeDelay = 0; + uint64_t timeTaken = 0; + memset(&s_encode_op, 0, sizeof(s_encode_op)); + GETTIME(&mTimeStart, nullptr); + TIME_DIFF(mTimeEnd, mTimeStart, timeDelay); + + if (inputBuffer) { + err = ihevce_encode(mCodecCtx, &s_encode_ip, &s_encode_op); + if (IHEVCE_EOK != err) { + ALOGE("Encode Frame failed : 0x%x", err); + mSignalledError = true; + work->result = C2_CORRUPTED; + work->workletsProcessed = 1u; + return; + } + } else if (!eos) { + fillEmptyWork(work); + } + + GETTIME(&mTimeEnd, nullptr); + /* Compute time taken for decode() */ + TIME_DIFF(mTimeStart, mTimeEnd, timeTaken); + + ALOGV("timeTaken=%6d delay=%6d numBytes=%6d", (int)timeTaken, + (int)timeDelay, s_encode_op.i4_bytes_generated); + + if (s_encode_op.i4_bytes_generated) { + finishWork(s_encode_op.u8_pts, work, pool, &s_encode_op); + } + + if (eos) { + drainInternal(DRAIN_COMPONENT_WITH_EOS, pool, work); + } +} + +class C2SoftHevcEncFactory : public C2ComponentFactory { + public: + C2SoftHevcEncFactory() + : mHelper(std::static_pointer_cast<C2ReflectorHelper>( + GetCodec2PlatformComponentStore()->getParamReflector())) {} + + c2_status_t createComponent( + c2_node_id_t id, + std::shared_ptr<C2Component>* const component, + std::function<void(C2Component*)> deleter) override { + *component = std::shared_ptr<C2Component>( + new C2SoftHevcEnc( + COMPONENT_NAME, id, + std::make_shared<C2SoftHevcEnc::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + c2_status_t createInterface( + c2_node_id_t id, + std::shared_ptr<C2ComponentInterface>* const interface, + std::function<void(C2ComponentInterface*)> deleter) override { + *interface = std::shared_ptr<C2ComponentInterface>( + new SimpleInterface<C2SoftHevcEnc::IntfImpl>( + COMPONENT_NAME, id, + std::make_shared<C2SoftHevcEnc::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + ~C2SoftHevcEncFactory() override = default; + + private: + std::shared_ptr<C2ReflectorHelper> mHelper; +}; + +} // namespace android + +extern "C" ::C2ComponentFactory* CreateCodec2Factory() { + ALOGV("in %s", __func__); + return new ::android::C2SoftHevcEncFactory(); +} + +extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) { + ALOGV("in %s", __func__); + delete factory; +}
diff --git a/media/codec2/components/hevc/C2SoftHevcEnc.h b/media/codec2/components/hevc/C2SoftHevcEnc.h new file mode 100644 index 0000000..f2c7642 --- /dev/null +++ b/media/codec2/components/hevc/C2SoftHevcEnc.h
@@ -0,0 +1,169 @@ +/* + * Copyright 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_C2_SOFT_HEVC_ENC_H_ +#define ANDROID_C2_SOFT_HEVC_ENC_H_ + +#include <SimpleC2Component.h> +#include <algorithm> +#include <map> +#include <media/stagefright/foundation/ColorUtils.h> +#include <utils/Vector.h> + +#include "ihevc_typedefs.h" + +namespace android { + +/** Get time */ +#define GETTIME(a, b) gettimeofday(a, b) + +/** Compute difference between start and end */ +#define TIME_DIFF(start, end, diff) \ + diff = (((end).tv_sec - (start).tv_sec) * 1000000) + \ + ((end).tv_usec - (start).tv_usec); + +#define CODEC_MAX_CORES 4 +#define MAX_B_FRAMES 1 +#define MAX_RC_LOOKAHEAD 1 + +#define DEFAULT_B_FRAMES 0 +#define DEFAULT_RC_LOOKAHEAD 0 + +struct C2SoftHevcEnc : public SimpleC2Component { + class IntfImpl; + + C2SoftHevcEnc(const char* name, c2_node_id_t id, + const std::shared_ptr<IntfImpl>& intfImpl); + + // From SimpleC2Component + c2_status_t onInit() override; + c2_status_t onStop() override; + void onReset() override; + void onRelease() override; + c2_status_t onFlush_sm() override; + void process(const std::unique_ptr<C2Work>& work, + const std::shared_ptr<C2BlockPool>& pool) override; + c2_status_t drain(uint32_t drainMode, + const std::shared_ptr<C2BlockPool>& pool) override; + + protected: + ~C2SoftHevcEnc() override; + + private: + std::shared_ptr<IntfImpl> mIntf; + ihevce_static_cfg_params_t mEncParams; + size_t mNumCores; + UWORD32 mIDRInterval; + IV_COLOR_FORMAT_T mIvVideoColorFormat; + UWORD32 mHevcEncProfile; + UWORD32 mHevcEncLevel; + bool mStarted; + bool mSpsPpsHeaderReceived; + bool mSignalledEos; + bool mSignalledError; + void* mCodecCtx; + MemoryBlockPool mConversionBuffers; + std::map<void*, MemoryBlock> mConversionBuffersInUse; + // configurations used by component in process + // (TODO: keep this in intf but make them internal only) + std::shared_ptr<C2StreamPictureSizeInfo::input> mSize; + std::shared_ptr<C2StreamFrameRateInfo::output> mFrameRate; + std::shared_ptr<C2StreamBitrateInfo::output> mBitrate; + std::shared_ptr<C2StreamBitrateModeTuning::output> mBitrateMode; + std::shared_ptr<C2StreamComplexityTuning::output> mComplexity; + std::shared_ptr<C2StreamQualityTuning::output> mQuality; + +#ifdef FILE_DUMP_ENABLE + char mInFile[200]; + char mOutFile[200]; +#endif /* FILE_DUMP_ENABLE */ + + // profile + struct timeval mTimeStart; + struct timeval mTimeEnd; + + c2_status_t initEncParams(); + c2_status_t initEncoder(); + c2_status_t releaseEncoder(); + c2_status_t setEncodeArgs(ihevce_inp_buf_t* ps_encode_ip, + const C2GraphicView* const input, + uint64_t workIndex); + void finishWork(uint64_t index, const std::unique_ptr<C2Work>& work, + const std::shared_ptr<C2BlockPool>& pool, + ihevce_out_buf_t* ps_encode_op); + c2_status_t drainInternal(uint32_t drainMode, + const std::shared_ptr<C2BlockPool>& pool, + const std::unique_ptr<C2Work>& work); + C2_DO_NOT_COPY(C2SoftHevcEnc); +}; +#ifdef FILE_DUMP_ENABLE + +#define INPUT_DUMP_PATH "/data/local/tmp/hevc" +#define INPUT_DUMP_EXT "yuv" +#define OUTPUT_DUMP_PATH "/data/local/tmp/hevc" +#define OUTPUT_DUMP_EXT "h265" +#define GENERATE_FILE_NAMES() \ +{ \ + GETTIME(&mTimeStart, NULL); \ + strcpy(mInFile, ""); \ + ALOGD("GENERATE_FILE_NAMES"); \ + sprintf(mInFile, "%s_%ld.%ld.%s", INPUT_DUMP_PATH, mTimeStart.tv_sec, \ + mTimeStart.tv_usec, INPUT_DUMP_EXT); \ + strcpy(mOutFile, ""); \ + sprintf(mOutFile, "%s_%ld.%ld.%s", OUTPUT_DUMP_PATH, \ + mTimeStart.tv_sec, mTimeStart.tv_usec, OUTPUT_DUMP_EXT); \ +} + +#define CREATE_DUMP_FILE(m_filename) \ +{ \ + FILE* fp = fopen(m_filename, "wb"); \ + if (fp != NULL) { \ + ALOGD("Opened file %s", m_filename); \ + fclose(fp); \ + } else { \ + ALOGD("Could not open file %s", m_filename); \ + } \ +} +#define DUMP_TO_FILE(m_filename, m_buf, m_size) \ +{ \ + FILE* fp = fopen(m_filename, "ab"); \ + if (fp != NULL && m_buf != NULL) { \ + int i; \ + ALOGD("Dump to file!"); \ + i = fwrite(m_buf, 1, m_size, fp); \ + if (i != (int)m_size) { \ + ALOGD("Error in fwrite, returned %d", i); \ + perror("Error in write to file"); \ + } \ + fclose(fp); \ + } else { \ + ALOGD("Could not write to file %s", m_filename); \ + if (fp != NULL) fclose(fp); \ + } \ +} +#else /* FILE_DUMP_ENABLE */ +#define INPUT_DUMP_PATH +#define INPUT_DUMP_EXT +#define OUTPUT_DUMP_PATH +#define OUTPUT_DUMP_EXT +#define GENERATE_FILE_NAMES() +#define CREATE_DUMP_FILE(m_filename) +#define DUMP_TO_FILE(m_filename, m_buf, m_size) +#endif /* FILE_DUMP_ENABLE */ + +} // namespace android + +#endif // C2_SOFT_HEVC_ENC_H__
diff --git a/media/codec2/components/mp3/Android.bp b/media/codec2/components/mp3/Android.bp new file mode 100644 index 0000000..66665ed --- /dev/null +++ b/media/codec2/components/mp3/Android.bp
@@ -0,0 +1,11 @@ +cc_library_shared { + name: "libcodec2_soft_mp3dec", + defaults: [ + "libcodec2_soft-defaults", + "libcodec2_soft_sanitize_all-defaults", + ], + + srcs: ["C2SoftMp3Dec.cpp"], + + static_libs: ["libstagefright_mp3dec"], +}
diff --git a/media/codec2/components/mp3/C2SoftMp3Dec.cpp b/media/codec2/components/mp3/C2SoftMp3Dec.cpp new file mode 100644 index 0000000..5ba7e3d --- /dev/null +++ b/media/codec2/components/mp3/C2SoftMp3Dec.cpp
@@ -0,0 +1,550 @@ +/* + * 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 "C2SoftMp3Dec" +#include <log/log.h> + +#include <numeric> + +#include <media/stagefright/foundation/MediaDefs.h> + +#include <C2PlatformSupport.h> +#include <SimpleC2Interface.h> + +#include "C2SoftMp3Dec.h" +#include "pvmp3decoder_api.h" + +namespace android { + +namespace { + +constexpr char COMPONENT_NAME[] = "c2.android.mp3.decoder"; + +} // namespace + +class C2SoftMP3::IntfImpl : public SimpleInterface<void>::BaseParams { +public: + explicit IntfImpl(const std::shared_ptr<C2ReflectorHelper> &helper) + : SimpleInterface<void>::BaseParams( + helper, + COMPONENT_NAME, + C2Component::KIND_DECODER, + C2Component::DOMAIN_AUDIO, + MEDIA_MIMETYPE_AUDIO_MPEG) { + noPrivateBuffers(); + noInputReferences(); + noOutputReferences(); + noInputLatency(); + noTimeStretch(); + setDerivedInstance(this); + + addParameter( + DefineParam(mAttrib, C2_PARAMKEY_COMPONENT_ATTRIBUTES) + .withConstValue(new C2ComponentAttributesSetting( + C2Component::ATTRIB_IS_TEMPORAL)) + .build()); + + addParameter( + DefineParam(mSampleRate, C2_PARAMKEY_SAMPLE_RATE) + .withDefault(new C2StreamSampleRateInfo::output(0u, 44100)) + .withFields({C2F(mSampleRate, value).oneOf({8000, 11025, 12000, 16000, + 22050, 24000, 32000, 44100, 48000})}) + .withSetter((Setter<decltype(*mSampleRate)>::StrictValueWithNoDeps)) + .build()); + + addParameter( + DefineParam(mChannelCount, C2_PARAMKEY_CHANNEL_COUNT) + .withDefault(new C2StreamChannelCountInfo::output(0u, 2)) + .withFields({C2F(mChannelCount, value).inRange(1, 2)}) + .withSetter(Setter<decltype(*mChannelCount)>::StrictValueWithNoDeps) + .build()); + + addParameter( + DefineParam(mBitrate, C2_PARAMKEY_BITRATE) + .withDefault(new C2StreamBitrateInfo::input(0u, 64000)) + .withFields({C2F(mBitrate, value).inRange(8000, 320000)}) + .withSetter(Setter<decltype(*mBitrate)>::NonStrictValueWithNoDeps) + .build()); + + addParameter( + DefineParam(mInputMaxBufSize, C2_PARAMKEY_INPUT_MAX_BUFFER_SIZE) + .withConstValue(new C2StreamMaxBufferSizeInfo::input(0u, 8192)) + .build()); + } + +private: + std::shared_ptr<C2StreamSampleRateInfo::output> mSampleRate; + std::shared_ptr<C2StreamChannelCountInfo::output> mChannelCount; + std::shared_ptr<C2StreamBitrateInfo::input> mBitrate; + std::shared_ptr<C2StreamMaxBufferSizeInfo::input> mInputMaxBufSize; +}; + +C2SoftMP3::C2SoftMP3(const char *name, c2_node_id_t id, + const std::shared_ptr<IntfImpl> &intfImpl) + : SimpleC2Component(std::make_shared<SimpleInterface<IntfImpl>>(name, id, intfImpl)), + mIntf(intfImpl), + mConfig(nullptr), + mDecoderBuf(nullptr) { +} + +C2SoftMP3::~C2SoftMP3() { + onRelease(); +} + +c2_status_t C2SoftMP3::onInit() { + status_t err = initDecoder(); + return err == OK ? C2_OK : C2_NO_MEMORY; +} + +c2_status_t C2SoftMP3::onStop() { + // Make sure that the next buffer output does not still + // depend on fragments from the last one decoded. + pvmp3_InitDecoder(mConfig, mDecoderBuf); + mSignalledError = false; + mIsFirst = true; + mSignalledOutputEos = false; + mAnchorTimeStamp = 0; + mProcessedSamples = 0; + + return C2_OK; +} + +void C2SoftMP3::onReset() { + (void)onStop(); +} + +void C2SoftMP3::onRelease() { + mGaplessBytes = false; + if (mDecoderBuf) { + free(mDecoderBuf); + mDecoderBuf = nullptr; + } + + if (mConfig) { + delete mConfig; + mConfig = nullptr; + } +} + +status_t C2SoftMP3::initDecoder() { + mConfig = new tPVMP3DecoderExternal{}; + if (!mConfig) return NO_MEMORY; + mConfig->equalizerType = flat; + mConfig->crcEnabled = false; + + size_t memRequirements = pvmp3_decoderMemRequirements(); + mDecoderBuf = malloc(memRequirements); + if (!mDecoderBuf) return NO_MEMORY; + + pvmp3_InitDecoder(mConfig, mDecoderBuf); + + mIsFirst = true; + mGaplessBytes = false; + mSignalledError = false; + mSignalledOutputEos = false; + mAnchorTimeStamp = 0; + mProcessedSamples = 0; + + return OK; +} + +/* The below code is borrowed from ./test/mp3reader.cpp */ +static bool parseMp3Header(uint32_t header, size_t *frame_size, + uint32_t *out_sampling_rate = nullptr, + uint32_t *out_channels = nullptr, + uint32_t *out_bitrate = nullptr, + uint32_t *out_num_samples = nullptr) { + *frame_size = 0; + if (out_sampling_rate) *out_sampling_rate = 0; + if (out_channels) *out_channels = 0; + if (out_bitrate) *out_bitrate = 0; + if (out_num_samples) *out_num_samples = 1152; + + if ((header & 0xffe00000) != 0xffe00000) return false; + + unsigned version = (header >> 19) & 3; + if (version == 0x01) return false; + + unsigned layer = (header >> 17) & 3; + if (layer == 0x00) return false; + + unsigned bitrate_index = (header >> 12) & 0x0f; + if (bitrate_index == 0 || bitrate_index == 0x0f) return false; + + unsigned sampling_rate_index = (header >> 10) & 3; + if (sampling_rate_index == 3) return false; + + static const int kSamplingRateV1[] = { 44100, 48000, 32000 }; + int sampling_rate = kSamplingRateV1[sampling_rate_index]; + if (version == 2 /* V2 */) { + sampling_rate /= 2; + } else if (version == 0 /* V2.5 */) { + sampling_rate /= 4; + } + + unsigned padding = (header >> 9) & 1; + + if (layer == 3) { // layer I + static const int kBitrateV1[] = + { + 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448 + }; + static const int kBitrateV2[] = + { + 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256 + }; + + int bitrate = (version == 3 /* V1 */) ? kBitrateV1[bitrate_index - 1] : + kBitrateV2[bitrate_index - 1]; + + if (out_bitrate) { + *out_bitrate = bitrate; + } + *frame_size = (12000 * bitrate / sampling_rate + padding) * 4; + if (out_num_samples) { + *out_num_samples = 384; + } + } else { // layer II or III + static const int kBitrateV1L2[] = + { + 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384 + }; + + static const int kBitrateV1L3[] = + { + 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320 + }; + + static const int kBitrateV2[] = + { + 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160 + }; + + int bitrate; + if (version == 3 /* V1 */) { + bitrate = (layer == 2 /* L2 */) ? kBitrateV1L2[bitrate_index - 1] : + kBitrateV1L3[bitrate_index - 1]; + + if (out_num_samples) { + *out_num_samples = 1152; + } + } else { // V2 (or 2.5) + bitrate = kBitrateV2[bitrate_index - 1]; + if (out_num_samples) { + *out_num_samples = (layer == 1 /* L3 */) ? 576 : 1152; + } + } + + if (out_bitrate) { + *out_bitrate = bitrate; + } + + if (version == 3 /* V1 */) { + *frame_size = 144000 * bitrate / sampling_rate + padding; + } else { // V2 or V2.5 + size_t tmp = (layer == 1 /* L3 */) ? 72000 : 144000; + *frame_size = tmp * bitrate / sampling_rate + padding; + } + } + + if (out_sampling_rate) { + *out_sampling_rate = sampling_rate; + } + + if (out_channels) { + int channel_mode = (header >> 6) & 3; + + *out_channels = (channel_mode == 3) ? 1 : 2; + } + + return true; +} + +static uint32_t U32_AT(const uint8_t *ptr) { + return ptr[0] << 24 | ptr[1] << 16 | ptr[2] << 8 | ptr[3]; +} + +static status_t calculateOutSize(uint8 *header, size_t inSize, + std::vector<size_t> *decodedSizes) { + uint32_t channels; + uint32_t numSamples; + size_t frameSize; + size_t totalInSize = 0; + + while (totalInSize + 4 < inSize) { + if (!parseMp3Header(U32_AT(header + totalInSize), &frameSize, + nullptr, &channels, nullptr, &numSamples)) { + ALOGE("Error in parse mp3 header during outSize estimation"); + return UNKNOWN_ERROR; + } + totalInSize += frameSize; + decodedSizes->push_back(numSamples * channels * sizeof(int16_t)); + } + + if (decodedSizes->empty()) return UNKNOWN_ERROR; + + return OK; +} + +c2_status_t C2SoftMP3::onFlush_sm() { + return onStop(); +} + +c2_status_t C2SoftMP3::drain( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool) { + (void) pool; + if (drainMode == NO_DRAIN) { + ALOGW("drain with NO_DRAIN: no-op"); + return C2_OK; + } + if (drainMode == DRAIN_CHAIN) { + ALOGW("DRAIN_CHAIN not supported"); + return C2_OMITTED; + } + + return C2_OK; +} + +// TODO: Can overall error checking be improved? As in the check for validity of +// work, pool ptr, work->input.buffers.size() == 1, ... +// TODO: Blind removal of 529 samples from the output may not work. Because +// mpeg layer 1 frame size is 384 samples per frame. This should introduce +// negative values and can cause SEG faults. Soft omx mp3 plugin can have +// this problem (CHECK!) +void C2SoftMP3::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.configUpdate.clear(); + work->worklets.front()->output.flags = work->input.flags; + + if (mSignalledError || mSignalledOutputEos) { + work->result = C2_BAD_VALUE; + return; + } + + bool eos = ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0); + size_t inSize = 0u; + C2ReadView rView = mDummyReadView; + if (!work->input.buffers.empty()) { + rView = work->input.buffers[0]->data().linearBlocks().front().map().get(); + inSize = rView.capacity(); + if (inSize && rView.error()) { + ALOGE("read view map failed %d", rView.error()); + work->result = rView.error(); + return; + } + } + + if (inSize == 0 && (!mGaplessBytes || !eos)) { + work->worklets.front()->output.flags = work->input.flags; + work->worklets.front()->output.buffers.clear(); + work->worklets.front()->output.ordinal = work->input.ordinal; + return; + } + ALOGV("in buffer attr. size %zu timestamp %d frameindex %d", inSize, + (int)work->input.ordinal.timestamp.peeku(), (int)work->input.ordinal.frameIndex.peeku()); + + int32_t numChannels = mConfig->num_channels; + size_t calOutSize; + std::vector<size_t> decodedSizes; + if (inSize && OK != calculateOutSize(const_cast<uint8 *>(rView.data()), + inSize, &decodedSizes)) { + work->result = C2_CORRUPTED; + return; + } + calOutSize = std::accumulate(decodedSizes.begin(), decodedSizes.end(), 0); + if (eos) { + calOutSize += kPVMP3DecoderDelay * numChannels * sizeof(int16_t); + } + + std::shared_ptr<C2LinearBlock> block; + C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE }; + c2_status_t err = pool->fetchLinearBlock(calOutSize, usage, &block); + if (err != C2_OK) { + ALOGE("fetchLinearBlock for Output failed with status %d", err); + work->result = C2_NO_MEMORY; + return; + } + C2WriteView wView = block->map().get(); + if (wView.error()) { + ALOGE("write view map failed %d", wView.error()); + work->result = wView.error(); + return; + } + + int outSize = 0; + int outOffset = 0; + auto it = decodedSizes.begin(); + size_t inPos = 0; + int32_t samplingRate = mConfig->samplingRate; + while (inPos < inSize) { + if (it == decodedSizes.end()) { + ALOGE("unexpected trailing bytes, ignoring them"); + break; + } + + mConfig->pInputBuffer = const_cast<uint8 *>(rView.data() + inPos); + mConfig->inputBufferCurrentLength = (inSize - inPos); + mConfig->inputBufferMaxLength = 0; + mConfig->inputBufferUsedLength = 0; + mConfig->outputFrameSize = (calOutSize - outSize); + mConfig->pOutputBuffer = reinterpret_cast<int16_t *> (wView.data() + outSize); + + ERROR_CODE decoderErr; + if ((decoderErr = pvmp3_framedecoder(mConfig, mDecoderBuf)) + != NO_DECODING_ERROR) { + ALOGE("mp3 decoder returned error %d", decoderErr); + if (decoderErr != NO_ENOUGH_MAIN_DATA_ERROR + && decoderErr != SIDE_INFO_ERROR) { + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } + + // This is recoverable, just ignore the current frame and + // play silence instead. + ALOGV("ignoring error and sending silence"); + if (mConfig->outputFrameSize == 0) { + mConfig->outputFrameSize = *it / sizeof(int16_t); + } + memset(mConfig->pOutputBuffer, 0, mConfig->outputFrameSize * sizeof(int16_t)); + } else if (mConfig->samplingRate != samplingRate + || mConfig->num_channels != numChannels) { + ALOGI("Reconfiguring decoder: %d->%d Hz, %d->%d channels", + samplingRate, mConfig->samplingRate, + numChannels, mConfig->num_channels); + samplingRate = mConfig->samplingRate; + numChannels = mConfig->num_channels; + + C2StreamSampleRateInfo::output sampleRateInfo(0u, samplingRate); + C2StreamChannelCountInfo::output channelCountInfo(0u, numChannels); + std::vector<std::unique_ptr<C2SettingResult>> failures; + c2_status_t err = mIntf->config( + { &sampleRateInfo, &channelCountInfo }, + C2_MAY_BLOCK, + &failures); + if (err == OK) { + work->worklets.front()->output.configUpdate.push_back(C2Param::Copy(sampleRateInfo)); + work->worklets.front()->output.configUpdate.push_back(C2Param::Copy(channelCountInfo)); + } else { + ALOGE("Config Update failed"); + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } + } + if (*it != mConfig->outputFrameSize * sizeof(int16_t)) { + ALOGE("panic, parsed size does not match decoded size"); + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } + outSize += mConfig->outputFrameSize * sizeof(int16_t); + inPos += mConfig->inputBufferUsedLength; + it++; + } + if (mIsFirst) { + mIsFirst = false; + mGaplessBytes = true; + // The decoder delay is 529 samples, so trim that many samples off + // the start of the first output buffer. This essentially makes this + // decoder have zero delay, which the rest of the pipeline assumes. + outOffset = kPVMP3DecoderDelay * numChannels * sizeof(int16_t); + mAnchorTimeStamp = work->input.ordinal.timestamp.peekull(); + } + if (eos) { + if (calOutSize >= + outSize + kPVMP3DecoderDelay * numChannels * sizeof(int16_t)) { + if (!memset(reinterpret_cast<int16_t*>(wView.data() + outSize), 0, + kPVMP3DecoderDelay * numChannels * sizeof(int16_t))) { + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } + ALOGV("Adding 529 samples at end"); + mGaplessBytes = false; + outSize += kPVMP3DecoderDelay * numChannels * sizeof(int16_t); + } + } + + uint64_t outTimeStamp = mProcessedSamples * 1000000ll / samplingRate; + 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(); + work->worklets.front()->output.buffers.push_back( + createLinearBuffer(block, outOffset, outSize - outOffset)); + work->worklets.front()->output.ordinal = work->input.ordinal; + work->worklets.front()->output.ordinal.timestamp = mAnchorTimeStamp + outTimeStamp; + if (eos) { + mSignalledOutputEos = true; + ALOGV("signalled EOS"); + } +} + +class C2SoftMp3DecFactory : public C2ComponentFactory { +public: + C2SoftMp3DecFactory() : mHelper(std::static_pointer_cast<C2ReflectorHelper>( + GetCodec2PlatformComponentStore()->getParamReflector())) { + } + + virtual c2_status_t createComponent( + c2_node_id_t id, + std::shared_ptr<C2Component>* const component, + std::function<void(C2Component*)> deleter) override { + *component = std::shared_ptr<C2Component>( + new C2SoftMP3(COMPONENT_NAME, + id, + std::make_shared<C2SoftMP3::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual c2_status_t createInterface( + c2_node_id_t id, + std::shared_ptr<C2ComponentInterface>* const interface, + std::function<void(C2ComponentInterface*)> deleter) override { + *interface = std::shared_ptr<C2ComponentInterface>( + new SimpleInterface<C2SoftMP3::IntfImpl>( + COMPONENT_NAME, id, std::make_shared<C2SoftMP3::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual ~C2SoftMp3DecFactory() override = default; + +private: + std::shared_ptr<C2ReflectorHelper> mHelper; +}; + +} // namespace android + +extern "C" ::C2ComponentFactory* CreateCodec2Factory() { + ALOGV("in %s", __func__); + return new ::android::C2SoftMp3DecFactory(); +} + +extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) { + ALOGV("in %s", __func__); + delete factory; +}
diff --git a/media/codec2/components/mp3/C2SoftMp3Dec.h b/media/codec2/components/mp3/C2SoftMp3Dec.h new file mode 100644 index 0000000..402bdc4 --- /dev/null +++ b/media/codec2/components/mp3/C2SoftMp3Dec.h
@@ -0,0 +1,76 @@ +/* + * 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_C2_SOFT_MP3_DEC_H_ +#define ANDROID_C2_SOFT_MP3_DEC_H_ + +#include <SimpleC2Component.h> + + +struct tPVMP3DecoderExternal; + +bool parseMp3Header(uint32_t header, size_t *frame_size, + uint32_t *out_sampling_rate = nullptr, + uint32_t *out_channels = nullptr, + uint32_t *out_bitrate = nullptr, + uint32_t *out_num_samples = nullptr); + +namespace android { + +struct C2SoftMP3 : public SimpleC2Component { + class IntfImpl; + + C2SoftMP3(const char *name, c2_node_id_t id, + const std::shared_ptr<IntfImpl> &intfImpl); + virtual ~C2SoftMP3(); + + // From SimpleC2Component + c2_status_t onInit() override; + c2_status_t onStop() override; + void onReset() override; + void onRelease() override; + c2_status_t onFlush_sm() override; + void process( + const std::unique_ptr<C2Work> &work, + const std::shared_ptr<C2BlockPool> &pool) override; + c2_status_t drain( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool) override; + +private: + enum { + kPVMP3DecoderDelay = 529 // samples + }; + + std::shared_ptr<IntfImpl> mIntf; + tPVMP3DecoderExternal *mConfig; + void *mDecoderBuf; + + bool mIsFirst; + bool mSignalledError; + bool mSignalledOutputEos; + bool mGaplessBytes; + uint64_t mAnchorTimeStamp; + uint64_t mProcessedSamples; + + status_t initDecoder(); + + C2_DO_NOT_COPY(C2SoftMP3); +}; + +} // namespace android + +#endif // ANDROID_C2_SOFT_MP3_DEC_H_
diff --git a/media/common_time/MODULE_LICENSE_APACHE2 b/media/codec2/components/mp3/MODULE_LICENSE_APACHE2 similarity index 100% copy from media/common_time/MODULE_LICENSE_APACHE2 copy to media/codec2/components/mp3/MODULE_LICENSE_APACHE2
diff --git a/media/common_time/NOTICE b/media/codec2/components/mp3/NOTICE similarity index 100% copy from media/common_time/NOTICE copy to media/codec2/components/mp3/NOTICE
diff --git a/media/codec2/components/mp3/patent_disclaimer.txt b/media/codec2/components/mp3/patent_disclaimer.txt new file mode 100644 index 0000000..b4bf11d --- /dev/null +++ b/media/codec2/components/mp3/patent_disclaimer.txt
@@ -0,0 +1,9 @@ + +THIS IS NOT A GRANT OF PATENT RIGHTS. + +Google makes no representation or warranty that the codecs for which +source code is made available hereunder are unencumbered by +third-party patents. Those intending to use this source code in +hardware or software products are advised that implementations of +these codecs, including in open source software or shareware, may +require patent licenses from the relevant patent holders.
diff --git a/media/codec2/components/mpeg2/Android.bp b/media/codec2/components/mpeg2/Android.bp new file mode 100644 index 0000000..841f0a9 --- /dev/null +++ b/media/codec2/components/mpeg2/Android.bp
@@ -0,0 +1,16 @@ +cc_library_shared { + name: "libcodec2_soft_mpeg2dec", + defaults: [ + "libcodec2_soft-defaults", + "libcodec2_soft_sanitize_signed-defaults", + ], + + srcs: ["C2SoftMpeg2Dec.cpp"], + + static_libs: ["libmpeg2dec"], + + include_dirs: [ + "external/libmpeg2/decoder", + "external/libmpeg2/common", + ], +}
diff --git a/media/codec2/components/mpeg2/C2SoftMpeg2Dec.cpp b/media/codec2/components/mpeg2/C2SoftMpeg2Dec.cpp new file mode 100644 index 0000000..df7b403 --- /dev/null +++ b/media/codec2/components/mpeg2/C2SoftMpeg2Dec.cpp
@@ -0,0 +1,1110 @@ +/* + * 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 "C2SoftMpeg2Dec" +#include <log/log.h> + +#include <media/stagefright/foundation/MediaDefs.h> + +#include <C2Debug.h> +#include <C2PlatformSupport.h> +#include <Codec2Mapper.h> +#include <SimpleC2Interface.h> + +#include "C2SoftMpeg2Dec.h" +#include "impeg2d.h" + +namespace android { + +constexpr char COMPONENT_NAME[] = "c2.android.mpeg2.decoder"; + +class C2SoftMpeg2Dec::IntfImpl : public SimpleInterface<void>::BaseParams { +public: + explicit IntfImpl(const std::shared_ptr<C2ReflectorHelper> &helper) + : SimpleInterface<void>::BaseParams( + helper, + COMPONENT_NAME, + C2Component::KIND_DECODER, + C2Component::DOMAIN_VIDEO, + MEDIA_MIMETYPE_VIDEO_MPEG2) { + noPrivateBuffers(); // TODO: account for our buffers here + noInputReferences(); + noOutputReferences(); + noInputLatency(); + noTimeStretch(); + + // TODO: output latency and reordering + + addParameter( + DefineParam(mAttrib, C2_PARAMKEY_COMPONENT_ATTRIBUTES) + .withConstValue(new C2ComponentAttributesSetting(C2Component::ATTRIB_IS_TEMPORAL)) + .build()); + + addParameter( + DefineParam(mSize, C2_PARAMKEY_PICTURE_SIZE) + .withDefault(new C2StreamPictureSizeInfo::output(0u, 320, 240)) + .withFields({ + C2F(mSize, width).inRange(16, 1920, 4), + C2F(mSize, height).inRange(16, 1088, 4), + }) + .withSetter(SizeSetter) + .build()); + + addParameter( + DefineParam(mProfileLevel, C2_PARAMKEY_PROFILE_LEVEL) + .withDefault(new C2StreamProfileLevelInfo::input(0u, + C2Config::PROFILE_MP2V_SIMPLE, C2Config::LEVEL_MP2V_HIGH)) + .withFields({ + C2F(mProfileLevel, profile).oneOf({ + C2Config::PROFILE_MP2V_SIMPLE, + C2Config::PROFILE_MP2V_MAIN}), + C2F(mProfileLevel, level).oneOf({ + C2Config::LEVEL_MP2V_LOW, + C2Config::LEVEL_MP2V_MAIN, + C2Config::LEVEL_MP2V_HIGH_1440, + C2Config::LEVEL_MP2V_HIGH}) + }) + .withSetter(ProfileLevelSetter, mSize) + .build()); + + addParameter( + DefineParam(mMaxSize, C2_PARAMKEY_MAX_PICTURE_SIZE) + .withDefault(new C2StreamMaxPictureSizeTuning::output(0u, 320, 240)) + .withFields({ + C2F(mSize, width).inRange(2, 1920, 2), + C2F(mSize, height).inRange(2, 1088, 2), + }) + .withSetter(MaxPictureSizeSetter, mSize) + .build()); + + addParameter( + DefineParam(mMaxInputSize, C2_PARAMKEY_INPUT_MAX_BUFFER_SIZE) + .withDefault(new C2StreamMaxBufferSizeInfo::input(0u, 320 * 240 * 3 / 2)) + .withFields({ + C2F(mMaxInputSize, value).any(), + }) + .calculatedAs(MaxInputSizeSetter, mMaxSize) + .build()); + + C2ChromaOffsetStruct locations[1] = { C2ChromaOffsetStruct::ITU_YUV_420_0() }; + std::shared_ptr<C2StreamColorInfo::output> defaultColorInfo = + C2StreamColorInfo::output::AllocShared( + 1u, 0u, 8u /* bitDepth */, C2Color::YUV_420); + memcpy(defaultColorInfo->m.locations, locations, sizeof(locations)); + + defaultColorInfo = + C2StreamColorInfo::output::AllocShared( + { C2ChromaOffsetStruct::ITU_YUV_420_0() }, + 0u, 8u /* bitDepth */, C2Color::YUV_420); + helper->addStructDescriptors<C2ChromaOffsetStruct>(); + + addParameter( + DefineParam(mColorInfo, C2_PARAMKEY_CODED_COLOR_INFO) + .withConstValue(defaultColorInfo) + .build()); + + addParameter( + DefineParam(mDefaultColorAspects, C2_PARAMKEY_DEFAULT_COLOR_ASPECTS) + .withDefault(new C2StreamColorAspectsTuning::output( + 0u, C2Color::RANGE_UNSPECIFIED, C2Color::PRIMARIES_UNSPECIFIED, + C2Color::TRANSFER_UNSPECIFIED, C2Color::MATRIX_UNSPECIFIED)) + .withFields({ + C2F(mDefaultColorAspects, range).inRange( + C2Color::RANGE_UNSPECIFIED, C2Color::RANGE_OTHER), + C2F(mDefaultColorAspects, primaries).inRange( + C2Color::PRIMARIES_UNSPECIFIED, C2Color::PRIMARIES_OTHER), + C2F(mDefaultColorAspects, transfer).inRange( + C2Color::TRANSFER_UNSPECIFIED, C2Color::TRANSFER_OTHER), + C2F(mDefaultColorAspects, matrix).inRange( + C2Color::MATRIX_UNSPECIFIED, C2Color::MATRIX_OTHER) + }) + .withSetter(DefaultColorAspectsSetter) + .build()); + + addParameter( + DefineParam(mCodedColorAspects, C2_PARAMKEY_VUI_COLOR_ASPECTS) + .withDefault(new C2StreamColorAspectsInfo::input( + 0u, C2Color::RANGE_LIMITED, C2Color::PRIMARIES_UNSPECIFIED, + C2Color::TRANSFER_UNSPECIFIED, C2Color::MATRIX_UNSPECIFIED)) + .withFields({ + C2F(mCodedColorAspects, range).inRange( + C2Color::RANGE_UNSPECIFIED, C2Color::RANGE_OTHER), + C2F(mCodedColorAspects, primaries).inRange( + C2Color::PRIMARIES_UNSPECIFIED, C2Color::PRIMARIES_OTHER), + C2F(mCodedColorAspects, transfer).inRange( + C2Color::TRANSFER_UNSPECIFIED, C2Color::TRANSFER_OTHER), + C2F(mCodedColorAspects, matrix).inRange( + C2Color::MATRIX_UNSPECIFIED, C2Color::MATRIX_OTHER) + }) + .withSetter(CodedColorAspectsSetter) + .build()); + + addParameter( + DefineParam(mColorAspects, C2_PARAMKEY_COLOR_ASPECTS) + .withDefault(new C2StreamColorAspectsInfo::output( + 0u, C2Color::RANGE_UNSPECIFIED, C2Color::PRIMARIES_UNSPECIFIED, + C2Color::TRANSFER_UNSPECIFIED, C2Color::MATRIX_UNSPECIFIED)) + .withFields({ + C2F(mColorAspects, range).inRange( + C2Color::RANGE_UNSPECIFIED, C2Color::RANGE_OTHER), + C2F(mColorAspects, primaries).inRange( + C2Color::PRIMARIES_UNSPECIFIED, C2Color::PRIMARIES_OTHER), + C2F(mColorAspects, transfer).inRange( + C2Color::TRANSFER_UNSPECIFIED, C2Color::TRANSFER_OTHER), + C2F(mColorAspects, matrix).inRange( + C2Color::MATRIX_UNSPECIFIED, C2Color::MATRIX_OTHER) + }) + .withSetter(ColorAspectsSetter, mDefaultColorAspects, mCodedColorAspects) + .build()); + + // TODO: support more formats? + addParameter( + DefineParam(mPixelFormat, C2_PARAMKEY_PIXEL_FORMAT) + .withConstValue(new C2StreamPixelFormatInfo::output( + 0u, HAL_PIXEL_FORMAT_YCBCR_420_888)) + .build()); + } + + static C2R SizeSetter(bool mayBlock, const C2P<C2StreamPictureSizeInfo::output> &oldMe, + C2P<C2StreamPictureSizeInfo::output> &me) { + (void)mayBlock; + C2R res = C2R::Ok(); + if (!me.F(me.v.width).supportsAtAll(me.v.width)) { + res = res.plus(C2SettingResultBuilder::BadValue(me.F(me.v.width))); + me.set().width = oldMe.v.width; + } + if (!me.F(me.v.height).supportsAtAll(me.v.height)) { + res = res.plus(C2SettingResultBuilder::BadValue(me.F(me.v.height))); + me.set().height = oldMe.v.height; + } + return res; + } + + static C2R MaxPictureSizeSetter(bool mayBlock, C2P<C2StreamMaxPictureSizeTuning::output> &me, + const C2P<C2StreamPictureSizeInfo::output> &size) { + (void)mayBlock; + // TODO: get max width/height from the size's field helpers vs. hardcoding + me.set().width = c2_min(c2_max(me.v.width, size.v.width), 1920u); + me.set().height = c2_min(c2_max(me.v.height, size.v.height), 1088u); + return C2R::Ok(); + } + + static C2R MaxInputSizeSetter(bool mayBlock, C2P<C2StreamMaxBufferSizeInfo::input> &me, + const C2P<C2StreamMaxPictureSizeTuning::output> &maxSize) { + (void)mayBlock; + // assume compression ratio of 1 + me.set().value = (((maxSize.v.width + 15) / 16) * ((maxSize.v.height + 15) / 16) * 384); + return C2R::Ok(); + } + + static C2R ProfileLevelSetter(bool mayBlock, C2P<C2StreamProfileLevelInfo::input> &me, + const C2P<C2StreamPictureSizeInfo::output> &size) { + (void)mayBlock; + (void)size; + (void)me; // TODO: validate + return C2R::Ok(); + } + + static C2R DefaultColorAspectsSetter(bool mayBlock, C2P<C2StreamColorAspectsTuning::output> &me) { + (void)mayBlock; + if (me.v.range > C2Color::RANGE_OTHER) { + me.set().range = C2Color::RANGE_OTHER; + } + if (me.v.primaries > C2Color::PRIMARIES_OTHER) { + me.set().primaries = C2Color::PRIMARIES_OTHER; + } + if (me.v.transfer > C2Color::TRANSFER_OTHER) { + me.set().transfer = C2Color::TRANSFER_OTHER; + } + if (me.v.matrix > C2Color::MATRIX_OTHER) { + me.set().matrix = C2Color::MATRIX_OTHER; + } + return C2R::Ok(); + } + + static C2R CodedColorAspectsSetter(bool mayBlock, C2P<C2StreamColorAspectsInfo::input> &me) { + (void)mayBlock; + if (me.v.range > C2Color::RANGE_OTHER) { + me.set().range = C2Color::RANGE_OTHER; + } + if (me.v.primaries > C2Color::PRIMARIES_OTHER) { + me.set().primaries = C2Color::PRIMARIES_OTHER; + } + if (me.v.transfer > C2Color::TRANSFER_OTHER) { + me.set().transfer = C2Color::TRANSFER_OTHER; + } + if (me.v.matrix > C2Color::MATRIX_OTHER) { + me.set().matrix = C2Color::MATRIX_OTHER; + } + return C2R::Ok(); + } + + static C2R ColorAspectsSetter(bool mayBlock, C2P<C2StreamColorAspectsInfo::output> &me, + const C2P<C2StreamColorAspectsTuning::output> &def, + const C2P<C2StreamColorAspectsInfo::input> &coded) { + (void)mayBlock; + // take default values for all unspecified fields, and coded values for specified ones + me.set().range = coded.v.range == RANGE_UNSPECIFIED ? def.v.range : coded.v.range; + me.set().primaries = coded.v.primaries == PRIMARIES_UNSPECIFIED + ? def.v.primaries : coded.v.primaries; + me.set().transfer = coded.v.transfer == TRANSFER_UNSPECIFIED + ? def.v.transfer : coded.v.transfer; + me.set().matrix = coded.v.matrix == MATRIX_UNSPECIFIED ? def.v.matrix : coded.v.matrix; + return C2R::Ok(); + } + + std::shared_ptr<C2StreamColorAspectsInfo::output> getColorAspects_l() { + return mColorAspects; + } + +private: + std::shared_ptr<C2StreamProfileLevelInfo::input> mProfileLevel; + std::shared_ptr<C2StreamPictureSizeInfo::output> mSize; + std::shared_ptr<C2StreamMaxPictureSizeTuning::output> mMaxSize; + std::shared_ptr<C2StreamMaxBufferSizeInfo::input> mMaxInputSize; + std::shared_ptr<C2StreamColorInfo::output> mColorInfo; + std::shared_ptr<C2StreamColorAspectsInfo::input> mCodedColorAspects; + std::shared_ptr<C2StreamColorAspectsTuning::output> mDefaultColorAspects; + std::shared_ptr<C2StreamColorAspectsInfo::output> mColorAspects; + std::shared_ptr<C2StreamPixelFormatInfo::output> mPixelFormat; +}; + +static size_t getCpuCoreCount() { + long cpuCoreCount = 1; +#if defined(_SC_NPROCESSORS_ONLN) + cpuCoreCount = sysconf(_SC_NPROCESSORS_ONLN); +#else + // _SC_NPROC_ONLN must be defined... + cpuCoreCount = sysconf(_SC_NPROC_ONLN); +#endif + CHECK(cpuCoreCount >= 1); + ALOGV("Number of CPU cores: %ld", cpuCoreCount); + return (size_t)cpuCoreCount; +} + +static void *ivd_aligned_malloc(WORD32 alignment, WORD32 size) { + return memalign(alignment, size); +} + +static void ivd_aligned_free(void *mem) { + free(mem); +} + +C2SoftMpeg2Dec::C2SoftMpeg2Dec( + const char *name, + c2_node_id_t id, + const std::shared_ptr<IntfImpl> &intfImpl) + : SimpleC2Component(std::make_shared<SimpleInterface<IntfImpl>>(name, id, intfImpl)), + mIntf(intfImpl), + mDecHandle(nullptr), + mMemRecords(nullptr), + mOutBufferDrain(nullptr), + mIvColorformat(IV_YUV_420P), + mWidth(320), + mHeight(240), + mOutIndex(0u) { + // If input dump is enabled, then open create an empty file + GENERATE_FILE_NAMES(); + CREATE_DUMP_FILE(mInFile); +} + +C2SoftMpeg2Dec::~C2SoftMpeg2Dec() { + onRelease(); +} + +c2_status_t C2SoftMpeg2Dec::onInit() { + status_t err = initDecoder(); + return err == OK ? C2_OK : C2_CORRUPTED; +} + +c2_status_t C2SoftMpeg2Dec::onStop() { + if (OK != resetDecoder()) return C2_CORRUPTED; + resetPlugin(); + return C2_OK; +} + +void C2SoftMpeg2Dec::onReset() { + (void) onStop(); +} + +void C2SoftMpeg2Dec::onRelease() { + (void) deleteDecoder(); + if (mOutBufferDrain) { + ivd_aligned_free(mOutBufferDrain); + mOutBufferDrain = nullptr; + } + if (mOutBlock) { + mOutBlock.reset(); + } + if (mMemRecords) { + ivd_aligned_free(mMemRecords); + mMemRecords = nullptr; + } +} + +c2_status_t C2SoftMpeg2Dec::onFlush_sm() { + if (OK != setFlushMode()) return C2_CORRUPTED; + + uint32_t displayStride = mStride; + uint32_t displayHeight = mHeight; + uint32_t bufferSize = displayStride * displayHeight * 3 / 2; + mOutBufferDrain = (uint8_t *)ivd_aligned_malloc(128, bufferSize); + if (!mOutBufferDrain) { + ALOGE("could not allocate tmp output buffer (for flush) of size %u ", bufferSize); + return C2_NO_MEMORY; + } + + while (true) { + ivd_video_decode_ip_t s_decode_ip; + ivd_video_decode_op_t s_decode_op; + + setDecodeArgs(&s_decode_ip, &s_decode_op, nullptr, nullptr, 0, 0, 0); + (void) ivdec_api_function(mDecHandle, &s_decode_ip, &s_decode_op); + if (0 == s_decode_op.u4_output_present) { + resetPlugin(); + break; + } + } + + if (mOutBufferDrain) { + ivd_aligned_free(mOutBufferDrain); + mOutBufferDrain = nullptr; + } + + return C2_OK; +} + +status_t C2SoftMpeg2Dec::getNumMemRecords() { + iv_num_mem_rec_ip_t s_num_mem_rec_ip; + iv_num_mem_rec_op_t s_num_mem_rec_op; + + s_num_mem_rec_ip.u4_size = sizeof(s_num_mem_rec_ip); + s_num_mem_rec_ip.e_cmd = IV_CMD_GET_NUM_MEM_REC; + s_num_mem_rec_op.u4_size = sizeof(s_num_mem_rec_op); + + IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle, + &s_num_mem_rec_ip, + &s_num_mem_rec_op); + if (IV_SUCCESS != status) { + ALOGE("Error in getting mem records: 0x%x", s_num_mem_rec_op.u4_error_code); + return UNKNOWN_ERROR; + } + mNumMemRecords = s_num_mem_rec_op.u4_num_mem_rec; + + return OK; +} + +status_t C2SoftMpeg2Dec::fillMemRecords() { + iv_mem_rec_t *ps_mem_rec = (iv_mem_rec_t *) ivd_aligned_malloc( + 128, mNumMemRecords * sizeof(iv_mem_rec_t)); + if (!ps_mem_rec) { + ALOGE("Allocation failure"); + return NO_MEMORY; + } + memset(ps_mem_rec, 0, mNumMemRecords * sizeof(iv_mem_rec_t)); + for (size_t i = 0; i < mNumMemRecords; i++) + ps_mem_rec[i].u4_size = sizeof(iv_mem_rec_t); + mMemRecords = ps_mem_rec; + + ivdext_fill_mem_rec_ip_t s_fill_mem_ip; + ivdext_fill_mem_rec_op_t s_fill_mem_op; + + s_fill_mem_ip.s_ivd_fill_mem_rec_ip_t.u4_size = sizeof(ivdext_fill_mem_rec_ip_t); + s_fill_mem_ip.u4_share_disp_buf = 0; + s_fill_mem_ip.e_output_format = mIvColorformat; + s_fill_mem_ip.u4_deinterlace = 1; + s_fill_mem_ip.s_ivd_fill_mem_rec_ip_t.e_cmd = IV_CMD_FILL_NUM_MEM_REC; + s_fill_mem_ip.s_ivd_fill_mem_rec_ip_t.pv_mem_rec_location = mMemRecords; + s_fill_mem_ip.s_ivd_fill_mem_rec_ip_t.u4_max_frm_wd = mWidth; + s_fill_mem_ip.s_ivd_fill_mem_rec_ip_t.u4_max_frm_ht = mHeight; + s_fill_mem_op.s_ivd_fill_mem_rec_op_t.u4_size = sizeof(ivdext_fill_mem_rec_op_t); + IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle, + &s_fill_mem_ip, + &s_fill_mem_op); + if (IV_SUCCESS != status) { + ALOGE("Error in filling mem records: 0x%x", + s_fill_mem_op.s_ivd_fill_mem_rec_op_t.u4_error_code); + return UNKNOWN_ERROR; + } + + CHECK_EQ(mNumMemRecords, s_fill_mem_op.s_ivd_fill_mem_rec_op_t.u4_num_mem_rec_filled); + for (size_t i = 0; i < mNumMemRecords; i++, ps_mem_rec++) { + ps_mem_rec->pv_base = ivd_aligned_malloc( + ps_mem_rec->u4_mem_alignment, ps_mem_rec->u4_mem_size); + if (!ps_mem_rec->pv_base) { + ALOGE("Allocation failure for memory record #%zu of size %u", + i, ps_mem_rec->u4_mem_size); + return NO_MEMORY; + } + } + + return OK; +} + +status_t C2SoftMpeg2Dec::createDecoder() { + ivdext_init_ip_t s_init_ip; + ivdext_init_op_t s_init_op; + + s_init_ip.s_ivd_init_ip_t.u4_size = sizeof(ivdext_init_ip_t); + s_init_ip.s_ivd_init_ip_t.e_cmd = (IVD_API_COMMAND_TYPE_T)IV_CMD_INIT; + s_init_ip.s_ivd_init_ip_t.pv_mem_rec_location = mMemRecords; + s_init_ip.s_ivd_init_ip_t.u4_frm_max_wd = mWidth; + s_init_ip.s_ivd_init_ip_t.u4_frm_max_ht = mHeight; + s_init_ip.u4_share_disp_buf = 0; + s_init_ip.u4_deinterlace = 1; + s_init_ip.s_ivd_init_ip_t.u4_num_mem_rec = mNumMemRecords; + s_init_ip.s_ivd_init_ip_t.e_output_format = mIvColorformat; + s_init_op.s_ivd_init_op_t.u4_size = sizeof(ivdext_init_op_t); + + mDecHandle = (iv_obj_t *)mMemRecords[0].pv_base; + mDecHandle->pv_fxns = (void *)ivdec_api_function; + mDecHandle->u4_size = sizeof(iv_obj_t); + + IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle, + &s_init_ip, + &s_init_op); + if (status != IV_SUCCESS) { + ALOGE("error in %s: 0x%x", __func__, + s_init_op.s_ivd_init_op_t.u4_error_code); + return UNKNOWN_ERROR; + } + + return OK; +} + +status_t C2SoftMpeg2Dec::setNumCores() { + ivdext_ctl_set_num_cores_ip_t s_set_num_cores_ip; + ivdext_ctl_set_num_cores_op_t s_set_num_cores_op; + + s_set_num_cores_ip.u4_size = sizeof(ivdext_ctl_set_num_cores_ip_t); + s_set_num_cores_ip.e_cmd = IVD_CMD_VIDEO_CTL; + s_set_num_cores_ip.e_sub_cmd = IVDEXT_CMD_CTL_SET_NUM_CORES; + s_set_num_cores_ip.u4_num_cores = mNumCores; + s_set_num_cores_op.u4_size = sizeof(ivdext_ctl_set_num_cores_op_t); + IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle, + &s_set_num_cores_ip, + &s_set_num_cores_op); + if (status != IV_SUCCESS) { + ALOGD("error in %s: 0x%x", __func__, s_set_num_cores_op.u4_error_code); + return UNKNOWN_ERROR; + } + + return OK; +} + +status_t C2SoftMpeg2Dec::setParams(size_t stride) { + ivd_ctl_set_config_ip_t s_set_dyn_params_ip; + ivd_ctl_set_config_op_t s_set_dyn_params_op; + + s_set_dyn_params_ip.u4_size = sizeof(ivd_ctl_set_config_ip_t); + s_set_dyn_params_ip.e_cmd = IVD_CMD_VIDEO_CTL; + s_set_dyn_params_ip.e_sub_cmd = IVD_CMD_CTL_SETPARAMS; + 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_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, + &s_set_dyn_params_op); + if (status != IV_SUCCESS) { + ALOGE("error in %s: 0x%x", __func__, s_set_dyn_params_op.u4_error_code); + return UNKNOWN_ERROR; + } + + return OK; +} + +status_t C2SoftMpeg2Dec::getVersion() { + ivd_ctl_getversioninfo_ip_t s_get_versioninfo_ip; + ivd_ctl_getversioninfo_op_t s_get_versioninfo_op; + UWORD8 au1_buf[512]; + + s_get_versioninfo_ip.u4_size = sizeof(ivd_ctl_getversioninfo_ip_t); + s_get_versioninfo_ip.e_cmd = IVD_CMD_VIDEO_CTL; + s_get_versioninfo_ip.e_sub_cmd = IVD_CMD_CTL_GETVERSION; + s_get_versioninfo_ip.pv_version_buffer = au1_buf; + s_get_versioninfo_ip.u4_version_buffer_size = sizeof(au1_buf); + s_get_versioninfo_op.u4_size = sizeof(ivd_ctl_getversioninfo_op_t); + IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle, + &s_get_versioninfo_ip, + &s_get_versioninfo_op); + if (status != IV_SUCCESS) { + ALOGD("error in %s: 0x%x", __func__, + s_get_versioninfo_op.u4_error_code); + } else { + ALOGV("ittiam decoder version number: %s", + (char *) s_get_versioninfo_ip.pv_version_buffer); + } + + return OK; +} + +status_t C2SoftMpeg2Dec::initDecoder() { + status_t ret = getNumMemRecords(); + if (OK != ret) return ret; + + ret = fillMemRecords(); + if (OK != ret) return ret; + + if (OK != createDecoder()) return UNKNOWN_ERROR; + + mNumCores = MIN(getCpuCoreCount(), MAX_NUM_CORES); + mStride = ALIGN64(mWidth); + mSignalledError = false; + resetPlugin(); + (void) setNumCores(); + if (OK != setParams(mStride)) return UNKNOWN_ERROR; + (void) getVersion(); + + return OK; +} + +bool C2SoftMpeg2Dec::setDecodeArgs(ivd_video_decode_ip_t *ps_decode_ip, + ivd_video_decode_op_t *ps_decode_op, + C2ReadView *inBuffer, + C2GraphicView *outBuffer, + size_t inOffset, + size_t inSize, + uint32_t tsMarker) { + uint32_t displayStride = mStride; + uint32_t displayHeight = mHeight; + size_t lumaSize = displayStride * displayHeight; + size_t chromaSize = lumaSize >> 2; + + ps_decode_ip->u4_size = sizeof(ivd_video_decode_ip_t); + ps_decode_ip->e_cmd = IVD_CMD_VIDEO_DECODE; + if (inBuffer) { + ps_decode_ip->u4_ts = tsMarker; + ps_decode_ip->pv_stream_buffer = const_cast<uint8_t *>(inBuffer->data() + inOffset); + ps_decode_ip->u4_num_Bytes = inSize; + } else { + ps_decode_ip->u4_ts = 0; + ps_decode_ip->pv_stream_buffer = nullptr; + ps_decode_ip->u4_num_Bytes = 0; + } + ps_decode_ip->s_out_buffer.u4_min_out_buf_size[0] = lumaSize; + ps_decode_ip->s_out_buffer.u4_min_out_buf_size[1] = chromaSize; + ps_decode_ip->s_out_buffer.u4_min_out_buf_size[2] = chromaSize; + if (outBuffer) { + if (outBuffer->width() < displayStride || outBuffer->height() < displayHeight) { + ALOGE("Output buffer too small: provided (%dx%d) required (%ux%u)", + outBuffer->width(), outBuffer->height(), displayStride, displayHeight); + return false; + } + ps_decode_ip->s_out_buffer.pu1_bufs[0] = outBuffer->data()[C2PlanarLayout::PLANE_Y]; + ps_decode_ip->s_out_buffer.pu1_bufs[1] = outBuffer->data()[C2PlanarLayout::PLANE_U]; + ps_decode_ip->s_out_buffer.pu1_bufs[2] = outBuffer->data()[C2PlanarLayout::PLANE_V]; + } else { + ps_decode_ip->s_out_buffer.pu1_bufs[0] = mOutBufferDrain; + ps_decode_ip->s_out_buffer.pu1_bufs[1] = mOutBufferDrain + lumaSize; + ps_decode_ip->s_out_buffer.pu1_bufs[2] = mOutBufferDrain + lumaSize + chromaSize; + } + ps_decode_ip->s_out_buffer.u4_num_bufs = 3; + ps_decode_op->u4_size = sizeof(ivd_video_decode_op_t); + + return true; +} + + +bool C2SoftMpeg2Dec::getSeqInfo() { + ivdext_ctl_get_seq_info_ip_t s_ctl_get_seq_info_ip; + ivdext_ctl_get_seq_info_op_t s_ctl_get_seq_info_op; + + s_ctl_get_seq_info_ip.u4_size = sizeof(ivdext_ctl_get_seq_info_ip_t); + s_ctl_get_seq_info_ip.e_cmd = IVD_CMD_VIDEO_CTL; + s_ctl_get_seq_info_ip.e_sub_cmd = + (IVD_CONTROL_API_COMMAND_TYPE_T)IMPEG2D_CMD_CTL_GET_SEQ_INFO; + s_ctl_get_seq_info_op.u4_size = sizeof(ivdext_ctl_get_seq_info_op_t); + IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle, + &s_ctl_get_seq_info_ip, + &s_ctl_get_seq_info_op); + if (status != IV_SUCCESS) { + ALOGW("Error in getting Sequence info: 0x%x", s_ctl_get_seq_info_op.u4_error_code); + return false; + } + + VuiColorAspects vuiColorAspects; + vuiColorAspects.primaries = s_ctl_get_seq_info_op.u1_colour_primaries; + vuiColorAspects.transfer = s_ctl_get_seq_info_op.u1_transfer_characteristics; + vuiColorAspects.coeffs = s_ctl_get_seq_info_op.u1_matrix_coefficients; + vuiColorAspects.fullRange = false; // mpeg2 video has limited range. + + // convert vui aspects to C2 values if changed + if (!(vuiColorAspects == mBitstreamColorAspects)) { + mBitstreamColorAspects = vuiColorAspects; + ColorAspects sfAspects; + C2StreamColorAspectsInfo::input codedAspects = { 0u }; + ColorUtils::convertIsoColorAspectsToCodecAspects( + vuiColorAspects.primaries, vuiColorAspects.transfer, vuiColorAspects.coeffs, + vuiColorAspects.fullRange, sfAspects); + if (!C2Mapper::map(sfAspects.mPrimaries, &codedAspects.primaries)) { + codedAspects.primaries = C2Color::PRIMARIES_UNSPECIFIED; + } + if (!C2Mapper::map(sfAspects.mRange, &codedAspects.range)) { + codedAspects.range = C2Color::RANGE_UNSPECIFIED; + } + if (!C2Mapper::map(sfAspects.mMatrixCoeffs, &codedAspects.matrix)) { + codedAspects.matrix = C2Color::MATRIX_UNSPECIFIED; + } + if (!C2Mapper::map(sfAspects.mTransfer, &codedAspects.transfer)) { + codedAspects.transfer = C2Color::TRANSFER_UNSPECIFIED; + } + std::vector<std::unique_ptr<C2SettingResult>> failures; + (void)mIntf->config({&codedAspects}, C2_MAY_BLOCK, &failures); + } + return true; +} + +status_t C2SoftMpeg2Dec::setFlushMode() { + ivd_ctl_flush_ip_t s_set_flush_ip; + ivd_ctl_flush_op_t s_set_flush_op; + + s_set_flush_ip.u4_size = sizeof(ivd_ctl_flush_ip_t); + s_set_flush_ip.e_cmd = IVD_CMD_VIDEO_CTL; + s_set_flush_ip.e_sub_cmd = IVD_CMD_CTL_FLUSH; + s_set_flush_op.u4_size = sizeof(ivd_ctl_flush_op_t); + IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle, + &s_set_flush_ip, + &s_set_flush_op); + if (status != IV_SUCCESS) { + ALOGE("error in %s: 0x%x", __func__, s_set_flush_op.u4_error_code); + return UNKNOWN_ERROR; + } + + return OK; +} + +status_t C2SoftMpeg2Dec::resetDecoder() { + ivd_ctl_reset_ip_t s_reset_ip; + ivd_ctl_reset_op_t s_reset_op; + + s_reset_ip.u4_size = sizeof(ivd_ctl_reset_ip_t); + s_reset_ip.e_cmd = IVD_CMD_VIDEO_CTL; + s_reset_ip.e_sub_cmd = IVD_CMD_CTL_RESET; + s_reset_op.u4_size = sizeof(ivd_ctl_reset_op_t); + IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle, + &s_reset_ip, + &s_reset_op); + if (IV_SUCCESS != status) { + ALOGE("error in %s: 0x%x", __func__, s_reset_op.u4_error_code); + return UNKNOWN_ERROR; + } + (void) setNumCores(); + mStride = 0; + mSignalledError = false; + + return OK; +} + +void C2SoftMpeg2Dec::resetPlugin() { + mSignalledOutputEos = false; + gettimeofday(&mTimeStart, nullptr); + gettimeofday(&mTimeEnd, nullptr); +} + +status_t C2SoftMpeg2Dec::deleteDecoder() { + if (mMemRecords) { + iv_mem_rec_t *ps_mem_rec = mMemRecords; + + for (size_t i = 0; i < mNumMemRecords; i++, ps_mem_rec++) { + if (ps_mem_rec->pv_base) { + ivd_aligned_free(ps_mem_rec->pv_base); + } + } + ivd_aligned_free(mMemRecords); + mMemRecords = nullptr; + } + mDecHandle = nullptr; + + return OK; +} + +status_t C2SoftMpeg2Dec::reInitDecoder() { + deleteDecoder(); + + status_t ret = initDecoder(); + if (OK != ret) { + ALOGE("Failed to initialize decoder"); + deleteDecoder(); + return ret; + } + return OK; +} + +void fillEmptyWork(const std::unique_ptr<C2Work> &work) { + uint32_t flags = 0; + if (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) { + flags |= C2FrameData::FLAG_END_OF_STREAM; + ALOGV("signalling eos"); + } + work->worklets.front()->output.flags = (C2FrameData::flags_t)flags; + work->worklets.front()->output.buffers.clear(); + work->worklets.front()->output.ordinal = work->input.ordinal; + work->workletsProcessed = 1u; +} + +void C2SoftMpeg2Dec::finishWork(uint64_t index, const std::unique_ptr<C2Work> &work) { + std::shared_ptr<C2Buffer> buffer = createGraphicBuffer(std::move(mOutBlock), + C2Rect(mWidth, mHeight)); + mOutBlock = nullptr; + { + IntfImpl::Lock lock = mIntf->lock(); + buffer->setInfo(mIntf->getColorAspects_l()); + } + + class FillWork { + public: + FillWork(uint32_t flags, C2WorkOrdinalStruct ordinal, + const std::shared_ptr<C2Buffer>& buffer) + : mFlags(flags), mOrdinal(ordinal), mBuffer(buffer) {} + ~FillWork() = default; + + void operator()(const std::unique_ptr<C2Work>& work) { + work->worklets.front()->output.flags = (C2FrameData::flags_t)mFlags; + work->worklets.front()->output.buffers.clear(); + work->worklets.front()->output.ordinal = mOrdinal; + work->workletsProcessed = 1u; + work->result = C2_OK; + if (mBuffer) { + work->worklets.front()->output.buffers.push_back(mBuffer); + } + ALOGV("timestamp = %lld, index = %lld, w/%s buffer", + mOrdinal.timestamp.peekll(), mOrdinal.frameIndex.peekll(), + mBuffer ? "" : "o"); + } + + private: + const uint32_t mFlags; + const C2WorkOrdinalStruct mOrdinal; + const std::shared_ptr<C2Buffer> mBuffer; + }; + + 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; + work->workletsProcessed = 1u; + }; + if (work && c2_cntr64_t(index) == work->input.ordinal.frameIndex) { + bool eos = ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0); + // TODO: Check if cloneAndSend can be avoided by tracking number of frames remaining + if (eos) { + if (buffer) { + mOutIndex = index; + C2WorkOrdinalStruct outOrdinal = work->input.ordinal; + cloneAndSend( + mOutIndex, work, + FillWork(C2FrameData::FLAG_INCOMPLETE, outOrdinal, buffer)); + buffer.reset(); + } + } else { + fillWork(work); + } + } else { + finish(index, fillWork); + } +} + +c2_status_t C2SoftMpeg2Dec::ensureDecoderState(const std::shared_ptr<C2BlockPool> &pool) { + if (!mDecHandle) { + ALOGE("not supposed to be here, invalid decoder context"); + return C2_CORRUPTED; + } + if (mStride != ALIGN64(mWidth)) { + mStride = ALIGN64(mWidth); + if (OK != setParams(mStride)) return C2_CORRUPTED; + } + if (mOutBlock && + (mOutBlock->width() != mStride || mOutBlock->height() != mHeight)) { + mOutBlock.reset(); + } + if (!mOutBlock) { + uint32_t format = HAL_PIXEL_FORMAT_YV12; + C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE }; + c2_status_t err = pool->fetchGraphicBlock(mStride, mHeight, format, usage, &mOutBlock); + if (err != C2_OK) { + ALOGE("fetchGraphicBlock for Output failed with status %d", err); + return err; + } + ALOGV("provided (%dx%d) required (%dx%d)", + mOutBlock->width(), mOutBlock->height(), mStride, mHeight); + } + + return C2_OK; +} + +// 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 +// TODO: pass coloraspects information to surface +// TODO: test support for dynamic change in resolution +// TODO: verify if the decoder sent back all frames +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; + } + + size_t inOffset = 0u; + size_t inSize = 0u; + uint32_t workIndex = work->input.ordinal.frameIndex.peeku() & 0xFFFFFFFF; + C2ReadView rView = mDummyReadView; + if (!work->input.buffers.empty()) { + rView = work->input.buffers[0]->data().linearBlocks().front().map().get(); + inSize = rView.capacity(); + if (inSize && rView.error()) { + ALOGE("read view map failed %d", rView.error()); + work->result = C2_CORRUPTED; + return; + } + } + bool eos = ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0); + bool hasPicture = false; + + ALOGV("in buffer attr. size %zu timestamp %d frameindex %d, flags %x", + inSize, (int)work->input.ordinal.timestamp.peeku(), + (int)work->input.ordinal.frameIndex.peeku(), work->input.flags); + size_t inPos = 0; + while (inPos < inSize) { + if (C2_OK != ensureDecoderState(pool)) { + mSignalledError = true; + work->workletsProcessed = 1u; + work->result = C2_CORRUPTED; + return; + } + C2GraphicView wView = mOutBlock->map().get(); + if (wView.error()) { + ALOGE("graphic view map failed %d", wView.error()); + work->result = C2_CORRUPTED; + return; + } + + ivd_video_decode_ip_t s_decode_ip; + ivd_video_decode_op_t s_decode_op; + 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, nullptr); + TIME_DIFF(mTimeEnd, mTimeStart, delay); + (void) ivdec_api_function(mDecHandle, &s_decode_ip, &s_decode_op); + WORD32 decodeTime; + GETTIME(&mTimeEnd, nullptr); + TIME_DIFF(mTimeStart, mTimeEnd, decodeTime); + ALOGV("decodeTime=%6d delay=%6d numBytes=%6d ", decodeTime, delay, + s_decode_op.u4_num_bytes_consumed); + if (IMPEG2D_UNSUPPORTED_DIMENSIONS == s_decode_op.u4_error_code) { + 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; + + ALOGI("Configuring decoder: mWidth %d , mHeight %d ", + mWidth, mHeight); + C2StreamPictureSizeInfo::output size(0u, mWidth, mHeight); + std::vector<std::unique_ptr<C2SettingResult>> failures; + 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; + } + + if (OK != reInitDecoder()) { + ALOGE("Failed to reinitialize decoder"); + mSignalledError = true; + work->workletsProcessed = 1u; + work->result = C2_CORRUPTED; + return; + } + continue; + } else if (IVD_RES_CHANGED == (s_decode_op.u4_error_code & 0xFF)) { + ALOGV("resolution changed"); + 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) { + 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; + CHECK_EQ(0u, s_decode_op.u4_output_present); + + ALOGI("Configuring decoder out: mWidth %d , mHeight %d ", + mWidth, mHeight); + C2StreamPictureSizeInfo::output size(0u, mWidth, mHeight); + std::vector<std::unique_ptr<C2SettingResult>> failures; + 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; + } + } + } + + (void) getSeqInfo(); + hasPicture |= (1 == s_decode_op.u4_frame_decoded_flag); + if (s_decode_op.u4_output_present) { + 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", + (int)inSize - (int)inPos); + break; + } + } + + if (eos) { + drainInternal(DRAIN_COMPONENT_WITH_EOS, pool, work); + mSignalledOutputEos = true; + } else if (!hasPicture) { + fillEmptyWork(work); + } +} + +c2_status_t C2SoftMpeg2Dec::drainInternal( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool, + const std::unique_ptr<C2Work> &work) { + if (drainMode == NO_DRAIN) { + ALOGW("drain with NO_DRAIN: no-op"); + return C2_OK; + } + if (drainMode == DRAIN_CHAIN) { + ALOGW("DRAIN_CHAIN not supported"); + return C2_OMITTED; + } + + if (OK != setFlushMode()) return C2_CORRUPTED; + while (true) { + if (C2_OK != ensureDecoderState(pool)) { + mSignalledError = true; + work->workletsProcessed = 1u; + work->result = C2_CORRUPTED; + return C2_CORRUPTED; + } + C2GraphicView wView = mOutBlock->map().get(); + if (wView.error()) { + ALOGE("graphic view map failed %d", wView.error()); + return C2_CORRUPTED; + } + ivd_video_decode_ip_t s_decode_ip; + 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; + } + } + + return C2_OK; +} + +c2_status_t C2SoftMpeg2Dec::drain( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool) { + return drainInternal(drainMode, pool, nullptr); +} + +class C2SoftMpeg2DecFactory : public C2ComponentFactory { +public: + C2SoftMpeg2DecFactory() : mHelper(std::static_pointer_cast<C2ReflectorHelper>( + GetCodec2PlatformComponentStore()->getParamReflector())) { + } + + virtual c2_status_t createComponent( + c2_node_id_t id, + std::shared_ptr<C2Component>* const component, + std::function<void(C2Component*)> deleter) override { + *component = std::shared_ptr<C2Component>( + new C2SoftMpeg2Dec(COMPONENT_NAME, + id, + std::make_shared<C2SoftMpeg2Dec::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual c2_status_t createInterface( + c2_node_id_t id, + std::shared_ptr<C2ComponentInterface>* const interface, + std::function<void(C2ComponentInterface*)> deleter) override { + *interface = std::shared_ptr<C2ComponentInterface>( + new SimpleInterface<C2SoftMpeg2Dec::IntfImpl>( + COMPONENT_NAME, id, std::make_shared<C2SoftMpeg2Dec::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual ~C2SoftMpeg2DecFactory() override = default; + +private: + std::shared_ptr<C2ReflectorHelper> mHelper; +}; + +} // namespace android + +extern "C" ::C2ComponentFactory* CreateCodec2Factory() { + ALOGV("in %s", __func__); + return new ::android::C2SoftMpeg2DecFactory(); +} + +extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) { + ALOGV("in %s", __func__); + delete factory; +}
diff --git a/media/codec2/components/mpeg2/C2SoftMpeg2Dec.h b/media/codec2/components/mpeg2/C2SoftMpeg2Dec.h new file mode 100644 index 0000000..65d3b87 --- /dev/null +++ b/media/codec2/components/mpeg2/C2SoftMpeg2Dec.h
@@ -0,0 +1,197 @@ +/* + * 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_C2_SOFT_MPEG2_DEC_H_ +#define ANDROID_C2_SOFT_MPEG2_DEC_H_ + +#include <atomic> +#include <SimpleC2Component.h> + +#include <media/stagefright/foundation/ColorUtils.h> + +#include "iv_datatypedef.h" +#include "iv.h" +#include "ivd.h" + +namespace android { + +#define ivdec_api_function impeg2d_api_function +#define ivdext_init_ip_t impeg2d_init_ip_t +#define ivdext_init_op_t impeg2d_init_op_t +#define ivdext_fill_mem_rec_ip_t impeg2d_fill_mem_rec_ip_t +#define ivdext_fill_mem_rec_op_t impeg2d_fill_mem_rec_op_t +#define ivdext_ctl_set_num_cores_ip_t impeg2d_ctl_set_num_cores_ip_t +#define ivdext_ctl_set_num_cores_op_t impeg2d_ctl_set_num_cores_op_t +#define ivdext_ctl_get_seq_info_ip_t impeg2d_ctl_get_seq_info_ip_t +#define ivdext_ctl_get_seq_info_op_t impeg2d_ctl_get_seq_info_op_t +#define ALIGN64(x) ((((x) + 63) >> 6) << 6) +#define MAX_NUM_CORES 4 +#define IVDEXT_CMD_CTL_SET_NUM_CORES \ + (IVD_CONTROL_API_COMMAND_TYPE_T)IMPEG2D_CMD_CTL_SET_NUM_CORES +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#define GETTIME(a, b) gettimeofday(a, b); +#define TIME_DIFF(start, end, diff) \ + diff = (((end).tv_sec - (start).tv_sec) * 1000000) + \ + ((end).tv_usec - (start).tv_usec); + +#ifdef FILE_DUMP_ENABLE + #define INPUT_DUMP_PATH "/sdcard/clips/mpeg2d_input" + #define INPUT_DUMP_EXT "m2v" + #define GENERATE_FILE_NAMES() { \ + GETTIME(&mTimeStart, NULL); \ + strcpy(mInFile, ""); \ + sprintf(mInFile, "%s_%ld.%ld.%s", INPUT_DUMP_PATH, \ + mTimeStart.tv_sec, mTimeStart.tv_usec, \ + INPUT_DUMP_EXT); \ + } + #define CREATE_DUMP_FILE(m_filename) { \ + FILE *fp = fopen(m_filename, "wb"); \ + if (fp != NULL) { \ + fclose(fp); \ + } else { \ + ALOGD("Could not open file %s", m_filename); \ + } \ + } + #define DUMP_TO_FILE(m_filename, m_buf, m_size) \ + { \ + FILE *fp = fopen(m_filename, "ab"); \ + if (fp != NULL && m_buf != NULL) { \ + uint32_t i; \ + i = fwrite(m_buf, 1, m_size, fp); \ + ALOGD("fwrite ret %d to write %d", i, m_size); \ + if (i != (uint32_t)m_size) { \ + ALOGD("Error in fwrite, returned %d", i); \ + perror("Error in write to file"); \ + } \ + fclose(fp); \ + } else { \ + ALOGD("Could not write to file %s", m_filename);\ + } \ + } +#else /* FILE_DUMP_ENABLE */ + #define INPUT_DUMP_PATH + #define INPUT_DUMP_EXT + #define OUTPUT_DUMP_PATH + #define OUTPUT_DUMP_EXT + #define GENERATE_FILE_NAMES() + #define CREATE_DUMP_FILE(m_filename) + #define DUMP_TO_FILE(m_filename, m_buf, m_size) +#endif /* FILE_DUMP_ENABLE */ + +struct C2SoftMpeg2Dec : public SimpleC2Component { + class IntfImpl; + + C2SoftMpeg2Dec(const char* name, c2_node_id_t id, + const std::shared_ptr<IntfImpl>& intfImpl); + virtual ~C2SoftMpeg2Dec(); + + // From SimpleC2Component + c2_status_t onInit() override; + c2_status_t onStop() override; + void onReset() override; + void onRelease() override; + c2_status_t onFlush_sm() override; + void process( + const std::unique_ptr<C2Work> &work, + const std::shared_ptr<C2BlockPool> &pool) override; + c2_status_t drain( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool) override; + private: + status_t getNumMemRecords(); + status_t fillMemRecords(); + status_t createDecoder(); + status_t setNumCores(); + status_t setParams(size_t stride); + status_t getVersion(); + status_t initDecoder(); + bool setDecodeArgs(ivd_video_decode_ip_t *ps_decode_ip, + ivd_video_decode_op_t *ps_decode_op, + C2ReadView *inBuffer, + C2GraphicView *outBuffer, + size_t inOffset, + size_t inSize, + uint32_t tsMarker); + bool getSeqInfo(); + c2_status_t ensureDecoderState(const std::shared_ptr<C2BlockPool> &pool); + void finishWork(uint64_t index, const std::unique_ptr<C2Work> &work); + status_t setFlushMode(); + c2_status_t drainInternal( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool, + const std::unique_ptr<C2Work> &work); + status_t resetDecoder(); + void resetPlugin(); + status_t deleteDecoder(); + status_t reInitDecoder(); + + // 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 + enum { + kNotSupported, + kPreferBitstream, + kPreferContainer, + }; + + std::shared_ptr<IntfImpl> mIntf; + iv_obj_t *mDecHandle; + iv_mem_rec_t *mMemRecords; + size_t mNumMemRecords; + std::shared_ptr<C2GraphicBlock> mOutBlock; + uint8_t *mOutBufferDrain; + + size_t mNumCores; + IV_COLOR_FORMAT_T mIvColorformat; + + uint32_t mWidth; + uint32_t mHeight; + uint32_t mStride; + bool mSignalledOutputEos; + bool mSignalledError; + std::atomic_uint64_t mOutIndex; + + // 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 { + uint8_t primaries; + uint8_t transfer; + uint8_t coeffs; + uint8_t fullRange; + + // default color aspects + VuiColorAspects() + : primaries(2), transfer(2), coeffs(2), fullRange(0) { } + + bool operator==(const VuiColorAspects &o) { + return primaries == o.primaries && transfer == o.transfer && coeffs == o.coeffs + && fullRange == o.fullRange; + } + } mBitstreamColorAspects; + + // profile + struct timeval mTimeStart; + struct timeval mTimeEnd; +#ifdef FILE_DUMP_ENABLE + char mInFile[200]; +#endif /* FILE_DUMP_ENABLE */ + + C2_DO_NOT_COPY(C2SoftMpeg2Dec); +}; + +} // namespace android + +#endif // ANDROID_C2_SOFT_MPEG2_DEC_H_
diff --git a/media/codec2/components/mpeg4_h263/Android.bp b/media/codec2/components/mpeg4_h263/Android.bp new file mode 100644 index 0000000..41e4f44 --- /dev/null +++ b/media/codec2/components/mpeg4_h263/Android.bp
@@ -0,0 +1,66 @@ +cc_library_shared { + name: "libcodec2_soft_mpeg4dec", + defaults: [ + "libcodec2_soft-defaults", + "libcodec2_soft_sanitize_signed-defaults", + ], + + srcs: ["C2SoftMpeg4Dec.cpp"], + + static_libs: ["libstagefright_m4vh263dec"], + + cflags: [ + "-DOSCL_IMPORT_REF=", + "-DMPEG4", + ], +} + +cc_library_shared { + name: "libcodec2_soft_h263dec", + defaults: [ + "libcodec2_soft-defaults", + "libcodec2_soft_sanitize_signed-defaults", + ], + + srcs: ["C2SoftMpeg4Dec.cpp"], + + static_libs: ["libstagefright_m4vh263dec"], + + cflags: [ + "-DOSCL_IMPORT_REF=", + ], +} + +cc_library_shared { + name: "libcodec2_soft_mpeg4enc", + defaults: [ + "libcodec2_soft-defaults", + "libcodec2_soft_sanitize_signed-defaults", + ], + + + srcs: ["C2SoftMpeg4Enc.cpp"], + + static_libs: ["libstagefright_m4vh263enc"], + + cflags: [ + "-DMPEG4", + "-DOSCL_IMPORT_REF=", + ], +} + +cc_library_shared { + name: "libcodec2_soft_h263enc", + defaults: [ + "libcodec2_soft-defaults", + "libcodec2_soft_sanitize_signed-defaults", + ], + + srcs: ["C2SoftMpeg4Enc.cpp"], + + static_libs: [ "libstagefright_m4vh263enc" ], + + cflags: [ + "-DOSCL_IMPORT_REF=", + ], +}
diff --git a/media/codec2/components/mpeg4_h263/C2SoftMpeg4Dec.cpp b/media/codec2/components/mpeg4_h263/C2SoftMpeg4Dec.cpp new file mode 100644 index 0000000..7e6685e --- /dev/null +++ b/media/codec2/components/mpeg4_h263/C2SoftMpeg4Dec.cpp
@@ -0,0 +1,758 @@ +/* + * 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 +#ifdef MPEG4 + #define LOG_TAG "C2SoftMpeg4Dec" +#else + #define LOG_TAG "C2SoftH263Dec" +#endif +#include <log/log.h> + +#include <media/stagefright/foundation/AUtils.h> +#include <media/stagefright/foundation/MediaDefs.h> + +#include <C2Debug.h> +#include <C2PlatformSupport.h> +#include <SimpleC2Interface.h> + +#include "C2SoftMpeg4Dec.h" +#include "mp4dec_api.h" + +namespace android { + +#ifdef MPEG4 +constexpr char COMPONENT_NAME[] = "c2.android.mpeg4.decoder"; +#else +constexpr char COMPONENT_NAME[] = "c2.android.h263.decoder"; +#endif + +class C2SoftMpeg4Dec::IntfImpl : public SimpleInterface<void>::BaseParams { +public: + explicit IntfImpl(const std::shared_ptr<C2ReflectorHelper> &helper) + : SimpleInterface<void>::BaseParams( + helper, + COMPONENT_NAME, + C2Component::KIND_DECODER, + C2Component::DOMAIN_VIDEO, +#ifdef MPEG4 + MEDIA_MIMETYPE_VIDEO_MPEG4 +#else + MEDIA_MIMETYPE_VIDEO_H263 +#endif + ) { + noPrivateBuffers(); // TODO: account for our buffers here + noInputReferences(); + noOutputReferences(); + noInputLatency(); + noTimeStretch(); + + // TODO: Proper support for reorder depth. + addParameter( + DefineParam(mActualOutputDelay, C2_PARAMKEY_OUTPUT_DELAY) + .withConstValue(new C2PortActualDelayTuning::output(1u)) + .build()); + + addParameter( + DefineParam(mAttrib, C2_PARAMKEY_COMPONENT_ATTRIBUTES) + .withConstValue(new C2ComponentAttributesSetting(C2Component::ATTRIB_IS_TEMPORAL)) + .build()); + + addParameter( + DefineParam(mSize, C2_PARAMKEY_PICTURE_SIZE) + .withDefault(new C2StreamPictureSizeInfo::output(0u, 176, 144)) + .withFields({ +#ifdef MPEG4 + C2F(mSize, width).inRange(2, 1920, 2), + C2F(mSize, height).inRange(2, 1088, 2), +#else + C2F(mSize, width).inRange(2, 352, 2), + C2F(mSize, height).inRange(2, 288, 2), +#endif + }) + .withSetter(SizeSetter) + .build()); + +#ifdef MPEG4 + addParameter( + DefineParam(mProfileLevel, C2_PARAMKEY_PROFILE_LEVEL) + .withDefault(new C2StreamProfileLevelInfo::input(0u, + C2Config::PROFILE_MP4V_SIMPLE, C2Config::LEVEL_MP4V_3)) + .withFields({ + C2F(mProfileLevel, profile).equalTo( + C2Config::PROFILE_MP4V_SIMPLE), + C2F(mProfileLevel, level).oneOf({ + C2Config::LEVEL_MP4V_0, + C2Config::LEVEL_MP4V_0B, + C2Config::LEVEL_MP4V_1, + C2Config::LEVEL_MP4V_2, + C2Config::LEVEL_MP4V_3, + C2Config::LEVEL_MP4V_3B, + C2Config::LEVEL_MP4V_4, + C2Config::LEVEL_MP4V_4A, + C2Config::LEVEL_MP4V_5, + C2Config::LEVEL_MP4V_6}) + }) + .withSetter(ProfileLevelSetter, mSize) + .build()); +#else + addParameter( + DefineParam(mProfileLevel, C2_PARAMKEY_PROFILE_LEVEL) + .withDefault(new C2StreamProfileLevelInfo::input(0u, + C2Config::PROFILE_H263_BASELINE, C2Config::LEVEL_H263_30)) + .withFields({ + C2F(mProfileLevel, profile).oneOf({ + C2Config::PROFILE_H263_BASELINE, + C2Config::PROFILE_H263_ISWV2}), + C2F(mProfileLevel, level).oneOf({ + C2Config::LEVEL_H263_10, + C2Config::LEVEL_H263_20, + C2Config::LEVEL_H263_30, + C2Config::LEVEL_H263_40, + C2Config::LEVEL_H263_45}) + }) + .withSetter(ProfileLevelSetter, mSize) + .build()); +#endif + + addParameter( + DefineParam(mMaxSize, C2_PARAMKEY_MAX_PICTURE_SIZE) +#ifdef MPEG4 + .withDefault(new C2StreamMaxPictureSizeTuning::output(0u, 1920, 1088)) +#else + .withDefault(new C2StreamMaxPictureSizeTuning::output(0u, 352, 288)) +#endif + .withFields({ +#ifdef MPEG4 + C2F(mSize, width).inRange(2, 1920, 2), + C2F(mSize, height).inRange(2, 1088, 2), +#else + C2F(mSize, width).inRange(2, 352, 2), + C2F(mSize, height).inRange(2, 288, 2), +#endif + }) + .withSetter(MaxPictureSizeSetter, mSize) + .build()); + + addParameter( + DefineParam(mMaxInputSize, C2_PARAMKEY_INPUT_MAX_BUFFER_SIZE) +#ifdef MPEG4 + .withDefault(new C2StreamMaxBufferSizeInfo::input(0u, 1920 * 1088 * 3 / 2)) +#else + .withDefault(new C2StreamMaxBufferSizeInfo::input(0u, 352 * 288 * 3 / 2)) +#endif + .withFields({ + C2F(mMaxInputSize, value).any(), + }) + .calculatedAs(MaxInputSizeSetter, mMaxSize) + .build()); + + C2ChromaOffsetStruct locations[1] = { C2ChromaOffsetStruct::ITU_YUV_420_0() }; + std::shared_ptr<C2StreamColorInfo::output> defaultColorInfo = + C2StreamColorInfo::output::AllocShared( + 1u, 0u, 8u /* bitDepth */, C2Color::YUV_420); + memcpy(defaultColorInfo->m.locations, locations, sizeof(locations)); + + defaultColorInfo = + C2StreamColorInfo::output::AllocShared( + { C2ChromaOffsetStruct::ITU_YUV_420_0() }, + 0u, 8u /* bitDepth */, C2Color::YUV_420); + helper->addStructDescriptors<C2ChromaOffsetStruct>(); + + addParameter( + DefineParam(mColorInfo, C2_PARAMKEY_CODED_COLOR_INFO) + .withConstValue(defaultColorInfo) + .build()); + + // TODO: support more formats? + addParameter( + DefineParam(mPixelFormat, C2_PARAMKEY_PIXEL_FORMAT) + .withConstValue(new C2StreamPixelFormatInfo::output( + 0u, HAL_PIXEL_FORMAT_YCBCR_420_888)) + .build()); + } + + static C2R SizeSetter(bool mayBlock, const C2P<C2StreamPictureSizeInfo::output> &oldMe, + C2P<C2StreamPictureSizeInfo::output> &me) { + (void)mayBlock; + C2R res = C2R::Ok(); + if (!me.F(me.v.width).supportsAtAll(me.v.width)) { + res = res.plus(C2SettingResultBuilder::BadValue(me.F(me.v.width))); + me.set().width = oldMe.v.width; + } + if (!me.F(me.v.height).supportsAtAll(me.v.height)) { + res = res.plus(C2SettingResultBuilder::BadValue(me.F(me.v.height))); + me.set().height = oldMe.v.height; + } + return res; + } + + static C2R MaxPictureSizeSetter(bool mayBlock, C2P<C2StreamMaxPictureSizeTuning::output> &me, + const C2P<C2StreamPictureSizeInfo::output> &size) { + (void)mayBlock; + // TODO: get max width/height from the size's field helpers vs. hardcoding +#ifdef MPEG4 + me.set().width = c2_min(c2_max(me.v.width, size.v.width), 1920u); + me.set().height = c2_min(c2_max(me.v.height, size.v.height), 1088u); +#else + me.set().width = c2_min(c2_max(me.v.width, size.v.width), 352u); + me.set().height = c2_min(c2_max(me.v.height, size.v.height), 288u); +#endif + return C2R::Ok(); + } + + static C2R MaxInputSizeSetter(bool mayBlock, C2P<C2StreamMaxBufferSizeInfo::input> &me, + const C2P<C2StreamMaxPictureSizeTuning::output> &maxSize) { + (void)mayBlock; + // assume compression ratio of 1 + me.set().value = (((maxSize.v.width + 15) / 16) * ((maxSize.v.height + 15) / 16) * 384); + return C2R::Ok(); + } + + static C2R ProfileLevelSetter(bool mayBlock, C2P<C2StreamProfileLevelInfo::input> &me, + const C2P<C2StreamPictureSizeInfo::output> &size) { + (void)mayBlock; + (void)size; + (void)me; // TODO: validate + return C2R::Ok(); + } + + uint32_t getMaxWidth() const { return mMaxSize->width; } + uint32_t getMaxHeight() const { return mMaxSize->height; } + +private: + std::shared_ptr<C2StreamProfileLevelInfo::input> mProfileLevel; + std::shared_ptr<C2StreamPictureSizeInfo::output> mSize; + std::shared_ptr<C2StreamMaxPictureSizeTuning::output> mMaxSize; + std::shared_ptr<C2StreamMaxBufferSizeInfo::input> mMaxInputSize; + std::shared_ptr<C2StreamColorInfo::output> mColorInfo; + std::shared_ptr<C2StreamPixelFormatInfo::output> mPixelFormat; +}; + +C2SoftMpeg4Dec::C2SoftMpeg4Dec( + const char *name, + c2_node_id_t id, + const std::shared_ptr<IntfImpl> &intfImpl) + : SimpleC2Component(std::make_shared<SimpleInterface<IntfImpl>>(name, id, intfImpl)), + mIntf(intfImpl), + mDecHandle(nullptr), + mOutputBuffer{}, + mInitialized(false) { +} + +C2SoftMpeg4Dec::~C2SoftMpeg4Dec() { + onRelease(); +} + +c2_status_t C2SoftMpeg4Dec::onInit() { + status_t err = initDecoder(); + return err == OK ? C2_OK : C2_CORRUPTED; +} + +c2_status_t C2SoftMpeg4Dec::onStop() { + if (mInitialized) { + if (mDecHandle) { + PVCleanUpVideoDecoder(mDecHandle); + } + mInitialized = false; + } + for (int32_t i = 0; i < kNumOutputBuffers; ++i) { + if (mOutputBuffer[i]) { + free(mOutputBuffer[i]); + mOutputBuffer[i] = nullptr; + } + } + mNumSamplesOutput = 0; + mFramesConfigured = false; + mSignalledOutputEos = false; + mSignalledError = false; + + return C2_OK; +} + +void C2SoftMpeg4Dec::onReset() { + (void)onStop(); + (void)onInit(); +} + +void C2SoftMpeg4Dec::onRelease() { + if (mInitialized) { + if (mDecHandle) { + PVCleanUpVideoDecoder(mDecHandle); + delete mDecHandle; + mDecHandle = nullptr; + } + mInitialized = false; + } + if (mOutBlock) { + mOutBlock.reset(); + } + for (int32_t i = 0; i < kNumOutputBuffers; ++i) { + if (mOutputBuffer[i]) { + free(mOutputBuffer[i]); + mOutputBuffer[i] = nullptr; + } + } +} + +c2_status_t C2SoftMpeg4Dec::onFlush_sm() { + if (mInitialized) { + if (PV_TRUE != PVResetVideoDecoder(mDecHandle)) { + return C2_CORRUPTED; + } + } + mSignalledOutputEos = false; + mSignalledError = false; + return C2_OK; +} + +status_t C2SoftMpeg4Dec::initDecoder() { +#ifdef MPEG4 + mIsMpeg4 = true; +#else + mIsMpeg4 = false; +#endif + 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 + * because, h263 doesn't seem to allow port reconfiguration. In OMX, the + * problem of larger width and height than default width and height is + * overcome by adaptivePlayBack() api call. This call gets width and height + * information from extractor. Such a thing is not possible here. + * So we are configuring to larger values.*/ + mWidth = 1408; + mHeight = 1152; + mNumSamplesOutput = 0; + mInitialized = false; + mFramesConfigured = false; + mSignalledOutputEos = false; + mSignalledError = false; + + return OK; +} + +void fillEmptyWork(const std::unique_ptr<C2Work> &work) { + uint32_t flags = 0; + if (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) { + flags |= C2FrameData::FLAG_END_OF_STREAM; + ALOGV("signalling eos"); + } + work->worklets.front()->output.flags = (C2FrameData::flags_t)flags; + work->worklets.front()->output.buffers.clear(); + work->worklets.front()->output.ordinal = work->input.ordinal; + work->workletsProcessed = 1u; +} + +void C2SoftMpeg4Dec::finishWork(uint64_t index, const std::unique_ptr<C2Work> &work) { + std::shared_ptr<C2Buffer> buffer = createGraphicBuffer(std::move(mOutBlock), + C2Rect(mWidth, mHeight)); + mOutBlock = nullptr; + 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; + work->worklets.front()->output.buffers.clear(); + work->worklets.front()->output.buffers.push_back(buffer); + work->worklets.front()->output.ordinal = work->input.ordinal; + work->workletsProcessed = 1u; + }; + if (work && c2_cntr64_t(index) == work->input.ordinal.frameIndex) { + fillWork(work); + } else { + finish(index, fillWork); + } +} + +c2_status_t C2SoftMpeg4Dec::ensureDecoderState(const std::shared_ptr<C2BlockPool> &pool) { + if (!mDecHandle) { + ALOGE("not supposed to be here, invalid decoder context"); + return C2_CORRUPTED; + } + + mOutputBufferSize = align(mIntf->getMaxWidth(), 16) * align(mIntf->getMaxHeight(), 16) * 3 / 2; + for (int32_t i = 0; i < kNumOutputBuffers; ++i) { + if (!mOutputBuffer[i]) { + mOutputBuffer[i] = (uint8_t *)malloc(mOutputBufferSize); + if (!mOutputBuffer[i]) { + return C2_NO_MEMORY; + } + } + } + if (mOutBlock && + (mOutBlock->width() != align(mWidth, 16) || mOutBlock->height() != mHeight)) { + mOutBlock.reset(); + } + if (!mOutBlock) { + uint32_t format = HAL_PIXEL_FORMAT_YV12; + C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE }; + c2_status_t err = pool->fetchGraphicBlock(align(mWidth, 16), mHeight, format, usage, &mOutBlock); + if (err != C2_OK) { + ALOGE("fetchGraphicBlock for Output failed with status %d", err); + return err; + } + ALOGV("provided (%dx%d) required (%dx%d)", + mOutBlock->width(), mOutBlock->height(), mWidth, mHeight); + } + return C2_OK; +} + +bool C2SoftMpeg4Dec::handleResChange(const std::unique_ptr<C2Work> &work) { + uint32_t disp_width, disp_height; + PVGetVideoDimensions(mDecHandle, (int32 *)&disp_width, (int32 *)&disp_height); + + uint32_t buf_width, buf_height; + PVGetBufferDimensions(mDecHandle, (int32 *)&buf_width, (int32 *)&buf_height); + + CHECK_LE(disp_width, buf_width); + CHECK_LE(disp_height, buf_height); + + ALOGV("display size (%dx%d), buffer size (%dx%d)", + disp_width, disp_height, buf_width, buf_height); + + bool resChanged = false; + if (disp_width != mWidth || disp_height != mHeight) { + mWidth = disp_width; + mHeight = disp_height; + resChanged = true; + for (int32_t i = 0; i < kNumOutputBuffers; ++i) { + if (mOutputBuffer[i]) { + free(mOutputBuffer[i]); + mOutputBuffer[i] = nullptr; + } + } + + if (!mIsMpeg4) { + PVCleanUpVideoDecoder(mDecHandle); + + uint8_t *vol_data[1]{}; + int32_t vol_size = 0; + + 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"); + mSignalledError = true; + work->result = C2_CORRUPTED; + return true; + } + } + mFramesConfigured = false; + } + return resChanged; +} + +/* TODO: can remove temporary copy after library supports writing to display + * buffer Y, U and V plane pointers using stride info. */ +static void copyOutputBufferToYuvPlanarFrame( + uint8_t *dst, uint8_t *src, + size_t dstYStride, size_t dstUVStride, + size_t srcYStride, uint32_t width, + uint32_t height) { + size_t srcUVStride = srcYStride / 2; + uint8_t *srcStart = src; + uint8_t *dstStart = dst; + size_t vStride = align(height, 16); + for (size_t i = 0; i < height; ++i) { + memcpy(dst, src, width); + src += srcYStride; + dst += dstYStride; + } + /* U buffer */ + src = srcStart + vStride * srcYStride; + dst = dstStart + (dstYStride * height) + (dstUVStride * height / 2); + for (size_t i = 0; i < height / 2; ++i) { + memcpy(dst, src, width / 2); + src += srcUVStride; + dst += dstUVStride; + } + /* V buffer */ + src = srcStart + vStride * srcYStride * 5 / 4; + dst = dstStart + (dstYStride * height); + for (size_t i = 0; i < height / 2; ++i) { + memcpy(dst, src, width / 2); + src += srcUVStride; + dst += dstUVStride; + } +} + +void C2SoftMpeg4Dec::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.configUpdate.clear(); + work->worklets.front()->output.flags = work->input.flags; + + if (mSignalledError || mSignalledOutputEos) { + work->result = C2_BAD_VALUE; + return; + } + + size_t inOffset = 0u; + size_t inSize = 0u; + uint32_t workIndex = work->input.ordinal.frameIndex.peeku() & 0xFFFFFFFF; + C2ReadView rView = mDummyReadView; + if (!work->input.buffers.empty()) { + rView = work->input.buffers[0]->data().linearBlocks().front().map().get(); + inSize = rView.capacity(); + if (inSize && rView.error()) { + ALOGE("read view map failed %d", rView.error()); + work->result = C2_CORRUPTED; + return; + } + } + ALOGV("in buffer attr. size %zu timestamp %d frameindex %d, flags %x", + inSize, (int)work->input.ordinal.timestamp.peeku(), + (int)work->input.ordinal.frameIndex.peeku(), work->input.flags); + + bool eos = ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0); + if (inSize == 0) { + fillEmptyWork(work); + if (eos) { + mSignalledOutputEos = true; + } + return; + } + + uint8_t *bitstream = const_cast<uint8_t *>(rView.data() + inOffset); + uint32_t *start_code = (uint32_t *)bitstream; + bool volHeader = *start_code == 0xB0010000; + if (volHeader) { + PVCleanUpVideoDecoder(mDecHandle); + mInitialized = false; + } + + if (!mInitialized) { + uint8_t *vol_data[1]{}; + int32_t vol_size = 0; + + bool codecConfig = (work->input.flags & C2FrameData::FLAG_CODEC_CONFIG) != 0; + if (codecConfig || volHeader) { + vol_data[0] = bitstream; + vol_size = inSize; + } + MP4DecodingMode mode = (mIsMpeg4) ? MPEG4_MODE : H263_MODE; + if (!PVInitVideoDecoder( + mDecHandle, vol_data, &vol_size, 1, + mIntf->getMaxWidth(), mIntf->getMaxHeight(), mode)) { + ALOGE("PVInitVideoDecoder failed. Unsupported content?"); + 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"); + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } + + PVSetPostProcType(mDecHandle, 0); + if (handleResChange(work)) { + ALOGI("Setting width and height"); + C2StreamPictureSizeInfo::output size(0u, mWidth, mHeight); + std::vector<std::unique_ptr<C2SettingResult>> failures; + 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("Config update size failed"); + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } + } + if (codecConfig) { + fillEmptyWork(work); + return; + } + } + + size_t inPos = 0; + while (inPos < inSize) { + c2_status_t err = ensureDecoderState(pool); + if (C2_OK != err) { + mSignalledError = true; + work->result = err; + return; + } + C2GraphicView wView = mOutBlock->map().get(); + if (wView.error()) { + ALOGE("graphic view map failed %d", wView.error()); + work->result = C2_CORRUPTED; + return; + } + + uint32_t yFrameSize = sizeof(uint8) * mDecHandle->size; + if (mOutputBufferSize < yFrameSize * 3 / 2){ + ALOGE("Too small output buffer: %zu bytes", mOutputBufferSize); + mSignalledError = true; + work->result = C2_NO_MEMORY; + return; + } + + if (!mFramesConfigured) { + PVSetReferenceYUV(mDecHandle,mOutputBuffer[1]); + mFramesConfigured = true; + } + + // Need to check if header contains new info, e.g., width/height, etc. + VopHeaderInfo header_info; + uint32_t useExtTimestamp = (inPos == 0); + int32_t tmpInSize = (int32_t)inSize; + uint8_t *bitstreamTmp = bitstream; + uint32_t timestamp = workIndex; + if (PVDecodeVopHeader( + mDecHandle, &bitstreamTmp, ×tamp, &tmpInSize, + &header_info, &useExtTimestamp, + mOutputBuffer[mNumSamplesOutput & 1]) != PV_TRUE) { + ALOGE("failed to decode vop header."); + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } + + // H263 doesn't have VOL header, the frame size information is in short header, i.e. the + // decoder may detect size change after PVDecodeVopHeader. + bool resChange = handleResChange(work); + if (mIsMpeg4 && resChange) { + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } else if (resChange) { + ALOGI("Setting width and height"); + C2StreamPictureSizeInfo::output size(0u, mWidth, mHeight); + std::vector<std::unique_ptr<C2SettingResult>> failures; + 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("Config update size failed"); + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } + continue; + } + + if (PVDecodeVopBody(mDecHandle, &tmpInSize) != PV_TRUE) { + ALOGE("failed to decode video frame."); + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } + if (handleResChange(work)) { + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } + + uint8_t *outputBufferY = wView.data()[C2PlanarLayout::PLANE_Y]; + C2PlanarLayout layout = wView.layout(); + size_t dstYStride = layout.planes[C2PlanarLayout::PLANE_Y].rowInc; + size_t dstUVStride = layout.planes[C2PlanarLayout::PLANE_U].rowInc; + (void)copyOutputBufferToYuvPlanarFrame( + outputBufferY, + mOutputBuffer[mNumSamplesOutput & 1], + dstYStride, dstUVStride, + align(mWidth, 16), mWidth, mHeight); + + inPos += inSize - (size_t)tmpInSize; + finishWork(workIndex, work); + ++mNumSamplesOutput; + if (inSize - inPos != 0) { + ALOGD("decoded frame, ignoring further trailing bytes %d", + (int)inSize - (int)inPos); + break; + } + } +} + +c2_status_t C2SoftMpeg4Dec::drain( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool) { + (void)pool; + if (drainMode == NO_DRAIN) { + ALOGW("drain with NO_DRAIN: no-op"); + return C2_OK; + } + if (drainMode == DRAIN_CHAIN) { + ALOGW("DRAIN_CHAIN not supported"); + return C2_OMITTED; + } + return C2_OK; +} + +class C2SoftMpeg4DecFactory : public C2ComponentFactory { +public: + C2SoftMpeg4DecFactory() : mHelper(std::static_pointer_cast<C2ReflectorHelper>( + GetCodec2PlatformComponentStore()->getParamReflector())) { + } + + virtual c2_status_t createComponent( + c2_node_id_t id, + std::shared_ptr<C2Component>* const component, + std::function<void(C2Component*)> deleter) override { + *component = std::shared_ptr<C2Component>( + new C2SoftMpeg4Dec(COMPONENT_NAME, + id, + std::make_shared<C2SoftMpeg4Dec::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual c2_status_t createInterface( + c2_node_id_t id, + std::shared_ptr<C2ComponentInterface>* const interface, + std::function<void(C2ComponentInterface*)> deleter) override { + *interface = std::shared_ptr<C2ComponentInterface>( + new SimpleInterface<C2SoftMpeg4Dec::IntfImpl>( + COMPONENT_NAME, id, std::make_shared<C2SoftMpeg4Dec::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual ~C2SoftMpeg4DecFactory() override = default; + +private: + std::shared_ptr<C2ReflectorHelper> mHelper; +}; + +} // namespace android + +extern "C" ::C2ComponentFactory* CreateCodec2Factory() { + ALOGV("in %s", __func__); + return new ::android::C2SoftMpeg4DecFactory(); +} + +extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) { + ALOGV("in %s", __func__); + delete factory; +}
diff --git a/media/codec2/components/mpeg4_h263/C2SoftMpeg4Dec.h b/media/codec2/components/mpeg4_h263/C2SoftMpeg4Dec.h new file mode 100644 index 0000000..716a095 --- /dev/null +++ b/media/codec2/components/mpeg4_h263/C2SoftMpeg4Dec.h
@@ -0,0 +1,77 @@ +/* + * 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_SOFT_MPEG4_DEC_H_ +#define C2_SOFT_MPEG4_DEC_H_ + +#include <SimpleC2Component.h> + + +struct tagvideoDecControls; + +namespace android { + +struct C2SoftMpeg4Dec : public SimpleC2Component { + class IntfImpl; + + C2SoftMpeg4Dec(const char* name, c2_node_id_t id, + const std::shared_ptr<IntfImpl>& intfImpl); + virtual ~C2SoftMpeg4Dec(); + + // From SimpleC2Component + c2_status_t onInit() override; + c2_status_t onStop() override; + void onReset() override; + void onRelease() override; + c2_status_t onFlush_sm() override; + void process( + const std::unique_ptr<C2Work> &work, + const std::shared_ptr<C2BlockPool> &pool) override; + c2_status_t drain( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool) override; + private: + enum { + kNumOutputBuffers = 2, + }; + + status_t initDecoder(); + c2_status_t ensureDecoderState(const std::shared_ptr<C2BlockPool> &pool); + void finishWork(uint64_t index, const std::unique_ptr<C2Work> &work); + bool handleResChange(const std::unique_ptr<C2Work> &work); + + std::shared_ptr<IntfImpl> mIntf; + tagvideoDecControls *mDecHandle; + std::shared_ptr<C2GraphicBlock> mOutBlock; + uint8_t *mOutputBuffer[kNumOutputBuffers]; + size_t mOutputBufferSize; + + uint32_t mWidth; + uint32_t mHeight; + uint32_t mNumSamplesOutput; + + bool mIsMpeg4; + bool mInitialized; + bool mFramesConfigured; + bool mSignalledOutputEos; + bool mSignalledError; + + C2_DO_NOT_COPY(C2SoftMpeg4Dec); +}; + +} // namespace android + +#endif // C2_SOFT_MPEG4_DEC_H_
diff --git a/media/codec2/components/mpeg4_h263/C2SoftMpeg4Enc.cpp b/media/codec2/components/mpeg4_h263/C2SoftMpeg4Enc.cpp new file mode 100644 index 0000000..36053f6 --- /dev/null +++ b/media/codec2/components/mpeg4_h263/C2SoftMpeg4Enc.cpp
@@ -0,0 +1,661 @@ +/* + * Copyright 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 +#ifdef MPEG4 + #define LOG_TAG "C2SoftMpeg4Enc" +#else + #define LOG_TAG "C2SoftH263Enc" +#endif +#include <log/log.h> + +#include <inttypes.h> + +#include <media/hardware/VideoAPI.h> +#include <media/stagefright/foundation/AUtils.h> +#include <media/stagefright/MediaDefs.h> +#include <utils/misc.h> + +#include <C2Debug.h> +#include <C2PlatformSupport.h> +#include <SimpleC2Interface.h> +#include <util/C2InterfaceHelper.h> + +#include "C2SoftMpeg4Enc.h" +#include "mp4enc_api.h" + +namespace android { + +namespace { + +#ifdef MPEG4 +constexpr char COMPONENT_NAME[] = "c2.android.mpeg4.encoder"; +const char *MEDIA_MIMETYPE_VIDEO = MEDIA_MIMETYPE_VIDEO_MPEG4; +#else +constexpr char COMPONENT_NAME[] = "c2.android.h263.encoder"; +const char *MEDIA_MIMETYPE_VIDEO = MEDIA_MIMETYPE_VIDEO_H263; +#endif + +} // namepsace + +class C2SoftMpeg4Enc::IntfImpl : public SimpleInterface<void>::BaseParams { + public: + explicit IntfImpl(const std::shared_ptr<C2ReflectorHelper> &helper) + : SimpleInterface<void>::BaseParams( + helper, + COMPONENT_NAME, + C2Component::KIND_ENCODER, + C2Component::DOMAIN_VIDEO, + MEDIA_MIMETYPE_VIDEO) { + noPrivateBuffers(); // TODO: account for our buffers here + noInputReferences(); + noOutputReferences(); + noInputLatency(); + noTimeStretch(); + setDerivedInstance(this); + + addParameter( + DefineParam(mAttrib, C2_PARAMKEY_COMPONENT_ATTRIBUTES) + .withConstValue(new C2ComponentAttributesSetting( + C2Component::ATTRIB_IS_TEMPORAL)) + .build()); + + addParameter( + DefineParam(mUsage, C2_PARAMKEY_INPUT_STREAM_USAGE) + .withConstValue(new C2StreamUsageTuning::input( + 0u, (uint64_t)C2MemoryUsage::CPU_READ)) + .build()); + + addParameter( + DefineParam(mSize, C2_PARAMKEY_PICTURE_SIZE) + .withDefault(new C2StreamPictureSizeInfo::input(0u, 176, 144)) + .withFields({ +#ifdef MPEG4 + C2F(mSize, width).inRange(16, 176, 16), + C2F(mSize, height).inRange(16, 144, 16), +#else + C2F(mSize, width).oneOf({176, 352}), + C2F(mSize, height).oneOf({144, 288}), +#endif + }) + .withSetter(SizeSetter) + .build()); + + addParameter( + DefineParam(mFrameRate, C2_PARAMKEY_FRAME_RATE) + .withDefault(new C2StreamFrameRateInfo::output(0u, 17.)) + // TODO: More restriction? + .withFields({C2F(mFrameRate, value).greaterThan(0.)}) + .withSetter( + Setter<decltype(*mFrameRate)>::StrictValueWithNoDeps) + .build()); + + addParameter( + DefineParam(mBitrate, C2_PARAMKEY_BITRATE) + .withDefault(new C2StreamBitrateInfo::output(0u, 64000)) + .withFields({C2F(mBitrate, value).inRange(4096, 12000000)}) + .withSetter(BitrateSetter) + .build()); + + addParameter( + DefineParam(mSyncFramePeriod, C2_PARAMKEY_SYNC_FRAME_INTERVAL) + .withDefault(new C2StreamSyncFrameIntervalTuning::output(0u, 1000000)) + .withFields({C2F(mSyncFramePeriod, value).any()}) + .withSetter(Setter<decltype(*mSyncFramePeriod)>::StrictValueWithNoDeps) + .build()); + +#ifdef MPEG4 + addParameter( + DefineParam(mProfileLevel, C2_PARAMKEY_PROFILE_LEVEL) + .withDefault(new C2StreamProfileLevelInfo::output( + 0u, PROFILE_MP4V_SIMPLE, LEVEL_MP4V_2)) + .withFields({ + C2F(mProfileLevel, profile).equalTo( + PROFILE_MP4V_SIMPLE), + C2F(mProfileLevel, level).oneOf({ + C2Config::LEVEL_MP4V_0, + C2Config::LEVEL_MP4V_0B, + C2Config::LEVEL_MP4V_1, + C2Config::LEVEL_MP4V_2}) + }) + .withSetter(ProfileLevelSetter) + .build()); +#else + addParameter( + DefineParam(mProfileLevel, C2_PARAMKEY_PROFILE_LEVEL) + .withDefault(new C2StreamProfileLevelInfo::output( + 0u, PROFILE_H263_BASELINE, LEVEL_H263_45)) + .withFields({ + C2F(mProfileLevel, profile).equalTo( + PROFILE_H263_BASELINE), + C2F(mProfileLevel, level).oneOf({ + C2Config::LEVEL_H263_10, + C2Config::LEVEL_H263_20, + C2Config::LEVEL_H263_30, + C2Config::LEVEL_H263_40, + C2Config::LEVEL_H263_45}) + }) + .withSetter(ProfileLevelSetter) + .build()); +#endif + } + + static C2R BitrateSetter(bool mayBlock, C2P<C2StreamBitrateInfo::output> &me) { + (void)mayBlock; + C2R res = C2R::Ok(); + if (me.v.value <= 4096) { + me.set().value = 4096; + } + return res; + } + + static C2R SizeSetter(bool mayBlock, const C2P<C2StreamPictureSizeInfo::input> &oldMe, + C2P<C2StreamPictureSizeInfo::input> &me) { + (void)mayBlock; + C2R res = C2R::Ok(); + if (!me.F(me.v.width).supportsAtAll(me.v.width)) { + res = res.plus(C2SettingResultBuilder::BadValue(me.F(me.v.width))); + me.set().width = oldMe.v.width; + } + if (!me.F(me.v.height).supportsAtAll(me.v.height)) { + res = res.plus(C2SettingResultBuilder::BadValue(me.F(me.v.height))); + me.set().height = oldMe.v.height; + } + return res; + } + + static C2R ProfileLevelSetter( + bool mayBlock, + C2P<C2StreamProfileLevelInfo::output> &me) { + (void)mayBlock; + if (!me.F(me.v.profile).supportsAtAll(me.v.profile)) { +#ifdef MPEG4 + me.set().profile = PROFILE_MP4V_SIMPLE; +#else + me.set().profile = PROFILE_H263_BASELINE; +#endif + } + if (!me.F(me.v.level).supportsAtAll(me.v.level)) { +#ifdef MPEG4 + me.set().level = LEVEL_MP4V_2; +#else + me.set().level = LEVEL_H263_45; +#endif + } + return C2R::Ok(); + } + + // unsafe getters + std::shared_ptr<C2StreamPictureSizeInfo::input> getSize_l() const { return mSize; } + std::shared_ptr<C2StreamFrameRateInfo::output> getFrameRate_l() const { return mFrameRate; } + std::shared_ptr<C2StreamBitrateInfo::output> getBitrate_l() const { return mBitrate; } + uint32_t getSyncFramePeriod() const { + if (mSyncFramePeriod->value < 0 || mSyncFramePeriod->value == INT64_MAX) { + return 0; + } + double period = mSyncFramePeriod->value / 1e6 * mFrameRate->value; + return (uint32_t)c2_max(c2_min(period + 0.5, double(UINT32_MAX)), 1.); + } + + private: + std::shared_ptr<C2StreamUsageTuning::input> mUsage; + std::shared_ptr<C2StreamPictureSizeInfo::input> mSize; + std::shared_ptr<C2StreamFrameRateInfo::output> mFrameRate; + std::shared_ptr<C2StreamBitrateInfo::output> mBitrate; + std::shared_ptr<C2StreamProfileLevelInfo::output> mProfileLevel; + std::shared_ptr<C2StreamSyncFrameIntervalTuning::output> mSyncFramePeriod; +}; + +C2SoftMpeg4Enc::C2SoftMpeg4Enc(const char* name, c2_node_id_t id, + const std::shared_ptr<IntfImpl>& intfImpl) + : SimpleC2Component( + std::make_shared<SimpleInterface<IntfImpl>>(name, id, intfImpl)), + mIntf(intfImpl), + mHandle(nullptr), + mEncParams(nullptr), + mStarted(false), + mOutBufferSize(524288) { +} + +C2SoftMpeg4Enc::~C2SoftMpeg4Enc() { + onRelease(); +} + +c2_status_t C2SoftMpeg4Enc::onInit() { +#ifdef MPEG4 + mEncodeMode = COMBINE_MODE_WITH_ERR_RES; +#else + mEncodeMode = H263_MODE; +#endif + if (!mHandle) { + mHandle = new tagvideoEncControls; + } + + if (!mEncParams) { + mEncParams = new tagvideoEncOptions; + } + + if (!(mEncParams && mHandle)) return C2_NO_MEMORY; + + mSignalledOutputEos = false; + mSignalledError = false; + + return initEncoder(); +} + +c2_status_t C2SoftMpeg4Enc::onStop() { + if (!mStarted) { + return C2_OK; + } + if (mHandle) { + (void)PVCleanUpVideoEncoder(mHandle); + } + mStarted = false; + mSignalledOutputEos = false; + mSignalledError = false; + return C2_OK; +} + +void C2SoftMpeg4Enc::onReset() { + onStop(); + initEncoder(); +} + +void C2SoftMpeg4Enc::onRelease() { + onStop(); + if (mEncParams) { + delete mEncParams; + mEncParams = nullptr; + } + if (mHandle) { + delete mHandle; + mHandle = nullptr; + } +} + +c2_status_t C2SoftMpeg4Enc::onFlush_sm() { + return C2_OK; +} + +static void fillEmptyWork(const std::unique_ptr<C2Work> &work) { + uint32_t flags = 0; + if (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) { + flags |= C2FrameData::FLAG_END_OF_STREAM; + ALOGV("signalling eos"); + } + work->worklets.front()->output.flags = (C2FrameData::flags_t)flags; + work->worklets.front()->output.buffers.clear(); + work->worklets.front()->output.ordinal = work->input.ordinal; + work->workletsProcessed = 1u; +} + +c2_status_t C2SoftMpeg4Enc::initEncParams() { + if (mHandle) { + memset(mHandle, 0, sizeof(tagvideoEncControls)); + } else return C2_CORRUPTED; + if (mEncParams) { + memset(mEncParams, 0, sizeof(tagvideoEncOptions)); + } else return C2_CORRUPTED; + + if (!PVGetDefaultEncOption(mEncParams, 0)) { + ALOGE("Failed to get default encoding parameters"); + return C2_CORRUPTED; + } + + if (mFrameRate->value == 0) { + ALOGE("Framerate should not be 0"); + return C2_BAD_VALUE; + } + + mEncParams->encMode = mEncodeMode; + mEncParams->encWidth[0] = mSize->width; + mEncParams->encHeight[0] = mSize->height; + mEncParams->encFrameRate[0] = mFrameRate->value + 0.5; + mEncParams->rcType = VBR_1; + mEncParams->vbvDelay = 5.0f; + mEncParams->profile_level = CORE_PROFILE_LEVEL2; + mEncParams->packetSize = 32; + mEncParams->rvlcEnable = PV_OFF; + mEncParams->numLayers = 1; + mEncParams->timeIncRes = 1000; + mEncParams->tickPerSrc = mEncParams->timeIncRes / (mFrameRate->value + 0.5); + mEncParams->bitRate[0] = mBitrate->value; + mEncParams->iQuant[0] = 15; + mEncParams->pQuant[0] = 12; + mEncParams->quantType[0] = 0; + mEncParams->noFrameSkipped = PV_OFF; + + // PV's MPEG4 encoder requires the video dimension of multiple + if (mSize->width % 16 != 0 || mSize->height % 16 != 0) { + ALOGE("Video frame size %dx%d must be a multiple of 16", + mSize->width, mSize->height); + return C2_BAD_VALUE; + } + + // Set IDR frame refresh interval + mEncParams->intraPeriod = mIntf->getSyncFramePeriod(); + mEncParams->numIntraMB = 0; + mEncParams->sceneDetect = PV_ON; + mEncParams->searchRange = 16; + mEncParams->mv8x8Enable = PV_OFF; + mEncParams->gobHeaderInterval = 0; + mEncParams->useACPred = PV_ON; + mEncParams->intraDCVlcTh = 0; + + return C2_OK; +} + +c2_status_t C2SoftMpeg4Enc::initEncoder() { + if (mStarted) { + return C2_OK; + } + { + IntfImpl::Lock lock = mIntf->lock(); + mSize = mIntf->getSize_l(); + mBitrate = mIntf->getBitrate_l(); + mFrameRate = mIntf->getFrameRate_l(); + } + c2_status_t err = initEncParams(); + if (C2_OK != err) { + ALOGE("Failed to initialized encoder params"); + mSignalledError = true; + return err; + } + if (!PVInitVideoEncoder(mHandle, mEncParams)) { + ALOGE("Failed to initialize the encoder"); + mSignalledError = true; + return C2_CORRUPTED; + } + + // 1st buffer for codec specific data + mNumInputFrames = -1; + mStarted = true; + return C2_OK; +} + +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; + } + + // Initialize encoder if not already initialized + if (!mStarted && C2_OK != initEncoder()) { + ALOGE("Failed to initialize encoder"); + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } + + std::shared_ptr<C2LinearBlock> block; + C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE }; + c2_status_t err = pool->fetchLinearBlock(mOutBufferSize, usage, &block); + if (err != C2_OK) { + ALOGE("fetchLinearBlock for Output failed with status %d", err); + work->result = C2_NO_MEMORY; + return; + } + + C2WriteView wView = block->map().get(); + if (wView.error()) { + ALOGE("write view map failed %d", wView.error()); + work->result = wView.error(); + return; + } + + uint8_t *outPtr = (uint8_t *)wView.data(); + if (mNumInputFrames < 0) { + // The very first thing we want to output is the codec specific data. + int32_t outputSize = mOutBufferSize; + if (!PVGetVolHeader(mHandle, outPtr, &outputSize, 0)) { + ALOGE("Failed to get VOL header"); + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } else { + ALOGV("Bytes Generated in header %d\n", outputSize); + } + + ++mNumInputFrames; + std::unique_ptr<C2StreamInitDataInfo::output> csd = + C2StreamInitDataInfo::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)); + } + + std::shared_ptr<const C2GraphicView> rView; + std::shared_ptr<C2Buffer> inputBuffer; + bool eos = ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0); + if (!work->input.buffers.empty()) { + inputBuffer = work->input.buffers[0]; + rView = std::make_shared<const C2GraphicView>( + inputBuffer->data().graphicBlocks().front().map().get()); + if (rView->error() != C2_OK) { + ALOGE("graphic view map err = %d", rView->error()); + work->result = rView->error(); + return; + } + } else { + fillEmptyWork(work); + if (eos) { + mSignalledOutputEos = true; + ALOGV("signalled EOS"); + } + return; + } + + uint64_t inputTimeStamp = work->input.ordinal.timestamp.peekull(); + const C2ConstGraphicBlock inBuffer = inputBuffer->data().graphicBlocks().front(); + if (inBuffer.width() < mSize->width || + inBuffer.height() < mSize->height) { + /* Expect width height to be configured */ + ALOGW("unexpected Capacity Aspect %d(%d) x %d(%d)", inBuffer.width(), + mSize->width, inBuffer.height(), mSize->height); + work->result = C2_BAD_VALUE; + return; + } + + const C2PlanarLayout &layout = rView->layout(); + uint8_t *yPlane = const_cast<uint8_t *>(rView->data()[C2PlanarLayout::PLANE_Y]); + uint8_t *uPlane = const_cast<uint8_t *>(rView->data()[C2PlanarLayout::PLANE_U]); + uint8_t *vPlane = const_cast<uint8_t *>(rView->data()[C2PlanarLayout::PLANE_V]); + int32_t yStride = layout.planes[C2PlanarLayout::PLANE_Y].rowInc; + int32_t uStride = layout.planes[C2PlanarLayout::PLANE_U].rowInc; + int32_t vStride = layout.planes[C2PlanarLayout::PLANE_V].rowInc; + uint32_t width = mSize->width; + uint32_t height = mSize->height; + // width and height are always even (as block size is 16x16) + CHECK_EQ((width & 1u), 0u); + CHECK_EQ((height & 1u), 0u); + size_t yPlaneSize = width * height; + switch (layout.type) { + case C2PlanarLayout::TYPE_RGB: + [[fallthrough]]; + case C2PlanarLayout::TYPE_RGBA: { + MemoryBlock conversionBuffer = mConversionBuffers.fetch(yPlaneSize * 3 / 2); + mConversionBuffersInUse.emplace(conversionBuffer.data(), conversionBuffer); + yPlane = conversionBuffer.data(); + uPlane = yPlane + yPlaneSize; + vPlane = uPlane + yPlaneSize / 4; + yStride = width; + uStride = vStride = width / 2; + ConvertRGBToPlanarYUV(yPlane, yStride, height, conversionBuffer.size(), *rView.get()); + break; + } + case C2PlanarLayout::TYPE_YUV: { + if (!IsYUV420(*rView)) { + ALOGE("input is not YUV420"); + work->result = C2_BAD_VALUE; + break; + } + + if (layout.planes[layout.PLANE_Y].colInc == 1 + && layout.planes[layout.PLANE_U].colInc == 1 + && layout.planes[layout.PLANE_V].colInc == 1 + && uStride == vStride + && yStride == 2 * vStride) { + // I420 compatible - planes are already set up above + break; + } + + // copy to I420 + MemoryBlock conversionBuffer = mConversionBuffers.fetch(yPlaneSize * 3 / 2); + mConversionBuffersInUse.emplace(conversionBuffer.data(), conversionBuffer); + MediaImage2 img = CreateYUV420PlanarMediaImage2(width, height, width, height); + status_t err = ImageCopy(conversionBuffer.data(), &img, *rView); + if (err != OK) { + ALOGE("Buffer conversion failed: %d", err); + work->result = C2_BAD_VALUE; + return; + } + yPlane = conversionBuffer.data(); + uPlane = yPlane + yPlaneSize; + vPlane = uPlane + yPlaneSize / 4; + yStride = width; + uStride = vStride = width / 2; + break; + } + + case C2PlanarLayout::TYPE_YUVA: + ALOGE("YUVA plane type is not supported"); + work->result = C2_BAD_VALUE; + return; + + default: + ALOGE("Unrecognized plane type: %d", layout.type); + work->result = C2_BAD_VALUE; + return; + } + + CHECK(NULL != yPlane); + /* Encode frames */ + VideoEncFrameIO vin, vout; + memset(&vin, 0, sizeof(vin)); + memset(&vout, 0, sizeof(vout)); + vin.yChan = yPlane; + vin.uChan = uPlane; + vin.vChan = vPlane; + vin.timestamp = (inputTimeStamp + 500) / 1000; // in ms + vin.height = align(height, 16); + vin.pitch = align(width, 16); + + uint32_t modTimeMs = 0; + int32_t nLayer = 0; + MP4HintTrack hintTrack; + int32_t outputSize = mOutBufferSize; + if (!PVEncodeVideoFrame(mHandle, &vin, &vout, &modTimeMs, outPtr, &outputSize, &nLayer) || + !PVGetHintTrack(mHandle, &hintTrack)) { + ALOGE("Failed to encode frame or get hint track at frame %" PRId64, mNumInputFrames); + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } + ALOGV("outputSize filled : %d", outputSize); + ++mNumInputFrames; + CHECK(NULL == PVGetOverrunBuffer(mHandle)); + + fillEmptyWork(work); + if (outputSize) { + std::shared_ptr<C2Buffer> buffer = createLinearBuffer(block, 0, outputSize); + work->worklets.front()->output.ordinal.timestamp = inputTimeStamp; + if (hintTrack.CodeType == 0) { + buffer->setInfo(std::make_shared<C2StreamPictureTypeMaskInfo::output>( + 0u /* stream id */, C2Config::SYNC_FRAME)); + } + work->worklets.front()->output.buffers.push_back(buffer); + } + if (eos) { + mSignalledOutputEos = true; + } + + mConversionBuffersInUse.erase(yPlane); +} + +c2_status_t C2SoftMpeg4Enc::drain( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool) { + (void)pool; + if (drainMode == NO_DRAIN) { + ALOGW("drain with NO_DRAIN: no-op"); + return C2_OK; + } + if (drainMode == DRAIN_CHAIN) { + ALOGW("DRAIN_CHAIN not supported"); + return C2_OMITTED; + } + + return C2_OK; +} + +class C2SoftMpeg4EncFactory : public C2ComponentFactory { +public: + C2SoftMpeg4EncFactory() + : mHelper(std::static_pointer_cast<C2ReflectorHelper>( + GetCodec2PlatformComponentStore()->getParamReflector())) {} + + virtual c2_status_t createComponent( + c2_node_id_t id, + std::shared_ptr<C2Component>* const component, + std::function<void(C2Component*)> deleter) override { + *component = std::shared_ptr<C2Component>( + new C2SoftMpeg4Enc( + COMPONENT_NAME, id, + std::make_shared<C2SoftMpeg4Enc::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual c2_status_t createInterface( + c2_node_id_t id, + std::shared_ptr<C2ComponentInterface>* const interface, + std::function<void(C2ComponentInterface*)> deleter) override { + *interface = std::shared_ptr<C2ComponentInterface>( + new SimpleInterface<C2SoftMpeg4Enc::IntfImpl>( + COMPONENT_NAME, id, + std::make_shared<C2SoftMpeg4Enc::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual ~C2SoftMpeg4EncFactory() override = default; + +private: + std::shared_ptr<C2ReflectorHelper> mHelper; +}; + +} // namespace android + +extern "C" ::C2ComponentFactory* CreateCodec2Factory() { + ALOGV("in %s", __func__); + return new ::android::C2SoftMpeg4EncFactory(); +} + +extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) { + ALOGV("in %s", __func__); + delete factory; +}
diff --git a/media/codec2/components/mpeg4_h263/C2SoftMpeg4Enc.h b/media/codec2/components/mpeg4_h263/C2SoftMpeg4Enc.h new file mode 100644 index 0000000..43461fc --- /dev/null +++ b/media/codec2/components/mpeg4_h263/C2SoftMpeg4Enc.h
@@ -0,0 +1,83 @@ +/* + * Copyright 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_SOFT_MPEG4_ENC_H__ +#define C2_SOFT_MPEG4_ENC_H__ + +#include <map> + +#include <Codec2BufferUtils.h> +#include <SimpleC2Component.h> + +#include "mp4enc_api.h" + +namespace android { + +struct C2SoftMpeg4Enc : public SimpleC2Component { + class IntfImpl; + + C2SoftMpeg4Enc(const char* name, c2_node_id_t id, + const std::shared_ptr<IntfImpl>& intfImpl); + + // From SimpleC2Component + c2_status_t onInit() override; + c2_status_t onStop() override; + void onReset() override; + void onRelease() override; + c2_status_t onFlush_sm() override; + void process( + const std::unique_ptr<C2Work> &work, + const std::shared_ptr<C2BlockPool> &pool) override; + c2_status_t drain( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool) override; + +protected: + + virtual ~C2SoftMpeg4Enc(); + +private: + std::shared_ptr<IntfImpl> mIntf; + + tagvideoEncControls *mHandle; + tagvideoEncOptions *mEncParams; + + bool mStarted; + bool mSignalledOutputEos; + bool mSignalledError; + + uint32_t mOutBufferSize; + // configurations used by component in process + // (TODO: keep this in intf but make them internal only) + std::shared_ptr<C2StreamPictureSizeInfo::input> mSize; + std::shared_ptr<C2StreamFrameRateInfo::output> mFrameRate; + std::shared_ptr<C2StreamBitrateInfo::output> mBitrate; + + int64_t mNumInputFrames; + MP4EncodingMode mEncodeMode; + + MemoryBlockPool mConversionBuffers; + std::map<void *, MemoryBlock> mConversionBuffersInUse; + + c2_status_t initEncParams(); + c2_status_t initEncoder(); + + C2_DO_NOT_COPY(C2SoftMpeg4Enc); +}; + +} // namespace android + +#endif // C2_SOFT_MPEG4_ENC_H__
diff --git a/media/common_time/MODULE_LICENSE_APACHE2 b/media/codec2/components/mpeg4_h263/MODULE_LICENSE_APACHE2 similarity index 100% copy from media/common_time/MODULE_LICENSE_APACHE2 copy to media/codec2/components/mpeg4_h263/MODULE_LICENSE_APACHE2
diff --git a/media/common_time/NOTICE b/media/codec2/components/mpeg4_h263/NOTICE similarity index 100% copy from media/common_time/NOTICE copy to media/codec2/components/mpeg4_h263/NOTICE
diff --git a/media/codec2/components/mpeg4_h263/patent_disclaimer.txt b/media/codec2/components/mpeg4_h263/patent_disclaimer.txt new file mode 100644 index 0000000..b4bf11d --- /dev/null +++ b/media/codec2/components/mpeg4_h263/patent_disclaimer.txt
@@ -0,0 +1,9 @@ + +THIS IS NOT A GRANT OF PATENT RIGHTS. + +Google makes no representation or warranty that the codecs for which +source code is made available hereunder are unencumbered by +third-party patents. Those intending to use this source code in +hardware or software products are advised that implementations of +these codecs, including in open source software or shareware, may +require patent licenses from the relevant patent holders.
diff --git a/media/codec2/components/opus/Android.bp b/media/codec2/components/opus/Android.bp new file mode 100644 index 0000000..0ed141b --- /dev/null +++ b/media/codec2/components/opus/Android.bp
@@ -0,0 +1,22 @@ +cc_library_shared { + name: "libcodec2_soft_opusdec", + defaults: [ + "libcodec2_soft-defaults", + "libcodec2_soft_sanitize_all-defaults", + ], + + srcs: ["C2SoftOpusDec.cpp"], + + shared_libs: ["libopus"], +} +cc_library_shared { + name: "libcodec2_soft_opusenc", + defaults: [ + "libcodec2_soft-defaults", + "libcodec2_soft_sanitize_all-defaults", + ], + + srcs: ["C2SoftOpusEnc.cpp"], + + shared_libs: ["libopus"], +}
diff --git a/media/codec2/components/opus/C2SoftOpusDec.cpp b/media/codec2/components/opus/C2SoftOpusDec.cpp new file mode 100644 index 0000000..6b6974f --- /dev/null +++ b/media/codec2/components/opus/C2SoftOpusDec.cpp
@@ -0,0 +1,484 @@ +/* + * 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 "C2SoftOpusDec" +#include <log/log.h> + +#include <media/stagefright/foundation/MediaDefs.h> +#include <media/stagefright/foundation/OpusHeader.h> +#include <C2PlatformSupport.h> +#include <SimpleC2Interface.h> +#include "C2SoftOpusDec.h" + +extern "C" { + #include <opus.h> + #include <opus_multistream.h> +} + +namespace android { + +namespace { + +constexpr char COMPONENT_NAME[] = "c2.android.opus.decoder"; + +} // namespace + +class C2SoftOpusDec::IntfImpl : public SimpleInterface<void>::BaseParams { +public: + explicit IntfImpl(const std::shared_ptr<C2ReflectorHelper> &helper) + : SimpleInterface<void>::BaseParams( + helper, + COMPONENT_NAME, + C2Component::KIND_DECODER, + C2Component::DOMAIN_AUDIO, + MEDIA_MIMETYPE_AUDIO_OPUS) { + noPrivateBuffers(); + noInputReferences(); + noOutputReferences(); + noInputLatency(); + noTimeStretch(); + setDerivedInstance(this); + + addParameter( + DefineParam(mAttrib, C2_PARAMKEY_COMPONENT_ATTRIBUTES) + .withConstValue(new C2ComponentAttributesSetting( + C2Component::ATTRIB_IS_TEMPORAL)) + .build()); + + addParameter( + DefineParam(mSampleRate, C2_PARAMKEY_SAMPLE_RATE) + .withDefault(new C2StreamSampleRateInfo::output(0u, 48000)) + .withFields({C2F(mSampleRate, value).equalTo(48000)}) + .withSetter((Setter<decltype(*mSampleRate)>::StrictValueWithNoDeps)) + .build()); + + addParameter( + DefineParam(mChannelCount, C2_PARAMKEY_CHANNEL_COUNT) + .withDefault(new C2StreamChannelCountInfo::output(0u, 1)) + .withFields({C2F(mChannelCount, value).inRange(1, 8)}) + .withSetter(Setter<decltype(*mChannelCount)>::StrictValueWithNoDeps) + .build()); + + addParameter( + DefineParam(mBitrate, C2_PARAMKEY_BITRATE) + .withDefault(new C2StreamBitrateInfo::input(0u, 6000)) + .withFields({C2F(mBitrate, value).inRange(6000, 510000)}) + .withSetter(Setter<decltype(*mBitrate)>::NonStrictValueWithNoDeps) + .build()); + + addParameter( + DefineParam(mInputMaxBufSize, C2_PARAMKEY_INPUT_MAX_BUFFER_SIZE) + .withConstValue(new C2StreamMaxBufferSizeInfo::input(0u, 960 * 6)) + .build()); + } + +private: + std::shared_ptr<C2StreamSampleRateInfo::output> mSampleRate; + std::shared_ptr<C2StreamChannelCountInfo::output> mChannelCount; + std::shared_ptr<C2StreamBitrateInfo::input> mBitrate; + std::shared_ptr<C2StreamMaxBufferSizeInfo::input> mInputMaxBufSize; +}; + +C2SoftOpusDec::C2SoftOpusDec(const char *name, c2_node_id_t id, + const std::shared_ptr<IntfImpl>& intfImpl) + : SimpleC2Component( + std::make_shared<SimpleInterface<IntfImpl>>(name, id, intfImpl)), + mIntf(intfImpl), + mDecoder(nullptr) { +} + +C2SoftOpusDec::~C2SoftOpusDec() { + onRelease(); +} + +c2_status_t C2SoftOpusDec::onInit() { + status_t err = initDecoder(); + return err == OK ? C2_OK : C2_NO_MEMORY; +} + +c2_status_t C2SoftOpusDec::onStop() { + if (mDecoder) { + opus_multistream_decoder_destroy(mDecoder); + mDecoder = nullptr; + } + memset(&mHeader, 0, sizeof(mHeader)); + mCodecDelay = 0; + mSeekPreRoll = 0; + mSamplesToDiscard = 0; + mInputBufferCount = 0; + mSignalledError = false; + mSignalledOutputEos = false; + + return C2_OK; +} + +void C2SoftOpusDec::onReset() { + (void)onStop(); +} + +void C2SoftOpusDec::onRelease() { + if (mDecoder) { + opus_multistream_decoder_destroy(mDecoder); + mDecoder = nullptr; + } +} + +status_t C2SoftOpusDec::initDecoder() { + memset(&mHeader, 0, sizeof(mHeader)); + mCodecDelay = 0; + mSeekPreRoll = 0; + mSamplesToDiscard = 0; + mInputBufferCount = 0; + mSignalledError = false; + mSignalledOutputEos = false; + + return OK; +} + +c2_status_t C2SoftOpusDec::onFlush_sm() { + if (mDecoder) { + opus_multistream_decoder_ctl(mDecoder, OPUS_RESET_STATE); + mSamplesToDiscard = mSeekPreRoll; + mSignalledOutputEos = false; + } + return C2_OK; +} + +c2_status_t C2SoftOpusDec::drain( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool) { + (void) pool; + if (drainMode == NO_DRAIN) { + ALOGW("drain with NO_DRAIN: no-op"); + return C2_OK; + } + if (drainMode == DRAIN_CHAIN) { + ALOGW("DRAIN_CHAIN not supported"); + return C2_OMITTED; + } + + return C2_OK; +} + +static void fillEmptyWork(const std::unique_ptr<C2Work> &work) { + 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; +} + +static const int kRate = 48000; + +// Opus uses Vorbis channel mapping, and Vorbis channel mapping specifies +// mappings for up to 8 channels. This information is part of the Vorbis I +// Specification: +// http://www.xiph.org/vorbis/doc/Vorbis_I_spec.html +static const int kMaxChannels = 8; + +// Maximum packet size used in Xiph's opusdec. +static const int kMaxOpusOutputPacketSizeSamples = 960 * 6; + +// Default audio output channel layout. Used to initialize |stream_map| in +// OpusHeader, and passed to opus_multistream_decoder_create() when the header +// does not contain mapping information. The values are valid only for mono and +// stereo output: Opus streams with more than 2 channels require a stream map. +static const int kMaxChannelsWithDefaultLayout = 2; +static const uint8_t kDefaultOpusChannelLayout[kMaxChannelsWithDefaultLayout] = { 0, 1 }; + + +// Convert nanoseconds to number of samples. +static uint64_t ns_to_samples(uint64_t ns, int rate) { + return static_cast<double>(ns) * rate / 1000000000; +} + +void C2SoftOpusDec::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.configUpdate.clear(); + work->worklets.front()->output.flags = work->input.flags; + + if (mSignalledError || mSignalledOutputEos) { + work->result = C2_BAD_VALUE; + return; + } + + bool eos = ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0); + size_t inOffset = 0u; + size_t inSize = 0u; + C2ReadView rView = mDummyReadView; + if (!work->input.buffers.empty()) { + rView = work->input.buffers[0]->data().linearBlocks().front().map().get(); + inSize = rView.capacity(); + if (inSize && rView.error()) { + ALOGE("read view map failed %d", rView.error()); + work->result = C2_CORRUPTED; + return; + } + } + if (inSize == 0) { + fillEmptyWork(work); + if (eos) { + mSignalledOutputEos = true; + ALOGV("signalled EOS"); + } + return; + } + + ALOGV("in buffer attr. size %zu timestamp %d frameindex %d", inSize, + (int)work->input.ordinal.timestamp.peeku(), (int)work->input.ordinal.frameIndex.peeku()); + const uint8_t *data = rView.data() + inOffset; + if (mInputBufferCount < 3) { + if (mInputBufferCount == 0) { + size_t opusHeadSize = 0; + size_t codecDelayBufSize = 0; + size_t seekPreRollBufSize = 0; + void *opusHeadBuf = NULL; + void *codecDelayBuf = NULL; + void *seekPreRollBuf = NULL; + + if (!GetOpusHeaderBuffers(data, inSize, &opusHeadBuf, + &opusHeadSize, &codecDelayBuf, + &codecDelayBufSize, &seekPreRollBuf, + &seekPreRollBufSize)) { + ALOGE("%s encountered error in GetOpusHeaderBuffers", __func__); + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } + + if (!ParseOpusHeader((uint8_t *)opusHeadBuf, opusHeadSize, &mHeader)) { + ALOGE("%s Encountered error while Parsing Opus Header.", __func__); + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } + uint8_t channel_mapping[kMaxChannels] = {0}; + if (mHeader.channels <= kMaxChannelsWithDefaultLayout) { + memcpy(&channel_mapping, + kDefaultOpusChannelLayout, + kMaxChannelsWithDefaultLayout); + } else { + memcpy(&channel_mapping, + mHeader.stream_map, + mHeader.channels); + } + int status = OPUS_INVALID_STATE; + mDecoder = opus_multistream_decoder_create(kRate, + mHeader.channels, + mHeader.num_streams, + mHeader.num_coupled, + channel_mapping, + &status); + if (!mDecoder || status != OPUS_OK) { + ALOGE("opus_multistream_decoder_create failed status = %s", + opus_strerror(status)); + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } + status = opus_multistream_decoder_ctl(mDecoder, + OPUS_SET_GAIN(mHeader.gain_db)); + if (status != OPUS_OK) { + ALOGE("Failed to set OPUS header gain; status = %s", + opus_strerror(status)); + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } + + if (codecDelayBuf && codecDelayBufSize == sizeof(uint64_t)) { + uint64_t value; + memcpy(&value, codecDelayBuf, sizeof(uint64_t)); + mCodecDelay = ns_to_samples(value, kRate); + mSamplesToDiscard = mCodecDelay; + ++mInputBufferCount; + } + if (seekPreRollBuf && seekPreRollBufSize == sizeof(uint64_t)) { + uint64_t value; + memcpy(&value, seekPreRollBuf, sizeof(uint64_t)); + mSeekPreRoll = ns_to_samples(value, kRate); + ++mInputBufferCount; + } + } else { + if (inSize < 8) { + ALOGE("Input sample size is too small."); + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } + int64_t samples = ns_to_samples( *(reinterpret_cast<int64_t*> + (const_cast<uint8_t *> (data))), kRate); + if (mInputBufferCount == 1) { + mCodecDelay = samples; + mSamplesToDiscard = mCodecDelay; + } + else { + mSeekPreRoll = samples; + } + } + + ++mInputBufferCount; + if (mInputBufferCount == 3) { + ALOGI("Configuring decoder: %d Hz, %d channels", + kRate, mHeader.channels); + C2StreamSampleRateInfo::output sampleRateInfo(0u, kRate); + C2StreamChannelCountInfo::output channelCountInfo(0u, mHeader.channels); + std::vector<std::unique_ptr<C2SettingResult>> failures; + c2_status_t err = mIntf->config( + { &sampleRateInfo, &channelCountInfo }, + C2_MAY_BLOCK, + &failures); + if (err == OK) { + work->worklets.front()->output.configUpdate.push_back(C2Param::Copy(sampleRateInfo)); + work->worklets.front()->output.configUpdate.push_back(C2Param::Copy(channelCountInfo)); + } else { + ALOGE("Config Update failed"); + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } + } + fillEmptyWork(work); + if (eos) { + mSignalledOutputEos = true; + ALOGV("signalled EOS"); + } + return; + } + + // Ignore CSD re-submissions. + if ((work->input.flags & C2FrameData::FLAG_CODEC_CONFIG)) { + fillEmptyWork(work); + return; + } + + // When seeking to zero, |mCodecDelay| samples has to be discarded + // instead of |mSeekPreRoll| samples (as we would when seeking to any + // other timestamp). + if (work->input.ordinal.timestamp.peeku() == 0) mSamplesToDiscard = mCodecDelay; + + std::shared_ptr<C2LinearBlock> block; + C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE }; + c2_status_t err = pool->fetchLinearBlock( + kMaxNumSamplesPerBuffer * kMaxChannels * sizeof(int16_t), + usage, &block); + if (err != C2_OK) { + ALOGE("fetchLinearBlock for Output failed with status %d", err); + work->result = C2_NO_MEMORY; + return; + } + C2WriteView wView = block->map().get(); + if (wView.error()) { + ALOGE("write view map failed %d", wView.error()); + work->result = C2_CORRUPTED; + return; + } + + int numSamples = opus_multistream_decode(mDecoder, + data, + inSize, + reinterpret_cast<int16_t *> (wView.data()), + kMaxOpusOutputPacketSizeSamples, + 0); + if (numSamples < 0) { + ALOGE("opus_multistream_decode returned numSamples %d", numSamples); + numSamples = 0; + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } + + int outOffset = 0; + if (mSamplesToDiscard > 0) { + if (mSamplesToDiscard > numSamples) { + mSamplesToDiscard -= numSamples; + numSamples = 0; + } else { + numSamples -= mSamplesToDiscard; + outOffset = mSamplesToDiscard * sizeof(int16_t) * mHeader.channels; + mSamplesToDiscard = 0; + } + } + + if (numSamples) { + int outSize = numSamples * sizeof(int16_t) * mHeader.channels; + ALOGV("out buffer attr. offset %d size %d ", outOffset, outSize); + + work->worklets.front()->output.flags = work->input.flags; + work->worklets.front()->output.buffers.clear(); + work->worklets.front()->output.buffers.push_back(createLinearBuffer(block, outOffset, outSize)); + work->worklets.front()->output.ordinal = work->input.ordinal; + work->workletsProcessed = 1u; + } else { + fillEmptyWork(work); + block.reset(); + } + if (eos) { + mSignalledOutputEos = true; + ALOGV("signalled EOS"); + } +} + +class C2SoftOpusDecFactory : public C2ComponentFactory { +public: + C2SoftOpusDecFactory() : mHelper(std::static_pointer_cast<C2ReflectorHelper>( + GetCodec2PlatformComponentStore()->getParamReflector())) { + } + + virtual c2_status_t createComponent( + c2_node_id_t id, + std::shared_ptr<C2Component>* const component, + std::function<void(C2Component*)> deleter) override { + *component = std::shared_ptr<C2Component>( + new C2SoftOpusDec(COMPONENT_NAME, + id, + std::make_shared<C2SoftOpusDec::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual c2_status_t createInterface( + c2_node_id_t id, + std::shared_ptr<C2ComponentInterface>* const interface, + std::function<void(C2ComponentInterface*)> deleter) override { + *interface = std::shared_ptr<C2ComponentInterface>( + new SimpleInterface<C2SoftOpusDec::IntfImpl>( + COMPONENT_NAME, id, std::make_shared<C2SoftOpusDec::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual ~C2SoftOpusDecFactory() override = default; + +private: + std::shared_ptr<C2ReflectorHelper> mHelper; +}; + +} // namespace android + +extern "C" ::C2ComponentFactory* CreateCodec2Factory() { + ALOGV("in %s", __func__); + return new ::android::C2SoftOpusDecFactory(); +} + +extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) { + ALOGV("in %s", __func__); + delete factory; +}
diff --git a/media/codec2/components/opus/C2SoftOpusDec.h b/media/codec2/components/opus/C2SoftOpusDec.h new file mode 100644 index 0000000..b0715ac --- /dev/null +++ b/media/codec2/components/opus/C2SoftOpusDec.h
@@ -0,0 +1,69 @@ +/* + * 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_C2_SOFT_OPUS_DEC_H_ +#define ANDROID_C2_SOFT_OPUS_DEC_H_ + +#include <SimpleC2Component.h> + + +struct OpusMSDecoder; + +namespace android { + +struct C2SoftOpusDec : public SimpleC2Component { + class IntfImpl; + + C2SoftOpusDec(const char *name, c2_node_id_t id, + const std::shared_ptr<IntfImpl> &intfImpl); + virtual ~C2SoftOpusDec(); + + // From SimpleC2Component + c2_status_t onInit() override; + c2_status_t onStop() override; + void onReset() override; + void onRelease() override; + c2_status_t onFlush_sm() override; + void process( + const std::unique_ptr<C2Work> &work, + const std::shared_ptr<C2BlockPool> &pool) override; + c2_status_t drain( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool) override; +private: + enum { + kMaxNumSamplesPerBuffer = 960 * 6 + }; + + std::shared_ptr<IntfImpl> mIntf; + OpusMSDecoder *mDecoder; + OpusHeader mHeader; + + int64_t mCodecDelay; + int64_t mSeekPreRoll; + int64_t mSamplesToDiscard; + size_t mInputBufferCount; + bool mSignalledError; + bool mSignalledOutputEos; + + status_t initDecoder(); + + C2_DO_NOT_COPY(C2SoftOpusDec); +}; + +} // namespace android + +#endif // ANDROID_C2_SOFT_OPUS_DEC_H_
diff --git a/media/codec2/components/opus/C2SoftOpusEnc.cpp b/media/codec2/components/opus/C2SoftOpusEnc.cpp new file mode 100644 index 0000000..70d1965 --- /dev/null +++ b/media/codec2/components/opus/C2SoftOpusEnc.cpp
@@ -0,0 +1,637 @@ +/* + * 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 "C2SoftOpusEnc" +#include <utils/Log.h> + +#include <C2PlatformSupport.h> +#include <SimpleC2Interface.h> +#include <media/stagefright/foundation/MediaDefs.h> +#include <media/stagefright/foundation/OpusHeader.h> +#include "C2SoftOpusEnc.h" + +extern "C" { + #include <opus.h> + #include <opus_multistream.h> +} + +#define DEFAULT_FRAME_DURATION_MS 20 +namespace android { + +namespace { + +constexpr char COMPONENT_NAME[] = "c2.android.opus.encoder"; + +} // namespace + +static const int kMaxNumChannelsSupported = 2; + +class C2SoftOpusEnc::IntfImpl : public SimpleInterface<void>::BaseParams { +public: + explicit IntfImpl(const std::shared_ptr<C2ReflectorHelper> &helper) + : SimpleInterface<void>::BaseParams( + helper, + COMPONENT_NAME, + C2Component::KIND_ENCODER, + C2Component::DOMAIN_AUDIO, + MEDIA_MIMETYPE_AUDIO_OPUS) { + noPrivateBuffers(); + noInputReferences(); + noOutputReferences(); + noInputLatency(); + noTimeStretch(); + setDerivedInstance(this); + + addParameter( + DefineParam(mAttrib, C2_PARAMKEY_COMPONENT_ATTRIBUTES) + .withConstValue(new C2ComponentAttributesSetting( + C2Component::ATTRIB_IS_TEMPORAL)) + .build()); + + addParameter( + DefineParam(mSampleRate, C2_PARAMKEY_SAMPLE_RATE) + .withDefault(new C2StreamSampleRateInfo::input(0u, 48000)) + .withFields({C2F(mSampleRate, value).oneOf({ + 8000, 12000, 16000, 24000, 48000})}) + .withSetter((Setter<decltype(*mSampleRate)>::StrictValueWithNoDeps)) + .build()); + + addParameter( + DefineParam(mChannelCount, C2_PARAMKEY_CHANNEL_COUNT) + .withDefault(new C2StreamChannelCountInfo::input(0u, 1)) + .withFields({C2F(mChannelCount, value).inRange(1, kMaxNumChannelsSupported)}) + .withSetter((Setter<decltype(*mChannelCount)>::StrictValueWithNoDeps)) + .build()); + + addParameter( + DefineParam(mBitrate, C2_PARAMKEY_BITRATE) + .withDefault(new C2StreamBitrateInfo::output(0u, 128000)) + .withFields({C2F(mBitrate, value).inRange(500, 512000)}) + .withSetter(Setter<decltype(*mBitrate)>::NonStrictValueWithNoDeps) + .build()); + + addParameter( + DefineParam(mComplexity, C2_PARAMKEY_COMPLEXITY) + .withDefault(new C2StreamComplexityTuning::output(0u, 10)) + .withFields({C2F(mComplexity, value).inRange(1, 10)}) + .withSetter(Setter<decltype(*mComplexity)>::NonStrictValueWithNoDeps) + .build()); + + addParameter( + DefineParam(mInputMaxBufSize, C2_PARAMKEY_INPUT_MAX_BUFFER_SIZE) + .withConstValue(new C2StreamMaxBufferSizeInfo::input(0u, 3840)) + .build()); + } + + uint32_t getSampleRate() const { return mSampleRate->value; } + uint32_t getChannelCount() const { return mChannelCount->value; } + uint32_t getBitrate() const { return mBitrate->value; } + uint32_t getComplexity() const { return mComplexity->value; } + +private: + std::shared_ptr<C2StreamSampleRateInfo::input> mSampleRate; + std::shared_ptr<C2StreamChannelCountInfo::input> mChannelCount; + std::shared_ptr<C2StreamBitrateInfo::output> mBitrate; + std::shared_ptr<C2StreamComplexityTuning::output> mComplexity; + std::shared_ptr<C2StreamMaxBufferSizeInfo::input> mInputMaxBufSize; +}; + +C2SoftOpusEnc::C2SoftOpusEnc(const char* name, c2_node_id_t id, + const std::shared_ptr<IntfImpl>& intfImpl) + : SimpleC2Component( + std::make_shared<SimpleInterface<IntfImpl>>(name, id, intfImpl)), + mIntf(intfImpl), + mOutputBlock(nullptr), + mEncoder(nullptr), + mInputBufferPcm16(nullptr), + mOutIndex(0u) { +} + +C2SoftOpusEnc::~C2SoftOpusEnc() { + onRelease(); +} + +c2_status_t C2SoftOpusEnc::onInit() { + return initEncoder(); +} + +c2_status_t C2SoftOpusEnc::configureEncoder() { + static const unsigned char mono_mapping[256] = {0}; + static const unsigned char stereo_mapping[256] = {0, 1}; + mSampleRate = mIntf->getSampleRate(); + mChannelCount = mIntf->getChannelCount(); + uint32_t bitrate = mIntf->getBitrate(); + int complexity = mIntf->getComplexity(); + mNumSamplesPerFrame = mSampleRate / (1000 / mFrameDurationMs); + mNumPcmBytesPerInputFrame = + mChannelCount * mNumSamplesPerFrame * sizeof(int16_t); + int err = C2_OK; + + const unsigned char* mapping; + if (mChannelCount == 1) { + mapping = mono_mapping; + } else if (mChannelCount == 2) { + mapping = stereo_mapping; + } else { + ALOGE("Number of channels (%d) is not supported", mChannelCount); + return C2_BAD_VALUE; + } + + if (mEncoder != nullptr) { + opus_multistream_encoder_destroy(mEncoder); + } + + mEncoder = opus_multistream_encoder_create(mSampleRate, mChannelCount, + 1, mChannelCount - 1, mapping, OPUS_APPLICATION_AUDIO, &err); + if (err) { + ALOGE("Could not create libopus encoder. Error code: %i", err); + return C2_CORRUPTED; + } + + // Complexity + if (opus_multistream_encoder_ctl( + mEncoder, OPUS_SET_COMPLEXITY(complexity)) != OPUS_OK) { + ALOGE("failed to set complexity"); + return C2_BAD_VALUE; + } + + // DTX + if (opus_multistream_encoder_ctl(mEncoder, OPUS_SET_DTX(0) != OPUS_OK)) { + ALOGE("failed to set dtx"); + return C2_BAD_VALUE; + } + + // Application + if (opus_multistream_encoder_ctl(mEncoder, + OPUS_SET_APPLICATION(OPUS_APPLICATION_AUDIO)) != OPUS_OK) { + ALOGE("failed to set application"); + return C2_BAD_VALUE; + } + + // Signal type + if (opus_multistream_encoder_ctl(mEncoder, OPUS_SET_SIGNAL(OPUS_AUTO)) != + OPUS_OK) { + ALOGE("failed to set signal"); + return C2_BAD_VALUE; + } + + // Constrained VBR + if (opus_multistream_encoder_ctl(mEncoder, OPUS_SET_VBR(1) != OPUS_OK)) { + ALOGE("failed to set vbr type"); + return C2_BAD_VALUE; + } + if (opus_multistream_encoder_ctl(mEncoder, OPUS_SET_VBR_CONSTRAINT(1) != + OPUS_OK)) { + ALOGE("failed to set vbr constraint"); + return C2_BAD_VALUE; + } + + // Bitrate + if (opus_multistream_encoder_ctl(mEncoder, OPUS_SET_BITRATE(bitrate)) != + OPUS_OK) { + ALOGE("failed to set bitrate"); + return C2_BAD_VALUE; + } + + // Set seek preroll to 80 ms + mSeekPreRoll = 80000000; + return C2_OK; +} + +c2_status_t C2SoftOpusEnc::initEncoder() { + mSignalledEos = false; + mSignalledError = false; + mHeaderGenerated = false; + mIsFirstFrame = true; + mEncoderFlushed = false; + mBufferAvailable = false; + mAnchorTimeStamp = 0ull; + mProcessedSamples = 0; + mFilledLen = 0; + mFrameDurationMs = DEFAULT_FRAME_DURATION_MS; + if (!mInputBufferPcm16) { + mInputBufferPcm16 = + (int16_t*)malloc(kFrameSize * kMaxNumChannels * sizeof(int16_t)); + } + if (!mInputBufferPcm16) return C2_NO_MEMORY; + + /* Default Configurations */ + c2_status_t status = configureEncoder(); + return status; +} + +c2_status_t C2SoftOpusEnc::onStop() { + mSignalledEos = false; + mSignalledError = false; + mIsFirstFrame = true; + mEncoderFlushed = false; + mBufferAvailable = false; + mAnchorTimeStamp = 0ull; + mProcessedSamples = 0u; + mFilledLen = 0; + if (mEncoder) { + int status = opus_multistream_encoder_ctl(mEncoder, OPUS_RESET_STATE); + if (status != OPUS_OK) { + ALOGE("OPUS_RESET_STATE failed status = %s", opus_strerror(status)); + mSignalledError = true; + return C2_CORRUPTED; + } + } + if (mOutputBlock) mOutputBlock.reset(); + mOutputBlock = nullptr; + + return C2_OK; +} + +void C2SoftOpusEnc::onReset() { + (void)onStop(); +} + +void C2SoftOpusEnc::onRelease() { + (void)onStop(); + if (mInputBufferPcm16) { + free(mInputBufferPcm16); + mInputBufferPcm16 = nullptr; + } + if (mEncoder) { + opus_multistream_encoder_destroy(mEncoder); + mEncoder = nullptr; + } +} + +c2_status_t C2SoftOpusEnc::onFlush_sm() { + return onStop(); +} + +// Drain the encoder to get last frames (if any) +int C2SoftOpusEnc::drainEncoder(uint8_t* outPtr) { + memset((uint8_t *)mInputBufferPcm16 + mFilledLen, 0, + (mNumPcmBytesPerInputFrame - mFilledLen)); + int encodedBytes = opus_multistream_encode( + mEncoder, mInputBufferPcm16, mNumSamplesPerFrame, outPtr, kMaxPayload); + if (encodedBytes > mOutputBlock->capacity()) { + ALOGE("not enough space left to write encoded data, dropping %d bytes", + mBytesEncoded); + // a fatal error would stop the encoding + return -1; + } + ALOGV("encoded %i Opus bytes from %zu PCM bytes", encodedBytes, + mNumPcmBytesPerInputFrame); + mEncoderFlushed = true; + mFilledLen = 0; + return encodedBytes; +} + +void C2SoftOpusEnc::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 || mSignalledEos) { + work->result = C2_BAD_VALUE; + return; + } + + bool eos = (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0; + C2ReadView rView = mDummyReadView; + size_t inOffset = 0u; + size_t inSize = 0u; + c2_status_t err = C2_OK; + if (!work->input.buffers.empty()) { + rView = + work->input.buffers[0]->data().linearBlocks().front().map().get(); + inSize = rView.capacity(); + if (inSize && rView.error()) { + ALOGE("read view map failed %d", rView.error()); + work->result = C2_CORRUPTED; + return; + } + } + + ALOGV("in buffer attr. size %zu timestamp %d frameindex %d, flags %x", + inSize, (int)work->input.ordinal.timestamp.peeku(), + (int)work->input.ordinal.frameIndex.peeku(), work->input.flags); + + if (!mEncoder) { + if (initEncoder() != C2_OK) { + ALOGE("initEncoder failed with status %d", err); + work->result = err; + mSignalledError = true; + return; + } + } + if (mIsFirstFrame && inSize > 0) { + mAnchorTimeStamp = work->input.ordinal.timestamp.peekull(); + mIsFirstFrame = false; + } + + C2MemoryUsage usage = {C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE}; + err = pool->fetchLinearBlock(kMaxPayload, usage, &mOutputBlock); + if (err != C2_OK) { + ALOGE("fetchLinearBlock for Output failed with status %d", err); + work->result = C2_NO_MEMORY; + return; + } + + C2WriteView wView = mOutputBlock->map().get(); + if (wView.error()) { + ALOGE("write view map failed %d", wView.error()); + work->result = C2_CORRUPTED; + mOutputBlock.reset(); + return; + } + + size_t inPos = 0; + size_t processSize = 0; + mBytesEncoded = 0; + uint64_t outTimeStamp = 0u; + std::shared_ptr<C2Buffer> buffer; + uint64_t inputIndex = work->input.ordinal.frameIndex.peeku(); + const uint8_t* inPtr = rView.data() + inOffset; + + class FillWork { + public: + FillWork(uint32_t flags, C2WorkOrdinalStruct ordinal, + const std::shared_ptr<C2Buffer> &buffer) + : mFlags(flags), mOrdinal(ordinal), mBuffer(buffer) { + } + ~FillWork() = default; + + void operator()(const std::unique_ptr<C2Work>& work) { + work->worklets.front()->output.flags = (C2FrameData::flags_t)mFlags; + work->worklets.front()->output.buffers.clear(); + work->worklets.front()->output.ordinal = mOrdinal; + work->workletsProcessed = 1u; + work->result = C2_OK; + if (mBuffer) { + work->worklets.front()->output.buffers.push_back(mBuffer); + } + ALOGV("timestamp = %lld, index = %lld, w/%s buffer", + mOrdinal.timestamp.peekll(), + mOrdinal.frameIndex.peekll(), + mBuffer ? "" : "o"); + } + + private: + const uint32_t mFlags; + const C2WorkOrdinalStruct mOrdinal; + const std::shared_ptr<C2Buffer> mBuffer; + }; + + C2WorkOrdinalStruct outOrdinal = work->input.ordinal; + + if (!mHeaderGenerated) { + uint8_t header[AOPUS_UNIFIED_CSD_MAXSIZE]; + memset(header, 0, sizeof(header)); + + // Get codecDelay + int32_t lookahead; + if (opus_multistream_encoder_ctl(mEncoder, OPUS_GET_LOOKAHEAD(&lookahead)) != + OPUS_OK) { + ALOGE("failed to get lookahead"); + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } + mCodecDelay = lookahead * 1000000000ll / mSampleRate; + + OpusHeader opusHeader; + memset(&opusHeader, 0, sizeof(opusHeader)); + opusHeader.channels = mChannelCount; + opusHeader.num_streams = mChannelCount; + opusHeader.num_coupled = 0; + opusHeader.channel_mapping = ((mChannelCount > 8) ? 255 : (mChannelCount > 2)); + opusHeader.gain_db = 0; + opusHeader.skip_samples = lookahead; + int headerLen = WriteOpusHeaders(opusHeader, mSampleRate, header, + sizeof(header), mCodecDelay, mSeekPreRoll); + + std::unique_ptr<C2StreamInitDataInfo::output> csd = + C2StreamInitDataInfo::output::AllocUnique(headerLen, 0u); + if (!csd) { + ALOGE("CSD allocation failed"); + mSignalledError = true; + work->result = C2_NO_MEMORY; + return; + } + ALOGV("put csd, %d bytes", headerLen); + memcpy(csd->m.value, header, headerLen); + work->worklets.front()->output.configUpdate.push_back(std::move(csd)); + mHeaderGenerated = true; + } + + /* + * For buffer size which is not a multiple of mNumPcmBytesPerInputFrame, we will + * accumulate the input and keep it. Once the input is filled with expected number + * of bytes, we will send it to encoder. mFilledLen manages the bytes of input yet + * to be processed. The next call will fill mNumPcmBytesPerInputFrame - mFilledLen + * bytes to input and send it to the encoder. + */ + while (inPos < inSize) { + const uint8_t* pcmBytes = inPtr + inPos; + int filledSamples = mFilledLen / sizeof(int16_t); + if ((inPos + (mNumPcmBytesPerInputFrame - mFilledLen)) <= inSize) { + processSize = mNumPcmBytesPerInputFrame - mFilledLen; + mBufferAvailable = true; + } else { + processSize = inSize - inPos; + mBufferAvailable = false; + if (eos) { + memset(mInputBufferPcm16 + filledSamples, 0, + (mNumPcmBytesPerInputFrame - mFilledLen)); + mBufferAvailable = true; + } + } + const unsigned nInputSamples = processSize / sizeof(int16_t); + + for (unsigned i = 0; i < nInputSamples; i++) { + int32_t data = pcmBytes[2 * i + 1] << 8 | pcmBytes[2 * i]; + data = ((data & 0xFFFF) ^ 0x8000) - 0x8000; + mInputBufferPcm16[i + filledSamples] = data; + } + inPos += processSize; + mFilledLen += processSize; + if (!mBufferAvailable) break; + uint8_t* outPtr = wView.data() + mBytesEncoded; + int encodedBytes = + opus_multistream_encode(mEncoder, mInputBufferPcm16, + mNumSamplesPerFrame, outPtr, kMaxPayload - mBytesEncoded); + ALOGV("encoded %i Opus bytes from %zu PCM bytes", encodedBytes, + processSize); + + if (encodedBytes < 0 || encodedBytes > (kMaxPayload - mBytesEncoded)) { + ALOGE("opus_encode failed, encodedBytes : %d", encodedBytes); + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } + if (buffer) { + outOrdinal.frameIndex = mOutIndex++; + outOrdinal.timestamp = mAnchorTimeStamp + outTimeStamp; + cloneAndSend( + inputIndex, work, + FillWork(C2FrameData::FLAG_INCOMPLETE, outOrdinal, buffer)); + buffer.reset(); + } + if (encodedBytes > 0) { + buffer = + createLinearBuffer(mOutputBlock, mBytesEncoded, encodedBytes); + } + mBytesEncoded += encodedBytes; + mProcessedSamples += (filledSamples + nInputSamples); + outTimeStamp = + mProcessedSamples * 1000000ll / mChannelCount / mSampleRate; + if ((processSize + mFilledLen) < mNumPcmBytesPerInputFrame) + mEncoderFlushed = true; + mFilledLen = 0; + } + + uint32_t flags = 0; + if (eos) { + ALOGV("signalled eos"); + mSignalledEos = true; + if (!mEncoderFlushed) { + if (buffer) { + outOrdinal.frameIndex = mOutIndex++; + outOrdinal.timestamp = mAnchorTimeStamp + outTimeStamp; + cloneAndSend( + inputIndex, work, + FillWork(C2FrameData::FLAG_INCOMPLETE, outOrdinal, buffer)); + buffer.reset(); + } + // drain the encoder for last buffer + drainInternal(pool, work); + } + flags = C2FrameData::FLAG_END_OF_STREAM; + } + if (buffer) { + outOrdinal.frameIndex = mOutIndex++; + outOrdinal.timestamp = mAnchorTimeStamp + outTimeStamp; + FillWork((C2FrameData::flags_t)(flags), outOrdinal, buffer)(work); + buffer.reset(); + } + mOutputBlock = nullptr; +} + +c2_status_t C2SoftOpusEnc::drainInternal( + const std::shared_ptr<C2BlockPool>& pool, + const std::unique_ptr<C2Work>& work) { + mBytesEncoded = 0; + std::shared_ptr<C2Buffer> buffer = nullptr; + C2WorkOrdinalStruct outOrdinal = work->input.ordinal; + bool eos = (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0; + + C2MemoryUsage usage = {C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE}; + c2_status_t err = pool->fetchLinearBlock(kMaxPayload, usage, &mOutputBlock); + if (err != C2_OK) { + ALOGE("fetchLinearBlock for Output failed with status %d", err); + return C2_NO_MEMORY; + } + + C2WriteView wView = mOutputBlock->map().get(); + if (wView.error()) { + ALOGE("write view map failed %d", wView.error()); + mOutputBlock.reset(); + return C2_CORRUPTED; + } + + int encBytes = drainEncoder(wView.data()); + if (encBytes > 0) mBytesEncoded += encBytes; + if (mBytesEncoded > 0) { + buffer = createLinearBuffer(mOutputBlock, 0, mBytesEncoded); + mOutputBlock.reset(); + } + mProcessedSamples += (mNumPcmBytesPerInputFrame / sizeof(int16_t)); + uint64_t outTimeStamp = + mProcessedSamples * 1000000ll / mChannelCount / mSampleRate; + outOrdinal.frameIndex = mOutIndex++; + outOrdinal.timestamp = mAnchorTimeStamp + outTimeStamp; + work->worklets.front()->output.flags = + (C2FrameData::flags_t)(eos ? C2FrameData::FLAG_END_OF_STREAM : 0); + work->worklets.front()->output.buffers.clear(); + work->worklets.front()->output.ordinal = outOrdinal; + work->workletsProcessed = 1u; + work->result = C2_OK; + if (buffer) { + work->worklets.front()->output.buffers.push_back(buffer); + } + mOutputBlock = nullptr; + return C2_OK; +} + +c2_status_t C2SoftOpusEnc::drain(uint32_t drainMode, + const std::shared_ptr<C2BlockPool>& pool) { + if (drainMode == NO_DRAIN) { + ALOGW("drain with NO_DRAIN: no-op"); + return C2_OK; + } + if (drainMode == DRAIN_CHAIN) { + ALOGW("DRAIN_CHAIN not supported"); + return C2_OMITTED; + } + mIsFirstFrame = true; + mAnchorTimeStamp = 0ull; + mProcessedSamples = 0u; + return drainInternal(pool, nullptr); +} + +class C2SoftOpusEncFactory : public C2ComponentFactory { +public: + C2SoftOpusEncFactory() + : mHelper(std::static_pointer_cast<C2ReflectorHelper>( + GetCodec2PlatformComponentStore()->getParamReflector())) {} + + virtual c2_status_t createComponent( + c2_node_id_t id, std::shared_ptr<C2Component>* const component, + std::function<void(C2Component*)> deleter) override { + *component = std::shared_ptr<C2Component>( + new C2SoftOpusEnc( + COMPONENT_NAME, id, + std::make_shared<C2SoftOpusEnc::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual c2_status_t createInterface( + c2_node_id_t id, std::shared_ptr<C2ComponentInterface>* const interface, + std::function<void(C2ComponentInterface*)> deleter) override { + *interface = std::shared_ptr<C2ComponentInterface>( + new SimpleInterface<C2SoftOpusEnc::IntfImpl>( + COMPONENT_NAME, id, + std::make_shared<C2SoftOpusEnc::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual ~C2SoftOpusEncFactory() override = default; +private: + std::shared_ptr<C2ReflectorHelper> mHelper; +}; + +} // namespace android + +extern "C" ::C2ComponentFactory* CreateCodec2Factory() { + ALOGV("in %s", __func__); + return new ::android::C2SoftOpusEncFactory(); +} + +extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) { + ALOGV("in %s", __func__); + delete factory; +}
diff --git a/media/codec2/components/opus/C2SoftOpusEnc.h b/media/codec2/components/opus/C2SoftOpusEnc.h new file mode 100644 index 0000000..2b4d8f2 --- /dev/null +++ b/media/codec2/components/opus/C2SoftOpusEnc.h
@@ -0,0 +1,92 @@ +/* + * 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_C2_SOFT_OPUS_ENC_H_ +#define ANDROID_C2_SOFT_OPUS_ENC_H_ + +#include <atomic> +#include <SimpleC2Component.h> +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) + +struct OpusMSEncoder; + +namespace android { + +struct C2SoftOpusEnc : public SimpleC2Component { + class IntfImpl; + + C2SoftOpusEnc(const char *name, c2_node_id_t id, + const std::shared_ptr<IntfImpl> &intfImpl); + virtual ~C2SoftOpusEnc(); + + // From SimpleC2Component + c2_status_t onInit() override; + c2_status_t onStop() override; + void onReset() override; + void onRelease() override; + c2_status_t onFlush_sm() override; + void process( + const std::unique_ptr<C2Work> &work, + const std::shared_ptr<C2BlockPool> &pool) override; + c2_status_t drain( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool) override; +private: + /* OPUS_FRAMESIZE_20_MS */ + const int kFrameSize = 960; + const int kMaxSampleRate = 48000; + const int kMinSampleRate = 8000; + const int kMaxPayload = (4000 * kMaxSampleRate) / kMinSampleRate; + const int kMaxNumChannels = 8; + + std::shared_ptr<IntfImpl> mIntf; + std::shared_ptr<C2LinearBlock> mOutputBlock; + + OpusMSEncoder* mEncoder; + int16_t* mInputBufferPcm16; + + bool mHeaderGenerated; + bool mIsFirstFrame; + bool mEncoderFlushed; + bool mBufferAvailable; + bool mSignalledEos; + bool mSignalledError; + uint32_t mSampleRate; + uint32_t mChannelCount; + uint32_t mFrameDurationMs; + uint64_t mAnchorTimeStamp; + uint64_t mProcessedSamples; + // Codec delay in ns + uint64_t mCodecDelay; + // Seek pre-roll in ns + uint64_t mSeekPreRoll; + int mNumSamplesPerFrame; + int mBytesEncoded; + int32_t mFilledLen; + size_t mNumPcmBytesPerInputFrame; + std::atomic_uint64_t mOutIndex; + c2_status_t initEncoder(); + c2_status_t configureEncoder(); + int drainEncoder(uint8_t* outPtr); + c2_status_t drainInternal(const std::shared_ptr<C2BlockPool>& pool, + const std::unique_ptr<C2Work>& work); + + C2_DO_NOT_COPY(C2SoftOpusEnc); +}; + +} // namespace android + +#endif // ANDROID_C2_SOFT_OPUS_ENC_H_
diff --git a/media/codec2/components/raw/Android.bp b/media/codec2/components/raw/Android.bp new file mode 100644 index 0000000..dc944da --- /dev/null +++ b/media/codec2/components/raw/Android.bp
@@ -0,0 +1,9 @@ +cc_library_shared { + name: "libcodec2_soft_rawdec", + defaults: [ + "libcodec2_soft-defaults", + "libcodec2_soft_sanitize_all-defaults", + ], + + srcs: ["C2SoftRawDec.cpp"], +}
diff --git a/media/codec2/components/raw/C2SoftRawDec.cpp b/media/codec2/components/raw/C2SoftRawDec.cpp new file mode 100644 index 0000000..95eb909 --- /dev/null +++ b/media/codec2/components/raw/C2SoftRawDec.cpp
@@ -0,0 +1,226 @@ +/* + * 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 "C2SoftRawDec" +#include <log/log.h> + +#include <media/stagefright/foundation/MediaDefs.h> + +#include <C2PlatformSupport.h> +#include <SimpleC2Interface.h> + +#include "C2SoftRawDec.h" + +namespace android { + +namespace { + +constexpr char COMPONENT_NAME[] = "c2.android.raw.decoder"; + +} // namespace + +class C2SoftRawDec::IntfImpl : public SimpleInterface<void>::BaseParams { +public: + explicit IntfImpl(const std::shared_ptr<C2ReflectorHelper> &helper) + : SimpleInterface<void>::BaseParams( + helper, + COMPONENT_NAME, + C2Component::KIND_DECODER, + C2Component::DOMAIN_AUDIO, + MEDIA_MIMETYPE_AUDIO_RAW) { + noPrivateBuffers(); + noInputReferences(); + noOutputReferences(); + noInputLatency(); + noTimeStretch(); + setDerivedInstance(this); + + addParameter( + DefineParam(mAttrib, C2_PARAMKEY_COMPONENT_ATTRIBUTES) + .withConstValue(new C2ComponentAttributesSetting( + C2Component::ATTRIB_IS_TEMPORAL)) + .build()); + + addParameter( + DefineParam(mSampleRate, C2_PARAMKEY_SAMPLE_RATE) + .withDefault(new C2StreamSampleRateInfo::output(0u, 44100)) + .withFields({C2F(mSampleRate, value).inRange(8000, 384000)}) + .withSetter((Setter<decltype(*mSampleRate)>::StrictValueWithNoDeps)) + .build()); + + addParameter( + DefineParam(mChannelCount, C2_PARAMKEY_CHANNEL_COUNT) + .withDefault(new C2StreamChannelCountInfo::output(0u, 2)) + .withFields({C2F(mChannelCount, value).inRange(1, 8)}) + .withSetter(Setter<decltype(*mChannelCount)>::StrictValueWithNoDeps) + .build()); + + addParameter( + DefineParam(mBitrate, C2_PARAMKEY_BITRATE) + .withDefault(new C2StreamBitrateInfo::input(0u, 64000)) + .withFields({C2F(mBitrate, value).inRange(1, 98304000)}) + .withSetter(Setter<decltype(*mBitrate)>::NonStrictValueWithNoDeps) + .build()); + + addParameter( + 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: + std::shared_ptr<C2StreamSampleRateInfo::output> mSampleRate; + std::shared_ptr<C2StreamChannelCountInfo::output> mChannelCount; + std::shared_ptr<C2StreamBitrateInfo::input> mBitrate; + std::shared_ptr<C2StreamMaxBufferSizeInfo::input> mInputMaxBufSize; + std::shared_ptr<C2StreamPcmEncodingInfo::output> mPcmEncodingInfo; +}; + +C2SoftRawDec::C2SoftRawDec( + const char *name, + c2_node_id_t id, + const std::shared_ptr<IntfImpl> &intfImpl) + : SimpleC2Component(std::make_shared<SimpleInterface<IntfImpl>>(name, id, intfImpl)), + mIntf(intfImpl) { +} + +C2SoftRawDec::~C2SoftRawDec() { + onRelease(); +} + +c2_status_t C2SoftRawDec::onInit() { + mSignalledEos = false; + return C2_OK; +} + +c2_status_t C2SoftRawDec::onStop() { + mSignalledEos = false; + return C2_OK; +} + +void C2SoftRawDec::onReset() { + (void)onStop(); +} + +void C2SoftRawDec::onRelease() { +} + +c2_status_t C2SoftRawDec::onFlush_sm() { + return onStop(); +} + +void C2SoftRawDec::process( + const std::unique_ptr<C2Work> &work, + const std::shared_ptr<C2BlockPool> &pool) { + (void)pool; + work->result = C2_OK; + work->workletsProcessed = 1u; + + if (mSignalledEos) { + work->result = C2_BAD_VALUE; + return; + } + + ALOGV("in buffer attr. timestamp %d frameindex %d", + (int)work->input.ordinal.timestamp.peeku(), (int)work->input.ordinal.frameIndex.peeku()); + + work->worklets.front()->output.flags = work->input.flags; + work->worklets.front()->output.buffers.clear(); + work->worklets.front()->output.ordinal = work->input.ordinal; + if (!work->input.buffers.empty()) { + work->worklets.front()->output.buffers.push_back(work->input.buffers[0]); + } + if (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) { + mSignalledEos = true; + ALOGV("signalled EOS"); + } +} + +c2_status_t C2SoftRawDec::drain( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool) { + (void) pool; + if (drainMode == NO_DRAIN) { + ALOGW("drain with NO_DRAIN: no-op"); + return C2_OK; + } + if (drainMode == DRAIN_CHAIN) { + ALOGW("DRAIN_CHAIN not supported"); + return C2_OMITTED; + } + + return C2_OK; +} + +class C2SoftRawDecFactory : public C2ComponentFactory { +public: + C2SoftRawDecFactory() : mHelper(std::static_pointer_cast<C2ReflectorHelper>( + GetCodec2PlatformComponentStore()->getParamReflector())) { + } + + virtual c2_status_t createComponent( + c2_node_id_t id, + std::shared_ptr<C2Component>* const component, + std::function<void(C2Component*)> deleter) override { + *component = std::shared_ptr<C2Component>( + new C2SoftRawDec(COMPONENT_NAME, + id, + std::make_shared<C2SoftRawDec::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual c2_status_t createInterface( + c2_node_id_t id, + std::shared_ptr<C2ComponentInterface>* const interface, + std::function<void(C2ComponentInterface*)> deleter) override { + *interface = std::shared_ptr<C2ComponentInterface>( + new SimpleInterface<C2SoftRawDec::IntfImpl>( + COMPONENT_NAME, id, std::make_shared<C2SoftRawDec::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual ~C2SoftRawDecFactory() override = default; + +private: + std::shared_ptr<C2ReflectorHelper> mHelper; +}; + +} // namespace android + +extern "C" ::C2ComponentFactory* CreateCodec2Factory() { + ALOGV("in %s", __func__); + return new ::android::C2SoftRawDecFactory(); +} + +extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) { + ALOGV("in %s", __func__); + delete factory; +}
diff --git a/media/codec2/components/raw/C2SoftRawDec.h b/media/codec2/components/raw/C2SoftRawDec.h new file mode 100644 index 0000000..7dfdec5 --- /dev/null +++ b/media/codec2/components/raw/C2SoftRawDec.h
@@ -0,0 +1,53 @@ +/* + * 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_C2_SOFT_RAW_DEC_H_ +#define ANDROID_C2_SOFT_RAW_DEC_H_ + +#include <SimpleC2Component.h> + + +namespace android { + +struct C2SoftRawDec : public SimpleC2Component { + class IntfImpl; + + C2SoftRawDec(const char* name, c2_node_id_t id, + const std::shared_ptr<IntfImpl>& intfImpl); + virtual ~C2SoftRawDec(); + + // From SimpleC2Component + c2_status_t onInit() override; + c2_status_t onStop() override; + void onReset() override; + void onRelease() override; + c2_status_t onFlush_sm() override; + void process( + const std::unique_ptr<C2Work> &work, + const std::shared_ptr<C2BlockPool> &pool) override; + c2_status_t drain( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool) override; +private: + std::shared_ptr<IntfImpl> mIntf; + bool mSignalledEos; + + C2_DO_NOT_COPY(C2SoftRawDec); +}; + +} // namespace android + +#endif // ANDROID_C2_SOFT_RAW_DEC_H_
diff --git a/media/common_time/MODULE_LICENSE_APACHE2 b/media/codec2/components/raw/MODULE_LICENSE_APACHE2 similarity index 100% copy from media/common_time/MODULE_LICENSE_APACHE2 copy to media/codec2/components/raw/MODULE_LICENSE_APACHE2
diff --git a/media/common_time/NOTICE b/media/codec2/components/raw/NOTICE similarity index 100% copy from media/common_time/NOTICE copy to media/codec2/components/raw/NOTICE
diff --git a/media/codec2/components/vorbis/Android.bp b/media/codec2/components/vorbis/Android.bp new file mode 100644 index 0000000..a5f485d --- /dev/null +++ b/media/codec2/components/vorbis/Android.bp
@@ -0,0 +1,11 @@ +cc_library_shared { + name: "libcodec2_soft_vorbisdec", + defaults: [ + "libcodec2_soft-defaults", + "libcodec2_soft_sanitize_all-defaults", + ], + + srcs: ["C2SoftVorbisDec.cpp"], + + shared_libs: ["libvorbisidec"], +}
diff --git a/media/codec2/components/vorbis/C2SoftVorbisDec.cpp b/media/codec2/components/vorbis/C2SoftVorbisDec.cpp new file mode 100644 index 0000000..18e6db2 --- /dev/null +++ b/media/codec2/components/vorbis/C2SoftVorbisDec.cpp
@@ -0,0 +1,484 @@ +/* + * 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 "C2SoftVorbisDec" +#include <log/log.h> + +#include <media/stagefright/foundation/MediaDefs.h> + +#include <C2PlatformSupport.h> +#include <SimpleC2Interface.h> + +#include "C2SoftVorbisDec.h" + +extern "C" { + #include <Tremolo/codec_internal.h> + + int _vorbis_unpack_books(vorbis_info *vi,oggpack_buffer *opb); + int _vorbis_unpack_info(vorbis_info *vi,oggpack_buffer *opb); + int _vorbis_unpack_comment(vorbis_comment *vc,oggpack_buffer *opb); +} + +namespace android { + +namespace { + +constexpr char COMPONENT_NAME[] = "c2.android.vorbis.decoder"; + +} // namespace + +class C2SoftVorbisDec::IntfImpl : public SimpleInterface<void>::BaseParams { +public: + explicit IntfImpl(const std::shared_ptr<C2ReflectorHelper> &helper) + : SimpleInterface<void>::BaseParams( + helper, + COMPONENT_NAME, + C2Component::KIND_DECODER, + C2Component::DOMAIN_AUDIO, + MEDIA_MIMETYPE_AUDIO_VORBIS) { + noPrivateBuffers(); + noInputReferences(); + noOutputReferences(); + noInputLatency(); + noTimeStretch(); + setDerivedInstance(this); + + addParameter( + DefineParam(mAttrib, C2_PARAMKEY_COMPONENT_ATTRIBUTES) + .withConstValue(new C2ComponentAttributesSetting( + C2Component::ATTRIB_IS_TEMPORAL)) + .build()); + + addParameter( + DefineParam(mSampleRate, C2_PARAMKEY_SAMPLE_RATE) + .withDefault(new C2StreamSampleRateInfo::output(0u, 48000)) + .withFields({C2F(mSampleRate, value).inRange(8000, 96000)}) + .withSetter((Setter<decltype(*mSampleRate)>::StrictValueWithNoDeps)) + .build()); + + addParameter( + DefineParam(mChannelCount, C2_PARAMKEY_CHANNEL_COUNT) + .withDefault(new C2StreamChannelCountInfo::output(0u, 1)) + .withFields({C2F(mChannelCount, value).inRange(1, 8)}) + .withSetter(Setter<decltype(*mChannelCount)>::StrictValueWithNoDeps) + .build()); + + addParameter( + DefineParam(mBitrate, C2_PARAMKEY_BITRATE) + .withDefault(new C2StreamBitrateInfo::input(0u, 64000)) + .withFields({C2F(mBitrate, value).inRange(32000, 500000)}) + .withSetter(Setter<decltype(*mBitrate)>::NonStrictValueWithNoDeps) + .build()); + + addParameter( + DefineParam(mInputMaxBufSize, C2_PARAMKEY_INPUT_MAX_BUFFER_SIZE) + .withConstValue(new C2StreamMaxBufferSizeInfo::input(0u, 8192 * 2 * sizeof(int16_t))) + .build()); + } + +private: + std::shared_ptr<C2StreamSampleRateInfo::output> mSampleRate; + std::shared_ptr<C2StreamChannelCountInfo::output> mChannelCount; + std::shared_ptr<C2StreamBitrateInfo::input> mBitrate; + std::shared_ptr<C2StreamMaxBufferSizeInfo::input> mInputMaxBufSize; +}; + +C2SoftVorbisDec::C2SoftVorbisDec( + const char *name, + c2_node_id_t id, + const std::shared_ptr<IntfImpl> &intfImpl) + : SimpleC2Component(std::make_shared<SimpleInterface<IntfImpl>>(name, id, intfImpl)), + mIntf(intfImpl), + mState(nullptr), + mVi(nullptr) { +} + +C2SoftVorbisDec::~C2SoftVorbisDec() { + onRelease(); +} + +c2_status_t C2SoftVorbisDec::onInit() { + status_t err = initDecoder(); + return err == OK ? C2_OK : C2_NO_MEMORY; +} + +c2_status_t C2SoftVorbisDec::onStop() { + if (mState) { + vorbis_dsp_clear(mState); + delete mState; + mState = nullptr; + } + + if (mVi) { + vorbis_info_clear(mVi); + delete mVi; + mVi = nullptr; + } + mNumFramesLeftOnPage = -1; + mSignalledOutputEos = false; + mSignalledError = false; + + return (initDecoder() == OK ? C2_OK : C2_CORRUPTED); +} + +void C2SoftVorbisDec::onReset() { + (void)onStop(); +} + +void C2SoftVorbisDec::onRelease() { + if (mState) { + vorbis_dsp_clear(mState); + delete mState; + mState = nullptr; + } + + if (mVi) { + vorbis_info_clear(mVi); + delete mVi; + mVi = nullptr; + } +} + +status_t C2SoftVorbisDec::initDecoder() { + mVi = new vorbis_info{}; + if (!mVi) return NO_MEMORY; + vorbis_info_clear(mVi); + + mState = new vorbis_dsp_state{}; + if (!mState) return NO_MEMORY; + vorbis_dsp_clear(mState); + + mNumFramesLeftOnPage = -1; + mSignalledError = false; + mSignalledOutputEos = false; + mInfoUnpacked = false; + mBooksUnpacked = false; + return OK; +} + +c2_status_t C2SoftVorbisDec::onFlush_sm() { + mNumFramesLeftOnPage = -1; + mSignalledOutputEos = false; + if (mState) vorbis_dsp_restart(mState); + + return C2_OK; +} + +c2_status_t C2SoftVorbisDec::drain( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool) { + (void) pool; + if (drainMode == NO_DRAIN) { + ALOGW("drain with NO_DRAIN: no-op"); + return C2_OK; + } + if (drainMode == DRAIN_CHAIN) { + ALOGW("DRAIN_CHAIN not supported"); + return C2_OMITTED; + } + + return C2_OK; +} + +static void fillEmptyWork(const std::unique_ptr<C2Work> &work) { + 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; +} + +static void makeBitReader( + const void *data, size_t size, + ogg_buffer *buf, ogg_reference *ref, oggpack_buffer *bits) { + buf->data = (uint8_t *)data; + buf->size = size; + buf->refcount = 1; + buf->ptr.owner = nullptr; + + ref->buffer = buf; + ref->begin = 0; + ref->length = size; + ref->next = nullptr; + + oggpack_readinit(bits, ref); +} + +// (CHECK!) multiframe is tricky. decode call doesnt return the number of bytes +// consumed by the component. Also it is unclear why numPageFrames is being +// tagged at the end of input buffers for new pages. Refer lines 297-300 in +// SimpleDecodingSource.cpp +void C2SoftVorbisDec::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.configUpdate.clear(); + work->worklets.front()->output.flags = work->input.flags; + + if (mSignalledError || mSignalledOutputEos) { + work->result = C2_BAD_VALUE; + return; + } + + bool eos = ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0); + size_t inOffset = 0u; + size_t inSize = 0u; + C2ReadView rView = mDummyReadView; + if (!work->input.buffers.empty()) { + rView = work->input.buffers[0]->data().linearBlocks().front().map().get(); + inSize = rView.capacity(); + if (inSize && rView.error()) { + ALOGE("read view map failed %d", rView.error()); + work->result = rView.error(); + return; + } + } + + if (inSize == 0) { + fillEmptyWork(work); + if (eos) { + mSignalledOutputEos = true; + ALOGV("signalled EOS"); + } + return; + } + + ALOGV("in buffer attr. size %zu timestamp %d frameindex %d", inSize, + (int)work->input.ordinal.timestamp.peeku(), (int)work->input.ordinal.frameIndex.peeku()); + const uint8_t *data = rView.data() + inOffset; + int32_t numChannels = mVi->channels; + int32_t samplingRate = mVi->rate; + /* Decode vorbis headers only once */ + if (inSize > 7 && !memcmp(&data[1], "vorbis", 6) && (!mInfoUnpacked || !mBooksUnpacked)) { + if ((data[0] != 1) && (data[0] != 5)) { + ALOGE("unexpected type received %d", data[0]); + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } + + ogg_buffer buf; + ogg_reference ref; + oggpack_buffer bits; + + // skip 7 <type + "vorbis"> bytes + makeBitReader((const uint8_t *)data + 7, inSize - 7, &buf, &ref, &bits); + if (data[0] == 1) { + vorbis_info_init(mVi); + if (0 != _vorbis_unpack_info(mVi, &bits)) { + ALOGE("Encountered error while unpacking info"); + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } + if (mVi->rate != samplingRate || + mVi->channels != numChannels) { + ALOGV("vorbis: rate/channels changed: %ld/%d", mVi->rate, mVi->channels); + samplingRate = mVi->rate; + numChannels = mVi->channels; + + C2StreamSampleRateInfo::output sampleRateInfo(0u, samplingRate); + C2StreamChannelCountInfo::output channelCountInfo(0u, numChannels); + std::vector<std::unique_ptr<C2SettingResult>> failures; + c2_status_t err = mIntf->config( + { &sampleRateInfo, &channelCountInfo }, + C2_MAY_BLOCK, + &failures); + if (err == OK) { + work->worklets.front()->output.configUpdate.push_back(C2Param::Copy(sampleRateInfo)); + work->worklets.front()->output.configUpdate.push_back(C2Param::Copy(channelCountInfo)); + } else { + ALOGE("Config Update failed"); + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } + } + mInfoUnpacked = true; + } else { + if (!mInfoUnpacked) { + ALOGE("Data with type:5 sent before sending type:1"); + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } + if (0 != _vorbis_unpack_books(mVi, &bits)) { + ALOGE("Encountered error while unpacking books"); + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } + if (0 != vorbis_dsp_init(mState, mVi)) { + ALOGE("Encountered error while dsp init"); + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } + mBooksUnpacked = true; + } + fillEmptyWork(work); + if (eos) { + mSignalledOutputEos = true; + ALOGV("signalled EOS"); + } + return; + } + + if (!mInfoUnpacked || !mBooksUnpacked) { + ALOGE("Missing CODEC_CONFIG data mInfoUnpacked: %d mBooksUnpack %d", mInfoUnpacked, mBooksUnpacked); + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } + + int32_t numPageFrames = 0; + if (inSize < sizeof(numPageFrames)) { + ALOGE("input header has size %zu, expected %zu", inSize, sizeof(numPageFrames)); + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } + memcpy(&numPageFrames, data + inSize - sizeof(numPageFrames), sizeof(numPageFrames)); + inSize -= sizeof(numPageFrames); + if (numPageFrames >= 0) { + mNumFramesLeftOnPage = numPageFrames; + } + + ogg_buffer buf; + buf.data = const_cast<unsigned char*>(data); + buf.size = inSize; + buf.refcount = 1; + buf.ptr.owner = nullptr; + + ogg_reference ref; + ref.buffer = &buf; + ref.begin = 0; + ref.length = buf.size; + ref.next = nullptr; + + ogg_packet pack; + pack.packet = &ref; + pack.bytes = ref.length; + pack.b_o_s = 0; + pack.e_o_s = 0; + pack.granulepos = 0; + pack.packetno = 0; + + size_t maxSamplesInBuffer = kMaxNumSamplesPerChannel * mVi->channels; + size_t outCapacity = maxSamplesInBuffer * sizeof(int16_t); + std::shared_ptr<C2LinearBlock> block; + C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE }; + c2_status_t err = pool->fetchLinearBlock(outCapacity, usage, &block); + if (err != C2_OK) { + ALOGE("fetchLinearBlock for Output failed with status %d", err); + work->result = C2_NO_MEMORY; + return; + } + C2WriteView wView = block->map().get(); + if (wView.error()) { + ALOGE("write view map failed %d", wView.error()); + work->result = wView.error(); + return; + } + + int numFrames = 0; + int ret = vorbis_dsp_synthesis(mState, &pack, 1); + if (0 != ret) { + ALOGD("vorbis_dsp_synthesis returned %d; ignored", ret); + } else { + numFrames = vorbis_dsp_pcmout( + mState, reinterpret_cast<int16_t *> (wView.data()), + kMaxNumSamplesPerChannel); + if (numFrames < 0) { + ALOGD("vorbis_dsp_pcmout returned %d", numFrames); + numFrames = 0; + } + } + + if (mNumFramesLeftOnPage >= 0) { + if (numFrames > mNumFramesLeftOnPage) { + ALOGV("discarding %d frames at end of page", numFrames - mNumFramesLeftOnPage); + numFrames = mNumFramesLeftOnPage; + } + mNumFramesLeftOnPage -= numFrames; + } + + if (numFrames) { + int outSize = numFrames * sizeof(int16_t) * mVi->channels; + + work->worklets.front()->output.flags = work->input.flags; + 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; + } else { + fillEmptyWork(work); + block.reset(); + } + if (eos) { + mSignalledOutputEos = true; + ALOGV("signalled EOS"); + } +} + +class C2SoftVorbisDecFactory : public C2ComponentFactory { +public: + C2SoftVorbisDecFactory() : mHelper(std::static_pointer_cast<C2ReflectorHelper>( + GetCodec2PlatformComponentStore()->getParamReflector())) { + } + + virtual c2_status_t createComponent( + c2_node_id_t id, + std::shared_ptr<C2Component>* const component, + std::function<void(C2Component*)> deleter) override { + *component = std::shared_ptr<C2Component>( + new C2SoftVorbisDec(COMPONENT_NAME, + id, + std::make_shared<C2SoftVorbisDec::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual c2_status_t createInterface( + c2_node_id_t id, + std::shared_ptr<C2ComponentInterface>* const interface, + std::function<void(C2ComponentInterface*)> deleter) override { + *interface = std::shared_ptr<C2ComponentInterface>( + new SimpleInterface<C2SoftVorbisDec::IntfImpl>( + COMPONENT_NAME, id, std::make_shared<C2SoftVorbisDec::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual ~C2SoftVorbisDecFactory() override = default; + +private: + std::shared_ptr<C2ReflectorHelper> mHelper; +}; + +} // namespace android + +extern "C" ::C2ComponentFactory* CreateCodec2Factory() { + ALOGV("in %s", __func__); + return new ::android::C2SoftVorbisDecFactory(); +} + +extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) { + ALOGV("in %s", __func__); + delete factory; +}
diff --git a/media/codec2/components/vorbis/C2SoftVorbisDec.h b/media/codec2/components/vorbis/C2SoftVorbisDec.h new file mode 100644 index 0000000..3bf7326 --- /dev/null +++ b/media/codec2/components/vorbis/C2SoftVorbisDec.h
@@ -0,0 +1,70 @@ +/* + * 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_C2_SOFT_VORBIS_DEC_H_ +#define ANDROID_C2_SOFT_VORBIS_DEC_H_ + +#include <SimpleC2Component.h> + + +struct vorbis_dsp_state; +struct vorbis_info; + +namespace android { + +struct C2SoftVorbisDec : public SimpleC2Component { + class IntfImpl; + + C2SoftVorbisDec(const char *name, c2_node_id_t id, + const std::shared_ptr<IntfImpl> &intfImpl); + virtual ~C2SoftVorbisDec(); + + // From SimpleC2Component + c2_status_t onInit() override; + c2_status_t onStop() override; + void onReset() override; + void onRelease() override; + c2_status_t onFlush_sm() override; + void process( + const std::unique_ptr<C2Work> &work, + const std::shared_ptr<C2BlockPool> &pool) override; + c2_status_t drain( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool) override; + + private: + enum { + kMaxNumSamplesPerChannel = 8192, + }; + + std::shared_ptr<IntfImpl> mIntf; + vorbis_dsp_state *mState; + vorbis_info *mVi; + + int32_t mNumFramesLeftOnPage; + bool mSignalledError; + bool mSignalledOutputEos; + bool mInfoUnpacked; + bool mBooksUnpacked; + status_t initDecoder(); + + C2_DO_NOT_COPY(C2SoftVorbisDec); +}; + +} // namespace android + +#endif // ANDROID_C2_SOFT_VORBIS_DEC_H_ +
diff --git a/media/common_time/MODULE_LICENSE_APACHE2 b/media/codec2/components/vorbis/MODULE_LICENSE_APACHE2 similarity index 100% copy from media/common_time/MODULE_LICENSE_APACHE2 copy to media/codec2/components/vorbis/MODULE_LICENSE_APACHE2
diff --git a/media/common_time/NOTICE b/media/codec2/components/vorbis/NOTICE similarity index 100% copy from media/common_time/NOTICE copy to media/codec2/components/vorbis/NOTICE
diff --git a/media/codec2/components/vpx/Android.bp b/media/codec2/components/vpx/Android.bp new file mode 100644 index 0000000..34f5753 --- /dev/null +++ b/media/codec2/components/vpx/Android.bp
@@ -0,0 +1,60 @@ +cc_library_shared { + name: "libcodec2_soft_vp9dec", + defaults: [ + "libcodec2_soft-defaults", + "libcodec2_soft_sanitize_all-defaults", + ], + + srcs: ["C2SoftVpxDec.cpp"], + + shared_libs: ["libvpx"], + + cflags: [ + "-DVP9", + ], +} + +cc_library_shared { + name: "libcodec2_soft_vp8dec", + defaults: [ + "libcodec2_soft-defaults", + "libcodec2_soft_sanitize_all-defaults", + ], + + srcs: ["C2SoftVpxDec.cpp"], + + shared_libs: ["libvpx"], +} + +cc_library_shared { + name: "libcodec2_soft_vp9enc", + defaults: [ + "libcodec2_soft-defaults", + "libcodec2_soft_sanitize_all-defaults", + ], + + srcs: [ + "C2SoftVp9Enc.cpp", + "C2SoftVpxEnc.cpp", + ], + + shared_libs: ["libvpx"], + + cflags: ["-DVP9"], +} + +cc_library_shared { + name: "libcodec2_soft_vp8enc", + defaults: [ + "libcodec2_soft-defaults", + "libcodec2_soft_sanitize_all-defaults", + ], + + srcs: [ + "C2SoftVp8Enc.cpp", + "C2SoftVpxEnc.cpp", + ], + + shared_libs: ["libvpx"], +} +
diff --git a/media/codec2/components/vpx/C2SoftVp8Enc.cpp b/media/codec2/components/vpx/C2SoftVp8Enc.cpp new file mode 100644 index 0000000..f18f5d0 --- /dev/null +++ b/media/codec2/components/vpx/C2SoftVp8Enc.cpp
@@ -0,0 +1,112 @@ +/* + * Copyright 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 "C2SoftVp8Enc" +#include <utils/Log.h> +#include <utils/misc.h> + +#include "C2SoftVp8Enc.h" + +namespace android { + +C2SoftVp8Enc::C2SoftVp8Enc(const char* name, c2_node_id_t id, + const std::shared_ptr<IntfImpl>& intfImpl) + : C2SoftVpxEnc(name, id, intfImpl), mDCTPartitions(0), mProfile(1) {} + +void C2SoftVp8Enc::setCodecSpecificInterface() { + mCodecInterface = vpx_codec_vp8_cx(); +} + +void C2SoftVp8Enc::setCodecSpecificConfiguration() { + switch (mProfile) { + case 1: + mCodecConfiguration->g_profile = 0; + break; + + case 2: + mCodecConfiguration->g_profile = 1; + break; + + case 4: + mCodecConfiguration->g_profile = 2; + break; + + case 8: + mCodecConfiguration->g_profile = 3; + break; + + default: + mCodecConfiguration->g_profile = 0; + } +} + +vpx_codec_err_t C2SoftVp8Enc::setCodecSpecificControls() { + vpx_codec_err_t codec_return = vpx_codec_control(mCodecContext, + VP8E_SET_TOKEN_PARTITIONS, + mDCTPartitions); + if (codec_return != VPX_CODEC_OK) { + ALOGE("Error setting dct partitions for vpx encoder."); + } + return codec_return; +} + +class C2SoftVp8EncFactory : public C2ComponentFactory { +public: + C2SoftVp8EncFactory() + : mHelper(std::static_pointer_cast<C2ReflectorHelper>( + GetCodec2PlatformComponentStore()->getParamReflector())) {} + + virtual c2_status_t createComponent( + c2_node_id_t id, + std::shared_ptr<C2Component>* const component, + std::function<void(C2Component*)> deleter) override { + *component = std::shared_ptr<C2Component>( + new C2SoftVp8Enc(COMPONENT_NAME, id, + std::make_shared<C2SoftVpxEnc::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual c2_status_t createInterface( + c2_node_id_t id, + std::shared_ptr<C2ComponentInterface>* const interface, + std::function<void(C2ComponentInterface*)> deleter) override { + *interface = std::shared_ptr<C2ComponentInterface>( + new SimpleInterface<C2SoftVpxEnc::IntfImpl>( + COMPONENT_NAME, id, + std::make_shared<C2SoftVpxEnc::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual ~C2SoftVp8EncFactory() override = default; + +private: + std::shared_ptr<C2ReflectorHelper> mHelper; +}; + +} // namespace android + +extern "C" ::C2ComponentFactory* CreateCodec2Factory() { + ALOGV("in %s", __func__); + return new ::android::C2SoftVp8EncFactory(); +} + +extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) { + ALOGV("in %s", __func__); + delete factory; +}
diff --git a/media/codec2/components/vpx/C2SoftVp8Enc.h b/media/codec2/components/vpx/C2SoftVp8Enc.h new file mode 100644 index 0000000..ed6f356 --- /dev/null +++ b/media/codec2/components/vpx/C2SoftVp8Enc.h
@@ -0,0 +1,60 @@ +/* + * Copyright 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_C2_SOFT_VP8_ENC_H__ +#define ANDROID_C2_SOFT_VP8_ENC_H__ + +#include "C2SoftVpxEnc.h" + +namespace android { + +// Exposes vp8 encoder as a c2 Component +// +// In addition to the base class settings, Only following encoder settings are +// available: +// - token partitioning +struct C2SoftVp8Enc : public C2SoftVpxEnc { + C2SoftVp8Enc(const char* name, c2_node_id_t id, + const std::shared_ptr<IntfImpl>& intfImpl); + + protected: + // Populates |mCodecInterface| with codec specific settings. + virtual void setCodecSpecificInterface(); + + // Sets codec specific configuration. + virtual void setCodecSpecificConfiguration(); + + // Initializes codec specific encoder settings. + virtual vpx_codec_err_t setCodecSpecificControls(); + + private: + // Max value supported for DCT partitions + static const uint32_t kMaxDCTPartitions = 3; + + // vp8 specific configuration parameter + // that enables token partitioning of + // the stream into substreams + int32_t mDCTPartitions; + + // C2 Profile parameter + int32_t mProfile; + + C2_DO_NOT_COPY(C2SoftVp8Enc); +}; + +} // namespace android + +#endif // ANDROID_C2_SOFT_VP8_ENC_H__
diff --git a/media/codec2/components/vpx/C2SoftVp9Enc.cpp b/media/codec2/components/vpx/C2SoftVp9Enc.cpp new file mode 100644 index 0000000..740dbda --- /dev/null +++ b/media/codec2/components/vpx/C2SoftVp9Enc.cpp
@@ -0,0 +1,142 @@ +/* + * Copyright 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 "C2SoftVp9Enc" +#include <utils/Log.h> +#include <utils/misc.h> + +#include "C2SoftVp9Enc.h" + +namespace android { + +C2SoftVp9Enc::C2SoftVp9Enc(const char* name, c2_node_id_t id, + const std::shared_ptr<IntfImpl>& intfImpl) + : C2SoftVpxEnc(name, id, intfImpl), + mProfile(1), + mLevel(0), + mTileColumns(0), + mFrameParallelDecoding(false) { +} + +void C2SoftVp9Enc::setCodecSpecificInterface() { + mCodecInterface = vpx_codec_vp9_cx(); +} + +void C2SoftVp9Enc::setCodecSpecificConfiguration() { + switch (mProfile) { + case 1: + mCodecConfiguration->g_profile = 0; + break; + + case 2: + mCodecConfiguration->g_profile = 1; + break; + + case 4: + mCodecConfiguration->g_profile = 2; + break; + + case 8: + mCodecConfiguration->g_profile = 3; + break; + + default: + mCodecConfiguration->g_profile = 0; + } +} + +vpx_codec_err_t C2SoftVp9Enc::setCodecSpecificControls() { + vpx_codec_err_t codecReturn = vpx_codec_control( + mCodecContext, VP9E_SET_TILE_COLUMNS, mTileColumns); + if (codecReturn != VPX_CODEC_OK) { + ALOGE("Error setting VP9E_SET_TILE_COLUMNS to %d. vpx_codec_control() " + "returned %d", mTileColumns, codecReturn); + return codecReturn; + } + codecReturn = vpx_codec_control( + mCodecContext, VP9E_SET_FRAME_PARALLEL_DECODING, + mFrameParallelDecoding); + if (codecReturn != VPX_CODEC_OK) { + ALOGE("Error setting VP9E_SET_FRAME_PARALLEL_DECODING to %d." + "vpx_codec_control() returned %d", mFrameParallelDecoding, + codecReturn); + return codecReturn; + } + codecReturn = vpx_codec_control(mCodecContext, VP9E_SET_ROW_MT, 1); + if (codecReturn != VPX_CODEC_OK) { + ALOGE("Error setting VP9E_SET_ROW_MT to 1. vpx_codec_control() " + "returned %d", codecReturn); + return codecReturn; + } + + // For VP9, we always set CPU_USED to 8 (because the realtime default is 0 + // which is too slow). + codecReturn = vpx_codec_control(mCodecContext, VP8E_SET_CPUUSED, 8); + if (codecReturn != VPX_CODEC_OK) { + ALOGE("Error setting VP8E_SET_CPUUSED to 8. vpx_codec_control() " + "returned %d", codecReturn); + return codecReturn; + } + return codecReturn; +} + +class C2SoftVp9EncFactory : public C2ComponentFactory { +public: + C2SoftVp9EncFactory() + : mHelper(std::static_pointer_cast<C2ReflectorHelper>( + GetCodec2PlatformComponentStore()->getParamReflector())) {} + + virtual c2_status_t createComponent( + c2_node_id_t id, + std::shared_ptr<C2Component>* const component, + std::function<void(C2Component*)> deleter) override { + *component = std::shared_ptr<C2Component>( + new C2SoftVp9Enc(COMPONENT_NAME, id, + std::make_shared<C2SoftVpxEnc::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual c2_status_t createInterface( + c2_node_id_t id, + std::shared_ptr<C2ComponentInterface>* const interface, + std::function<void(C2ComponentInterface*)> deleter) override { + *interface = std::shared_ptr<C2ComponentInterface>( + new SimpleInterface<C2SoftVpxEnc::IntfImpl>( + COMPONENT_NAME, id, + std::make_shared<C2SoftVpxEnc::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual ~C2SoftVp9EncFactory() override = default; + +private: + std::shared_ptr<C2ReflectorHelper> mHelper; +}; + +} // namespace android + +extern "C" ::C2ComponentFactory* CreateCodec2Factory() { + ALOGV("in %s", __func__); + return new ::android::C2SoftVp9EncFactory(); +} + +extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) { + ALOGV("in %s", __func__); + delete factory; +}
diff --git a/media/codec2/components/vpx/C2SoftVp9Enc.h b/media/codec2/components/vpx/C2SoftVp9Enc.h new file mode 100644 index 0000000..77ef8fd --- /dev/null +++ b/media/codec2/components/vpx/C2SoftVp9Enc.h
@@ -0,0 +1,59 @@ +/* + * Copyright 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_C2_SOFT_VP9_ENC_H__ +#define ANDROID_C2_SOFT_VP9_ENC_H__ + +#include "C2SoftVpxEnc.h" + +namespace android { + +// Exposes vp9 encoder as a c2 Component +// +// In addition to the base class settings, Only following encoder settings are +// available: +// - tile rows +// - tile columns +// - frame parallel mode +struct C2SoftVp9Enc : public C2SoftVpxEnc { + C2SoftVp9Enc(const char* name, c2_node_id_t id, + const std::shared_ptr<IntfImpl>& intfImpl); + + protected: + // Populates |mCodecInterface| with codec specific settings. + virtual void setCodecSpecificInterface(); + + // Sets codec specific configuration. + virtual void setCodecSpecificConfiguration(); + + // Initializes codec specific encoder settings. + virtual vpx_codec_err_t setCodecSpecificControls(); + + private: + // C2 Profile & Level parameter + int32_t mProfile; + int32_t mLevel __unused; + + int32_t mTileColumns; + + bool mFrameParallelDecoding; + + C2_DO_NOT_COPY(C2SoftVp9Enc); +}; + +} // namespace android + +#endif // ANDROID_C2_SOFT_VP9_ENC_H__
diff --git a/media/codec2/components/vpx/C2SoftVpxDec.cpp b/media/codec2/components/vpx/C2SoftVpxDec.cpp new file mode 100644 index 0000000..a52ca15 --- /dev/null +++ b/media/codec2/components/vpx/C2SoftVpxDec.cpp
@@ -0,0 +1,939 @@ +/* + * 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 "C2SoftVpxDec" +#include <log/log.h> + +#include <algorithm> + +#include <media/stagefright/foundation/AUtils.h> +#include <media/stagefright/foundation/MediaDefs.h> + +#include <C2Debug.h> +#include <C2PlatformSupport.h> +#include <SimpleC2Interface.h> + +#include "C2SoftVpxDec.h" + +namespace android { + +#ifdef VP9 +constexpr char COMPONENT_NAME[] = "c2.android.vp9.decoder"; +#else +constexpr char COMPONENT_NAME[] = "c2.android.vp8.decoder"; +#endif + +class C2SoftVpxDec::IntfImpl : public SimpleInterface<void>::BaseParams { +public: + explicit IntfImpl(const std::shared_ptr<C2ReflectorHelper> &helper) + : SimpleInterface<void>::BaseParams( + helper, + COMPONENT_NAME, + C2Component::KIND_DECODER, + C2Component::DOMAIN_VIDEO, +#ifdef VP9 + MEDIA_MIMETYPE_VIDEO_VP9 +#else + MEDIA_MIMETYPE_VIDEO_VP8 +#endif + ) { + noPrivateBuffers(); // TODO: account for our buffers here + noInputReferences(); + noOutputReferences(); + noInputLatency(); + noTimeStretch(); + + // TODO: output latency and reordering + + addParameter( + DefineParam(mAttrib, C2_PARAMKEY_COMPONENT_ATTRIBUTES) + .withConstValue(new C2ComponentAttributesSetting(C2Component::ATTRIB_IS_TEMPORAL)) + .build()); + + addParameter( + DefineParam(mSize, C2_PARAMKEY_PICTURE_SIZE) + .withDefault(new C2StreamPictureSizeInfo::output(0u, 320, 240)) + .withFields({ + C2F(mSize, width).inRange(2, 2048, 2), + C2F(mSize, height).inRange(2, 2048, 2), + }) + .withSetter(SizeSetter) + .build()); + +#ifdef VP9 + // TODO: Add C2Config::PROFILE_VP9_2HDR ?? + addParameter( + DefineParam(mProfileLevel, C2_PARAMKEY_PROFILE_LEVEL) + .withDefault(new C2StreamProfileLevelInfo::input(0u, + C2Config::PROFILE_VP9_0, C2Config::LEVEL_VP9_5)) + .withFields({ + C2F(mProfileLevel, profile).oneOf({ + C2Config::PROFILE_VP9_0, + C2Config::PROFILE_VP9_2}), + C2F(mProfileLevel, level).oneOf({ + C2Config::LEVEL_VP9_1, + C2Config::LEVEL_VP9_1_1, + C2Config::LEVEL_VP9_2, + C2Config::LEVEL_VP9_2_1, + C2Config::LEVEL_VP9_3, + C2Config::LEVEL_VP9_3_1, + C2Config::LEVEL_VP9_4, + C2Config::LEVEL_VP9_4_1, + C2Config::LEVEL_VP9_5, + }) + }) + .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>(); + mHdrStaticInfo->mastering = { + .red = { .x = 0.708, .y = 0.292 }, + .green = { .x = 0.170, .y = 0.797 }, + .blue = { .x = 0.131, .y = 0.046 }, + .white = { .x = 0.3127, .y = 0.3290 }, + .maxLuminance = 1000, + .minLuminance = 0.1, + }; + mHdrStaticInfo->maxCll = 1000; + mHdrStaticInfo->maxFall = 120; + + mHdrStaticInfo->maxLuminance = 0; // disable static info + + helper->addStructDescriptors<C2MasteringDisplayColorVolumeStruct, C2ColorXyStruct>(); + addParameter( + DefineParam(mHdrStaticInfo, C2_PARAMKEY_HDR_STATIC_INFO) + .withDefault(mHdrStaticInfo) + .withFields({ + C2F(mHdrStaticInfo, mastering.red.x).inRange(0, 1), + // TODO + }) + .withSetter(HdrStaticInfoSetter) + .build()); +#endif +#else + addParameter( + DefineParam(mProfileLevel, C2_PARAMKEY_PROFILE_LEVEL) + .withConstValue(new C2StreamProfileLevelInfo::input(0u, + C2Config::PROFILE_UNUSED, C2Config::LEVEL_UNUSED)) + .build()); +#endif + + addParameter( + DefineParam(mMaxSize, C2_PARAMKEY_MAX_PICTURE_SIZE) + .withDefault(new C2StreamMaxPictureSizeTuning::output(0u, 320, 240)) + .withFields({ + C2F(mSize, width).inRange(2, 2048, 2), + C2F(mSize, height).inRange(2, 2048, 2), + }) + .withSetter(MaxPictureSizeSetter, mSize) + .build()); + + addParameter( + DefineParam(mMaxInputSize, C2_PARAMKEY_INPUT_MAX_BUFFER_SIZE) + .withDefault(new C2StreamMaxBufferSizeInfo::input(0u, 320 * 240 * 3 / 4)) + .withFields({ + C2F(mMaxInputSize, value).any(), + }) + .calculatedAs(MaxInputSizeSetter, mMaxSize) + .build()); + + C2ChromaOffsetStruct locations[1] = { C2ChromaOffsetStruct::ITU_YUV_420_0() }; + std::shared_ptr<C2StreamColorInfo::output> defaultColorInfo = + C2StreamColorInfo::output::AllocShared( + 1u, 0u, 8u /* bitDepth */, C2Color::YUV_420); + memcpy(defaultColorInfo->m.locations, locations, sizeof(locations)); + + defaultColorInfo = + C2StreamColorInfo::output::AllocShared( + { C2ChromaOffsetStruct::ITU_YUV_420_0() }, + 0u, 8u /* bitDepth */, C2Color::YUV_420); + helper->addStructDescriptors<C2ChromaOffsetStruct>(); + + addParameter( + DefineParam(mColorInfo, C2_PARAMKEY_CODED_COLOR_INFO) + .withConstValue(defaultColorInfo) + .build()); + + addParameter( + DefineParam(mDefaultColorAspects, C2_PARAMKEY_DEFAULT_COLOR_ASPECTS) + .withDefault(new C2StreamColorAspectsTuning::output( + 0u, C2Color::RANGE_UNSPECIFIED, C2Color::PRIMARIES_UNSPECIFIED, + C2Color::TRANSFER_UNSPECIFIED, C2Color::MATRIX_UNSPECIFIED)) + .withFields({ + C2F(mDefaultColorAspects, range).inRange( + C2Color::RANGE_UNSPECIFIED, C2Color::RANGE_OTHER), + C2F(mDefaultColorAspects, primaries).inRange( + C2Color::PRIMARIES_UNSPECIFIED, C2Color::PRIMARIES_OTHER), + C2F(mDefaultColorAspects, transfer).inRange( + C2Color::TRANSFER_UNSPECIFIED, C2Color::TRANSFER_OTHER), + C2F(mDefaultColorAspects, matrix).inRange( + C2Color::MATRIX_UNSPECIFIED, C2Color::MATRIX_OTHER) + }) + .withSetter(DefaultColorAspectsSetter) + .build()); + + // TODO: support more formats? + addParameter( + DefineParam(mPixelFormat, C2_PARAMKEY_PIXEL_FORMAT) + .withConstValue(new C2StreamPixelFormatInfo::output( + 0u, HAL_PIXEL_FORMAT_YCBCR_420_888)) + .build()); + } + + static C2R SizeSetter(bool mayBlock, const C2P<C2StreamPictureSizeInfo::output> &oldMe, + C2P<C2StreamPictureSizeInfo::output> &me) { + (void)mayBlock; + C2R res = C2R::Ok(); + if (!me.F(me.v.width).supportsAtAll(me.v.width)) { + res = res.plus(C2SettingResultBuilder::BadValue(me.F(me.v.width))); + me.set().width = oldMe.v.width; + } + if (!me.F(me.v.height).supportsAtAll(me.v.height)) { + res = res.plus(C2SettingResultBuilder::BadValue(me.F(me.v.height))); + me.set().height = oldMe.v.height; + } + return res; + } + + static C2R MaxPictureSizeSetter(bool mayBlock, C2P<C2StreamMaxPictureSizeTuning::output> &me, + const C2P<C2StreamPictureSizeInfo::output> &size) { + (void)mayBlock; + // TODO: get max width/height from the size's field helpers vs. hardcoding + me.set().width = c2_min(c2_max(me.v.width, size.v.width), 2048u); + me.set().height = c2_min(c2_max(me.v.height, size.v.height), 2048u); + return C2R::Ok(); + } + + static C2R MaxInputSizeSetter(bool mayBlock, C2P<C2StreamMaxBufferSizeInfo::input> &me, + const C2P<C2StreamMaxPictureSizeTuning::output> &maxSize) { + (void)mayBlock; + // assume compression ratio of 2 + me.set().value = (((maxSize.v.width + 63) / 64) * ((maxSize.v.height + 63) / 64) * 3072); + return C2R::Ok(); + } + + static C2R DefaultColorAspectsSetter(bool mayBlock, C2P<C2StreamColorAspectsTuning::output> &me) { + (void)mayBlock; + if (me.v.range > C2Color::RANGE_OTHER) { + me.set().range = C2Color::RANGE_OTHER; + } + if (me.v.primaries > C2Color::PRIMARIES_OTHER) { + me.set().primaries = C2Color::PRIMARIES_OTHER; + } + if (me.v.transfer > C2Color::TRANSFER_OTHER) { + me.set().transfer = C2Color::TRANSFER_OTHER; + } + if (me.v.matrix > C2Color::MATRIX_OTHER) { + me.set().matrix = C2Color::MATRIX_OTHER; + } + return C2R::Ok(); + } + + static C2R ProfileLevelSetter(bool mayBlock, C2P<C2StreamProfileLevelInfo::input> &me, + const C2P<C2StreamPictureSizeInfo::output> &size) { + (void)mayBlock; + (void)size; + (void)me; // TODO: validate + return C2R::Ok(); + } + std::shared_ptr<C2StreamColorAspectsTuning::output> getDefaultColorAspects_l() { + return mDefaultColorAspects; + } + + 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; + std::shared_ptr<C2StreamMaxPictureSizeTuning::output> mMaxSize; + std::shared_ptr<C2StreamMaxBufferSizeInfo::input> mMaxInputSize; + std::shared_ptr<C2StreamColorInfo::output> mColorInfo; + std::shared_ptr<C2StreamPixelFormatInfo::output> mPixelFormat; + std::shared_ptr<C2StreamColorAspectsTuning::output> mDefaultColorAspects; +#ifdef VP9 +#if 0 + std::shared_ptr<C2StreamHdrStaticInfo::output> mHdrStaticInfo; +#endif + std::shared_ptr<C2StreamHdr10PlusInfo::input> mHdr10PlusInfoInput; + std::shared_ptr<C2StreamHdr10PlusInfo::output> mHdr10PlusInfoOutput; +#endif +}; + +C2SoftVpxDec::ConverterThread::ConverterThread( + const std::shared_ptr<Mutexed<ConversionQueue>> &queue) + : Thread(false), mQueue(queue) {} + +bool C2SoftVpxDec::ConverterThread::threadLoop() { + Mutexed<ConversionQueue>::Locked queue(*mQueue); + if (queue->entries.empty()) { + queue.waitForCondition(queue->cond); + if (queue->entries.empty()) { + return true; + } + } + std::function<void()> convert = queue->entries.front(); + queue->entries.pop_front(); + if (!queue->entries.empty()) { + queue->cond.signal(); + } + queue.unlock(); + + convert(); + + queue.lock(); + if (--queue->numPending == 0u) { + queue->cond.broadcast(); + } + return true; +} + +C2SoftVpxDec::C2SoftVpxDec( + const char *name, + c2_node_id_t id, + const std::shared_ptr<IntfImpl> &intfImpl) + : SimpleC2Component(std::make_shared<SimpleInterface<IntfImpl>>(name, id, intfImpl)), + mIntf(intfImpl), + mCodecCtx(nullptr), + mCoreCount(1), + mQueue(new Mutexed<ConversionQueue>) { +} + +C2SoftVpxDec::~C2SoftVpxDec() { + onRelease(); +} + +c2_status_t C2SoftVpxDec::onInit() { + status_t err = initDecoder(); + return err == OK ? C2_OK : C2_CORRUPTED; +} + +c2_status_t C2SoftVpxDec::onStop() { + mSignalledError = false; + mSignalledOutputEos = false; + + return C2_OK; +} + +void C2SoftVpxDec::onReset() { + (void)onStop(); + c2_status_t err = onFlush_sm(); + if (err != C2_OK) + { + ALOGW("Failed to flush decoder. Try to hard reset decoder"); + destroyDecoder(); + (void)initDecoder(); + } +} + +void C2SoftVpxDec::onRelease() { + destroyDecoder(); +} + +c2_status_t C2SoftVpxDec::onFlush_sm() { + if (mFrameParallelMode) { + // Flush decoder by passing nullptr data ptr and 0 size. + // Ideally, this should never fail. + if (vpx_codec_decode(mCodecCtx, nullptr, 0, nullptr, 0)) { + ALOGE("Failed to flush on2 decoder."); + return C2_CORRUPTED; + } + } + + // Drop all the decoded frames in decoder. + vpx_codec_iter_t iter = nullptr; + while (vpx_codec_get_frame(mCodecCtx, &iter)) { + } + + mSignalledError = false; + mSignalledOutputEos = false; + return C2_OK; +} + +static int GetCPUCoreCount() { + int cpuCoreCount = 1; +#if defined(_SC_NPROCESSORS_ONLN) + cpuCoreCount = sysconf(_SC_NPROCESSORS_ONLN); +#else + // _SC_NPROC_ONLN must be defined... + cpuCoreCount = sysconf(_SC_NPROC_ONLN); +#endif + CHECK(cpuCoreCount >= 1); + ALOGV("Number of CPU cores: %d", cpuCoreCount); + return cpuCoreCount; +} + +status_t C2SoftVpxDec::initDecoder() { +#ifdef VP9 + mMode = MODE_VP9; +#else + mMode = MODE_VP8; +#endif + + mWidth = 320; + mHeight = 240; + mFrameParallelMode = false; + mSignalledOutputEos = false; + mSignalledError = false; + + 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)); + cfg.threads = mCoreCount = GetCPUCoreCount(); + + vpx_codec_flags_t flags; + memset(&flags, 0, sizeof(vpx_codec_flags_t)); + if (mFrameParallelMode) flags |= VPX_CODEC_USE_FRAME_THREADING; + + vpx_codec_err_t vpx_err; + if ((vpx_err = vpx_codec_dec_init( + mCodecCtx, mMode == MODE_VP8 ? &vpx_codec_vp8_dx_algo : &vpx_codec_vp9_dx_algo, + &cfg, flags))) { + ALOGE("on2 decoder failed to initialize. (%d)", vpx_err); + return UNKNOWN_ERROR; + } + + if (mMode == MODE_VP9) { + using namespace std::string_literals; + for (int i = 0; i < mCoreCount; ++i) { + sp<ConverterThread> thread(new ConverterThread(mQueue)); + mConverterThreads.push_back(thread); + if (thread->run(("vp9conv #"s + std::to_string(i)).c_str(), + ANDROID_PRIORITY_AUDIO) != OK) { + return UNKNOWN_ERROR; + } + } + } + + return OK; +} + +status_t C2SoftVpxDec::destroyDecoder() { + if (mCodecCtx) { + vpx_codec_destroy(mCodecCtx); + delete mCodecCtx; + mCodecCtx = nullptr; + } + bool running = true; + for (const sp<ConverterThread> &thread : mConverterThreads) { + thread->requestExit(); + } + while (running) { + mQueue->lock()->cond.broadcast(); + running = false; + for (const sp<ConverterThread> &thread : mConverterThreads) { + if (thread->isRunning()) { + running = true; + break; + } + } + } + mConverterThreads.clear(); + + return OK; +} + +void fillEmptyWork(const std::unique_ptr<C2Work> &work) { + uint32_t flags = 0; + if (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) { + flags |= C2FrameData::FLAG_END_OF_STREAM; + ALOGV("signalling eos"); + } + work->worklets.front()->output.flags = (C2FrameData::flags_t)flags; + work->worklets.front()->output.buffers.clear(); + work->worklets.front()->output.ordinal = work->input.ordinal; + work->workletsProcessed = 1u; +} + +void C2SoftVpxDec::finishWork(uint64_t index, const std::unique_ptr<C2Work> &work, + const std::shared_ptr<C2GraphicBlock> &block) { + std::shared_ptr<C2Buffer> buffer = createGraphicBuffer(block, + C2Rect(mWidth, mHeight)); + 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)) { + flags |= C2FrameData::FLAG_END_OF_STREAM; + ALOGV("signalling eos"); + } + work->worklets.front()->output.flags = (C2FrameData::flags_t)flags; + work->worklets.front()->output.buffers.clear(); + 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); + } else { + finish(index, fillWork); + } +} + +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; + } + + size_t inOffset = 0u; + size_t inSize = 0u; + C2ReadView rView = mDummyReadView; + if (!work->input.buffers.empty()) { + rView = work->input.buffers[0]->data().linearBlocks().front().map().get(); + inSize = rView.capacity(); + if (inSize && rView.error()) { + ALOGE("read view map failed %d", rView.error()); + work->result = C2_CORRUPTED; + return; + } + } + + bool codecConfig = ((work->input.flags & C2FrameData::FLAG_CODEC_CONFIG) !=0); + bool eos = ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0); + + ALOGV("in buffer attr. size %zu timestamp %d frameindex %d, flags %x", + inSize, (int)work->input.ordinal.timestamp.peeku(), + (int)work->input.ordinal.frameIndex.peeku(), work->input.flags); + + // Software VP9 Decoder does not need the Codec Specific Data (CSD) + // (specified in http://www.webmproject.org/vp9/profiles/). Ignore it if + // it was passed. + if (codecConfig) { + // Ignore CSD buffer for VP9. + if (mMode == MODE_VP9) { + fillEmptyWork(work); + return; + } else { + // Tolerate the CSD buffer for VP8. This is a workaround + // for b/28689536. continue + ALOGW("WARNING: Got CSD buffer for VP8. Continue"); + } + } + + int64_t frameIndex = work->input.ordinal.frameIndex.peekll(); + + if (inSize) { + uint8_t *bitstream = const_cast<uint8_t *>(rView.data() + inOffset); + vpx_codec_err_t err = vpx_codec_decode( + mCodecCtx, bitstream, inSize, &frameIndex, 0); + if (err != VPX_CODEC_OK) { + ALOGE("on2 decoder failed to decode frame. err: %d", err); + mSignalledError = true; + work->workletsProcessed = 1u; + work->result = C2_CORRUPTED; + return; + } + } + + (void)outputBuffer(pool, work); + + if (eos) { + drainInternal(DRAIN_COMPONENT_WITH_EOS, pool, work); + mSignalledOutputEos = true; + } else if (!inSize) { + fillEmptyWork(work); + } +} + +static void copyOutputBufferToYuvPlanarFrame( + uint8_t *dst, const uint8_t *srcY, const uint8_t *srcU, const uint8_t *srcV, + size_t srcYStride, size_t srcUStride, size_t srcVStride, + size_t dstYStride, size_t dstUVStride, + uint32_t width, uint32_t height) { + uint8_t *dstStart = dst; + + for (size_t i = 0; i < height; ++i) { + memcpy(dst, srcY, width); + srcY += srcYStride; + dst += dstYStride; + } + + dst = dstStart + dstYStride * height; + for (size_t i = 0; i < height / 2; ++i) { + memcpy(dst, srcV, width / 2); + srcV += srcVStride; + dst += dstUVStride; + } + + dst = dstStart + (dstYStride * height) + (dstUVStride * height / 2); + for (size_t i = 0; i < height / 2; ++i) { + memcpy(dst, srcU, width / 2); + srcU += srcUStride; + dst += dstUVStride; + } +} + +static void convertYUV420Planar16ToY410(uint32_t *dst, + const uint16_t *srcY, const uint16_t *srcU, const uint16_t *srcV, + size_t srcYStride, size_t srcUStride, size_t srcVStride, + size_t dstStride, size_t width, size_t height) { + + // Converting two lines at a time, slightly faster + for (size_t y = 0; y < height; y += 2) { + uint32_t *dstTop = (uint32_t *) dst; + uint32_t *dstBot = (uint32_t *) (dst + dstStride); + uint16_t *ySrcTop = (uint16_t*) srcY; + uint16_t *ySrcBot = (uint16_t*) (srcY + srcYStride); + uint16_t *uSrc = (uint16_t*) srcU; + uint16_t *vSrc = (uint16_t*) srcV; + + uint32_t u01, v01, y01, y23, y45, y67, uv0, uv1; + size_t x = 0; + for (; x < width - 3; x += 4) { + + u01 = *((uint32_t*)uSrc); uSrc += 2; + v01 = *((uint32_t*)vSrc); vSrc += 2; + + y01 = *((uint32_t*)ySrcTop); ySrcTop += 2; + y23 = *((uint32_t*)ySrcTop); ySrcTop += 2; + y45 = *((uint32_t*)ySrcBot); ySrcBot += 2; + y67 = *((uint32_t*)ySrcBot); ySrcBot += 2; + + uv0 = (u01 & 0x3FF) | ((v01 & 0x3FF) << 20); + uv1 = (u01 >> 16) | ((v01 >> 16) << 20); + + *dstTop++ = 3 << 30 | ((y01 & 0x3FF) << 10) | uv0; + *dstTop++ = 3 << 30 | ((y01 >> 16) << 10) | uv0; + *dstTop++ = 3 << 30 | ((y23 & 0x3FF) << 10) | uv1; + *dstTop++ = 3 << 30 | ((y23 >> 16) << 10) | uv1; + + *dstBot++ = 3 << 30 | ((y45 & 0x3FF) << 10) | uv0; + *dstBot++ = 3 << 30 | ((y45 >> 16) << 10) | uv0; + *dstBot++ = 3 << 30 | ((y67 & 0x3FF) << 10) | uv1; + *dstBot++ = 3 << 30 | ((y67 >> 16) << 10) | uv1; + } + + // There should be at most 2 more pixels to process. Note that we don't + // need to consider odd case as the buffer is always aligned to even. + if (x < width) { + u01 = *uSrc; + v01 = *vSrc; + y01 = *((uint32_t*)ySrcTop); + y45 = *((uint32_t*)ySrcBot); + uv0 = (u01 & 0x3FF) | ((v01 & 0x3FF) << 20); + *dstTop++ = ((y01 & 0x3FF) << 10) | uv0; + *dstTop++ = ((y01 >> 16) << 10) | uv0; + *dstBot++ = ((y45 & 0x3FF) << 10) | uv0; + *dstBot++ = ((y45 >> 16) << 10) | uv0; + } + + srcY += srcYStride * 2; + srcU += srcUStride; + srcV += srcVStride; + dst += dstStride * 2; + } + + return; +} + +static void convertYUV420Planar16ToYUV420Planar(uint8_t *dst, + const uint16_t *srcY, const uint16_t *srcU, const uint16_t *srcV, + size_t srcYStride, size_t srcUStride, size_t srcVStride, + size_t dstYStride, size_t dstUVStride, size_t width, size_t height) { + + uint8_t *dstY = (uint8_t *)dst; + size_t dstYSize = dstYStride * height; + size_t dstUVSize = dstUVStride * height / 2; + uint8_t *dstV = dstY + dstYSize; + uint8_t *dstU = dstV + dstUVSize; + + for (size_t y = 0; y < height; ++y) { + for (size_t x = 0; x < width; ++x) { + dstY[x] = (uint8_t)(srcY[x] >> 2); + } + + srcY += srcYStride; + dstY += dstYStride; + } + + for (size_t y = 0; y < (height + 1) / 2; ++y) { + for (size_t x = 0; x < (width + 1) / 2; ++x) { + dstU[x] = (uint8_t)(srcU[x] >> 2); + dstV[x] = (uint8_t)(srcV[x] >> 2); + } + + srcU += srcUStride; + srcV += srcVStride; + dstU += dstUVStride; + dstV += dstUVStride; + } + return; +} +bool C2SoftVpxDec::outputBuffer( + const std::shared_ptr<C2BlockPool> &pool, + const std::unique_ptr<C2Work> &work) +{ + if (!(work && pool)) return false; + + vpx_codec_iter_t iter = nullptr; + vpx_image_t *img = vpx_codec_get_frame(mCodecCtx, &iter); + + if (!img) return false; + + if (img->d_w != mWidth || img->d_h != mHeight) { + mWidth = img->d_w; + mHeight = img->d_h; + + C2StreamPictureSizeInfo::output size(0u, mWidth, mHeight); + std::vector<std::unique_ptr<C2SettingResult>> failures; + c2_status_t err = mIntf->config({&size}, C2_MAY_BLOCK, &failures); + if (err == C2_OK) { + work->worklets.front()->output.configUpdate.push_back( + C2Param::Copy(size)); + } else { + ALOGE("Config update size failed"); + mSignalledError = true; + work->workletsProcessed = 1u; + work->result = C2_CORRUPTED; + return false; + } + + } + CHECK(img->fmt == VPX_IMG_FMT_I420 || img->fmt == VPX_IMG_FMT_I42016); + + std::shared_ptr<C2GraphicBlock> block; + uint32_t format = HAL_PIXEL_FORMAT_YV12; + if (img->fmt == VPX_IMG_FMT_I42016) { + IntfImpl::Lock lock = mIntf->lock(); + std::shared_ptr<C2StreamColorAspectsTuning::output> defaultColorAspects = mIntf->getDefaultColorAspects_l(); + + if (defaultColorAspects->primaries == C2Color::PRIMARIES_BT2020 && + defaultColorAspects->matrix == C2Color::MATRIX_BT2020 && + defaultColorAspects->transfer == C2Color::TRANSFER_ST2084) { + format = HAL_PIXEL_FORMAT_RGBA_1010102; + } + } + C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE }; + c2_status_t err = pool->fetchGraphicBlock(align(mWidth, 16), mHeight, format, usage, &block); + if (err != C2_OK) { + ALOGE("fetchGraphicBlock for Output failed with status %d", err); + work->result = err; + return false; + } + + C2GraphicView wView = block->map().get(); + if (wView.error()) { + ALOGE("graphic view map failed %d", wView.error()); + work->result = C2_CORRUPTED; + return false; + } + + ALOGV("provided (%dx%d) required (%dx%d), out frameindex %d", + block->width(), block->height(), mWidth, mHeight, (int)*(int64_t *)img->user_priv); + + uint8_t *dst = const_cast<uint8_t *>(wView.data()[C2PlanarLayout::PLANE_Y]); + size_t srcYStride = img->stride[VPX_PLANE_Y]; + size_t srcUStride = img->stride[VPX_PLANE_U]; + size_t srcVStride = img->stride[VPX_PLANE_V]; + C2PlanarLayout layout = wView.layout(); + size_t dstYStride = layout.planes[C2PlanarLayout::PLANE_Y].rowInc; + size_t dstUVStride = layout.planes[C2PlanarLayout::PLANE_U].rowInc; + + if (img->fmt == VPX_IMG_FMT_I42016) { + const uint16_t *srcY = (const uint16_t *)img->planes[VPX_PLANE_Y]; + const uint16_t *srcU = (const uint16_t *)img->planes[VPX_PLANE_U]; + const uint16_t *srcV = (const uint16_t *)img->planes[VPX_PLANE_V]; + + if (format == HAL_PIXEL_FORMAT_RGBA_1010102) { + Mutexed<ConversionQueue>::Locked queue(*mQueue); + size_t i = 0; + constexpr size_t kHeight = 64; + for (; i < mHeight; i += kHeight) { + queue->entries.push_back( + [dst, srcY, srcU, srcV, + srcYStride, srcUStride, srcVStride, dstYStride, + width = mWidth, height = std::min(mHeight - i, kHeight)] { + convertYUV420Planar16ToY410( + (uint32_t *)dst, srcY, srcU, srcV, srcYStride / 2, + srcUStride / 2, srcVStride / 2, dstYStride / sizeof(uint32_t), + width, height); + }); + srcY += srcYStride / 2 * kHeight; + srcU += srcUStride / 2 * (kHeight / 2); + srcV += srcVStride / 2 * (kHeight / 2); + dst += dstYStride * kHeight; + } + CHECK_EQ(0u, queue->numPending); + queue->numPending = queue->entries.size(); + while (queue->numPending > 0) { + queue->cond.signal(); + queue.waitForCondition(queue->cond); + } + } else { + convertYUV420Planar16ToYUV420Planar(dst, srcY, srcU, srcV, srcYStride / 2, + srcUStride / 2, srcVStride / 2, + dstYStride, dstUVStride, + mWidth, mHeight); + } + } else { + const uint8_t *srcY = (const uint8_t *)img->planes[VPX_PLANE_Y]; + const uint8_t *srcU = (const uint8_t *)img->planes[VPX_PLANE_U]; + const uint8_t *srcV = (const uint8_t *)img->planes[VPX_PLANE_V]; + copyOutputBufferToYuvPlanarFrame( + dst, srcY, srcU, srcV, + srcYStride, srcUStride, srcVStride, + dstYStride, dstUVStride, + mWidth, mHeight); + } + finishWork(*(int64_t *)img->user_priv, work, std::move(block)); + return true; +} + +c2_status_t C2SoftVpxDec::drainInternal( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool, + const std::unique_ptr<C2Work> &work) { + if (drainMode == NO_DRAIN) { + ALOGW("drain with NO_DRAIN: no-op"); + return C2_OK; + } + if (drainMode == DRAIN_CHAIN) { + ALOGW("DRAIN_CHAIN not supported"); + return C2_OMITTED; + } + + while ((outputBuffer(pool, work))) { + } + + if (drainMode == DRAIN_COMPONENT_WITH_EOS && + work && work->workletsProcessed == 0u) { + fillEmptyWork(work); + } + + return C2_OK; +} +c2_status_t C2SoftVpxDec::drain( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool) { + return drainInternal(drainMode, pool, nullptr); +} + +class C2SoftVpxFactory : public C2ComponentFactory { +public: + C2SoftVpxFactory() : mHelper(std::static_pointer_cast<C2ReflectorHelper>( + GetCodec2PlatformComponentStore()->getParamReflector())) { + } + + virtual c2_status_t createComponent( + c2_node_id_t id, + std::shared_ptr<C2Component>* const component, + std::function<void(C2Component*)> deleter) override { + *component = std::shared_ptr<C2Component>( + new C2SoftVpxDec(COMPONENT_NAME, id, + std::make_shared<C2SoftVpxDec::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual c2_status_t createInterface( + c2_node_id_t id, + std::shared_ptr<C2ComponentInterface>* const interface, + std::function<void(C2ComponentInterface*)> deleter) override { + *interface = std::shared_ptr<C2ComponentInterface>( + new SimpleInterface<C2SoftVpxDec::IntfImpl>( + COMPONENT_NAME, id, + std::make_shared<C2SoftVpxDec::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual ~C2SoftVpxFactory() override = default; + +private: + std::shared_ptr<C2ReflectorHelper> mHelper; +}; + +} // namespace android + +extern "C" ::C2ComponentFactory* CreateCodec2Factory() { + ALOGV("in %s", __func__); + return new ::android::C2SoftVpxFactory(); +} + +extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) { + ALOGV("in %s", __func__); + delete factory; +}
diff --git a/media/codec2/components/vpx/C2SoftVpxDec.h b/media/codec2/components/vpx/C2SoftVpxDec.h new file mode 100644 index 0000000..e51bcee --- /dev/null +++ b/media/codec2/components/vpx/C2SoftVpxDec.h
@@ -0,0 +1,101 @@ +/* + * 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_C2_SOFT_VPX_DEC_H_ +#define ANDROID_C2_SOFT_VPX_DEC_H_ + +#include <SimpleC2Component.h> + + +#include "vpx/vpx_decoder.h" +#include "vpx/vp8dx.h" + +namespace android { + +struct C2SoftVpxDec : public SimpleC2Component { + class IntfImpl; + + C2SoftVpxDec(const char* name, c2_node_id_t id, + const std::shared_ptr<IntfImpl>& intfImpl); + virtual ~C2SoftVpxDec(); + + // From SimpleC2Component + c2_status_t onInit() override; + c2_status_t onStop() override; + void onReset() override; + void onRelease() override; + c2_status_t onFlush_sm() override; + void process( + const std::unique_ptr<C2Work> &work, + const std::shared_ptr<C2BlockPool> &pool) override; + c2_status_t drain( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool) override; + private: + enum { + MODE_VP8, + MODE_VP9, + } mMode; + + struct ConversionQueue; + + class ConverterThread : public Thread { + public: + explicit ConverterThread( + const std::shared_ptr<Mutexed<ConversionQueue>> &queue); + ~ConverterThread() override = default; + bool threadLoop() override; + + private: + std::shared_ptr<Mutexed<ConversionQueue>> mQueue; + }; + + std::shared_ptr<IntfImpl> mIntf; + vpx_codec_ctx_t *mCodecCtx; + bool mFrameParallelMode; // Frame parallel is only supported by VP9 decoder. + + uint32_t mWidth; + uint32_t mHeight; + bool mSignalledOutputEos; + bool mSignalledError; + + int mCoreCount; + struct ConversionQueue { + std::list<std::function<void()>> entries; + Condition cond; + size_t numPending{0u}; + }; + std::shared_ptr<Mutexed<ConversionQueue>> mQueue; + std::vector<sp<ConverterThread>> mConverterThreads; + + status_t initDecoder(); + status_t destroyDecoder(); + void finishWork(uint64_t index, const std::unique_ptr<C2Work> &work, + const std::shared_ptr<C2GraphicBlock> &block); + bool outputBuffer( + const std::shared_ptr<C2BlockPool> &pool, + const std::unique_ptr<C2Work> &work); + c2_status_t drainInternal( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool, + const std::unique_ptr<C2Work> &work); + + C2_DO_NOT_COPY(C2SoftVpxDec); +}; + +} // namespace android + +#endif // ANDROID_C2_SOFT_VPX_DEC_H_
diff --git a/media/codec2/components/vpx/C2SoftVpxEnc.cpp b/media/codec2/components/vpx/C2SoftVpxEnc.cpp new file mode 100644 index 0000000..6dab70b --- /dev/null +++ b/media/codec2/components/vpx/C2SoftVpxEnc.cpp
@@ -0,0 +1,670 @@ +/* + * Copyright 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 "C2SoftVpxEnc" +#include <log/log.h> +#include <utils/misc.h> + +#include <media/hardware/VideoAPI.h> + +#include <Codec2BufferUtils.h> +#include <C2Debug.h> +#include "C2SoftVpxEnc.h" + +#ifndef INT32_MAX +#define INT32_MAX 2147483647 +#endif + +namespace android { + +#if 0 +static size_t getCpuCoreCount() { + long cpuCoreCount = 1; +#if defined(_SC_NPROCESSORS_ONLN) + cpuCoreCount = sysconf(_SC_NPROCESSORS_ONLN); +#else + // _SC_NPROC_ONLN must be defined... + cpuCoreCount = sysconf(_SC_NPROC_ONLN); +#endif + CHECK(cpuCoreCount >= 1); + ALOGV("Number of CPU cores: %ld", cpuCoreCount); + return (size_t)cpuCoreCount; +} +#endif + +C2SoftVpxEnc::C2SoftVpxEnc(const char* name, c2_node_id_t id, + const std::shared_ptr<IntfImpl>& intfImpl) + : SimpleC2Component( + std::make_shared<SimpleInterface<IntfImpl>>(name, id, intfImpl)), + mIntf(intfImpl), + mCodecContext(nullptr), + mCodecConfiguration(nullptr), + mCodecInterface(nullptr), + mStrideAlign(2), + mColorFormat(VPX_IMG_FMT_I420), + mBitrateControlMode(VPX_VBR), + mErrorResilience(false), + mMinQuantizer(0), + mMaxQuantizer(0), + mTemporalLayers(0), + mTemporalPatternType(VPXTemporalLayerPatternNone), + mTemporalPatternLength(0), + mTemporalPatternIdx(0), + mLastTimestamp(0x7FFFFFFFFFFFFFFFull), + mSignalledOutputEos(false), + mSignalledError(false) { + memset(mTemporalLayerBitrateRatio, 0, sizeof(mTemporalLayerBitrateRatio)); + mTemporalLayerBitrateRatio[0] = 100; +} + +C2SoftVpxEnc::~C2SoftVpxEnc() { + onRelease(); +} + +c2_status_t C2SoftVpxEnc::onInit() { + status_t err = initEncoder(); + return err == OK ? C2_OK : C2_CORRUPTED; +} + +void C2SoftVpxEnc::onRelease() { + if (mCodecContext) { + vpx_codec_destroy(mCodecContext); + delete mCodecContext; + mCodecContext = nullptr; + } + + if (mCodecConfiguration) { + delete mCodecConfiguration; + mCodecConfiguration = nullptr; + } + + // this one is not allocated by us + mCodecInterface = nullptr; +} + +c2_status_t C2SoftVpxEnc::onStop() { + onRelease(); + mLastTimestamp = 0x7FFFFFFFFFFFFFFFLL; + mSignalledOutputEos = false; + mSignalledError = false; + return C2_OK; +} + +void C2SoftVpxEnc::onReset() { + (void)onStop(); +} + +c2_status_t C2SoftVpxEnc::onFlush_sm() { + return onStop(); +} + +status_t C2SoftVpxEnc::initEncoder() { + vpx_codec_err_t codec_return; + status_t result = UNKNOWN_ERROR; + { + IntfImpl::Lock lock = mIntf->lock(); + mSize = mIntf->getSize_l(); + mBitrate = mIntf->getBitrate_l(); + mBitrateMode = mIntf->getBitrateMode_l(); + mFrameRate = mIntf->getFrameRate_l(); + mIntraRefresh = mIntf->getIntraRefresh_l(); + mRequestSync = mIntf->getRequestSync_l(); + mTemporalLayers = mIntf->getTemporalLayers_l()->m.layerCount; + } + + switch (mBitrateMode->value) { + case C2Config::BITRATE_CONST: + mBitrateControlMode = VPX_CBR; + break; + case C2Config::BITRATE_VARIABLE: + [[fallthrough]]; + default: + mBitrateControlMode = VPX_VBR; + break; + } + + setCodecSpecificInterface(); + if (!mCodecInterface) goto CleanUp; + + ALOGD("VPx: initEncoder. BRMode: %u. TSLayers: %zu. KF: %u. QP: %u - %u", + (uint32_t)mBitrateControlMode, mTemporalLayers, mIntf->getSyncFramePeriod(), + mMinQuantizer, mMaxQuantizer); + + mCodecConfiguration = new vpx_codec_enc_cfg_t; + if (!mCodecConfiguration) goto CleanUp; + codec_return = vpx_codec_enc_config_default(mCodecInterface, + mCodecConfiguration, + 0); + if (codec_return != VPX_CODEC_OK) { + ALOGE("Error populating default configuration for vpx encoder."); + goto CleanUp; + } + + mCodecConfiguration->g_w = mSize->width; + mCodecConfiguration->g_h = mSize->height; + //mCodecConfiguration->g_threads = getCpuCoreCount(); + mCodecConfiguration->g_threads = 0; + mCodecConfiguration->g_error_resilient = mErrorResilience; + + // timebase unit is microsecond + // g_timebase is in seconds (i.e. 1/1000000 seconds) + mCodecConfiguration->g_timebase.num = 1; + mCodecConfiguration->g_timebase.den = 1000000; + // rc_target_bitrate is in kbps, mBitrate in bps + mCodecConfiguration->rc_target_bitrate = (mBitrate->value + 500) / 1000; + mCodecConfiguration->rc_end_usage = mBitrateControlMode; + // Disable frame drop - not allowed in MediaCodec now. + mCodecConfiguration->rc_dropframe_thresh = 0; + // Disable lagged encoding. + mCodecConfiguration->g_lag_in_frames = 0; + if (mBitrateControlMode == VPX_CBR) { + // Disable spatial resizing. + mCodecConfiguration->rc_resize_allowed = 0; + // Single-pass mode. + mCodecConfiguration->g_pass = VPX_RC_ONE_PASS; + // Maximum amount of bits that can be subtracted from the target + // bitrate - expressed as percentage of the target bitrate. + mCodecConfiguration->rc_undershoot_pct = 100; + // Maximum amount of bits that can be added to the target + // bitrate - expressed as percentage of the target bitrate. + mCodecConfiguration->rc_overshoot_pct = 15; + // Initial value of the buffer level in ms. + mCodecConfiguration->rc_buf_initial_sz = 500; + // Amount of data that the encoder should try to maintain in ms. + mCodecConfiguration->rc_buf_optimal_sz = 600; + // The amount of data that may be buffered by the decoding + // application in ms. + mCodecConfiguration->rc_buf_sz = 1000; + // Enable error resilience - needed for packet loss. + mCodecConfiguration->g_error_resilient = 1; + // Maximum key frame interval - for CBR boost to 3000 + mCodecConfiguration->kf_max_dist = 3000; + // Encoder determines optimal key frame placement automatically. + mCodecConfiguration->kf_mode = VPX_KF_AUTO; + } + + // Frames temporal pattern - for now WebRTC like pattern is only supported. + switch (mTemporalLayers) { + case 0: + mTemporalPatternLength = 0; + break; + case 1: + mCodecConfiguration->ts_number_layers = 1; + mCodecConfiguration->ts_rate_decimator[0] = 1; + mCodecConfiguration->ts_periodicity = 1; + mCodecConfiguration->ts_layer_id[0] = 0; + mTemporalPattern[0] = kTemporalUpdateLastRefAll; + mTemporalPatternLength = 1; + break; + case 2: + mCodecConfiguration->ts_number_layers = 2; + mCodecConfiguration->ts_rate_decimator[0] = 2; + mCodecConfiguration->ts_rate_decimator[1] = 1; + mCodecConfiguration->ts_periodicity = 2; + mCodecConfiguration->ts_layer_id[0] = 0; + mCodecConfiguration->ts_layer_id[1] = 1; + mTemporalPattern[0] = kTemporalUpdateLastAndGoldenRefAltRef; + mTemporalPattern[1] = kTemporalUpdateGoldenWithoutDependencyRefAltRef; + mTemporalPattern[2] = kTemporalUpdateLastRefAltRef; + mTemporalPattern[3] = kTemporalUpdateGoldenRefAltRef; + mTemporalPattern[4] = kTemporalUpdateLastRefAltRef; + mTemporalPattern[5] = kTemporalUpdateGoldenRefAltRef; + mTemporalPattern[6] = kTemporalUpdateLastRefAltRef; + mTemporalPattern[7] = kTemporalUpdateNone; + mTemporalPatternLength = 8; + break; + case 3: + mCodecConfiguration->ts_number_layers = 3; + mCodecConfiguration->ts_rate_decimator[0] = 4; + mCodecConfiguration->ts_rate_decimator[1] = 2; + mCodecConfiguration->ts_rate_decimator[2] = 1; + mCodecConfiguration->ts_periodicity = 4; + mCodecConfiguration->ts_layer_id[0] = 0; + mCodecConfiguration->ts_layer_id[1] = 2; + mCodecConfiguration->ts_layer_id[2] = 1; + mCodecConfiguration->ts_layer_id[3] = 2; + mTemporalPattern[0] = kTemporalUpdateLastAndGoldenRefAltRef; + mTemporalPattern[1] = kTemporalUpdateNoneNoRefGoldenRefAltRef; + mTemporalPattern[2] = kTemporalUpdateGoldenWithoutDependencyRefAltRef; + mTemporalPattern[3] = kTemporalUpdateNone; + mTemporalPattern[4] = kTemporalUpdateLastRefAltRef; + mTemporalPattern[5] = kTemporalUpdateNone; + mTemporalPattern[6] = kTemporalUpdateGoldenRefAltRef; + mTemporalPattern[7] = kTemporalUpdateNone; + mTemporalPatternLength = 8; + break; + default: + ALOGE("Wrong number of temporal layers %zu", mTemporalLayers); + goto CleanUp; + } + // Set bitrate values for each layer + for (size_t i = 0; i < mCodecConfiguration->ts_number_layers; i++) { + mCodecConfiguration->ts_target_bitrate[i] = + mCodecConfiguration->rc_target_bitrate * + mTemporalLayerBitrateRatio[i] / 100; + } + if (mIntf->getSyncFramePeriod() >= 0) { + mCodecConfiguration->kf_max_dist = mIntf->getSyncFramePeriod(); + mCodecConfiguration->kf_min_dist = mIntf->getSyncFramePeriod(); + mCodecConfiguration->kf_mode = VPX_KF_AUTO; + } + if (mMinQuantizer > 0) { + mCodecConfiguration->rc_min_quantizer = mMinQuantizer; + } + if (mMaxQuantizer > 0) { + mCodecConfiguration->rc_max_quantizer = mMaxQuantizer; + } + setCodecSpecificConfiguration(); + mCodecContext = new vpx_codec_ctx_t; + if (!mCodecContext) goto CleanUp; + codec_return = vpx_codec_enc_init(mCodecContext, + mCodecInterface, + mCodecConfiguration, + 0); // flags + if (codec_return != VPX_CODEC_OK) { + ALOGE("Error initializing vpx encoder"); + goto CleanUp; + } + + // Extra CBR settings + if (mBitrateControlMode == VPX_CBR) { + codec_return = vpx_codec_control(mCodecContext, + VP8E_SET_STATIC_THRESHOLD, + 1); + if (codec_return == VPX_CODEC_OK) { + uint32_t rc_max_intra_target = + (uint32_t)(mCodecConfiguration->rc_buf_optimal_sz * mFrameRate->value / 20 + 0.5); + // Don't go below 3 times per frame bandwidth. + if (rc_max_intra_target < 300) { + rc_max_intra_target = 300; + } + codec_return = vpx_codec_control(mCodecContext, + VP8E_SET_MAX_INTRA_BITRATE_PCT, + rc_max_intra_target); + } + if (codec_return == VPX_CODEC_OK) { + codec_return = vpx_codec_control(mCodecContext, + VP8E_SET_CPUUSED, + -8); + } + if (codec_return != VPX_CODEC_OK) { + ALOGE("Error setting cbr parameters for vpx encoder."); + goto CleanUp; + } + } + + codec_return = setCodecSpecificControls(); + if (codec_return != VPX_CODEC_OK) goto CleanUp; + + { + uint32_t width = mSize->width; + uint32_t height = mSize->height; + if (((uint64_t)width * height) > + ((uint64_t)INT32_MAX / 3)) { + ALOGE("b/25812794, Buffer size is too big, width=%u, height=%u.", width, height); + } else { + uint32_t stride = (width + mStrideAlign - 1) & ~(mStrideAlign - 1); + uint32_t vstride = (height + mStrideAlign - 1) & ~(mStrideAlign - 1); + mConversionBuffer = MemoryBlock::Allocate(stride * vstride * 3 / 2); + if (!mConversionBuffer.size()) { + ALOGE("Allocating conversion buffer failed."); + } else { + mNumInputFrames = -1; + return OK; + } + } + } + +CleanUp: + onRelease(); + return result; +} + +vpx_enc_frame_flags_t C2SoftVpxEnc::getEncodeFlags() { + vpx_enc_frame_flags_t flags = 0; + if (mTemporalPatternLength > 0) { + int patternIdx = mTemporalPatternIdx % mTemporalPatternLength; + mTemporalPatternIdx++; + switch (mTemporalPattern[patternIdx]) { + case kTemporalUpdateLast: + flags |= VP8_EFLAG_NO_UPD_GF; + flags |= VP8_EFLAG_NO_UPD_ARF; + flags |= VP8_EFLAG_NO_REF_GF; + flags |= VP8_EFLAG_NO_REF_ARF; + break; + case kTemporalUpdateGoldenWithoutDependency: + flags |= VP8_EFLAG_NO_REF_GF; + [[fallthrough]]; + case kTemporalUpdateGolden: + flags |= VP8_EFLAG_NO_REF_ARF; + flags |= VP8_EFLAG_NO_UPD_ARF; + flags |= VP8_EFLAG_NO_UPD_LAST; + break; + case kTemporalUpdateAltrefWithoutDependency: + flags |= VP8_EFLAG_NO_REF_ARF; + flags |= VP8_EFLAG_NO_REF_GF; + [[fallthrough]]; + case kTemporalUpdateAltref: + flags |= VP8_EFLAG_NO_UPD_GF; + flags |= VP8_EFLAG_NO_UPD_LAST; + break; + case kTemporalUpdateNoneNoRefAltref: + flags |= VP8_EFLAG_NO_REF_ARF; + [[fallthrough]]; + case kTemporalUpdateNone: + flags |= VP8_EFLAG_NO_UPD_GF; + flags |= VP8_EFLAG_NO_UPD_ARF; + flags |= VP8_EFLAG_NO_UPD_LAST; + flags |= VP8_EFLAG_NO_UPD_ENTROPY; + break; + case kTemporalUpdateNoneNoRefGoldenRefAltRef: + flags |= VP8_EFLAG_NO_REF_GF; + flags |= VP8_EFLAG_NO_UPD_GF; + flags |= VP8_EFLAG_NO_UPD_ARF; + flags |= VP8_EFLAG_NO_UPD_LAST; + flags |= VP8_EFLAG_NO_UPD_ENTROPY; + break; + case kTemporalUpdateGoldenWithoutDependencyRefAltRef: + flags |= VP8_EFLAG_NO_REF_GF; + flags |= VP8_EFLAG_NO_UPD_ARF; + flags |= VP8_EFLAG_NO_UPD_LAST; + break; + case kTemporalUpdateLastRefAltRef: + flags |= VP8_EFLAG_NO_UPD_GF; + flags |= VP8_EFLAG_NO_UPD_ARF; + flags |= VP8_EFLAG_NO_REF_GF; + break; + case kTemporalUpdateGoldenRefAltRef: + flags |= VP8_EFLAG_NO_UPD_ARF; + flags |= VP8_EFLAG_NO_UPD_LAST; + break; + case kTemporalUpdateLastAndGoldenRefAltRef: + flags |= VP8_EFLAG_NO_UPD_ARF; + flags |= VP8_EFLAG_NO_REF_GF; + break; + case kTemporalUpdateLastRefAll: + flags |= VP8_EFLAG_NO_UPD_ARF; + flags |= VP8_EFLAG_NO_UPD_GF; + break; + } + } + return flags; +} + +// TODO: add support for YUV input color formats +// TODO: add support for SVC, ARF. SVC and ARF returns multiple frames +// (hierarchical / noshow) in one call. These frames should be combined in to +// a single buffer and sent back to the client +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; + } + // Initialize encoder if not already + if (!mCodecContext && OK != initEncoder()) { + ALOGE("Failed to initialize encoder"); + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } + + std::shared_ptr<const C2GraphicView> rView; + std::shared_ptr<C2Buffer> inputBuffer; + if (!work->input.buffers.empty()) { + inputBuffer = work->input.buffers[0]; + rView = std::make_shared<const C2GraphicView>( + inputBuffer->data().graphicBlocks().front().map().get()); + if (rView->error() != C2_OK) { + ALOGE("graphic view map err = %d", rView->error()); + work->result = C2_CORRUPTED; + return; + } + } else { + ALOGV("Empty input Buffer"); + uint32_t flags = 0; + if (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) { + flags |= C2FrameData::FLAG_END_OF_STREAM; + } + work->worklets.front()->output.flags = (C2FrameData::flags_t)flags; + work->worklets.front()->output.buffers.clear(); + work->worklets.front()->output.ordinal = work->input.ordinal; + work->workletsProcessed = 1u; + return; + } + + const C2ConstGraphicBlock inBuffer = + inputBuffer->data().graphicBlocks().front(); + if (inBuffer.width() != mSize->width || + inBuffer.height() != mSize->height) { + 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; + } + bool eos = ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0); + vpx_image_t raw_frame; + const C2PlanarLayout &layout = rView->layout(); + uint32_t width = rView->width(); + uint32_t height = rView->height(); + if (width > 0x8000 || height > 0x8000) { + ALOGE("Image too big: %u x %u", width, height); + work->result = C2_BAD_VALUE; + return; + } + uint32_t stride = (width + mStrideAlign - 1) & ~(mStrideAlign - 1); + uint32_t vstride = (height + mStrideAlign - 1) & ~(mStrideAlign - 1); + switch (layout.type) { + case C2PlanarLayout::TYPE_RGB: + case C2PlanarLayout::TYPE_RGBA: { + ConvertRGBToPlanarYUV(mConversionBuffer.data(), stride, vstride, + mConversionBuffer.size(), *rView.get()); + vpx_img_wrap(&raw_frame, VPX_IMG_FMT_I420, width, height, + mStrideAlign, mConversionBuffer.data()); + break; + } + case C2PlanarLayout::TYPE_YUV: { + if (!IsYUV420(*rView)) { + ALOGE("input is not YUV420"); + work->result = C2_BAD_VALUE; + return; + } + + if (layout.planes[layout.PLANE_Y].colInc == 1 + && layout.planes[layout.PLANE_U].colInc == 1 + && layout.planes[layout.PLANE_V].colInc == 1) { + // I420 compatible - though with custom offset and stride + vpx_img_wrap(&raw_frame, VPX_IMG_FMT_I420, width, height, + mStrideAlign, (uint8_t*)rView->data()[0]); + raw_frame.planes[1] = (uint8_t*)rView->data()[1]; + raw_frame.planes[2] = (uint8_t*)rView->data()[2]; + raw_frame.stride[0] = layout.planes[layout.PLANE_Y].rowInc; + raw_frame.stride[1] = layout.planes[layout.PLANE_U].rowInc; + raw_frame.stride[2] = layout.planes[layout.PLANE_V].rowInc; + } else { + // copy to I420 + MediaImage2 img = CreateYUV420PlanarMediaImage2(width, height, stride, vstride); + if (mConversionBuffer.size() >= stride * vstride * 3 / 2) { + status_t err = ImageCopy(mConversionBuffer.data(), &img, *rView); + if (err != OK) { + ALOGE("Buffer conversion failed: %d", err); + work->result = C2_BAD_VALUE; + return; + } + vpx_img_wrap(&raw_frame, VPX_IMG_FMT_I420, stride, vstride, + mStrideAlign, (uint8_t*)rView->data()[0]); + vpx_img_set_rect(&raw_frame, 0, 0, width, height); + } else { + ALOGE("Conversion buffer is too small: %u x %u for %zu", + stride, vstride, mConversionBuffer.size()); + work->result = C2_BAD_VALUE; + return; + } + } + break; + } + default: + ALOGE("Unrecognized plane type: %d", layout.type); + work->result = C2_BAD_VALUE; + return; + } + + vpx_enc_frame_flags_t flags = getEncodeFlags(); + // handle dynamic config parameters + { + IntfImpl::Lock lock = mIntf->lock(); + std::shared_ptr<C2StreamIntraRefreshTuning::output> intraRefresh = mIntf->getIntraRefresh_l(); + std::shared_ptr<C2StreamBitrateInfo::output> bitrate = mIntf->getBitrate_l(); + std::shared_ptr<C2StreamRequestSyncFrameTuning::output> requestSync = mIntf->getRequestSync_l(); + lock.unlock(); + + if (intraRefresh != mIntraRefresh) { + mIntraRefresh = intraRefresh; + ALOGV("Got mIntraRefresh request"); + } + + if (requestSync != mRequestSync) { + // we can handle IDR immediately + if (requestSync->value) { + // unset request + C2StreamRequestSyncFrameTuning::output clearSync(0u, C2_FALSE); + std::vector<std::unique_ptr<C2SettingResult>> failures; + mIntf->config({ &clearSync }, C2_MAY_BLOCK, &failures); + ALOGV("Got sync request"); + flags |= VPX_EFLAG_FORCE_KF; + } + mRequestSync = requestSync; + } + + if (bitrate != mBitrate) { + mBitrate = bitrate; + mCodecConfiguration->rc_target_bitrate = + (mBitrate->value + 500) / 1000; + vpx_codec_err_t res = vpx_codec_enc_config_set(mCodecContext, + mCodecConfiguration); + 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; + } + } + } + + uint64_t inputTimeStamp = work->input.ordinal.timestamp.peekull(); + uint32_t frameDuration; + if (inputTimeStamp > mLastTimestamp) { + frameDuration = (uint32_t)(inputTimeStamp - mLastTimestamp); + } else { + // Use default of 30 fps in case of 0 frame rate. + float frameRate = mFrameRate->value; + if (frameRate < 0.001) { + frameRate = 30; + } + frameDuration = (uint32_t)(1000000 / frameRate + 0.5); + } + mLastTimestamp = inputTimeStamp; + + vpx_codec_err_t codec_return = vpx_codec_encode(mCodecContext, &raw_frame, + inputTimeStamp, + frameDuration, flags, + VPX_DL_REALTIME); + if (codec_return != VPX_CODEC_OK) { + ALOGE("vpx encoder failed to encode frame"); + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } + + bool populated = false; + vpx_codec_iter_t encoded_packet_iterator = nullptr; + const vpx_codec_cx_pkt_t* encoded_packet; + while ((encoded_packet = vpx_codec_get_cx_data( + mCodecContext, &encoded_packet_iterator))) { + if (encoded_packet->kind == VPX_CODEC_CX_FRAME_PKT) { + std::shared_ptr<C2LinearBlock> block; + C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE }; + c2_status_t err = pool->fetchLinearBlock(encoded_packet->data.frame.sz, usage, &block); + if (err != C2_OK) { + ALOGE("fetchLinearBlock for Output failed with status %d", err); + work->result = C2_NO_MEMORY; + return; + } + C2WriteView wView = block->map().get(); + if (wView.error()) { + ALOGE("write view map failed %d", wView.error()); + work->result = C2_CORRUPTED; + return; + } + + memcpy(wView.data(), encoded_packet->data.frame.buf, encoded_packet->data.frame.sz); + ++mNumInputFrames; + + ALOGD("bytes generated %zu", encoded_packet->data.frame.sz); + uint32_t flags = 0; + if (eos) { + flags |= C2FrameData::FLAG_END_OF_STREAM; + } + work->worklets.front()->output.flags = (C2FrameData::flags_t)flags; + work->worklets.front()->output.buffers.clear(); + std::shared_ptr<C2Buffer> buffer = createLinearBuffer(block); + if (encoded_packet->data.frame.flags & VPX_FRAME_IS_KEY) { + buffer->setInfo(std::make_shared<C2StreamPictureTypeMaskInfo::output>( + 0u /* stream id */, C2Config::SYNC_FRAME)); + } + work->worklets.front()->output.buffers.push_back(buffer); + work->worklets.front()->output.ordinal = work->input.ordinal; + work->worklets.front()->output.ordinal.timestamp = encoded_packet->data.frame.pts; + work->workletsProcessed = 1u; + populated = true; + if (eos) { + mSignalledOutputEos = true; + ALOGV("signalled EOS"); + } + } + } + if (!populated) { + work->workletsProcessed = 0u; + } +} + +c2_status_t C2SoftVpxEnc::drain( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool) { + (void)pool; + if (drainMode == NO_DRAIN) { + ALOGW("drain with NO_DRAIN: no-op"); + return C2_OK; + } + if (drainMode == DRAIN_CHAIN) { + ALOGW("DRAIN_CHAIN not supported"); + return C2_OMITTED; + } + + return C2_OK; +} + +} // namespace android
diff --git a/media/codec2/components/vpx/C2SoftVpxEnc.h b/media/codec2/components/vpx/C2SoftVpxEnc.h new file mode 100644 index 0000000..62ccd1b --- /dev/null +++ b/media/codec2/components/vpx/C2SoftVpxEnc.h
@@ -0,0 +1,433 @@ +/* + * Copyright 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_C2_SOFT_VPX_ENC_H__ +#define ANDROID_C2_SOFT_VPX_ENC_H__ + +#include <media/stagefright/foundation/MediaDefs.h> + +#include <C2PlatformSupport.h> +#include <Codec2BufferUtils.h> +#include <SimpleC2Component.h> +#include <SimpleC2Interface.h> +#include <util/C2InterfaceHelper.h> + +#include "vpx/vpx_encoder.h" +#include "vpx/vpx_codec.h" +#include "vpx/vpx_image.h" +#include "vpx/vp8cx.h" + +namespace android { + +// TODO: These defs taken from deprecated OMX_VideoExt.h. Move these definitions +// to a new header file and include it. + +/** Maximum number of temporal layers */ +#define MAXTEMPORALLAYERS 3 + +/** temporal layer patterns */ +typedef enum TemporalPatternType { + VPXTemporalLayerPatternNone = 0, + VPXTemporalLayerPatternWebRTC = 1, + VPXTemporalLayerPatternMax = 0x7FFFFFFF +} TemporalPatternType; + +// Base class for a VPX Encoder Component +// +// Only following encoder settings are available (codec specific settings might +// be available in the sub-classes): +// - video resolution +// - target bitrate +// - rate control (constant / variable) +// - frame rate +// - error resilience +// - reconstruction & loop filters (g_profile) +// +// Only following color formats are recognized +// - C2PlanarLayout::TYPE_RGB +// - C2PlanarLayout::TYPE_RGBA +// +// Following settings are not configurable by the client +// - encoding deadline is realtime +// - multithreaded encoding utilizes a number of threads equal +// to online cpu's available +// - the algorithm interface for encoder is decided by the sub-class in use +// - fractional bits of frame rate is discarded +// - timestamps are in microseconds, therefore encoder timebase is fixed +// to 1/1000000 + +struct C2SoftVpxEnc : public SimpleC2Component { + class IntfImpl; + + C2SoftVpxEnc(const char* name, c2_node_id_t id, + const std::shared_ptr<IntfImpl>& intfImpl); + + // From SimpleC2Component + c2_status_t onInit() override final; + c2_status_t onStop() override final; + void onReset() override final; + void onRelease() override final; + c2_status_t onFlush_sm() override final; + + void process( + const std::unique_ptr<C2Work> &work, + const std::shared_ptr<C2BlockPool> &pool) override final; + c2_status_t drain( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool) override final; + + protected: + std::shared_ptr<IntfImpl> mIntf; + virtual ~C2SoftVpxEnc(); + + // Initializes vpx encoder with available settings. + status_t initEncoder(); + + // Populates mCodecInterface with codec specific settings. + virtual void setCodecSpecificInterface() = 0; + + // Sets codec specific configuration. + virtual void setCodecSpecificConfiguration() = 0; + + // Sets codec specific encoder controls. + virtual vpx_codec_err_t setCodecSpecificControls() = 0; + + // Get current encode flags. + virtual vpx_enc_frame_flags_t getEncodeFlags(); + + enum TemporalReferences { + // For 1 layer case: reference all (last, golden, and alt ref), but only + // update last. + kTemporalUpdateLastRefAll = 12, + // First base layer frame for 3 temporal layers, which updates last and + // golden with alt ref dependency. + kTemporalUpdateLastAndGoldenRefAltRef = 11, + // First enhancement layer with alt ref dependency. + kTemporalUpdateGoldenRefAltRef = 10, + // First enhancement layer with alt ref dependency. + kTemporalUpdateGoldenWithoutDependencyRefAltRef = 9, + // Base layer with alt ref dependency. + kTemporalUpdateLastRefAltRef = 8, + // Highest enhacement layer without dependency on golden with alt ref + // dependency. + kTemporalUpdateNoneNoRefGoldenRefAltRef = 7, + // Second layer and last frame in cycle, for 2 layers. + kTemporalUpdateNoneNoRefAltref = 6, + // Highest enhancement layer. + kTemporalUpdateNone = 5, + // Second enhancement layer. + kTemporalUpdateAltref = 4, + // Second enhancement layer without dependency on previous frames in + // the second enhancement layer. + kTemporalUpdateAltrefWithoutDependency = 3, + // First enhancement layer. + kTemporalUpdateGolden = 2, + // First enhancement layer without dependency on previous frames in + // the first enhancement layer. + kTemporalUpdateGoldenWithoutDependency = 1, + // Base layer. + kTemporalUpdateLast = 0, + }; + enum { + kMaxTemporalPattern = 8 + }; + + // vpx specific opaque data structure that + // stores encoder state + vpx_codec_ctx_t* mCodecContext; + + // vpx specific data structure that + // stores encoder configuration + vpx_codec_enc_cfg_t* mCodecConfiguration; + + // vpx specific read-only data structure + // that specifies algorithm interface (e.g. vp8) + vpx_codec_iface_t* mCodecInterface; + + // align stride to the power of 2 + int32_t mStrideAlign; + + // Color format for the input port + vpx_img_fmt_t mColorFormat; + + // Bitrate control mode, either constant or variable + vpx_rc_mode mBitrateControlMode; + + // Parameter that denotes whether error resilience + // is enabled in encoder + bool mErrorResilience; + + // Minimum (best quality) quantizer + uint32_t mMinQuantizer; + + // Maximum (worst quality) quantizer + uint32_t mMaxQuantizer; + + // Number of coding temporal layers to be used. + size_t mTemporalLayers; + + // Temporal layer bitrare ratio in percentage + uint32_t mTemporalLayerBitrateRatio[MAXTEMPORALLAYERS]; + + // Temporal pattern type + TemporalPatternType mTemporalPatternType; + + // Temporal pattern length + size_t mTemporalPatternLength; + + // Temporal pattern current index + size_t mTemporalPatternIdx; + + // Frame type temporal pattern + TemporalReferences mTemporalPattern[kMaxTemporalPattern]; + + // Last input buffer timestamp + uint64_t mLastTimestamp; + + // Number of input frames + int64_t mNumInputFrames; + + // Conversion buffer is needed to input to + // yuv420 planar format. + MemoryBlock mConversionBuffer; + + // Signalled EOS + bool mSignalledOutputEos; + + // Signalled Error + bool mSignalledError; + + // configurations used by component in process + // (TODO: keep this in intf but make them internal only) + std::shared_ptr<C2StreamPictureSizeInfo::input> mSize; + std::shared_ptr<C2StreamIntraRefreshTuning::output> mIntraRefresh; + std::shared_ptr<C2StreamFrameRateInfo::output> mFrameRate; + std::shared_ptr<C2StreamBitrateInfo::output> mBitrate; + std::shared_ptr<C2StreamBitrateModeTuning::output> mBitrateMode; + std::shared_ptr<C2StreamRequestSyncFrameTuning::output> mRequestSync; + + C2_DO_NOT_COPY(C2SoftVpxEnc); +}; + +namespace { + +#ifdef VP9 +constexpr char COMPONENT_NAME[] = "c2.android.vp9.encoder"; +const char *MEDIA_MIMETYPE_VIDEO = MEDIA_MIMETYPE_VIDEO_VP9; +#else +constexpr char COMPONENT_NAME[] = "c2.android.vp8.encoder"; +const char *MEDIA_MIMETYPE_VIDEO = MEDIA_MIMETYPE_VIDEO_VP8; +#endif + +} // namepsace + +class C2SoftVpxEnc::IntfImpl : public SimpleInterface<void>::BaseParams { + public: + explicit IntfImpl(const std::shared_ptr<C2ReflectorHelper> &helper) + : SimpleInterface<void>::BaseParams( + helper, + COMPONENT_NAME, + C2Component::KIND_ENCODER, + C2Component::DOMAIN_VIDEO, + MEDIA_MIMETYPE_VIDEO) { + noPrivateBuffers(); // TODO: account for our buffers here + noInputReferences(); + noOutputReferences(); + noInputLatency(); + noTimeStretch(); + setDerivedInstance(this); + + addParameter( + DefineParam(mAttrib, C2_PARAMKEY_COMPONENT_ATTRIBUTES) + .withConstValue(new C2ComponentAttributesSetting( + C2Component::ATTRIB_IS_TEMPORAL)) + .build()); + + addParameter( + DefineParam(mUsage, C2_PARAMKEY_INPUT_STREAM_USAGE) + .withConstValue(new C2StreamUsageTuning::input( + 0u, (uint64_t)C2MemoryUsage::CPU_READ)) + .build()); + + addParameter( + DefineParam(mSize, C2_PARAMKEY_PICTURE_SIZE) + .withDefault(new C2StreamPictureSizeInfo::input(0u, 320, 240)) + .withFields({ + C2F(mSize, width).inRange(2, 2048, 2), + C2F(mSize, height).inRange(2, 2048, 2), + }) + .withSetter(SizeSetter) + .build()); + + addParameter( + DefineParam(mBitrateMode, C2_PARAMKEY_BITRATE_MODE) + .withDefault(new C2StreamBitrateModeTuning::output( + 0u, C2Config::BITRATE_VARIABLE)) + .withFields({ + C2F(mBitrateMode, value).oneOf({ + C2Config::BITRATE_CONST, C2Config::BITRATE_VARIABLE }) + }) + .withSetter( + Setter<decltype(*mBitrateMode)>::StrictValueWithNoDeps) + .build()); + + addParameter( + DefineParam(mFrameRate, C2_PARAMKEY_FRAME_RATE) + .withDefault(new C2StreamFrameRateInfo::output(0u, 30.)) + // TODO: More restriction? + .withFields({C2F(mFrameRate, value).greaterThan(0.)}) + .withSetter( + Setter<decltype(*mFrameRate)>::StrictValueWithNoDeps) + .build()); + + addParameter( + DefineParam(mLayering, C2_PARAMKEY_TEMPORAL_LAYERING) + .withDefault(C2StreamTemporalLayeringTuning::output::AllocShared(0u, 0, 0, 0)) + .withFields({ + C2F(mLayering, m.layerCount).inRange(0, 4), + C2F(mLayering, m.bLayerCount).inRange(0, 0), + C2F(mLayering, m.bitrateRatios).inRange(0., 1.) + }) + .withSetter(LayeringSetter) + .build()); + + addParameter( + DefineParam(mSyncFramePeriod, C2_PARAMKEY_SYNC_FRAME_INTERVAL) + .withDefault(new C2StreamSyncFrameIntervalTuning::output(0u, 1000000)) + .withFields({C2F(mSyncFramePeriod, value).any()}) + .withSetter(Setter<decltype(*mSyncFramePeriod)>::StrictValueWithNoDeps) + .build()); + + addParameter( + DefineParam(mBitrate, C2_PARAMKEY_BITRATE) + .withDefault(new C2StreamBitrateInfo::output(0u, 64000)) + .withFields({C2F(mBitrate, value).inRange(4096, 40000000)}) + .withSetter(BitrateSetter) + .build()); + + addParameter( + DefineParam(mIntraRefresh, C2_PARAMKEY_INTRA_REFRESH) + .withConstValue(new C2StreamIntraRefreshTuning::output( + 0u, C2Config::INTRA_REFRESH_DISABLED, 0.)) + .build()); + + addParameter( + DefineParam(mProfileLevel, C2_PARAMKEY_PROFILE_LEVEL) + .withDefault(new C2StreamProfileLevelInfo::output( + 0u, PROFILE_VP9_0, LEVEL_VP9_4_1)) + .withFields({ + C2F(mProfileLevel, profile).equalTo( + PROFILE_VP9_0 + ), + C2F(mProfileLevel, level).equalTo( + LEVEL_VP9_4_1), + }) + .withSetter(ProfileLevelSetter) + .build()); + + addParameter( + DefineParam(mRequestSync, C2_PARAMKEY_REQUEST_SYNC_FRAME) + .withDefault(new C2StreamRequestSyncFrameTuning::output(0u, C2_FALSE)) + .withFields({C2F(mRequestSync, value).oneOf({ C2_FALSE, C2_TRUE }) }) + .withSetter(Setter<decltype(*mRequestSync)>::NonStrictValueWithNoDeps) + .build()); + } + + static C2R BitrateSetter(bool mayBlock, C2P<C2StreamBitrateInfo::output> &me) { + (void)mayBlock; + C2R res = C2R::Ok(); + if (me.v.value <= 4096) { + me.set().value = 4096; + } + return res; + } + + static C2R SizeSetter(bool mayBlock, const C2P<C2StreamPictureSizeInfo::input> &oldMe, + C2P<C2StreamPictureSizeInfo::input> &me) { + (void)mayBlock; + C2R res = C2R::Ok(); + if (!me.F(me.v.width).supportsAtAll(me.v.width)) { + res = res.plus(C2SettingResultBuilder::BadValue(me.F(me.v.width))); + me.set().width = oldMe.v.width; + } + if (!me.F(me.v.height).supportsAtAll(me.v.height)) { + res = res.plus(C2SettingResultBuilder::BadValue(me.F(me.v.height))); + me.set().height = oldMe.v.height; + } + return res; + } + + static C2R ProfileLevelSetter( + bool mayBlock, + C2P<C2StreamProfileLevelInfo::output> &me) { + (void)mayBlock; + if (!me.F(me.v.profile).supportsAtAll(me.v.profile)) { + me.set().profile = PROFILE_VP9_0; + } + if (!me.F(me.v.level).supportsAtAll(me.v.level)) { + me.set().level = LEVEL_VP9_4_1; + } + return C2R::Ok(); + } + + static C2R LayeringSetter(bool mayBlock, C2P<C2StreamTemporalLayeringTuning::output>& me) { + (void)mayBlock; + C2R res = C2R::Ok(); + if (me.v.m.layerCount > 4) { + me.set().m.layerCount = 4; + } + me.set().m.bLayerCount = 0; + // ensure ratios are monotonic and clamped between 0 and 1 + for (size_t ix = 0; ix < me.v.flexCount(); ++ix) { + me.set().m.bitrateRatios[ix] = c2_clamp( + ix > 0 ? me.v.m.bitrateRatios[ix - 1] : 0, me.v.m.bitrateRatios[ix], 1.); + } + ALOGI("setting temporal layering %u + %u", me.v.m.layerCount, me.v.m.bLayerCount); + return res; + } + + // unsafe getters + std::shared_ptr<C2StreamPictureSizeInfo::input> getSize_l() const { return mSize; } + std::shared_ptr<C2StreamIntraRefreshTuning::output> getIntraRefresh_l() const { return mIntraRefresh; } + std::shared_ptr<C2StreamFrameRateInfo::output> getFrameRate_l() const { return mFrameRate; } + std::shared_ptr<C2StreamBitrateInfo::output> getBitrate_l() const { return mBitrate; } + std::shared_ptr<C2StreamBitrateModeTuning::output> getBitrateMode_l() const { return mBitrateMode; } + std::shared_ptr<C2StreamRequestSyncFrameTuning::output> getRequestSync_l() const { return mRequestSync; } + std::shared_ptr<C2StreamTemporalLayeringTuning::output> getTemporalLayers_l() const { return mLayering; } + uint32_t getSyncFramePeriod() const { + if (mSyncFramePeriod->value < 0 || mSyncFramePeriod->value == INT64_MAX) { + return 0; + } + double period = mSyncFramePeriod->value / 1e6 * mFrameRate->value; + return (uint32_t)c2_max(c2_min(period + 0.5, double(UINT32_MAX)), 1.); + } + + private: + std::shared_ptr<C2StreamUsageTuning::input> mUsage; + std::shared_ptr<C2StreamPictureSizeInfo::input> mSize; + std::shared_ptr<C2StreamFrameRateInfo::output> mFrameRate; + std::shared_ptr<C2StreamTemporalLayeringTuning::output> mLayering; + std::shared_ptr<C2StreamIntraRefreshTuning::output> mIntraRefresh; + std::shared_ptr<C2StreamRequestSyncFrameTuning::output> mRequestSync; + std::shared_ptr<C2StreamSyncFrameIntervalTuning::output> mSyncFramePeriod; + std::shared_ptr<C2StreamBitrateInfo::output> mBitrate; + std::shared_ptr<C2StreamBitrateModeTuning::output> mBitrateMode; + std::shared_ptr<C2StreamProfileLevelInfo::output> mProfileLevel; +}; + +} // namespace android + +#endif // ANDROID_C2_SOFT_VPX_ENC_H__
diff --git a/media/common_time/MODULE_LICENSE_APACHE2 b/media/codec2/components/vpx/MODULE_LICENSE_APACHE2 similarity index 100% copy from media/common_time/MODULE_LICENSE_APACHE2 copy to media/codec2/components/vpx/MODULE_LICENSE_APACHE2
diff --git a/media/codec2/components/vpx/NOTICE b/media/codec2/components/vpx/NOTICE new file mode 100644 index 0000000..faed58a --- /dev/null +++ b/media/codec2/components/vpx/NOTICE
@@ -0,0 +1,190 @@ + + Copyright (c) 2005-2013, 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. + + 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. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS +
diff --git a/media/codec2/components/xaac/Android.bp b/media/codec2/components/xaac/Android.bp new file mode 100644 index 0000000..7795cc1 --- /dev/null +++ b/media/codec2/components/xaac/Android.bp
@@ -0,0 +1,11 @@ +cc_library_shared { + name: "libcodec2_soft_xaacdec", + defaults: [ + "libcodec2_soft-defaults", + "libcodec2_soft_sanitize_all-defaults", + ], + + srcs: ["C2SoftXaacDec.cpp"], + + static_libs: ["libxaacdec"], +}
diff --git a/media/codec2/components/xaac/C2SoftXaacDec.cpp b/media/codec2/components/xaac/C2SoftXaacDec.cpp new file mode 100644 index 0000000..a3ebadb --- /dev/null +++ b/media/codec2/components/xaac/C2SoftXaacDec.cpp
@@ -0,0 +1,1595 @@ +/* + * 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 "C2SoftXaacDec" +#include <log/log.h> + +#include <inttypes.h> + +#include <cutils/properties.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/MediaDefs.h> +#include <media/stagefright/foundation/hexdump.h> + +#include <C2PlatformSupport.h> +#include <SimpleC2Interface.h> + +#include "C2SoftXaacDec.h" + +#define DRC_DEFAULT_MOBILE_REF_LEVEL -16.0 /* 64*-0.25dB = -16 dB below full scale for mobile conf */ +#define DRC_DEFAULT_MOBILE_DRC_CUT 1.0 /* maximum compression of dynamic range for mobile conf */ +#define DRC_DEFAULT_MOBILE_DRC_BOOST 1.0 /* maximum compression of dynamic range for mobile conf */ +#define DRC_DEFAULT_MOBILE_DRC_HEAVY C2Config::DRC_COMPRESSION_HEAVY /* switch for heavy compression for mobile conf */ +#define DRC_DEFAULT_MOBILE_DRC_EFFECT 3 /* MPEG-D DRC effect type; 3 => Limited playback range */ +#define DRC_DEFAULT_MOBILE_ENC_LEVEL (0.25) /* encoder target level; -1 => the value is unknown, otherwise dB step value (e.g. 64 for -16 dB) */ +#define MAX_CHANNEL_COUNT 8 /* maximum number of audio channels that can be decoded */ +// names of properties that can be used to override the default DRC settings +#define PROP_DRC_OVERRIDE_REF_LEVEL "aac_drc_reference_level" +#define PROP_DRC_OVERRIDE_CUT "aac_drc_cut" +#define PROP_DRC_OVERRIDE_BOOST "aac_drc_boost" +#define PROP_DRC_OVERRIDE_HEAVY "aac_drc_heavy" +#define PROP_DRC_OVERRIDE_ENC_LEVEL "aac_drc_enc_target_level" +#define PROP_DRC_OVERRIDE_EFFECT_TYPE "ro.aac_drc_effect_type" + +#define RETURN_IF_FATAL(retval, str) \ + if (retval & IA_FATAL_ERROR) { \ + ALOGE("Error in %s: Returned: %d", str, retval); \ + return retval; \ + } else if (retval != IA_NO_ERROR) { \ + ALOGW("Warning in %s: Returned: %d", str, retval); \ + } + + +namespace android { + +namespace { + +constexpr char COMPONENT_NAME[] = "c2.android.xaac.decoder"; + +} // namespace + +class C2SoftXaacDec::IntfImpl : public SimpleInterface<void>::BaseParams { +public: + explicit IntfImpl(const std::shared_ptr<C2ReflectorHelper> &helper) + : SimpleInterface<void>::BaseParams( + helper, + COMPONENT_NAME, + C2Component::KIND_DECODER, + C2Component::DOMAIN_AUDIO, + MEDIA_MIMETYPE_AUDIO_AAC) { + noPrivateBuffers(); + noInputReferences(); + noOutputReferences(); + noInputLatency(); + noTimeStretch(); + + addParameter( + DefineParam(mAttrib, C2_PARAMKEY_COMPONENT_ATTRIBUTES) + .withConstValue(new C2ComponentAttributesSetting( + C2Component::ATTRIB_IS_TEMPORAL)) + .build()); + + addParameter( + DefineParam(mSampleRate, C2_PARAMKEY_SAMPLE_RATE) + .withDefault(new C2StreamSampleRateInfo::output(0u, 44100)) + .withFields({C2F(mSampleRate, value).oneOf({ + 7350, 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 + })}) + .withSetter((Setter<decltype(*mSampleRate)>::StrictValueWithNoDeps)) + .build()); + + addParameter( + DefineParam(mChannelCount, C2_PARAMKEY_CHANNEL_COUNT) + .withDefault(new C2StreamChannelCountInfo::output(0u, 1)) + .withFields({C2F(mChannelCount, value).inRange(1, 8)}) + .withSetter(Setter<decltype(*mChannelCount)>::StrictValueWithNoDeps) + .build()); + + addParameter( + DefineParam(mBitrate, C2_PARAMKEY_BITRATE) + .withDefault(new C2StreamBitrateInfo::input(0u, 64000)) + .withFields({C2F(mBitrate, value).inRange(8000, 960000)}) + .withSetter(Setter<decltype(*mBitrate)>::NonStrictValueWithNoDeps) + .build()); + + addParameter( + DefineParam(mInputMaxBufSize, C2_PARAMKEY_INPUT_MAX_BUFFER_SIZE) + .withConstValue(new C2StreamMaxBufferSizeInfo::input(0u, 8192)) + .build()); + + addParameter( + DefineParam(mAacFormat, C2_PARAMKEY_AAC_PACKAGING) + .withDefault(new C2StreamAacFormatInfo::input(0u, C2Config::AAC_PACKAGING_RAW)) + .withFields({C2F(mAacFormat, value).oneOf({ + C2Config::AAC_PACKAGING_RAW, C2Config::AAC_PACKAGING_ADTS + })}) + .withSetter(Setter<decltype(*mAacFormat)>::StrictValueWithNoDeps) + .build()); + + addParameter( + DefineParam(mProfileLevel, C2_PARAMKEY_PROFILE_LEVEL) + .withDefault(new C2StreamProfileLevelInfo::input(0u, + C2Config::PROFILE_AAC_LC, C2Config::LEVEL_UNUSED)) + .withFields({ + C2F(mProfileLevel, profile).oneOf({ + C2Config::PROFILE_AAC_LC, + C2Config::PROFILE_AAC_HE, + C2Config::PROFILE_AAC_HE_PS, + C2Config::PROFILE_AAC_LD, + C2Config::PROFILE_AAC_ELD, + C2Config::PROFILE_AAC_XHE}), + C2F(mProfileLevel, level).oneOf({ + C2Config::LEVEL_UNUSED + }) + }) + .withSetter(ProfileLevelSetter) + .build()); + + addParameter( + DefineParam(mDrcCompressMode, C2_PARAMKEY_DRC_COMPRESSION_MODE) + .withDefault(new C2StreamDrcCompressionModeTuning::input(0u, C2Config::DRC_COMPRESSION_HEAVY)) + .withFields({ + C2F(mDrcCompressMode, value).oneOf({ + C2Config::DRC_COMPRESSION_ODM_DEFAULT, + C2Config::DRC_COMPRESSION_NONE, + C2Config::DRC_COMPRESSION_LIGHT, + C2Config::DRC_COMPRESSION_HEAVY}) + }) + .withSetter(Setter<decltype(*mDrcCompressMode)>::StrictValueWithNoDeps) + .build()); + + addParameter( + DefineParam(mDrcTargetRefLevel, C2_PARAMKEY_DRC_TARGET_REFERENCE_LEVEL) + .withDefault(new C2StreamDrcTargetReferenceLevelTuning::input(0u, DRC_DEFAULT_MOBILE_REF_LEVEL)) + .withFields({C2F(mDrcTargetRefLevel, value).inRange(-31.75, 0.25)}) + .withSetter(Setter<decltype(*mDrcTargetRefLevel)>::StrictValueWithNoDeps) + .build()); + + addParameter( + DefineParam(mDrcEncTargetLevel, C2_PARAMKEY_DRC_ENCODED_TARGET_LEVEL) + .withDefault(new C2StreamDrcEncodedTargetLevelTuning::input(0u, DRC_DEFAULT_MOBILE_ENC_LEVEL)) + .withFields({C2F(mDrcEncTargetLevel, value).inRange(-31.75, 0.25)}) + .withSetter(Setter<decltype(*mDrcEncTargetLevel)>::StrictValueWithNoDeps) + .build()); + + addParameter( + DefineParam(mDrcBoostFactor, C2_PARAMKEY_DRC_BOOST_FACTOR) + .withDefault(new C2StreamDrcBoostFactorTuning::input(0u, DRC_DEFAULT_MOBILE_DRC_BOOST)) + .withFields({C2F(mDrcBoostFactor, value).inRange(0, 1.)}) + .withSetter(Setter<decltype(*mDrcBoostFactor)>::StrictValueWithNoDeps) + .build()); + + addParameter( + DefineParam(mDrcAttenuationFactor, C2_PARAMKEY_DRC_ATTENUATION_FACTOR) + .withDefault(new C2StreamDrcAttenuationFactorTuning::input(0u, DRC_DEFAULT_MOBILE_DRC_CUT)) + .withFields({C2F(mDrcAttenuationFactor, value).inRange(0, 1.)}) + .withSetter(Setter<decltype(*mDrcAttenuationFactor)>::StrictValueWithNoDeps) + .build()); + + addParameter( + DefineParam(mDrcEffectType, C2_PARAMKEY_DRC_EFFECT_TYPE) + .withDefault(new C2StreamDrcEffectTypeTuning::input(0u, C2Config::DRC_EFFECT_LIMITED_PLAYBACK_RANGE)) + .withFields({ + C2F(mDrcEffectType, value).oneOf({ + C2Config::DRC_EFFECT_ODM_DEFAULT, + C2Config::DRC_EFFECT_OFF, + C2Config::DRC_EFFECT_NONE, + C2Config::DRC_EFFECT_LATE_NIGHT, + C2Config::DRC_EFFECT_NOISY_ENVIRONMENT, + C2Config::DRC_EFFECT_LIMITED_PLAYBACK_RANGE, + C2Config::DRC_EFFECT_LOW_PLAYBACK_LEVEL, + C2Config::DRC_EFFECT_DIALOG_ENHANCEMENT, + C2Config::DRC_EFFECT_GENERAL_COMPRESSION}) + }) + .withSetter(Setter<decltype(*mDrcEffectType)>::StrictValueWithNoDeps) + .build()); + } + + bool isAdts() const { return mAacFormat->value == C2Config::AAC_PACKAGING_ADTS; } + uint32_t getBitrate() const { return mBitrate->value; } + static C2R ProfileLevelSetter(bool mayBlock, C2P<C2StreamProfileLevelInfo::input> &me) { + (void)mayBlock; + (void)me; // TODO: validate + return C2R::Ok(); + } + int32_t getDrcCompressMode() const { return mDrcCompressMode->value == C2Config::DRC_COMPRESSION_HEAVY ? 1 : 0; } + int32_t getDrcTargetRefLevel() const { return (mDrcTargetRefLevel->value <= 0 ? -mDrcTargetRefLevel->value * 4. + 0.5 : -1); } + int32_t getDrcEncTargetLevel() const { return (mDrcEncTargetLevel->value <= 0 ? -mDrcEncTargetLevel->value * 4. + 0.5 : -1); } + int32_t getDrcBoostFactor() const { return mDrcBoostFactor->value * 127. + 0.5; } + int32_t getDrcAttenuationFactor() const { return mDrcAttenuationFactor->value * 127. + 0.5; } + int32_t getDrcEffectType() const { return mDrcEffectType->value; } + +private: + std::shared_ptr<C2StreamSampleRateInfo::output> mSampleRate; + std::shared_ptr<C2StreamChannelCountInfo::output> mChannelCount; + std::shared_ptr<C2StreamBitrateInfo::input> mBitrate; + std::shared_ptr<C2StreamMaxBufferSizeInfo::input> mInputMaxBufSize; + std::shared_ptr<C2StreamAacFormatInfo::input> mAacFormat; + std::shared_ptr<C2StreamProfileLevelInfo::input> mProfileLevel; + std::shared_ptr<C2StreamDrcCompressionModeTuning::input> mDrcCompressMode; + std::shared_ptr<C2StreamDrcTargetReferenceLevelTuning::input> mDrcTargetRefLevel; + std::shared_ptr<C2StreamDrcEncodedTargetLevelTuning::input> mDrcEncTargetLevel; + std::shared_ptr<C2StreamDrcBoostFactorTuning::input> mDrcBoostFactor; + std::shared_ptr<C2StreamDrcAttenuationFactorTuning::input> mDrcAttenuationFactor; + std::shared_ptr<C2StreamDrcEffectTypeTuning::input> mDrcEffectType; + // TODO Add : C2StreamAacSbrModeTuning +}; + +C2SoftXaacDec::C2SoftXaacDec( + const char* name, + c2_node_id_t id, + const std::shared_ptr<IntfImpl> &intfImpl) + : SimpleC2Component(std::make_shared<SimpleInterface<IntfImpl>>(name, id, intfImpl)), + mIntf(intfImpl), + mXheaacCodecHandle(nullptr), + mMpegDDrcHandle(nullptr), + mOutputDrainBuffer(nullptr) { +} + +C2SoftXaacDec::~C2SoftXaacDec() { + onRelease(); +} + +c2_status_t C2SoftXaacDec::onInit() { + mOutputFrameLength = 1024; + mInputBuffer = nullptr; + mOutputBuffer = nullptr; + mSampFreq = 0; + mNumChannels = 0; + mPcmWdSz = 0; + mChannelMask = 0; + mNumOutBytes = 0; + mCurFrameIndex = 0; + mCurTimestamp = 0; + mIsCodecInitialized = false; + mIsCodecConfigFlushRequired = false; + mSignalledOutputEos = false; + mSignalledError = false; + mOutputDrainBufferWritePos = 0; + mDRCFlag = 0; + mMpegDDRCPresent = 0; + mMemoryVec.clear(); + mDrcMemoryVec.clear(); + + IA_ERRORCODE err = initDecoder(); + return err == IA_NO_ERROR ? C2_OK : C2_CORRUPTED; + +} + +c2_status_t C2SoftXaacDec::onStop() { + mOutputFrameLength = 1024; + drainDecoder(); + // reset the "configured" state + mSampFreq = 0; + mNumChannels = 0; + mPcmWdSz = 0; + mChannelMask = 0; + mNumOutBytes = 0; + mCurFrameIndex = 0; + mCurTimestamp = 0; + mSignalledOutputEos = false; + mSignalledError = false; + mOutputDrainBufferWritePos = 0; + mDRCFlag = 0; + mMpegDDRCPresent = 0; + + return C2_OK; +} + +void C2SoftXaacDec::onReset() { + (void)onStop(); +} + +void C2SoftXaacDec::onRelease() { + IA_ERRORCODE errCode = deInitXAACDecoder(); + if (IA_NO_ERROR != errCode) ALOGE("deInitXAACDecoder() failed %d", errCode); + + errCode = deInitMPEGDDDrc(); + if (IA_NO_ERROR != errCode) ALOGE("deInitMPEGDDDrc() failed %d", errCode); + + if (mOutputDrainBuffer) { + delete[] mOutputDrainBuffer; + mOutputDrainBuffer = nullptr; + } +} + +IA_ERRORCODE C2SoftXaacDec::initDecoder() { + ALOGV("initDecoder()"); + IA_ERRORCODE err_code = IA_NO_ERROR; + + err_code = initXAACDecoder(); + if (err_code != IA_NO_ERROR) { + ALOGE("initXAACDecoder Failed"); + /* Call deInit to free any allocated memory */ + deInitXAACDecoder(); + return IA_FATAL_ERROR; + } + + if (!mOutputDrainBuffer) { + mOutputDrainBuffer = new (std::nothrow) char[kOutputDrainBufferSize]; + if (!mOutputDrainBuffer) return IA_FATAL_ERROR; + } + + err_code = initXAACDrc(); + RETURN_IF_FATAL(err_code, "initXAACDrc"); + + + return IA_NO_ERROR; +} + +static void fillEmptyWork(const std::unique_ptr<C2Work>& work) { + uint32_t flags = 0; + if (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) { + flags |= C2FrameData::FLAG_END_OF_STREAM; + ALOGV("signalling eos"); + } + work->worklets.front()->output.flags = (C2FrameData::flags_t)flags; + work->worklets.front()->output.buffers.clear(); + work->worklets.front()->output.ordinal = work->input.ordinal; + work->workletsProcessed = 1u; +} + +void C2SoftXaacDec::finishWork(const std::unique_ptr<C2Work>& work, + const std::shared_ptr<C2BlockPool>& pool) { + ALOGV("mCurFrameIndex = %" PRIu64, mCurFrameIndex); + + std::shared_ptr<C2LinearBlock> block; + C2MemoryUsage usage = {C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE}; + // TODO: error handling, proper usage, etc. + c2_status_t err = + pool->fetchLinearBlock(mOutputDrainBufferWritePos, usage, &block); + 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); + mOutputDrainBufferWritePos = 0; + + auto fillWork = [buffer = createLinearBuffer(block)]( + const std::unique_ptr<C2Work>& work) { + uint32_t flags = 0; + if (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) { + flags |= C2FrameData::FLAG_END_OF_STREAM; + ALOGV("signalling eos"); + } + work->worklets.front()->output.flags = (C2FrameData::flags_t)flags; + work->worklets.front()->output.buffers.clear(); + work->worklets.front()->output.buffers.push_back(buffer); + work->worklets.front()->output.ordinal = work->input.ordinal; + work->workletsProcessed = 1u; + }; + if (work && work->input.ordinal.frameIndex == c2_cntr64_t(mCurFrameIndex)) { + fillWork(work); + } else { + finish(mCurFrameIndex, fillWork); + } + + ALOGV("out timestamp %" PRIu64 " / %u", mCurTimestamp, block->capacity()); +} + +void C2SoftXaacDec::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.configUpdate.clear(); + work->worklets.front()->output.flags = work->input.flags; + + if (mSignalledError || mSignalledOutputEos) { + work->result = C2_BAD_VALUE; + return; + } + uint8_t* inBuffer = nullptr; + uint32_t inBufferLength = 0; + C2ReadView view = mDummyReadView; + size_t offset = 0u; + size_t size = 0u; + if (!work->input.buffers.empty()) { + view = work->input.buffers[0]->data().linearBlocks().front().map().get(); + size = view.capacity(); + } + if (size && view.error()) { + ALOGE("read view map failed %d", view.error()); + work->result = view.error(); + return; + } + + 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"); + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } + // const_cast because of libAACdec method signature. + inBuffer = const_cast<uint8_t*>(view.data() + offset); + inBufferLength = size; + + /* GA header configuration sent to Decoder! */ + IA_ERRORCODE err_code = configXAACDecoder(inBuffer, inBufferLength); + if (IA_NO_ERROR != err_code) { + ALOGE("configXAACDecoder err_code = %d", err_code); + mSignalledError = true; + 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(); + 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) < + (mOutputFrameLength * sizeof(int16_t) * mNumChannels)) { + ALOGV("skipping decode: not enough space left in DrainBuffer"); + break; + } + + ALOGV("inAttribute size = %zu", size); + if (mIntf->isAdts()) { + ALOGV("ADTS"); + size_t adtsHeaderSize = 0; + // skip 30 bits, aac_frame_length follows. + // ssssssss ssssiiip ppffffPc ccohCCll llllllll lll????? + + const uint8_t* adtsHeader = view.data() + offset; + bool signalError = false; + if (size < 7) { + ALOGE("Audio data too short to contain even the ADTS header. " + "Got %zu bytes.", size); + hexdump(adtsHeader, size); + signalError = true; + } else { + bool protectionAbsent = (adtsHeader[1] & 1); + unsigned aac_frame_length = ((adtsHeader[3] & 3) << 11) | + (adtsHeader[4] << 3) | + (adtsHeader[5] >> 5); + + if (size < aac_frame_length) { + ALOGE("Not enough audio data for the complete frame. " + "Got %zu bytes, frame size according to the ADTS " + "header is %u bytes.", size, aac_frame_length); + hexdump(adtsHeader, size); + signalError = true; + } else { + adtsHeaderSize = (protectionAbsent ? 7 : 9); + if (aac_frame_length < adtsHeaderSize) { + signalError = true; + } else { + // const_cast because of libAACdec method signature. + inBuffer = + const_cast<uint8_t*>(adtsHeader + adtsHeaderSize); + inBufferLength = aac_frame_length - adtsHeaderSize; + + offset += adtsHeaderSize; + size -= adtsHeaderSize; + } + } + } + + if (signalError) { + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } + } else { + ALOGV("Non ADTS"); + // const_cast because of libAACdec method signature. + inBuffer = const_cast<uint8_t*>(view.data() + offset); + inBufferLength = size; + } + + signed int prevSampleRate = mSampFreq; + signed int prevNumChannels = mNumChannels; + + /* XAAC decoder expects first frame to be fed via configXAACDecoder API + * which should initialize the codec. Once this state is reached, call the + * decodeXAACStream API with same frame to decode! */ + if (!mIsCodecInitialized) { + IA_ERRORCODE err_code = configXAACDecoder(inBuffer, inBufferLength); + if (IA_NO_ERROR != err_code) { + ALOGE("configXAACDecoder Failed 2 err_code = %d", err_code); + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } + + if ((mSampFreq != prevSampleRate) || + (mNumChannels != prevNumChannels)) { + ALOGI("Reconfiguring decoder: %d->%d Hz, %d->%d channels", + prevSampleRate, mSampFreq, prevNumChannels, mNumChannels); + + C2StreamSampleRateInfo::output sampleRateInfo(0u, mSampFreq); + C2StreamChannelCountInfo::output channelCountInfo(0u, mNumChannels); + std::vector<std::unique_ptr<C2SettingResult>> failures; + c2_status_t err = mIntf->config( + { &sampleRateInfo, &channelCountInfo }, + C2_MAY_BLOCK, + &failures); + if (err == OK) { + work->worklets.front()->output.configUpdate.push_back( + C2Param::Copy(sampleRateInfo)); + work->worklets.front()->output.configUpdate.push_back( + C2Param::Copy(channelCountInfo)); + } else { + ALOGE("Config Update failed"); + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } + } + } + + signed int bytesConsumed = 0; + IA_ERRORCODE errorCode = IA_NO_ERROR; + if (mIsCodecInitialized) { + mIsCodecConfigFlushRequired = true; + errorCode = decodeXAACStream(inBuffer, inBufferLength, + &bytesConsumed, &mNumOutBytes); + } else if (!mIsCodecConfigFlushRequired) { + ALOGW("Assumption that first frame after header initializes decoder Failed!"); + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } + size -= bytesConsumed; + offset += bytesConsumed; + + if (inBufferLength != (uint32_t)bytesConsumed) + ALOGW("All data not consumed"); + + /* In case of error, decoder would have given out empty buffer */ + if ((IA_NO_ERROR != errorCode) && (0 == mNumOutBytes) && mIsCodecInitialized) + mNumOutBytes = mOutputFrameLength * (mPcmWdSz / 8) * mNumChannels; + + if (!bytesConsumed) { + ALOGW("bytesConsumed = 0 should never happen"); + } + + if ((uint32_t)mNumOutBytes > + mOutputFrameLength * sizeof(int16_t) * mNumChannels) { + ALOGE("mNumOutBytes > mOutputFrameLength * sizeof(int16_t) * mNumChannels, should never happen"); + mSignalledError = true; + work->result = C2_CORRUPTED; + return; + } + + if (IA_NO_ERROR != errorCode) { + // TODO: check for overflow, ASAN + memset(mOutputBuffer, 0, mNumOutBytes); + + // Discard input buffer. + size = 0; + + // fall through + } + memcpy(tempOutputDrainBuffer, mOutputBuffer, mNumOutBytes); + tempOutputDrainBuffer += mNumOutBytes; + mOutputDrainBufferWritePos += mNumOutBytes; + } + + if (mOutputDrainBufferWritePos) { + finishWork(work, pool); + } else { + fillEmptyWork(work); + } + if (eos) mSignalledOutputEos = true; +} + +c2_status_t C2SoftXaacDec::drain(uint32_t drainMode, + const std::shared_ptr<C2BlockPool>& pool) { + (void)pool; + if (drainMode == NO_DRAIN) { + ALOGW("drain with NO_DRAIN: no-op"); + return C2_OK; + } + if (drainMode == DRAIN_CHAIN) { + ALOGW("DRAIN_CHAIN not supported"); + return C2_OMITTED; + } + + return C2_OK; +} + +IA_ERRORCODE C2SoftXaacDec::configflushDecode() { + IA_ERRORCODE err_code; + uint32_t ui_init_done; + uint32_t inBufferLength = 8203; + + err_code = ixheaacd_dec_api(mXheaacCodecHandle, + IA_API_CMD_INIT, + IA_CMD_TYPE_FLUSH_MEM, + nullptr); + RETURN_IF_FATAL(err_code, "IA_CMD_TYPE_FLUSH_MEM"); + + err_code = ixheaacd_dec_api(mXheaacCodecHandle, + IA_API_CMD_SET_INPUT_BYTES, + 0, + &inBufferLength); + RETURN_IF_FATAL(err_code, "IA_API_CMD_SET_INPUT_BYTES"); + + err_code = ixheaacd_dec_api(mXheaacCodecHandle, + IA_API_CMD_INIT, + IA_CMD_TYPE_FLUSH_MEM, + nullptr); + RETURN_IF_FATAL(err_code, "IA_CMD_TYPE_FLUSH_MEM"); + + err_code = ixheaacd_dec_api(mXheaacCodecHandle, + IA_API_CMD_INIT, + IA_CMD_TYPE_INIT_DONE_QUERY, + &ui_init_done); + RETURN_IF_FATAL(err_code, "IA_CMD_TYPE_INIT_DONE_QUERY"); + + if (ui_init_done) { + err_code = getXAACStreamInfo(); + RETURN_IF_FATAL(err_code, "getXAACStreamInfo"); + ALOGV("Found Codec with below config---\nsampFreq %d\nnumChannels %d\npcmWdSz %d\nchannelMask %d\noutputFrameLength %d", + mSampFreq, mNumChannels, mPcmWdSz, mChannelMask, mOutputFrameLength); + mIsCodecInitialized = true; + } + return IA_NO_ERROR; +} + +c2_status_t C2SoftXaacDec::onFlush_sm() { + if (mIsCodecInitialized) { + IA_ERRORCODE err_code = configflushDecode(); + if (err_code != IA_NO_ERROR) { + ALOGE("Error in configflushDecode: Error %d", err_code); + } + } + drainDecoder(); + mSignalledOutputEos = false; + mSignalledError = false; + + return C2_OK; +} + +IA_ERRORCODE C2SoftXaacDec::drainDecoder() { + /* Output delay compensation logic should sit here. */ + /* Nothing to be done as XAAC decoder does not introduce output buffer delay */ + + return 0; +} + +IA_ERRORCODE C2SoftXaacDec::initXAACDecoder() { + /* First part */ + /* Error Handler Init */ + /* Get Library Name, Library Version and API Version */ + /* Initialize API structure + Default config set */ + /* Set config params from user */ + /* Initialize memory tables */ + /* Get memory information and allocate memory */ + + mInputBufferSize = 0; + mInputBuffer = nullptr; + mOutputBuffer = nullptr; + /* Process struct initing end */ + + /* ******************************************************************/ + /* Initialize API structure and set config params to default */ + /* ******************************************************************/ + /* API size */ + uint32_t pui_api_size; + /* Get the API size */ + IA_ERRORCODE err_code = ixheaacd_dec_api(nullptr, + IA_API_CMD_GET_API_SIZE, + 0, + &pui_api_size); + RETURN_IF_FATAL(err_code, "IA_API_CMD_GET_API_SIZE"); + + /* Allocate memory for API */ + mXheaacCodecHandle = memalign(4, pui_api_size); + if (!mXheaacCodecHandle) { + ALOGE("malloc for pui_api_size + 4 >> %d Failed", pui_api_size + 4); + return IA_FATAL_ERROR; + } + mMemoryVec.push(mXheaacCodecHandle); + + /* Set the config params to default values */ + err_code = ixheaacd_dec_api(mXheaacCodecHandle, + IA_API_CMD_INIT, + IA_CMD_TYPE_INIT_API_PRE_CONFIG_PARAMS, + nullptr); + RETURN_IF_FATAL(err_code, "IA_CMD_TYPE_INIT_API_PRE_CONFIG_PARAMS"); + + /* Get the 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"); + + /* Allocate memory for API */ + mMpegDDrcHandle = memalign(4, pui_api_size); + if (!mMpegDDrcHandle) { + ALOGE("malloc for pui_api_size + 4 >> %d Failed", pui_api_size + 4); + return IA_FATAL_ERROR; + } + mMemoryVec.push(mMpegDDrcHandle); + + /* 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, nullptr); + + RETURN_IF_FATAL(err_code, "IA_CMD_TYPE_INIT_API_PRE_CONFIG_PARAMS"); + + /* ******************************************************************/ + /* Set config parameters */ + /* ******************************************************************/ + uint32_t ui_mp4_flag = 1; + err_code = ixheaacd_dec_api(mXheaacCodecHandle, + IA_API_CMD_SET_CONFIG_PARAM, + IA_ENHAACPLUS_DEC_CONFIG_PARAM_ISMP4, + &ui_mp4_flag); + RETURN_IF_FATAL(err_code, "IA_ENHAACPLUS_DEC_CONFIG_PARAM_ISMP4"); + + /* ******************************************************************/ + /* Initialize Memory info tables */ + /* ******************************************************************/ + uint32_t ui_proc_mem_tabs_size; + pVOID pv_alloc_ptr; + /* Get memory info tables size */ + err_code = ixheaacd_dec_api(mXheaacCodecHandle, + IA_API_CMD_GET_MEMTABS_SIZE, + 0, + &ui_proc_mem_tabs_size); + RETURN_IF_FATAL(err_code, "IA_API_CMD_GET_MEMTABS_SIZE"); + + pv_alloc_ptr = memalign(4, ui_proc_mem_tabs_size); + if (!pv_alloc_ptr) { + ALOGE("Malloc for size (ui_proc_mem_tabs_size + 4) = %d failed!", ui_proc_mem_tabs_size + 4); + return IA_FATAL_ERROR; + } + mMemoryVec.push(pv_alloc_ptr); + + /* Set pointer for process memory tables */ + err_code = ixheaacd_dec_api(mXheaacCodecHandle, + IA_API_CMD_SET_MEMTABS_PTR, + 0, + pv_alloc_ptr); + RETURN_IF_FATAL(err_code, "IA_API_CMD_SET_MEMTABS_PTR"); + + /* initialize the API, post config, fill memory tables */ + err_code = ixheaacd_dec_api(mXheaacCodecHandle, + IA_API_CMD_INIT, + IA_CMD_TYPE_INIT_API_POST_CONFIG_PARAMS, + nullptr); + RETURN_IF_FATAL(err_code, "IA_CMD_TYPE_INIT_API_POST_CONFIG_PARAMS"); + + /* ******************************************************************/ + /* Allocate Memory with info from library */ + /* ******************************************************************/ + /* There are four different types of memories, that needs to be allocated */ + /* persistent,scratch,input and output */ + for (int i = 0; i < 4; i++) { + int ui_size = 0, ui_alignment = 0, ui_type = 0; + + /* Get memory size */ + err_code = ixheaacd_dec_api(mXheaacCodecHandle, + IA_API_CMD_GET_MEM_INFO_SIZE, + i, + &ui_size); + RETURN_IF_FATAL(err_code, "IA_API_CMD_GET_MEM_INFO_SIZE"); + + /* Get memory alignment */ + err_code = ixheaacd_dec_api(mXheaacCodecHandle, + IA_API_CMD_GET_MEM_INFO_ALIGNMENT, + i, + &ui_alignment); + RETURN_IF_FATAL(err_code, "IA_API_CMD_GET_MEM_INFO_ALIGNMENT"); + + /* Get memory type */ + err_code = ixheaacd_dec_api(mXheaacCodecHandle, + IA_API_CMD_GET_MEM_INFO_TYPE, + i, + &ui_type); + RETURN_IF_FATAL(err_code, "IA_API_CMD_GET_MEM_INFO_TYPE"); + + pv_alloc_ptr = memalign(ui_alignment, ui_size); + if (!pv_alloc_ptr) { + ALOGE("Malloc for size (ui_size + ui_alignment) = %d failed!", + ui_size + ui_alignment); + return IA_FATAL_ERROR; + } + mMemoryVec.push(pv_alloc_ptr); + + /* Set the buffer pointer */ + err_code = ixheaacd_dec_api(mXheaacCodecHandle, + IA_API_CMD_SET_MEM_PTR, + i, + pv_alloc_ptr); + RETURN_IF_FATAL(err_code, "IA_API_CMD_SET_MEM_PTR"); + if (ui_type == IA_MEMTYPE_INPUT) { + mInputBuffer = (pWORD8)pv_alloc_ptr; + mInputBufferSize = ui_size; + } + if (ui_type == IA_MEMTYPE_OUTPUT) + mOutputBuffer = (pWORD8)pv_alloc_ptr; + } + /* End first part */ + + return IA_NO_ERROR; +} + +status_t C2SoftXaacDec::initXAACDrc() { + IA_ERRORCODE err_code = IA_NO_ERROR; + unsigned int ui_drc_val; + // DRC_PRES_MODE_WRAP_DESIRED_TARGET + int32_t targetRefLevel = mIntf->getDrcTargetRefLevel(); + ALOGV("AAC decoder using desired DRC target reference level of %d", targetRefLevel); + ui_drc_val = (unsigned int)targetRefLevel; + err_code = ixheaacd_dec_api(mXheaacCodecHandle, + IA_API_CMD_SET_CONFIG_PARAM, + IA_ENHAACPLUS_DEC_CONFIG_PARAM_DRC_TARGET_LEVEL, + &ui_drc_val); + RETURN_IF_FATAL(err_code, "IA_ENHAACPLUS_DEC_CONFIG_PARAM_DRC_TARGET_LEVEL"); + + /* Use ui_drc_val from PROP_DRC_OVERRIDE_REF_LEVEL or DRC_DEFAULT_MOBILE_REF_LEVEL + * for IA_ENHAACPLUS_DEC_DRC_TARGET_LOUDNESS too */ + err_code = ixheaacd_dec_api(mXheaacCodecHandle, IA_API_CMD_SET_CONFIG_PARAM, + IA_ENHAACPLUS_DEC_DRC_TARGET_LOUDNESS, &ui_drc_val); + + RETURN_IF_FATAL(err_code, "IA_ENHAACPLUS_DEC_DRC_TARGET_LOUDNESS"); + + int32_t attenuationFactor = mIntf->getDrcAttenuationFactor(); + ALOGV("AAC decoder using desired DRC attenuation factor of %d", attenuationFactor); + ui_drc_val = (unsigned int)attenuationFactor; + err_code = ixheaacd_dec_api(mXheaacCodecHandle, + IA_API_CMD_SET_CONFIG_PARAM, + IA_ENHAACPLUS_DEC_CONFIG_PARAM_DRC_CUT, + &ui_drc_val); + RETURN_IF_FATAL(err_code, "IA_ENHAACPLUS_DEC_CONFIG_PARAM_DRC_CUT"); + + // DRC_PRES_MODE_WRAP_DESIRED_BOOST_FACTOR + int32_t boostFactor = mIntf->getDrcBoostFactor(); + ALOGV("AAC decoder using desired DRC boost factor of %d", boostFactor); + ui_drc_val = (unsigned int)boostFactor; + err_code = ixheaacd_dec_api(mXheaacCodecHandle, + IA_API_CMD_SET_CONFIG_PARAM, + IA_ENHAACPLUS_DEC_CONFIG_PARAM_DRC_BOOST, + &ui_drc_val); + RETURN_IF_FATAL(err_code, "IA_ENHAACPLUS_DEC_CONFIG_PARAM_DRC_BOOST"); + + // DRC_PRES_MODE_WRAP_DESIRED_HEAVY + int32_t compressMode = mIntf->getDrcCompressMode(); + ALOGV("AAC decoder using desried DRC heavy compression switch of %d", compressMode); + ui_drc_val = (unsigned int)compressMode; + + err_code = ixheaacd_dec_api(mXheaacCodecHandle, + IA_API_CMD_SET_CONFIG_PARAM, + IA_ENHAACPLUS_DEC_CONFIG_PARAM_DRC_HEAVY_COMP, + &ui_drc_val); + RETURN_IF_FATAL(err_code, "IA_ENHAACPLUS_DEC_CONFIG_PARAM_DRC_HEAVY_COMP"); + + // AAC_UNIDRC_SET_EFFECT + int32_t effectType = mIntf->getDrcEffectType(); + ALOGV("AAC decoder using MPEG-D DRC effect type %d", effectType); + ui_drc_val = (unsigned int)effectType; + err_code = ixheaacd_dec_api(mXheaacCodecHandle, IA_API_CMD_SET_CONFIG_PARAM, + IA_ENHAACPLUS_DEC_DRC_EFFECT_TYPE, &ui_drc_val); + + RETURN_IF_FATAL(err_code, "IA_ENHAACPLUS_DEC_DRC_EFFECT_TYPE"); + + return IA_NO_ERROR; +} + +IA_ERRORCODE C2SoftXaacDec::deInitXAACDecoder() { + ALOGV("deInitXAACDecoder"); + + /* Error code */ + IA_ERRORCODE err_code = IA_NO_ERROR; + + if (mXheaacCodecHandle) { + /* Tell that the input is over in this buffer */ + err_code = ixheaacd_dec_api(mXheaacCodecHandle, + IA_API_CMD_INPUT_OVER, + 0, + nullptr); + } + + /* Irrespective of error returned in IA_API_CMD_INPUT_OVER, free allocated memory */ + for (void* buf : mMemoryVec) { + if (buf) free(buf); + } + mMemoryVec.clear(); + mXheaacCodecHandle = nullptr; + + return err_code; +} + +IA_ERRORCODE C2SoftXaacDec::deInitMPEGDDDrc() { + ALOGV("deInitMPEGDDDrc"); + + for (void* buf : mDrcMemoryVec) { + if (buf) free(buf); + } + mDrcMemoryVec.clear(); + return IA_NO_ERROR; +} + +IA_ERRORCODE C2SoftXaacDec::configXAACDecoder(uint8_t* inBuffer, uint32_t inBufferLength) { + if (mInputBufferSize < inBufferLength) { + ALOGE("Cannot config AAC, input buffer size %d < inBufferLength %d", mInputBufferSize, inBufferLength); + return false; + } + /* Copy the buffer passed by Android plugin to codec input buffer */ + memcpy(mInputBuffer, inBuffer, inBufferLength); + + /* Set number of bytes to be processed */ + IA_ERRORCODE err_code = ixheaacd_dec_api(mXheaacCodecHandle, + IA_API_CMD_SET_INPUT_BYTES, + 0, + &inBufferLength); + RETURN_IF_FATAL(err_code, "IA_API_CMD_SET_INPUT_BYTES"); + + if (mIsCodecConfigFlushRequired) { + /* If codec is already initialized, then GA header is passed again */ + /* Need to call the Flush API instead of INIT_PROCESS */ + mIsCodecInitialized = false; /* Codec needs to be Reinitialized after flush */ + err_code = ixheaacd_dec_api(mXheaacCodecHandle, + IA_API_CMD_INIT, + IA_CMD_TYPE_GA_HDR, + 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, + nullptr); + RETURN_IF_FATAL(err_code, "IA_CMD_TYPE_INIT_PROCESS"); + } + + uint32_t ui_init_done; + /* Checking for end of initialization */ + err_code = ixheaacd_dec_api(mXheaacCodecHandle, + IA_API_CMD_INIT, + IA_CMD_TYPE_INIT_DONE_QUERY, + &ui_init_done); + RETURN_IF_FATAL(err_code, "IA_CMD_TYPE_INIT_DONE_QUERY"); + + /* How much buffer is used in input buffers */ + int32_t i_bytes_consumed; + err_code = ixheaacd_dec_api(mXheaacCodecHandle, + IA_API_CMD_GET_CURIDX_INPUT_BUF, + 0, + &i_bytes_consumed); + RETURN_IF_FATAL(err_code, "IA_API_CMD_GET_CURIDX_INPUT_BUF"); + + if (ui_init_done) { + err_code = getXAACStreamInfo(); + RETURN_IF_FATAL(err_code, "getXAACStreamInfo"); + ALOGI("Found Codec with below config---\nsampFreq %d\nnumChannels %d\npcmWdSz %d\nchannelMask %d\noutputFrameLength %d", + mSampFreq, mNumChannels, mPcmWdSz, mChannelMask, mOutputFrameLength); + mIsCodecInitialized = true; + + err_code = configMPEGDDrc(); + RETURN_IF_FATAL(err_code, "configMPEGDDrc"); + } + + return IA_NO_ERROR; +} +IA_ERRORCODE C2SoftXaacDec::initMPEGDDDrc() { + IA_ERRORCODE err_code = IA_NO_ERROR; + + for (int i = 0; i < (WORD32)2; i++) { + WORD32 ui_size, ui_alignment, ui_type; + pVOID pv_alloc_ptr; + + /* Get memory size */ + err_code = ia_drc_dec_api(mMpegDDrcHandle, IA_API_CMD_GET_MEM_INFO_SIZE, i, &ui_size); + + RETURN_IF_FATAL(err_code, "IA_API_CMD_GET_MEM_INFO_SIZE"); + + /* Get memory alignment */ + err_code = + ia_drc_dec_api(mMpegDDrcHandle, IA_API_CMD_GET_MEM_INFO_ALIGNMENT, i, &ui_alignment); + + RETURN_IF_FATAL(err_code, "IA_API_CMD_GET_MEM_INFO_ALIGNMENT"); + + /* Get memory type */ + err_code = ia_drc_dec_api(mMpegDDrcHandle, IA_API_CMD_GET_MEM_INFO_TYPE, i, &ui_type); + RETURN_IF_FATAL(err_code, "IA_API_CMD_GET_MEM_INFO_TYPE"); + + pv_alloc_ptr = memalign(4, ui_size); + if (pv_alloc_ptr == nullptr) { + ALOGE(" Cannot create requested memory %d", ui_size); + return IA_FATAL_ERROR; + } + mDrcMemoryVec.push(pv_alloc_ptr); + + /* Set the buffer pointer */ + err_code = ia_drc_dec_api(mMpegDDrcHandle, IA_API_CMD_SET_MEM_PTR, i, pv_alloc_ptr); + + RETURN_IF_FATAL(err_code, "IA_API_CMD_SET_MEM_PTR"); + } + + WORD32 ui_size; + ui_size = 8192 * 2; + + mDrcInBuf = (int8_t*)memalign(4, ui_size); + if (mDrcInBuf == nullptr) { + ALOGE(" Cannot create requested memory %d", ui_size); + return IA_FATAL_ERROR; + } + mDrcMemoryVec.push(mDrcInBuf); + + err_code = ia_drc_dec_api(mMpegDDrcHandle, IA_API_CMD_SET_MEM_PTR, 2, mDrcInBuf); + RETURN_IF_FATAL(err_code, "IA_API_CMD_SET_MEM_PTR"); + + mDrcOutBuf = (int8_t*)memalign(4, ui_size); + if (mDrcOutBuf == nullptr) { + ALOGE(" Cannot create requested memory %d", ui_size); + return IA_FATAL_ERROR; + } + mDrcMemoryVec.push(mDrcOutBuf); + + err_code = ia_drc_dec_api(mMpegDDrcHandle, IA_API_CMD_SET_MEM_PTR, 3, mDrcOutBuf); + RETURN_IF_FATAL(err_code, "IA_API_CMD_SET_MEM_PTR"); + + return IA_NO_ERROR; +} +int C2SoftXaacDec::configMPEGDDrc() { + IA_ERRORCODE err_code = IA_NO_ERROR; + int i_effect_type; + int i_loud_norm; + int i_target_loudness; + unsigned int i_sbr_mode; + uint32_t ui_proc_mem_tabs_size = 0; + pVOID pv_alloc_ptr = NULL; + + /* Sampling Frequency */ + err_code = ia_drc_dec_api(mMpegDDrcHandle, IA_API_CMD_SET_CONFIG_PARAM, + IA_DRC_DEC_CONFIG_PARAM_SAMP_FREQ, &mSampFreq); + RETURN_IF_FATAL(err_code, "IA_DRC_DEC_CONFIG_PARAM_SAMP_FREQ"); + /* Total Number of Channels */ + err_code = ia_drc_dec_api(mMpegDDrcHandle, IA_API_CMD_SET_CONFIG_PARAM, + IA_DRC_DEC_CONFIG_PARAM_NUM_CHANNELS, &mNumChannels); + RETURN_IF_FATAL(err_code, "IA_DRC_DEC_CONFIG_PARAM_NUM_CHANNELS"); + + /* PCM word size */ + err_code = ia_drc_dec_api(mMpegDDrcHandle, IA_API_CMD_SET_CONFIG_PARAM, + IA_DRC_DEC_CONFIG_PARAM_PCM_WDSZ, &mPcmWdSz); + RETURN_IF_FATAL(err_code, "IA_DRC_DEC_CONFIG_PARAM_PCM_WDSZ"); + + /*Set Effect Type*/ + + err_code = ixheaacd_dec_api(mXheaacCodecHandle, IA_API_CMD_GET_CONFIG_PARAM, + IA_ENHAACPLUS_DEC_CONFIG_PARAM_DRC_EFFECT_TYPE, &i_effect_type); + RETURN_IF_FATAL(err_code, "IA_ENHAACPLUS_DEC_CONFIG_PARAM_DRC_EFFECT_TYPE"); + + err_code = ia_drc_dec_api(mMpegDDrcHandle, IA_API_CMD_SET_CONFIG_PARAM, + IA_DRC_DEC_CONFIG_DRC_EFFECT_TYPE, &i_effect_type); + RETURN_IF_FATAL(err_code, "IA_DRC_DEC_CONFIG_DRC_EFFECT_TYPE"); + + /*Set target loudness */ + err_code = ixheaacd_dec_api(mXheaacCodecHandle, IA_API_CMD_GET_CONFIG_PARAM, + IA_ENHAACPLUS_DEC_CONFIG_PARAM_DRC_TARGET_LOUDNESS, + &i_target_loudness); + RETURN_IF_FATAL(err_code, "IA_ENHAACPLUS_DEC_CONFIG_PARAM_DRC_TARGET_LOUDNESS"); + + err_code = ia_drc_dec_api(mMpegDDrcHandle, IA_API_CMD_SET_CONFIG_PARAM, + IA_DRC_DEC_CONFIG_DRC_TARGET_LOUDNESS, &i_target_loudness); + RETURN_IF_FATAL(err_code, "IA_DRC_DEC_CONFIG_DRC_TARGET_LOUDNESS"); + + /*Set loud_norm_flag*/ + err_code = ixheaacd_dec_api(mXheaacCodecHandle, IA_API_CMD_GET_CONFIG_PARAM, + IA_ENHAACPLUS_DEC_CONFIG_PARAM_DRC_LOUD_NORM, &i_loud_norm); + RETURN_IF_FATAL(err_code, "IA_ENHAACPLUS_DEC_CONFIG_PARAM_DRC_LOUD_NORM"); + + err_code = ia_drc_dec_api(mMpegDDrcHandle, IA_API_CMD_SET_CONFIG_PARAM, + IA_DRC_DEC_CONFIG_DRC_LOUD_NORM, &i_loud_norm); + RETURN_IF_FATAL(err_code, "IA_DRC_DEC_CONFIG_DRC_LOUD_NORM"); + + err_code = ixheaacd_dec_api(mXheaacCodecHandle, IA_API_CMD_GET_CONFIG_PARAM, + IA_ENHAACPLUS_DEC_CONFIG_PARAM_SBR_MODE, &i_sbr_mode); + RETURN_IF_FATAL(err_code, "IA_ENHAACPLUS_DEC_CONFIG_PARAM_SBR_MODE"); + + /* Get memory info tables size */ + err_code = ia_drc_dec_api(mMpegDDrcHandle, IA_API_CMD_GET_MEMTABS_SIZE, 0, + &ui_proc_mem_tabs_size); + RETURN_IF_FATAL(err_code, "IA_API_CMD_GET_MEMTABS_SIZE"); + + pv_alloc_ptr = memalign(4, ui_proc_mem_tabs_size); + if (pv_alloc_ptr == NULL) { + ALOGE(" Cannot create requested memory %d", ui_proc_mem_tabs_size); + return IA_FATAL_ERROR; + } + memset(pv_alloc_ptr, 0, ui_proc_mem_tabs_size); + mMemoryVec.push(pv_alloc_ptr); + + /* Set pointer for process memory tables */ + err_code = ia_drc_dec_api(mMpegDDrcHandle, IA_API_CMD_SET_MEMTABS_PTR, 0, + pv_alloc_ptr); + RETURN_IF_FATAL(err_code, "IA_API_CMD_SET_MEMTABS_PTR"); + + err_code = ia_drc_dec_api(mMpegDDrcHandle, IA_API_CMD_INIT, + IA_CMD_TYPE_INIT_API_POST_CONFIG_PARAMS, nullptr); + + RETURN_IF_FATAL(err_code, "IA_CMD_TYPE_INIT_API_POST_CONFIG_PARAMS"); + + /* Free any memory that is allocated for MPEG D Drc so far */ + deInitMPEGDDDrc(); + + err_code = initMPEGDDDrc(); + if (err_code != IA_NO_ERROR) { + ALOGE("initMPEGDDDrc failed with error %d", err_code); + deInitMPEGDDDrc(); + return err_code; + } + + /* DRC buffers + buf[0] - contains extension element pay load loudness related + buf[1] - contains extension element pay load*/ + { + VOID* p_array[2][16]; + WORD32 ii; + WORD32 buf_sizes[2][16]; + WORD32 num_elements; + WORD32 num_config_ext; + WORD32 bit_str_fmt = 1; + + WORD32 uo_num_chan; + + memset(buf_sizes, 0, 32 * sizeof(WORD32)); + + err_code = + ixheaacd_dec_api(mXheaacCodecHandle, IA_API_CMD_GET_CONFIG_PARAM, + IA_ENHAACPLUS_DEC_CONFIG_EXT_ELE_BUF_SIZES, &buf_sizes[0][0]); + RETURN_IF_FATAL(err_code, "IA_ENHAACPLUS_DEC_CONFIG_EXT_ELE_BUF_SIZES"); + + err_code = ixheaacd_dec_api(mXheaacCodecHandle, IA_API_CMD_GET_CONFIG_PARAM, + IA_ENHAACPLUS_DEC_CONFIG_EXT_ELE_PTR, &p_array); + 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, 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, + IA_ENHAACPLUS_DEC_CONFIG_NUM_ELE, &num_elements); + RETURN_IF_FATAL(err_code, "IA_ENHAACPLUS_DEC_CONFIG_NUM_ELE"); + + err_code = ixheaacd_dec_api(mXheaacCodecHandle, IA_API_CMD_GET_CONFIG_PARAM, + IA_ENHAACPLUS_DEC_CONFIG_NUM_CONFIG_EXT, &num_config_ext); + RETURN_IF_FATAL(err_code, "IA_ENHAACPLUS_DEC_CONFIG_NUM_CONFIG_EXT"); + + for (ii = 0; ii < num_config_ext; ii++) { + /*copy loudness bitstream*/ + if (buf_sizes[0][ii] > 0) { + memcpy(mDrcInBuf, p_array[0][ii], buf_sizes[0][ii]); + + /*Set bitstream_split_format */ + err_code = ia_drc_dec_api(mMpegDDrcHandle, IA_API_CMD_SET_CONFIG_PARAM, + IA_DRC_DEC_CONFIG_PARAM_BITS_FORMAT, &bit_str_fmt); + RETURN_IF_FATAL(err_code, "IA_DRC_DEC_CONFIG_PARAM_BITS_FORMAT"); + + /* Set number of bytes to be processed */ + err_code = ia_drc_dec_api(mMpegDDrcHandle, IA_API_CMD_SET_INPUT_BYTES_IL_BS, 0, + &buf_sizes[0][ii]); + RETURN_IF_FATAL(err_code, "IA_API_CMD_SET_INPUT_BYTES_IL_BS"); + + /* Execute process */ + err_code = ia_drc_dec_api(mMpegDDrcHandle, IA_API_CMD_INIT, + IA_CMD_TYPE_INIT_CPY_IL_BSF_BUFF, nullptr); + RETURN_IF_FATAL(err_code, "IA_CMD_TYPE_INIT_CPY_IL_BSF_BUFF"); + + mDRCFlag = 1; + } + } + + for (ii = 0; ii < num_elements; ii++) { + /*copy config bitstream*/ + if (buf_sizes[1][ii] > 0) { + memcpy(mDrcInBuf, p_array[1][ii], buf_sizes[1][ii]); + /* Set number of bytes to be processed */ + + /*Set bitstream_split_format */ + err_code = ia_drc_dec_api(mMpegDDrcHandle, IA_API_CMD_SET_CONFIG_PARAM, + IA_DRC_DEC_CONFIG_PARAM_BITS_FORMAT, &bit_str_fmt); + RETURN_IF_FATAL(err_code, "IA_DRC_DEC_CONFIG_PARAM_BITS_FORMAT"); + + err_code = ia_drc_dec_api(mMpegDDrcHandle, IA_API_CMD_SET_INPUT_BYTES_IC_BS, 0, + &buf_sizes[1][ii]); + RETURN_IF_FATAL(err_code, "IA_API_CMD_SET_INPUT_BYTES_IC_BS"); + + /* Execute process */ + err_code = ia_drc_dec_api(mMpegDDrcHandle, IA_API_CMD_INIT, + IA_CMD_TYPE_INIT_CPY_IC_BSF_BUFF, nullptr); + + RETURN_IF_FATAL(err_code, "IA_CMD_TYPE_INIT_CPY_IC_BSF_BUFF"); + + mDRCFlag = 1; + } + } + + if (mDRCFlag == 1) { + mMpegDDRCPresent = 1; + } else { + mMpegDDRCPresent = 0; + } + + /*Read interface buffer config file bitstream*/ + if (mMpegDDRCPresent == 1) { + WORD32 interface_is_present = 1; + + if (i_sbr_mode != 0) { + if (i_sbr_mode == 1) { + mOutputFrameLength = 2048; + } else if (i_sbr_mode == 3) { + mOutputFrameLength = 4096; + } else { + mOutputFrameLength = 1024; + } + } else { + mOutputFrameLength = 4096; + } + + err_code = ia_drc_dec_api(mMpegDDrcHandle, IA_API_CMD_SET_CONFIG_PARAM, + IA_DRC_DEC_CONFIG_PARAM_FRAME_SIZE, (WORD32 *)&mOutputFrameLength); + RETURN_IF_FATAL(err_code, "IA_DRC_DEC_CONFIG_PARAM_FRAME_SIZE"); + + err_code = + ia_drc_dec_api(mMpegDDrcHandle, IA_API_CMD_SET_CONFIG_PARAM, + IA_DRC_DEC_CONFIG_PARAM_INT_PRESENT, &interface_is_present); + RETURN_IF_FATAL(err_code, "IA_DRC_DEC_CONFIG_PARAM_INT_PRESENT"); + + /* Execute process */ + err_code = ia_drc_dec_api(mMpegDDrcHandle, IA_API_CMD_INIT, + 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, nullptr); + RETURN_IF_FATAL(err_code, "IA_CMD_TYPE_INIT_PROCESS"); + + err_code = ia_drc_dec_api(mMpegDDrcHandle, IA_API_CMD_GET_CONFIG_PARAM, + IA_DRC_DEC_CONFIG_PARAM_NUM_CHANNELS, &uo_num_chan); + RETURN_IF_FATAL(err_code, "IA_DRC_DEC_CONFIG_PARAM_NUM_CHANNELS"); + } + } + + return err_code; +} + +IA_ERRORCODE C2SoftXaacDec::decodeXAACStream(uint8_t* inBuffer, + uint32_t inBufferLength, + int32_t* bytesConsumed, + int32_t* outBytes) { + if (mInputBufferSize < inBufferLength) { + ALOGE("Cannot config AAC, input buffer size %d < inBufferLength %d", mInputBufferSize, inBufferLength); + return -1; + } + /* Copy the buffer passed by Android plugin to codec input buffer */ + memcpy(mInputBuffer, inBuffer, inBufferLength); + + /* Set number of bytes to be processed */ + IA_ERRORCODE err_code = ixheaacd_dec_api(mXheaacCodecHandle, + IA_API_CMD_SET_INPUT_BYTES, + 0, + &inBufferLength); + RETURN_IF_FATAL(err_code, "IA_API_CMD_SET_INPUT_BYTES"); + + /* Execute process */ + err_code = ixheaacd_dec_api(mXheaacCodecHandle, + IA_API_CMD_EXECUTE, + IA_CMD_TYPE_DO_EXECUTE, + nullptr); + RETURN_IF_FATAL(err_code, "IA_CMD_TYPE_DO_EXECUTE"); + + /* Checking for end of processing */ + uint32_t ui_exec_done; + err_code = ixheaacd_dec_api(mXheaacCodecHandle, + IA_API_CMD_EXECUTE, + IA_CMD_TYPE_DONE_QUERY, + &ui_exec_done); + RETURN_IF_FATAL(err_code, "IA_CMD_TYPE_DONE_QUERY"); + + if (ui_exec_done != 1) { + VOID* p_array; // ITTIAM:buffer to handle gain payload + WORD32 buf_size = 0; // ITTIAM:gain payload length + WORD32 bit_str_fmt = 1; + WORD32 gain_stream_flag = 1; + + err_code = ixheaacd_dec_api(mXheaacCodecHandle, IA_API_CMD_GET_CONFIG_PARAM, + IA_ENHAACPLUS_DEC_CONFIG_GAIN_PAYLOAD_LEN, &buf_size); + RETURN_IF_FATAL(err_code, "IA_ENHAACPLUS_DEC_CONFIG_GAIN_PAYLOAD_LEN"); + + err_code = ixheaacd_dec_api(mXheaacCodecHandle, IA_API_CMD_GET_CONFIG_PARAM, + IA_ENHAACPLUS_DEC_CONFIG_GAIN_PAYLOAD_BUF, &p_array); + RETURN_IF_FATAL(err_code, "IA_ENHAACPLUS_DEC_CONFIG_GAIN_PAYLOAD_BUF"); + + if (buf_size > 0) { + /*Set bitstream_split_format */ + err_code = ia_drc_dec_api(mMpegDDrcHandle, IA_API_CMD_SET_CONFIG_PARAM, + IA_DRC_DEC_CONFIG_PARAM_BITS_FORMAT, &bit_str_fmt); + RETURN_IF_FATAL(err_code, "IA_DRC_DEC_CONFIG_PARAM_BITS_FORMAT"); + + memcpy(mDrcInBuf, p_array, buf_size); + /* Set number of bytes to be processed */ + err_code = + ia_drc_dec_api(mMpegDDrcHandle, IA_API_CMD_SET_INPUT_BYTES_BS, 0, &buf_size); + RETURN_IF_FATAL(err_code, "IA_DRC_DEC_CONFIG_PARAM_BITS_FORMAT"); + + err_code = ia_drc_dec_api(mMpegDDrcHandle, IA_API_CMD_SET_CONFIG_PARAM, + IA_DRC_DEC_CONFIG_GAIN_STREAM_FLAG, &gain_stream_flag); + RETURN_IF_FATAL(err_code, "IA_DRC_DEC_CONFIG_PARAM_BITS_FORMAT"); + + /* Execute process */ + err_code = ia_drc_dec_api(mMpegDDrcHandle, IA_API_CMD_INIT, + IA_CMD_TYPE_INIT_CPY_BSF_BUFF, nullptr); + RETURN_IF_FATAL(err_code, "IA_DRC_DEC_CONFIG_PARAM_BITS_FORMAT"); + + mMpegDDRCPresent = 1; + } + } + + /* How much buffer is used in input buffers */ + err_code = ixheaacd_dec_api(mXheaacCodecHandle, + IA_API_CMD_GET_CURIDX_INPUT_BUF, + 0, + bytesConsumed); + RETURN_IF_FATAL(err_code, "IA_API_CMD_GET_CURIDX_INPUT_BUF"); + + /* Get the output bytes */ + err_code = ixheaacd_dec_api(mXheaacCodecHandle, + IA_API_CMD_GET_OUTPUT_BYTES, + 0, + outBytes); + RETURN_IF_FATAL(err_code, "IA_API_CMD_GET_OUTPUT_BYTES"); + + if (mMpegDDRCPresent == 1) { + memcpy(mDrcInBuf, mOutputBuffer, *outBytes); + err_code = ia_drc_dec_api(mMpegDDrcHandle, IA_API_CMD_SET_INPUT_BYTES, 0, outBytes); + 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, nullptr); + RETURN_IF_FATAL(err_code, "IA_CMD_TYPE_DO_EXECUTE"); + + memcpy(mOutputBuffer, mDrcOutBuf, *outBytes); + } + return IA_NO_ERROR; +} + +IA_ERRORCODE C2SoftXaacDec::getXAACStreamInfo() { + IA_ERRORCODE err_code = IA_NO_ERROR; + + /* Sampling frequency */ + err_code = ixheaacd_dec_api(mXheaacCodecHandle, + IA_API_CMD_GET_CONFIG_PARAM, + IA_ENHAACPLUS_DEC_CONFIG_PARAM_SAMP_FREQ, + &mSampFreq); + RETURN_IF_FATAL(err_code, "IA_ENHAACPLUS_DEC_CONFIG_PARAM_SAMP_FREQ"); + + /* Total Number of Channels */ + err_code = ixheaacd_dec_api(mXheaacCodecHandle, + IA_API_CMD_GET_CONFIG_PARAM, + IA_ENHAACPLUS_DEC_CONFIG_PARAM_NUM_CHANNELS, + &mNumChannels); + RETURN_IF_FATAL(err_code, "IA_ENHAACPLUS_DEC_CONFIG_PARAM_NUM_CHANNELS"); + if (mNumChannels > MAX_CHANNEL_COUNT) { + ALOGE(" No of channels are more than max channels\n"); + return IA_FATAL_ERROR; + } + + /* PCM word size */ + err_code = ixheaacd_dec_api(mXheaacCodecHandle, + IA_API_CMD_GET_CONFIG_PARAM, + IA_ENHAACPLUS_DEC_CONFIG_PARAM_PCM_WDSZ, + &mPcmWdSz); + RETURN_IF_FATAL(err_code, "IA_ENHAACPLUS_DEC_CONFIG_PARAM_PCM_WDSZ"); + if ((mPcmWdSz / 8) != 2) { + ALOGE(" No of channels are more than max channels\n"); + return IA_FATAL_ERROR; + } + + /* channel mask to tell the arrangement of channels in bit stream */ + err_code = ixheaacd_dec_api(mXheaacCodecHandle, + IA_API_CMD_GET_CONFIG_PARAM, + IA_ENHAACPLUS_DEC_CONFIG_PARAM_CHANNEL_MASK, + &mChannelMask); + RETURN_IF_FATAL(err_code, "IA_ENHAACPLUS_DEC_CONFIG_PARAM_CHANNEL_MASK"); + + /* Channel mode to tell MONO/STEREO/DUAL-MONO/NONE_OF_THESE */ + uint32_t ui_channel_mode; + err_code = ixheaacd_dec_api(mXheaacCodecHandle, + IA_API_CMD_GET_CONFIG_PARAM, + IA_ENHAACPLUS_DEC_CONFIG_PARAM_CHANNEL_MODE, + &ui_channel_mode); + RETURN_IF_FATAL(err_code, "IA_ENHAACPLUS_DEC_CONFIG_PARAM_CHANNEL_MODE"); + if (ui_channel_mode == 0) + ALOGV("Channel Mode: MONO_OR_PS\n"); + else if (ui_channel_mode == 1) + ALOGV("Channel Mode: STEREO\n"); + else if (ui_channel_mode == 2) + ALOGV("Channel Mode: DUAL-MONO\n"); + else + ALOGV("Channel Mode: NONE_OF_THESE or MULTICHANNEL\n"); + + /* Channel mode to tell SBR PRESENT/NOT_PRESENT */ + uint32_t ui_sbr_mode; + err_code = ixheaacd_dec_api(mXheaacCodecHandle, + IA_API_CMD_GET_CONFIG_PARAM, + IA_ENHAACPLUS_DEC_CONFIG_PARAM_SBR_MODE, + &ui_sbr_mode); + RETURN_IF_FATAL(err_code, "IA_ENHAACPLUS_DEC_CONFIG_PARAM_SBR_MODE"); + if (ui_sbr_mode == 0) + ALOGV("SBR Mode: NOT_PRESENT\n"); + else if (ui_sbr_mode == 1) + ALOGV("SBR Mode: PRESENT\n"); + else + ALOGV("SBR Mode: ILLEGAL\n"); + + /* mOutputFrameLength = 1024 * (1 + SBR_MODE) for AAC */ + /* For USAC it could be 1024 * 3 , support to query */ + /* not yet added in codec */ + mOutputFrameLength = 1024 * (1 + ui_sbr_mode); + ALOGI("mOutputFrameLength %d ui_sbr_mode %d", mOutputFrameLength, ui_sbr_mode); + + return IA_NO_ERROR; +} + +IA_ERRORCODE C2SoftXaacDec::setXAACDRCInfo(int32_t drcCut, int32_t drcBoost, + int32_t drcRefLevel, + int32_t drcHeavyCompression, + int32_t drEffectType) { + IA_ERRORCODE err_code = IA_NO_ERROR; + + int32_t ui_drc_enable = 1; + err_code = ixheaacd_dec_api(mXheaacCodecHandle, IA_API_CMD_SET_CONFIG_PARAM, + IA_ENHAACPLUS_DEC_CONFIG_PARAM_DRC_ENABLE, + &ui_drc_enable); + RETURN_IF_FATAL(err_code, "IA_ENHAACPLUS_DEC_CONFIG_PARAM_DRC_ENABLE"); + if (drcCut != -1) { + err_code = + ixheaacd_dec_api(mXheaacCodecHandle, IA_API_CMD_SET_CONFIG_PARAM, + IA_ENHAACPLUS_DEC_CONFIG_PARAM_DRC_CUT, &drcCut); + RETURN_IF_FATAL(err_code, "IA_ENHAACPLUS_DEC_CONFIG_PARAM_DRC_CUT"); + } + + if (drcBoost != -1) { + err_code = ixheaacd_dec_api( + mXheaacCodecHandle, IA_API_CMD_SET_CONFIG_PARAM, + IA_ENHAACPLUS_DEC_CONFIG_PARAM_DRC_BOOST, &drcBoost); + RETURN_IF_FATAL(err_code, "IA_ENHAACPLUS_DEC_CONFIG_PARAM_DRC_BOOST"); + } + + if (drcRefLevel != -1) { + err_code = ixheaacd_dec_api( + mXheaacCodecHandle, IA_API_CMD_SET_CONFIG_PARAM, + IA_ENHAACPLUS_DEC_CONFIG_PARAM_DRC_TARGET_LEVEL, &drcRefLevel); + RETURN_IF_FATAL(err_code, + "IA_ENHAACPLUS_DEC_CONFIG_PARAM_DRC_TARGET_LEVEL"); + } + + if (drcRefLevel != -1) { + err_code = ixheaacd_dec_api( + mXheaacCodecHandle, IA_API_CMD_SET_CONFIG_PARAM, + IA_ENHAACPLUS_DEC_DRC_TARGET_LOUDNESS, &drcRefLevel); + RETURN_IF_FATAL(err_code, "IA_ENHAACPLUS_DEC_DRC_TARGET_LOUDNESS"); + } + + if (drcHeavyCompression != -1) { + err_code = + ixheaacd_dec_api(mXheaacCodecHandle, IA_API_CMD_SET_CONFIG_PARAM, + IA_ENHAACPLUS_DEC_CONFIG_PARAM_DRC_HEAVY_COMP, + &drcHeavyCompression); + RETURN_IF_FATAL(err_code, + "IA_ENHAACPLUS_DEC_CONFIG_PARAM_DRC_HEAVY_COMP"); + } + + err_code = + ixheaacd_dec_api(mXheaacCodecHandle, IA_API_CMD_SET_CONFIG_PARAM, + IA_ENHAACPLUS_DEC_DRC_EFFECT_TYPE, &drEffectType); + RETURN_IF_FATAL(err_code, "IA_ENHAACPLUS_DEC_DRC_EFFECT_TYPE"); + + int32_t i_effect_type, i_target_loudness, i_loud_norm; + /*Set Effect Type*/ + err_code = ixheaacd_dec_api(mXheaacCodecHandle, IA_API_CMD_GET_CONFIG_PARAM, + IA_ENHAACPLUS_DEC_CONFIG_PARAM_DRC_EFFECT_TYPE, + &i_effect_type); + RETURN_IF_FATAL(err_code, "IA_ENHAACPLUS_DEC_CONFIG_PARAM_DRC_EFFECT_TYPE"); + + err_code = + ia_drc_dec_api(mMpegDDrcHandle, IA_API_CMD_SET_CONFIG_PARAM, + IA_DRC_DEC_CONFIG_DRC_EFFECT_TYPE, &i_effect_type); + + RETURN_IF_FATAL(err_code, "IA_DRC_DEC_CONFIG_DRC_EFFECT_TYPE"); + + /*Set target loudness */ + err_code = ixheaacd_dec_api( + mXheaacCodecHandle, IA_API_CMD_GET_CONFIG_PARAM, + IA_ENHAACPLUS_DEC_CONFIG_PARAM_DRC_TARGET_LOUDNESS, &i_target_loudness); + RETURN_IF_FATAL(err_code, + "IA_ENHAACPLUS_DEC_CONFIG_PARAM_DRC_TARGET_LOUDNESS"); + + err_code = ia_drc_dec_api(mMpegDDrcHandle, IA_API_CMD_SET_CONFIG_PARAM, + IA_DRC_DEC_CONFIG_DRC_TARGET_LOUDNESS, + &i_target_loudness); + RETURN_IF_FATAL(err_code, "IA_DRC_DEC_CONFIG_DRC_TARGET_LOUDNESS"); + + /*Set loud_norm_flag*/ + err_code = ixheaacd_dec_api(mXheaacCodecHandle, IA_API_CMD_GET_CONFIG_PARAM, + IA_ENHAACPLUS_DEC_CONFIG_PARAM_DRC_LOUD_NORM, + &i_loud_norm); + RETURN_IF_FATAL(err_code, "IA_ENHAACPLUS_DEC_CONFIG_PARAM_DRC_LOUD_NORM"); + + err_code = ia_drc_dec_api(mMpegDDrcHandle, IA_API_CMD_SET_CONFIG_PARAM, + IA_DRC_DEC_CONFIG_DRC_LOUD_NORM, &i_loud_norm); + + RETURN_IF_FATAL(err_code, "IA_DRC_DEC_CONFIG_DRC_LOUD_NORM"); + + return IA_NO_ERROR; +} + +class C2SoftXaacDecFactory : public C2ComponentFactory { +public: + C2SoftXaacDecFactory() : mHelper(std::static_pointer_cast<C2ReflectorHelper>( + GetCodec2PlatformComponentStore()->getParamReflector())) { + } + + virtual c2_status_t createComponent( + c2_node_id_t id, + std::shared_ptr<C2Component>* const component, + std::function<void(C2Component*)> deleter) override { + *component = std::shared_ptr<C2Component>( + new C2SoftXaacDec(COMPONENT_NAME, + id, + std::make_shared<C2SoftXaacDec::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual c2_status_t createInterface( + c2_node_id_t id, + std::shared_ptr<C2ComponentInterface>* const interface, + std::function<void(C2ComponentInterface*)> deleter) override { + *interface = std::shared_ptr<C2ComponentInterface>( + new SimpleInterface<C2SoftXaacDec::IntfImpl>( + COMPONENT_NAME, id, std::make_shared<C2SoftXaacDec::IntfImpl>(mHelper)), + deleter); + return C2_OK; + } + + virtual ~C2SoftXaacDecFactory() override = default; + +private: + std::shared_ptr<C2ReflectorHelper> mHelper; +}; + +} // namespace android + +extern "C" ::C2ComponentFactory* CreateCodec2Factory() { + ALOGV("in %s", __func__); + return new ::android::C2SoftXaacDecFactory(); +} + +extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) { + ALOGV("in %s", __func__); + delete factory; +}
diff --git a/media/codec2/components/xaac/C2SoftXaacDec.h b/media/codec2/components/xaac/C2SoftXaacDec.h new file mode 100644 index 0000000..5c8567f --- /dev/null +++ b/media/codec2/components/xaac/C2SoftXaacDec.h
@@ -0,0 +1,131 @@ +/* + * 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_C2_SOFT_XAAC_DEC_H_ +#define ANDROID_C2_SOFT_XAAC_DEC_H_ +#include <utils/Vector.h> +#include <SimpleC2Component.h> + +#include "ixheaacd_type_def.h" +#include "ixheaacd_error_standards.h" +#include "ixheaacd_error_handler.h" +#include "ixheaacd_apicmd_standards.h" +#include "ixheaacd_memory_standards.h" +#include "ixheaacd_aac_config.h" + +#include "impd_apicmd_standards.h" +#include "impd_drc_config_params.h" + +#define MAX_CHANNEL_COUNT 8 /* maximum number of audio channels that can be decoded */ +#define MAX_NUM_BLOCKS 8 /* maximum number of audio blocks that can be decoded */ + +extern "C" IA_ERRORCODE ixheaacd_dec_api(pVOID p_ia_module_obj, + WORD32 i_cmd, WORD32 i_idx, pVOID pv_value); +extern "C" IA_ERRORCODE ia_drc_dec_api(pVOID p_ia_module_obj, + WORD32 i_cmd, WORD32 i_idx, pVOID pv_value); +extern "C" IA_ERRORCODE ixheaacd_get_config_param(pVOID p_ia_process_api_obj, + pWORD32 pi_samp_freq, + pWORD32 pi_num_chan, + pWORD32 pi_pcm_wd_sz, + pWORD32 pi_channel_mask); + +namespace android { + +struct C2SoftXaacDec : public SimpleC2Component { + class IntfImpl; + + C2SoftXaacDec(const char* name, c2_node_id_t id, + const std::shared_ptr<IntfImpl>& intfImpl); + virtual ~C2SoftXaacDec(); + + // From SimpleC2Component + c2_status_t onInit() override; + c2_status_t onStop() override; + void onReset() override; + void onRelease() override; + c2_status_t onFlush_sm() override; + void process( + const std::unique_ptr<C2Work> &work, + const std::shared_ptr<C2BlockPool> &pool) override; + c2_status_t drain( + uint32_t drainMode, + const std::shared_ptr<C2BlockPool> &pool) override; + +private: + enum { + kOutputDrainBufferSize = 2048 * MAX_CHANNEL_COUNT * MAX_NUM_BLOCKS, + }; + + std::shared_ptr<IntfImpl> mIntf; + void* mXheaacCodecHandle; + void* mMpegDDrcHandle; + uint32_t mInputBufferSize; + uint32_t mOutputFrameLength; + int8_t* mInputBuffer; + int8_t* mOutputBuffer; + int32_t mSampFreq; + int32_t mNumChannels; + int32_t mPcmWdSz; + int32_t mChannelMask; + int32_t mNumOutBytes; + uint64_t mCurFrameIndex; + uint64_t mCurTimestamp; + bool mIsCodecInitialized; + bool mIsCodecConfigFlushRequired; + int8_t* mDrcInBuf; + int8_t* mDrcOutBuf; + int32_t mMpegDDRCPresent; + int32_t mDRCFlag; + + Vector<void*> mMemoryVec; + Vector<void*> mDrcMemoryVec; + + size_t mInputBufferCount __unused; + size_t mOutputBufferCount __unused; + bool mSignalledOutputEos; + bool mSignalledError; + char* mOutputDrainBuffer; + uint32_t mOutputDrainBufferWritePos; + + IA_ERRORCODE initDecoder(); + IA_ERRORCODE setDrcParameter(); + IA_ERRORCODE configflushDecode(); + IA_ERRORCODE drainDecoder(); + void finishWork(const std::unique_ptr<C2Work>& work, + const std::shared_ptr<C2BlockPool>& pool); + + IA_ERRORCODE initXAACDrc(); + IA_ERRORCODE initXAACDecoder(); + IA_ERRORCODE deInitXAACDecoder(); + IA_ERRORCODE initMPEGDDDrc(); + IA_ERRORCODE deInitMPEGDDDrc(); + IA_ERRORCODE configXAACDecoder(uint8_t* inBuffer, uint32_t inBufferLength); + int configMPEGDDrc(); + IA_ERRORCODE decodeXAACStream(uint8_t* inBuffer, + uint32_t inBufferLength, + int32_t* bytesConsumed, + int32_t* outBytes); + IA_ERRORCODE getXAACStreamInfo(); + IA_ERRORCODE setXAACDRCInfo(int32_t drcCut, int32_t drcBoost, + int32_t drcRefLevel, int32_t drcHeavyCompression, + int32_t drEffectType); + + C2_DO_NOT_COPY(C2SoftXaacDec); +}; + +} // namespace android + +#endif // C2_SOFT_XAAC_H_
diff --git a/media/codec2/core/Android.bp b/media/codec2/core/Android.bp new file mode 100644 index 0000000..a7e8997 --- /dev/null +++ b/media/codec2/core/Android.bp
@@ -0,0 +1,48 @@ +cc_library_headers { + name: "libcodec2_headers", + vendor_available: true, + export_include_dirs: ["include"], +} + +cc_library_shared { + name: "libcodec2", + vendor_available: true, + vndk: { + enabled: true, + }, + + srcs: ["C2.cpp"], + + cflags: [ + "-Wall", + "-Werror", + ], + + header_libs: [ + "libcodec2_headers", + "libhardware_headers", + "libutils_headers", + "media_plugin_headers", + ], + + export_header_lib_headers: [ + "libcodec2_headers", + "libhardware_headers", + "libutils_headers", + "media_plugin_headers", + ], + + sanitize: { + misc_undefined: [ + "unsigned-integer-overflow", + "signed-integer-overflow", + ], + cfi: false, // true, + diag: { + cfi: false, // true, + }, + }, + + ldflags: ["-Wl,-Bsymbolic"], +} +
diff --git a/media/codec2/core/C2.cpp b/media/codec2/core/C2.cpp new file mode 100644 index 0000000..359d4e5 --- /dev/null +++ b/media/codec2/core/C2.cpp
@@ -0,0 +1,33 @@ +/* + * Copyright (C) 2016 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 <C2.h> +#include <C2Buffer.h> +#include <C2Component.h> +#include <C2Config.h> +#include <C2Param.h> +#include <C2ParamDef.h> +#include <C2Work.h> + +/** + * There is nothing here yet. This library is built to see what symbols and methods get + * defined as part of the API include files. + * + * Going forward, the Codec2 library will contain utility methods that are useful for + * Codec2 clients. + */ + +
diff --git a/media/codec2/core/include/C2.h b/media/codec2/core/include/C2.h new file mode 100644 index 0000000..ef3466d --- /dev/null +++ b/media/codec2/core/include/C2.h
@@ -0,0 +1,552 @@ +/* + * Copyright (C) 2016 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_H_ +#define C2_H_ + +#include <errno.h> + +#include <string> + +/** nanoseconds with arbitrary origin. */ +typedef int64_t c2_nsecs_t; + +/** \mainpage Codec2 + * + * Codec2 is a generic frame-based data processing API. + * + * The media subsystem accesses components via the \ref API. + */ + +/** \ingroup API + * + * The Codec2 API defines the operation of data processing components and their interaction with + * the rest of the system. + * + * Coding Conventions + * + * Mitigating Binary Compatibility. + * + * While full binary compatibility is not a goal of the API (due to our use of STL), we try to + * mitigate binary breaks by adhering to the following conventions: + * + * - at most one vtable with placeholder virtual methods + * - all optional/placeholder virtual methods returning a c2_status_t, with C2_OMITTED not requiring + * any update to input/output arguments. + * - limiting symbol export of inline methods + * - use of pimpl (or shared-pimpl) + * + * Naming + * + * - all classes and types prefix with C2 + * - classes for internal use prefix with _C2 + * - enum values in global namespace prefix with C2_ all caps + * - enum values inside classes have no C2_ prefix as class already has it + * - supporting two kinds of enum naming: all-caps and kCamelCase + * \todo revisit kCamelCase for param-type + * + * Aspects + * + * Aspects define certain common behavior across a group of objects. + * - classes whose name matches _C2.*Aspect + * - only protected constructors + * - no desctructor and copiable + * - all methods are inline or static (this is opposite of the interface paradigm where all methods + * are virtual, which would not work due to the at most one vtable rule.) + * - only private variables (this prevents subclasses interfering with the aspects.) + */ + +/// \defgroup types Common Types +/// @{ + +/** + * C2String: basic string implementation + */ +typedef std::string C2String; + +/** + * C2StringLiteral: basic string literal implementation. + * \note these are never owned by any object, and can only refer to C string literals. + */ +typedef const char *C2StringLiteral; + +/** + * c2_status_t: status codes used. + */ +enum c2_status_t : int32_t { +/* + * Use POSIX errno constants. + */ + C2_OK = 0, ///< operation completed successfully + + // bad input + C2_BAD_VALUE = EINVAL, ///< argument has invalid value (user error) + C2_BAD_INDEX = ENXIO, ///< argument uses invalid index (user error) + C2_CANNOT_DO = ENOTSUP, ///< argument/index is valid but not possible + + // bad sequencing of events + C2_DUPLICATE = EEXIST, ///< object already exists + C2_NOT_FOUND = ENOENT, ///< object not found + C2_BAD_STATE = EPERM, ///< operation is not permitted in the current state + C2_BLOCKING = EWOULDBLOCK, ///< operation would block but blocking is not permitted + C2_CANCELED = EINTR, ///< operation interrupted/canceled + + // bad environment + C2_NO_MEMORY = ENOMEM, ///< not enough memory to complete operation + C2_REFUSED = EACCES, ///< missing permission to complete operation + + C2_TIMED_OUT = ETIMEDOUT, ///< operation did not complete within timeout + + // bad versioning + C2_OMITTED = ENOSYS, ///< operation is not implemented/supported (optional only) + + // unknown fatal + C2_CORRUPTED = EFAULT, ///< some unexpected error prevented the operation + C2_NO_INIT = ENODEV, ///< status has not been initialized +}; + +/** + * Type that describes the desired blocking behavior for variable blocking calls. Blocking in this + * API is used in a somewhat modified meaning such that operations that merely update variables + * protected by mutexes are still considered "non-blocking" (always used in quotes). + */ +enum c2_blocking_t : int32_t { + /** + * The operation SHALL be "non-blocking". This means that it shall not perform any file + * operations, or call/wait on other processes. It may use a protected region as long as the + * mutex is never used to protect code that is otherwise "may block". + */ + C2_DONT_BLOCK = false, + /** + * The operation MAY be temporarily blocking. + */ + C2_MAY_BLOCK = true, +}; + +/// @} + +/// \defgroup utils Utilities +/// @{ + +#define C2_DO_NOT_COPY(type) \ + type& operator=(const type &) = delete; \ + type(const type &) = delete; \ + +#define C2_DEFAULT_MOVE(type) \ + type& operator=(type &&) = default; \ + type(type &&) = default; \ + +#define C2_ALLOW_OVERFLOW __attribute__((no_sanitize("integer"))) +#define C2_CONST __attribute__((const)) +#define C2_HIDE __attribute__((visibility("hidden"))) +#define C2_INLINE inline C2_HIDE +#define C2_INTERNAL __attribute__((internal_linkage)) +#define C2_PACK __attribute__((aligned(4))) +#define C2_PURE __attribute__((pure)) + +#define DEFINE_OTHER_COMPARISON_OPERATORS(type) \ + inline bool operator!=(const type &other) const { return !(*this == other); } \ + inline bool operator<=(const type &other) const { return (*this == other) || (*this < other); } \ + inline bool operator>=(const type &other) const { return !(*this < other); } \ + inline bool operator>(const type &other) const { return !(*this < other) && !(*this == other); } + +#define DEFINE_FIELD_BASED_COMPARISON_OPERATORS(type, field) \ + inline bool operator<(const type &other) const { return field < other.field; } \ + inline bool operator==(const type &other) const { return field == other.field; } \ + DEFINE_OTHER_COMPARISON_OPERATORS(type) + +#define DEFINE_FIELD_AND_MASK_BASED_COMPARISON_OPERATORS(type, field, mask) \ + inline bool operator<(const type &other) const { \ + return (field & mask) < (other.field & (mask)); \ + } \ + inline bool operator==(const type &other) const { \ + return (field & mask) == (other.field & (mask)); \ + } \ + DEFINE_OTHER_COMPARISON_OPERATORS(type) + +#define DEFINE_ENUM_OPERATORS(etype) \ + inline constexpr etype operator|(etype a, etype b) { return (etype)(std::underlying_type<etype>::type(a) | std::underlying_type<etype>::type(b)); } \ + inline constexpr etype &operator|=(etype &a, etype b) { a = (etype)(std::underlying_type<etype>::type(a) | std::underlying_type<etype>::type(b)); return a; } \ + inline constexpr etype operator&(etype a, etype b) { return (etype)(std::underlying_type<etype>::type(a) & std::underlying_type<etype>::type(b)); } \ + inline constexpr etype &operator&=(etype &a, etype b) { a = (etype)(std::underlying_type<etype>::type(a) & std::underlying_type<etype>::type(b)); return a; } \ + inline constexpr etype operator^(etype a, etype b) { return (etype)(std::underlying_type<etype>::type(a) ^ std::underlying_type<etype>::type(b)); } \ + inline constexpr etype &operator^=(etype &a, etype b) { a = (etype)(std::underlying_type<etype>::type(a) ^ std::underlying_type<etype>::type(b)); return a; } \ + inline constexpr etype operator~(etype a) { return (etype)(~std::underlying_type<etype>::type(a)); } + +template<typename T, typename B> +class C2_HIDE c2_cntr_t; + +/// \cond INTERNAL + +/// \defgroup utils_internal +/// @{ + +template<typename T> +struct C2_HIDE _c2_cntr_compat_helper { + template<typename U, typename E=typename std::enable_if<std::is_integral<U>::value>::type> + C2_ALLOW_OVERFLOW + inline static constexpr T get(const U &value) { + return T(value); + } + + template<typename U, typename E=typename std::enable_if<(sizeof(U) >= sizeof(T))>::type> + C2_ALLOW_OVERFLOW + inline static constexpr T get(const c2_cntr_t<U, void> &value) { + return T(value.mValue); + } +}; + +/// @} + +/// \endcond + +/** + * Integral counter type. + * + * This is basically an unsigned integral type that is NEVER checked for overflow/underflow - and + * comparison operators are redefined. + * + * \note Comparison of counter types is not fully transitive, e.g. + * it could be that a > b > c but a !> c. + * std::less<>, greater<>, less_equal<> and greater_equal<> specializations yield total ordering, + * but may not match semantic ordering of the values. + * + * Technically: counter types represent integer values: A * 2^N + value, where A can be arbitrary. + * This makes addition, subtraction, multiplication (as well as bitwise operations) well defined. + * However, division is in general not well defined, as the result may depend on A. This is also + * true for logical operators and boolean conversion. + * + * Even though well defined, bitwise operators are not implemented for counter types as they are not + * meaningful. + */ +template<typename T, typename B=typename std::enable_if<std::is_integral<T>::value && std::is_unsigned<T>::value>::type> +class C2_HIDE c2_cntr_t { + using compat = _c2_cntr_compat_helper<T>; + + T mValue; + constexpr static T HALF_RANGE = T(~0) ^ (T(~0) >> 1); + + template<typename U> + friend struct _c2_cntr_compat_helper; +public: + + /** + * Default constructor. Initialized counter to 0. + */ + inline constexpr c2_cntr_t() : mValue(T(0)) {} + + /** + * Construct from a compatible type. + */ + template<typename U> + inline constexpr c2_cntr_t(const U &value) : mValue(compat::get(value)) {} + + /** + * Peek as underlying signed type. + */ + C2_ALLOW_OVERFLOW + inline constexpr typename std::make_signed<T>::type peek() const { + return static_cast<typename std::make_signed<T>::type>(mValue); + } + + /** + * Peek as underlying unsigned type. + */ + inline constexpr T peeku() const { + return mValue; + } + + /** + * Peek as long long - e.g. for printing. + */ + C2_ALLOW_OVERFLOW + inline constexpr long long peekll() const { + return (long long)mValue; + } + + /** + * Peek as unsigned long long - e.g. for printing. + */ + C2_ALLOW_OVERFLOW + inline constexpr unsigned long long peekull() const { + return (unsigned long long)mValue; + } + + /** + * Convert to a smaller counter type. This is always safe. + */ + template<typename U, typename E=typename std::enable_if<(sizeof(U) < sizeof(T))>::type> + inline constexpr operator c2_cntr_t<U>() { + return c2_cntr_t<U>(mValue); + } + + /** + * Arithmetic operators + */ + +#define DEFINE_C2_CNTR_BINARY_OP(attrib, op, op_assign) \ + template<typename U> \ + attrib inline c2_cntr_t<T>& operator op_assign(const U &value) { \ + mValue op_assign compat::get(value); \ + return *this; \ + } \ + \ + template<typename U, typename E=decltype(compat::get(U(0)))> \ + attrib inline constexpr c2_cntr_t<T> operator op(const U &value) const { \ + return c2_cntr_t<T>(mValue op compat::get(value)); \ + } \ + \ + template<typename U, typename E=typename std::enable_if<(sizeof(U) < sizeof(T))>::type> \ + attrib inline constexpr c2_cntr_t<U> operator op(const c2_cntr_t<U> &value) const { \ + return c2_cntr_t<U>(U(mValue) op value.peeku()); \ + } + +#define DEFINE_C2_CNTR_UNARY_OP(attrib, op) \ + attrib inline constexpr c2_cntr_t<T> operator op() const { \ + return c2_cntr_t<T>(op mValue); \ + } + +#define DEFINE_C2_CNTR_CREMENT_OP(attrib, op) \ + attrib inline c2_cntr_t<T> &operator op() { \ + op mValue; \ + return *this; \ + } \ + attrib inline c2_cntr_t<T> operator op(int) { \ + return c2_cntr_t<T, void>(mValue op); \ + } + + DEFINE_C2_CNTR_BINARY_OP(C2_ALLOW_OVERFLOW, +, +=) + DEFINE_C2_CNTR_BINARY_OP(C2_ALLOW_OVERFLOW, -, -=) + DEFINE_C2_CNTR_BINARY_OP(C2_ALLOW_OVERFLOW, *, *=) + + DEFINE_C2_CNTR_UNARY_OP(C2_ALLOW_OVERFLOW, -) + DEFINE_C2_CNTR_UNARY_OP(C2_ALLOW_OVERFLOW, +) + + DEFINE_C2_CNTR_CREMENT_OP(C2_ALLOW_OVERFLOW, ++) + DEFINE_C2_CNTR_CREMENT_OP(C2_ALLOW_OVERFLOW, --) + + template<typename U, typename E=typename std::enable_if<std::is_unsigned<U>::value>::type> + C2_ALLOW_OVERFLOW + inline constexpr c2_cntr_t<T> operator<<(const U &value) const { + return c2_cntr_t<T>(mValue << value); + } + + template<typename U, typename E=typename std::enable_if<std::is_unsigned<U>::value>::type> + C2_ALLOW_OVERFLOW + inline c2_cntr_t<T> &operator<<=(const U &value) { + mValue <<= value; + return *this; + } + + /** + * Comparison operators + */ + C2_ALLOW_OVERFLOW + inline constexpr bool operator<=(const c2_cntr_t<T> &other) const { + return T(other.mValue - mValue) < HALF_RANGE; + } + + C2_ALLOW_OVERFLOW + inline constexpr bool operator>=(const c2_cntr_t<T> &other) const { + return T(mValue - other.mValue) < HALF_RANGE; + } + + inline constexpr bool operator==(const c2_cntr_t<T> &other) const { + return mValue == other.mValue; + } + + inline constexpr bool operator!=(const c2_cntr_t<T> &other) const { + return !(*this == other); + } + + inline constexpr bool operator<(const c2_cntr_t<T> &other) const { + return *this <= other && *this != other; + } + + inline constexpr bool operator>(const c2_cntr_t<T> &other) const { + return *this >= other && *this != other; + } +}; + +template<typename U, typename T, typename E=typename std::enable_if<std::is_integral<U>::value>::type> +inline constexpr c2_cntr_t<T> operator+(const U &a, const c2_cntr_t<T> &b) { + return b + a; +} + +template<typename U, typename T, typename E=typename std::enable_if<std::is_integral<U>::value>::type> +inline constexpr c2_cntr_t<T> operator-(const U &a, const c2_cntr_t<T> &b) { + return c2_cntr_t<T>(a) - b; +} + +template<typename U, typename T, typename E=typename std::enable_if<std::is_integral<U>::value>::type> +inline constexpr c2_cntr_t<T> operator*(const U &a, const c2_cntr_t<T> &b) { + return b * a; +} + +typedef c2_cntr_t<uint32_t> c2_cntr32_t; /** 32-bit counter type */ +typedef c2_cntr_t<uint64_t> c2_cntr64_t; /** 64-bit counter type */ + +/// \cond INTERNAL + +/// \defgroup utils_internal +/// @{ + +template<typename... T> struct c2_types; + +/** specialization for a single type */ +template<typename T> +struct c2_types<T> { + typedef typename std::decay<T>::type wide_type; + typedef wide_type narrow_type; + typedef wide_type min_type; // type for min(T...) +}; + +/** specialization for two types */ +template<typename T, typename U> +struct c2_types<T, U> { + static_assert(std::is_floating_point<T>::value == std::is_floating_point<U>::value, + "mixing floating point and non-floating point types is disallowed"); + static_assert(std::is_signed<T>::value == std::is_signed<U>::value, + "mixing signed and unsigned types is disallowed"); + + typedef typename std::decay< + decltype(true ? std::declval<T>() : std::declval<U>())>::type wide_type; + typedef typename std::decay< + typename std::conditional<sizeof(T) < sizeof(U), T, U>::type>::type narrow_type; + typedef typename std::conditional< + std::is_signed<T>::value, wide_type, narrow_type>::type min_type; +}; + +/// @} + +/// \endcond + +/** + * Type support utility class. Only supports similar classes, such as: + * - all floating point + * - all unsigned/all signed + * - all pointer + */ +template<typename T, typename U, typename... V> +struct c2_types<T, U, V...> { + /** Common type that accommodates all template parameter types. */ + typedef typename c2_types<typename c2_types<T, U>::wide_type, V...>::wide_type wide_type; + /** Narrowest type of the template parameter types. */ + typedef typename c2_types<typename c2_types<T, U>::narrow_type, V...>::narrow_type narrow_type; + /** Type that accommodates the minimum value for any input for the template parameter types. */ + typedef typename c2_types<typename c2_types<T, U>::min_type, V...>::min_type min_type; +}; + +/** + * \ingroup utils_internal + * specialization for two values */ +template<typename T, typename U> +inline constexpr typename c2_types<T, U>::wide_type c2_max(const T a, const U b) { + typedef typename c2_types<T, U>::wide_type wide_type; + return ({ wide_type a_(a), b_(b); a_ > b_ ? a_ : b_; }); +} + +/** + * Finds the maximum value of a list of "similarly typed" values. + * + * This is an extension to std::max where the types do not have to be identical, and the smallest + * resulting type is used that accommodates the argument types. + * + * \note Value types must be similar, e.g. all floating point, all pointers, all signed, or all + * unsigned. + * + * @return the largest of the input arguments. + */ +template<typename T, typename U, typename... V> +constexpr typename c2_types<T, U, V...>::wide_type c2_max(const T a, const U b, const V ... c) { + typedef typename c2_types<T, U, V...>::wide_type wide_type; + return ({ wide_type a_(a), b_(c2_max(b, c...)); a_ > b_ ? a_ : b_; }); +} + +/** + * \ingroup utils_internal + * specialization for two values */ +template<typename T, typename U> +inline constexpr typename c2_types<T, U>::min_type c2_min(const T a, const U b) { + typedef typename c2_types<T, U>::wide_type wide_type; + return ({ + wide_type a_(a), b_(b); + static_cast<typename c2_types<T, U>::min_type>(a_ < b_ ? a_ : b_); + }); +} + +/** + * Finds the minimum value of a list of "similarly typed" values. + * + * This is an extension to std::min where the types do not have to be identical, and the smallest + * resulting type is used that accommodates the argument types. + * + * \note Value types must be similar, e.g. all floating point, all pointers, all signed, or all + * unsigned. + * + * @return the smallest of the input arguments. + */ +template<typename T, typename U, typename... V> +constexpr typename c2_types<T, U, V...>::min_type c2_min(const T a, const U b, const V ... c) { + typedef typename c2_types<U, V...>::min_type rest_type; + typedef typename c2_types<T, rest_type>::wide_type wide_type; + return ({ + wide_type a_(a), b_(c2_min(b, c...)); + static_cast<typename c2_types<T, rest_type>::min_type>(a_ < b_ ? a_ : b_); + }); +} + +/** + * \ingroup utils_internal + */ +template<typename T, typename U, typename V> +inline constexpr typename c2_types<T, V>::wide_type c2_clamp(const T a, const U b, const V c) { + typedef typename c2_types<T, U, V>::wide_type wide_type; + return ({ + wide_type a_(a), b_(b), c_(c); + static_cast<typename c2_types<T, V>::wide_type>(b_ < a_ ? a_ : b_ > c_ ? c_ : b_); + }); +} + +/// @} + +#include <functional> +template<typename T> +struct std::less<::c2_cntr_t<T>> { + constexpr bool operator()(const ::c2_cntr_t<T> &lh, const ::c2_cntr_t<T> &rh) const { + return lh.peeku() < rh.peeku(); + } +}; +template<typename T> +struct std::less_equal<::c2_cntr_t<T>> { + constexpr bool operator()(const ::c2_cntr_t<T> &lh, const ::c2_cntr_t<T> &rh) const { + return lh.peeku() <= rh.peeku(); + } +}; +template<typename T> +struct std::greater<::c2_cntr_t<T>> { + constexpr bool operator()(const ::c2_cntr_t<T> &lh, const ::c2_cntr_t<T> &rh) const { + return lh.peeku() > rh.peeku(); + } +}; +template<typename T> +struct std::greater_equal<::c2_cntr_t<T>> { + constexpr bool operator()(const ::c2_cntr_t<T> &lh, const ::c2_cntr_t<T> &rh) const { + return lh.peeku() >= rh.peeku(); + } +}; + +#endif // C2_H_
diff --git a/media/codec2/core/include/C2Buffer.h b/media/codec2/core/include/C2Buffer.h new file mode 100644 index 0000000..3d3587c --- /dev/null +++ b/media/codec2/core/include/C2Buffer.h
@@ -0,0 +1,2184 @@ +/* + * Copyright (C) 2016 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 C2BUFFER_H_ +#define C2BUFFER_H_ + +#include <C2.h> +#include <C2BufferBase.h> +#include <C2Param.h> // for C2Info + +#include <memory> +#include <vector> + +#ifdef __ANDROID__ +#include <android-C2Buffer.h> +#else + +typedef void* C2Handle; + +#endif + +/// \defgroup buffer Buffers +/// @{ + +/// \defgroup buffer_sync Synchronization +/// @{ + +/** + * Synchronization is accomplished using event and fence objects. + * + * These are cross-process extensions of promise/future infrastructure. + * Events are analogous to std::promise<void>, whereas fences are to std::shared_future<void>. + * + * Fences and events are shareable/copyable. + * + * Fences are used in two scenarios, and all copied instances refer to the same event. + * \todo do events need to be copyable or should they be unique? + * + * acquire sync fence object: signaled when it is safe for the component or client to access + * (the contents of) an object. + * + * release sync fence object: \todo + * + * Fences can be backed by hardware. Hardware fences are guaranteed to signal NO MATTER WHAT within + * a short (platform specific) amount of time; this guarantee is usually less than 15 msecs. + */ + +/** + * Fence object used by components and the framework. + * + * Implements the waiting for an event, analogous to a 'future'. + * + * To be implemented by vendors if using HW fences. + */ +class C2Fence { +public: + /** + * Waits for a fence to be signaled with a timeout. + * + * \todo a mechanism to cancel a wait - for now the only way to do this is to abandon the + * event, but fences are shared so canceling a wait will cancel all waits. + * + * \param timeoutNs the maximum time to wait in nsecs + * + * \retval C2_OK the fence has been signaled + * \retval C2_TIMED_OUT the fence has not been signaled within the timeout + * \retval C2_BAD_STATE the fence has been abandoned without being signaled (it will never + * be signaled) + * \retval C2_REFUSED no permission to wait for the fence (unexpected - system) + * \retval C2_CORRUPTED some unknown error prevented waiting for the fence (unexpected) + */ + c2_status_t wait(c2_nsecs_t timeoutNs); + + /** + * Used to check if this fence is valid (if there is a chance for it to be signaled.) + * A fence becomes invalid if the controling event is destroyed without it signaling the fence. + * + * \return whether this fence is valid + */ + bool valid() const; + + /** + * Used to check if this fence has been signaled (is ready). + * + * \return whether this fence has been signaled + */ + bool ready() const; + + /** + * Returns a file descriptor that can be used to wait for this fence in a select system call. + * \note The returned file descriptor, if valid, must be closed by the caller. + * + * This can be used in e.g. poll() system calls. This file becomes readable (POLLIN) when the + * fence is signaled, and bad (POLLERR) if the fence is abandoned. + * + * \return a file descriptor representing this fence (with ownership), or -1 if the fence + * has already been signaled (\todo or abandoned). + * + * \todo this must be compatible with fences used by gralloc + */ + int fd() const; + + /** + * Returns whether this fence is a hardware-backed fence. + * \return whether this is a hardware fence + */ + bool isHW() const; + + /** + * Null-fence. A fence that has fired. + */ + constexpr C2Fence() : mImpl(nullptr) { } + +private: + class Impl; + std::shared_ptr<Impl> mImpl; + C2Fence(std::shared_ptr<Impl> impl); + friend struct _C2FenceFactory; +}; + +/** + * Event object used by components and the framework. + * + * Implements the signaling of an event, analogous to a 'promise'. + * + * Hardware backed events do not go through this object, and must be exposed directly as fences + * by vendors. + */ +class C2Event { +public: + /** + * Returns a fence for this event. + */ + C2Fence fence() const; + + /** + * Signals (all) associated fence(s). + * This has no effect no effect if the event was already signaled or abandoned. + * + * \retval C2_OK the fence(s) were successfully signaled + * \retval C2_BAD_STATE the fence(s) have already been abandoned or merged (caller error) + * \retval C2_DUPLICATE the fence(s) have already been signaled (caller error) + * \retval C2_REFUSED no permission to signal the fence (unexpected - system) + * \retval C2_CORRUPTED some unknown error prevented signaling the fence(s) (unexpected) + */ + c2_status_t fire(); + + /** + * Trigger this event from the merging of the supplied fences. This means that it will be + * abandoned if any of these fences have been abandoned, and it will be fired if all of these + * fences have been signaled. + * + * \retval C2_OK the merging was successfully done + * \retval C2_NO_MEMORY not enough memory to perform the merging + * \retval C2_DUPLICATE the fence have already been merged (caller error) + * \retval C2_BAD_STATE the fence have already been signaled or abandoned (caller error) + * \retval C2_REFUSED no permission to merge the fence (unexpected - system) + * \retval C2_CORRUPTED some unknown error prevented merging the fence(s) (unexpected) + */ + c2_status_t merge(std::vector<C2Fence> fences); + + /** + * Abandons the event and any associated fence(s). + * \note Call this to explicitly abandon an event before it is destructed to avoid a warning. + * + * This has no effect no effect if the event was already signaled or abandoned. + * + * \retval C2_OK the fence(s) were successfully signaled + * \retval C2_BAD_STATE the fence(s) have already been signaled or merged (caller error) + * \retval C2_DUPLICATE the fence(s) have already been abandoned (caller error) + * \retval C2_REFUSED no permission to abandon the fence (unexpected - system) + * \retval C2_CORRUPTED some unknown error prevented signaling the fence(s) (unexpected) + */ + c2_status_t abandon(); + +private: + class Impl; + std::shared_ptr<Impl> mImpl; +}; + +/// \addtogroup buf_internal Internal +/// @{ + +/** + * Interface for objects that encapsulate an updatable status value. + */ +struct _C2InnateStatus { + inline c2_status_t status() const { return mStatus; } + +protected: + _C2InnateStatus(c2_status_t status) : mStatus(status) { } + + c2_status_t mStatus; // this status is updatable by the object +}; + +/// @} + +/** + * This is a utility template for objects protected by an acquire fence, so that errors during + * acquiring the object are propagated to the object itself. + */ +template<typename T> +class C2Acquirable : public C2Fence { +public: + /** + * Acquires the object protected by an acquire fence. Any errors during the mapping will be + * passed to the object. + * + * \return acquired object potentially invalidated if waiting for the fence failed. + */ + T get() { + // TODO: + // wait(); + return mT; + } + +protected: + C2Acquirable(c2_status_t error, C2Fence fence, T t) : C2Fence(fence), mInitialError(error), mT(t) { } + +private: + c2_status_t mInitialError; + T mT; // TODO: move instead of copy +}; + +/// @} + +/// \defgroup linear Linear Data Blocks +/// @{ + +/************************************************************************************************** + LINEAR ASPECTS, BLOCKS AND VIEWS +**************************************************************************************************/ + +/** + * Basic segment math support. + */ +struct C2Segment { + uint32_t offset; + uint32_t size; + + inline constexpr C2Segment(uint32_t offset_, uint32_t size_) + : offset(offset_), + size(size_) { + } + + inline constexpr bool isEmpty() const { + return size == 0; + } + + inline constexpr bool isValid() const { + return offset <= ~size; + } + + inline constexpr operator bool() const { + return isValid() && !isEmpty(); + } + + inline constexpr bool operator!() const { + return !bool(*this); + } + + C2_ALLOW_OVERFLOW + inline constexpr bool contains(const C2Segment &other) const { + if (!isValid() || !other.isValid()) { + return false; + } else { + return offset <= other.offset + && offset + size >= other.offset + other.size; + } + } + + inline constexpr bool operator==(const C2Segment &other) const { + if (!isValid()) { + return !other.isValid(); + } else { + return offset == other.offset && size == other.size; + } + } + + inline constexpr bool operator!=(const C2Segment &other) const { + return !operator==(other); + } + + inline constexpr bool operator>=(const C2Segment &other) const { + return contains(other); + } + + inline constexpr bool operator>(const C2Segment &other) const { + return contains(other) && !operator==(other); + } + + inline constexpr bool operator<=(const C2Segment &other) const { + return other.contains(*this); + } + + inline constexpr bool operator<(const C2Segment &other) const { + return other.contains(*this) && !operator==(other); + } + + C2_ALLOW_OVERFLOW + inline constexpr uint32_t end() const { + return offset + size; + } + + C2_ALLOW_OVERFLOW + inline constexpr C2Segment intersect(const C2Segment &other) const { + return C2Segment(c2_max(offset, other.offset), + c2_min(end(), other.end()) - c2_max(offset, other.offset)); + } + + /** clamps end to offset if it overflows */ + inline constexpr C2Segment normalize() const { + return C2Segment(offset, c2_max(offset, end()) - offset); + } + + /** clamps end to max if it overflows */ + inline constexpr C2Segment saturate() const { + return C2Segment(offset, c2_min(size, ~offset)); + } + +}; + +/** + * Common aspect for all objects that have a linear capacity. + */ +class _C2LinearCapacityAspect { +/// \name Linear capacity interface +/// @{ +public: + inline constexpr uint32_t capacity() const { return mCapacity; } + + inline constexpr operator C2Segment() const { + return C2Segment(0, mCapacity); + } + +protected: + +#if UINTPTR_MAX == 0xffffffff + static_assert(sizeof(size_t) == sizeof(uint32_t), "size_t is too big"); +#else + static_assert(sizeof(size_t) > sizeof(uint32_t), "size_t is too small"); + // explicitly disable construction from size_t + inline explicit _C2LinearCapacityAspect(size_t capacity) = delete; +#endif + + inline explicit constexpr _C2LinearCapacityAspect(uint32_t capacity) + : mCapacity(capacity) { } + + inline explicit constexpr _C2LinearCapacityAspect(const _C2LinearCapacityAspect *parent) + : mCapacity(parent == nullptr ? 0 : parent->capacity()) { } + +private: + uint32_t mCapacity; +/// @} +}; + +/** + * Aspect for objects that have a linear range inside a linear capacity. + * + * This class is copiable. + */ +class _C2LinearRangeAspect : public _C2LinearCapacityAspect { +/// \name Linear range interface +/// @{ +public: + inline constexpr uint32_t offset() const { return mOffset; } + inline constexpr uint32_t endOffset() const { return mOffset + mSize; } + inline constexpr uint32_t size() const { return mSize; } + + inline constexpr operator C2Segment() const { + return C2Segment(mOffset, mSize); + } + +private: + // subrange of capacity [0, capacity] & [size, size + offset] + inline constexpr _C2LinearRangeAspect(uint32_t capacity_, size_t offset, size_t size) + : _C2LinearCapacityAspect(capacity_), + mOffset(c2_min(offset, capacity())), + mSize(c2_min(size, capacity() - mOffset)) { + } + +protected: + // copy constructor (no error check) + inline constexpr _C2LinearRangeAspect(const _C2LinearRangeAspect &other) + : _C2LinearCapacityAspect(other.capacity()), + mOffset(other.offset()), + mSize(other.size()) { + } + + // parent capacity range [0, capacity] + inline constexpr explicit _C2LinearRangeAspect(const _C2LinearCapacityAspect *parent) + : _C2LinearCapacityAspect(parent), + mOffset(0), + mSize(capacity()) { + } + + // subrange of parent capacity [0, capacity] & [size, size + offset] + inline constexpr _C2LinearRangeAspect(const _C2LinearCapacityAspect *parent, size_t offset, size_t size) + : _C2LinearCapacityAspect(parent), + mOffset(c2_min(offset, capacity())), + mSize(c2_min(size, capacity() - mOffset)) { + } + + // subsection of the parent's and [offset, offset + size] ranges + inline constexpr _C2LinearRangeAspect(const _C2LinearRangeAspect *parent, size_t offset, size_t size) + : _C2LinearCapacityAspect(parent), + mOffset(c2_min(c2_max(offset, parent == nullptr ? 0 : parent->offset()), capacity())), + mSize(std::min(c2_min(size, parent == nullptr ? 0 : parent->size()), capacity() - mOffset)) { + } + +public: + inline constexpr _C2LinearRangeAspect childRange(size_t offset, size_t size) const { + return _C2LinearRangeAspect( + mSize, + c2_min(c2_max(offset, mOffset), capacity()) - mOffset, + c2_min(c2_min(size, mSize), capacity() - c2_min(c2_max(offset, mOffset), capacity()))); + } + + friend class _C2EditableLinearRangeAspect; + // invariants 0 <= mOffset <= mOffset + mSize <= capacity() + uint32_t mOffset; + uint32_t mSize; +/// @} +}; + +/** + * Utility class for safe range calculations using size_t-s. + */ +class C2LinearRange : public _C2LinearRangeAspect { +public: + inline constexpr C2LinearRange(const _C2LinearCapacityAspect &parent, size_t offset, size_t size) + : _C2LinearRangeAspect(&parent, offset, size) { } + + inline constexpr C2LinearRange(const _C2LinearRangeAspect &parent, size_t offset, size_t size) + : _C2LinearRangeAspect(&parent, offset, size) { } + + inline constexpr C2LinearRange intersect(size_t offset, size_t size) const { + return C2LinearRange(*this, offset, size); + } +}; + +/** + * Utility class for simple and safe capacity and range construction. + */ +class C2LinearCapacity : public _C2LinearCapacityAspect { +public: + inline constexpr explicit C2LinearCapacity(size_t capacity) + : _C2LinearCapacityAspect(c2_min(capacity, std::numeric_limits<uint32_t>::max())) { } + + inline constexpr C2LinearRange range(size_t offset, size_t size) const { + return C2LinearRange(*this, offset, size); + } +}; + +/** + * Aspect for objects that have an editable linear range. + * + * This class is copiable. + */ +class _C2EditableLinearRangeAspect : public _C2LinearRangeAspect { + using _C2LinearRangeAspect::_C2LinearRangeAspect; + +public: +/// \name Editable linear range interface +/// @{ + + /** + * Sets the offset to |offset|, while trying to keep the end of the buffer unchanged (e.g. + * size will grow if offset is decreased, and may shrink if offset is increased.) Returns + * true if successful, which is equivalent to if 0 <= |offset| <= capacity(). + * + * Note: setting offset and size will yield different result depending on the order of the + * operations. Always set offset first to ensure proper size. + */ + inline bool setOffset(uint32_t offset) { + if (offset > capacity()) { + return false; + } + + if (offset > mOffset + mSize) { + mSize = 0; + } else { + mSize = mOffset + mSize - offset; + } + mOffset = offset; + return true; + } + + /** + * Sets the size to |size|. Returns true if successful, which is equivalent to + * if 0 <= |size| <= capacity() - offset(). + * + * Note: setting offset and size will yield different result depending on the order of the + * operations. Always set offset first to ensure proper size. + */ + inline bool setSize(uint32_t size) { + if (size > capacity() - mOffset) { + return false; + } else { + mSize = size; + return true; + } + } + + /** + * Sets the offset to |offset| with best effort. Same as setOffset() except that offset will + * be clamped to the buffer capacity. + * + * Note: setting offset and size (even using best effort) will yield different result depending + * on the order of the operations. Always set offset first to ensure proper size. + */ + inline void setOffset_be(uint32_t offset) { + (void)setOffset(c2_min(offset, capacity())); + } + + /** + * Sets the size to |size| with best effort. Same as setSize() except that the selected region + * will be clamped to the buffer capacity (e.g. size is clamped to [0, capacity() - offset()]). + * + * Note: setting offset and size (even using best effort) will yield different result depending + * on the order of the operations. Always set offset first to ensure proper size. + */ + inline void setSize_be(uint32_t size) { + mSize = c2_min(size, capacity() - mOffset); + } +/// @} +}; + +/************************************************************************************************** + ALLOCATIONS +**************************************************************************************************/ + +/// \ingroup allocator Allocation and memory placement +/// @{ + +class C2LinearAllocation; +class C2GraphicAllocation; + +/** + * Allocators are used by the framework to allocate memory (allocations) for buffers. They can + * support either 1D or 2D allocations. + * + * \note In theory they could support both, but in practice, we will use only one or the other. + * + * Never constructed on stack. + * + * Allocators are provided by vendors. + */ +class C2Allocator { +public: + /** + * Allocator ID type. + */ + typedef uint32_t id_t; + enum : id_t { + BAD_ID = 0xBADD, // invalid allocator ID + }; + + /** + * Allocation types. This is a bitmask and is used in C2Allocator::Info + * to list the supported allocation types of an allocator. + */ + enum type_t : uint32_t { + LINEAR = 1 << 0, // + GRAPHIC = 1 << 1, + }; + + /** + * Information about an allocator. + * + * Allocators don't have a query API so all queriable information is stored here. + */ + struct Traits { + C2String name; ///< allocator name + id_t id; ///< allocator ID + type_t supportedTypes; ///< supported allocation types + C2MemoryUsage minimumUsage; ///< usage that is minimally required for allocations + C2MemoryUsage maximumUsage; ///< usage that is maximally allowed for allocations + }; + + /** + * Returns the unique name of this allocator. + * + * This method MUST be "non-blocking" and return within 1ms. + * + * \return the name of this allocator. + * \retval an empty string if there was not enough memory to allocate the actual name. + */ + virtual C2String getName() const = 0; + + /** + * Returns a unique ID for this allocator. This ID is used to get this allocator from the + * allocator store, and to identify this allocator across all processes. + * + * This method MUST be "non-blocking" and return within 1ms. + * + * \return a unique ID for this allocator. + */ + virtual id_t getId() const = 0; + + /** + * Returns the allocator traits. + * + * This method MUST be "non-blocking" and return within 1ms. + * + * Allocators don't have a full-fledged query API, only this method. + * + * \return allocator information + */ + virtual std::shared_ptr<const Traits> getTraits() const = 0; + + /** + * Allocates a 1D allocation of given |capacity| and |usage|. If successful, the allocation is + * stored in |allocation|. Otherwise, |allocation| is set to 'nullptr'. + * + * \param capacity the size of requested allocation (the allocation could be slightly + * larger, e.g. to account for any system-required alignment) + * \param usage the memory usage info for the requested allocation. \note that the + * returned allocation may be later used/mapped with different usage. + * The allocator should layout the buffer to be optimized for this usage, + * but must support any usage. One exception: protected buffers can + * only be used in a protected scenario. + * \param allocation pointer to where the allocation shall be stored on success. nullptr + * will be stored here on failure + * + * \retval C2_OK the allocation was successful + * \retval C2_NO_MEMORY not enough memory to complete the allocation + * \retval C2_TIMED_OUT the allocation timed out + * \retval C2_REFUSED no permission to complete the allocation + * \retval C2_BAD_VALUE capacity or usage are not supported (invalid) (caller error) + * \retval C2_OMITTED this allocator does not support 1D allocations + * \retval C2_CORRUPTED some unknown, unrecoverable error occured during allocation (unexpected) + */ + virtual c2_status_t newLinearAllocation( + uint32_t capacity __unused, C2MemoryUsage usage __unused, + std::shared_ptr<C2LinearAllocation> *allocation /* nonnull */) { + *allocation = nullptr; + return C2_OMITTED; + } + + /** + * (Re)creates a 1D allocation from a native |handle|. If successful, the allocation is stored + * in |allocation|. Otherwise, |allocation| is set to 'nullptr'. + * + * \param handle the handle for the existing allocation. On success, the allocation will + * take ownership of |handle|. + * \param allocation pointer to where the allocation shall be stored on success. nullptr + * will be stored here on failure + * + * \retval C2_OK the allocation was recreated successfully + * \retval C2_NO_MEMORY not enough memory to recreate the allocation + * \retval C2_TIMED_OUT the recreation timed out (unexpected) + * \retval C2_REFUSED no permission to recreate the allocation + * \retval C2_BAD_VALUE invalid handle (caller error) + * \retval C2_OMITTED this allocator does not support 1D allocations + * \retval C2_CORRUPTED some unknown, unrecoverable error occured during allocation (unexpected) + */ + virtual c2_status_t priorLinearAllocation( + const C2Handle *handle __unused, + std::shared_ptr<C2LinearAllocation> *allocation /* nonnull */) { + *allocation = nullptr; + return C2_OMITTED; + } + + /** + * Allocates a 2D allocation of given |width|, |height|, |format| and |usage|. If successful, + * the allocation is stored in |allocation|. Otherwise, |allocation| is set to 'nullptr'. + * + * \param width the width of requested allocation (the allocation could be slightly + * larger, e.g. to account for any system-required alignment) + * \param height the height of requested allocation (the allocation could be slightly + * larger, e.g. to account for any system-required alignment) + * \param format the pixel format of requested allocation. This could be a vendor + * specific format. + * \param usage the memory usage info for the requested allocation. \note that the + * returned allocation may be later used/mapped with different usage. + * The allocator should layout the buffer to be optimized for this usage, + * but must support any usage. One exception: protected buffers can + * only be used in a protected scenario. + * \param allocation pointer to where the allocation shall be stored on success. nullptr + * will be stored here on failure + * + * \retval C2_OK the allocation was successful + * \retval C2_NO_MEMORY not enough memory to complete the allocation + * \retval C2_TIMED_OUT the allocation timed out + * \retval C2_REFUSED no permission to complete the allocation + * \retval C2_BAD_VALUE width, height, format or usage are not supported (invalid) (caller error) + * \retval C2_OMITTED this allocator does not support 2D allocations + * \retval C2_CORRUPTED some unknown, unrecoverable error occured during allocation (unexpected) + */ + virtual c2_status_t newGraphicAllocation( + uint32_t width __unused, uint32_t height __unused, uint32_t format __unused, + C2MemoryUsage usage __unused, + std::shared_ptr<C2GraphicAllocation> *allocation /* nonnull */) { + *allocation = nullptr; + return C2_OMITTED; + } + + /** + * (Re)creates a 2D allocation from a native handle. If successful, the allocation is stored + * in |allocation|. Otherwise, |allocation| is set to 'nullptr'. + * + * \param handle the handle for the existing allocation. On success, the allocation will + * take ownership of |handle|. + * \param allocation pointer to where the allocation shall be stored on success. nullptr + * will be stored here on failure + * + * \retval C2_OK the allocation was recreated successfully + * \retval C2_NO_MEMORY not enough memory to recreate the allocation + * \retval C2_TIMED_OUT the recreation timed out (unexpected) + * \retval C2_REFUSED no permission to recreate the allocation + * \retval C2_BAD_VALUE invalid handle (caller error) + * \retval C2_OMITTED this allocator does not support 2D allocations + * \retval C2_CORRUPTED some unknown, unrecoverable error occured during recreation (unexpected) + */ + virtual c2_status_t priorGraphicAllocation( + const C2Handle *handle __unused, + std::shared_ptr<C2GraphicAllocation> *allocation /* nonnull */) { + *allocation = nullptr; + return C2_OMITTED; + } + + virtual ~C2Allocator() = default; +protected: + C2Allocator() = default; +}; + +/** + * \ingroup linear allocator + * 1D allocation interface. + */ +class C2LinearAllocation : public _C2LinearCapacityAspect { +public: + /** + * Maps a portion of an allocation starting from |offset| with |size| into local process memory. + * Stores the starting address into |addr|, or NULL if the operation was unsuccessful. + * |fence| will contain an acquire sync fence object. If it is already + * safe to access the buffer contents, then it will contain an empty (already fired) fence. + * + * \param offset starting position of the portion to be mapped (this does not have to + * be page aligned) + * \param size size of the portion to be mapped (this does not have to be page + * aligned) + * \param usage the desired usage. \todo this must be kSoftwareRead and/or + * kSoftwareWrite. + * \param fence a pointer to a fence object if an async mapping is requested. If + * not-null, and acquire fence will be stored here on success, or empty + * fence on failure. If null, the mapping will be synchronous. + * \param addr a pointer to where the starting address of the mapped portion will be + * stored. On failure, nullptr will be stored here. + * + * \todo Only one portion can be mapped at the same time - this is true for gralloc, but there + * is no need for this for 1D buffers. + * \todo Do we need to support sync operation as we could just wait for the fence? + * + * \retval C2_OK the operation was successful + * \retval C2_REFUSED no permission to map the portion + * \retval C2_TIMED_OUT the operation timed out + * \retval C2_DUPLICATE if the allocation is already mapped. + * \retval C2_NO_MEMORY not enough memory to complete the operation + * \retval C2_BAD_VALUE the parameters (offset/size) are invalid or outside the allocation, or + * the usage flags are invalid (caller error) + * \retval C2_CORRUPTED some unknown error prevented the operation from completing (unexpected) + */ + virtual c2_status_t map( + size_t offset, size_t size, C2MemoryUsage usage, C2Fence *fence /* nullable */, + void **addr /* nonnull */) = 0; + + /** + * Unmaps a portion of an allocation at |addr| with |size|. These must be parameters previously + * passed to and returned by |map|; otherwise, this operation is a no-op. + * + * \param addr starting address of the mapped region + * \param size size of the mapped region + * \param fence a pointer to a fence object if an async unmapping is requested. If + * not-null, a release fence will be stored here on success, or empty fence + * on failure. This fence signals when the original allocation contains + * all changes that happened to the mapped region. If null, the unmapping + * will be synchronous. + * + * \retval C2_OK the operation was successful + * \retval C2_TIMED_OUT the operation timed out + * \retval C2_NOT_FOUND if the allocation was not mapped previously. + * \retval C2_BAD_VALUE the parameters (addr/size) do not correspond to previously mapped + * regions (caller error) + * \retval C2_CORRUPTED some unknown error prevented the operation from completing (unexpected) + * \retval C2_REFUSED no permission to unmap the portion (unexpected - system) + */ + virtual c2_status_t unmap(void *addr, size_t size, C2Fence *fence /* nullable */) = 0; + + /** + * Returns the allocator ID for this allocation. This is useful to put the handle into context. + */ + virtual C2Allocator::id_t getAllocatorId() const = 0; + + /** + * Returns a pointer to the allocation handle. + */ + virtual const C2Handle *handle() const = 0; + + /** + * Returns true if this is the same allocation as |other|. + */ + virtual bool equals(const std::shared_ptr<C2LinearAllocation> &other) const = 0; + +protected: + // \todo should we limit allocation directly? + C2LinearAllocation(size_t capacity) : _C2LinearCapacityAspect(c2_min(capacity, UINT32_MAX)) {} + virtual ~C2LinearAllocation() = default; +}; + +class C2CircularBlock; +class C2LinearBlock; +class C2GraphicBlock; + +/** + * Block pools are used by components to obtain output buffers in an efficient way. They can + * support either linear (1D), circular (1D) or graphic (2D) blocks. + * + * Block pools decouple the recycling of memory/allocations from the components. They are meant to + * be an opaque service (there are no public APIs other than obtaining blocks) provided by the + * platform. Block pools are also meant to decouple allocations from memory used by buffers. This + * is accomplished by allowing pools to allot multiple memory 'blocks' on a single allocation. As + * their name suggest, block pools maintain a pool of memory blocks. When a component asks for + * a memory block, pools will try to return a free memory block already in the pool. If no such + * block exists, they will allocate memory using the backing allocator and allot a block on that + * allocation. When blocks are no longer used in the system, they are recycled back to the block + * pool and are available as free blocks. + * + * Never constructed on stack. + */ +class C2BlockPool { +public: + /** + * Block pool ID type. + */ + typedef uint64_t local_id_t; + + enum : local_id_t { + BASIC_LINEAR = 0, ///< ID of basic (unoptimized) block pool for fetching 1D blocks + BASIC_GRAPHIC = 1, ///< ID of basic (unoptimized) block pool for fetching 2D blocks + PLATFORM_START = 0x10, + }; + + /** + * Returns the ID for this block pool. This ID is used to get this block pool from the platform. + * It is only valid in the current process. + * + * This method MUST be "non-blocking" and return within 1ms. + * + * \return a local ID for this block pool. + */ + virtual local_id_t getLocalId() const = 0; + + /** + * Returns the ID of the backing allocator of this block pool. + * + * This method MUST be "non-blocking" and return within 1ms. + * + * \return the ID of the backing allocator of this block pool. + */ + virtual C2Allocator::id_t getAllocatorId() const = 0; + + /** + * Obtains a linear writeable block of given |capacity| and |usage|. If successful, the + * block is stored in |block|. Otherwise, |block| is set to 'nullptr'. + * + * \param capacity the size of requested block. + * \param usage the memory usage info for the requested block. Returned blocks will be + * optimized for this usage, but may be used with any usage. One exception: + * protected blocks/buffers can only be used in a protected scenario. + * \param block pointer to where the obtained block shall be stored on success. nullptr will + * be stored here on failure + * + * \retval C2_OK the operation was successful + * \retval C2_NO_MEMORY not enough memory to complete any required allocation + * \retval C2_TIMED_OUT the operation timed out + * \retval C2_BLOCKING the operation is blocked + * \retval C2_REFUSED no permission to complete any required allocation + * \retval C2_BAD_VALUE capacity or usage are not supported (invalid) (caller error) + * \retval C2_OMITTED this pool does not support linear blocks + * \retval C2_CORRUPTED some unknown, unrecoverable error occured during operation (unexpected) + */ + virtual c2_status_t fetchLinearBlock( + uint32_t capacity __unused, C2MemoryUsage usage __unused, + std::shared_ptr<C2LinearBlock> *block /* nonnull */) { + *block = nullptr; + return C2_OMITTED; + } + + /** + * Obtains a circular writeable block of given |capacity| and |usage|. If successful, the + * block is stored in |block|. Otherwise, |block| is set to 'nullptr'. + * + * \param capacity the size of requested circular block. (note: the size of the obtained + * block could be slightly larger, e.g. to accommodate any system-required + * alignment) + * \param usage the memory usage info for the requested block. Returned blocks will be + * optimized for this usage, but may be used with any usage. One exception: + * protected blocks/buffers can only be used in a protected scenario. + * \param block pointer to where the obtained block shall be stored on success. nullptr + * will be stored here on failure + * + * \retval C2_OK the operation was successful + * \retval C2_NO_MEMORY not enough memory to complete any required allocation + * \retval C2_TIMED_OUT the operation timed out + * \retval C2_BLOCKING the operation is blocked + * \retval C2_REFUSED no permission to complete any required allocation + * \retval C2_BAD_VALUE capacity or usage are not supported (invalid) (caller error) + * \retval C2_OMITTED this pool does not support circular blocks + * \retval C2_CORRUPTED some unknown, unrecoverable error occured during operation (unexpected) + */ + virtual c2_status_t fetchCircularBlock( + uint32_t capacity __unused, C2MemoryUsage usage __unused, + std::shared_ptr<C2CircularBlock> *block /* nonnull */) { + *block = nullptr; + return C2_OMITTED; + } + + /** + * Obtains a 2D graphic block of given |width|, |height|, |format| and |usage|. If successful, + * the block is stored in |block|. Otherwise, |block| is set to 'nullptr'. + * + * \param width the width of requested block (the obtained block could be slightly larger, e.g. + * to accommodate any system-required alignment) + * \param height the height of requested block (the obtained block could be slightly larger, + * e.g. to accommodate any system-required alignment) + * \param format the pixel format of requested block. This could be a vendor specific format. + * \param usage the memory usage info for the requested block. Returned blocks will be + * optimized for this usage, but may be used with any usage. One exception: + * protected blocks/buffers can only be used in a protected scenario. + * \param block pointer to where the obtained block shall be stored on success. nullptr + * will be stored here on failure + * + * \retval C2_OK the operation was successful + * \retval C2_NO_MEMORY not enough memory to complete any required allocation + * \retval C2_TIMED_OUT the operation timed out + * \retval C2_BLOCKING the operation is blocked + * \retval C2_REFUSED no permission to complete any required allocation + * \retval C2_BAD_VALUE width, height, format or usage are not supported (invalid) (caller + * error) + * \retval C2_OMITTED this pool does not support 2D blocks + * \retval C2_CORRUPTED some unknown, unrecoverable error occured during operation (unexpected) + */ + virtual c2_status_t fetchGraphicBlock( + uint32_t width __unused, uint32_t height __unused, uint32_t format __unused, + C2MemoryUsage usage __unused, + std::shared_ptr<C2GraphicBlock> *block /* nonnull */) { + *block = nullptr; + return C2_OMITTED; + } + + virtual ~C2BlockPool() = default; +protected: + C2BlockPool() = default; +}; + +/// @} + +// ================================================================================================ +// BLOCKS +// ================================================================================================ + +/** + * Blocks are sections of allocations. They can be either 1D or 2D. + */ + +class C2LinearAllocation; + +/** + * A 1D block. + * + * \note capacity() is not meaningful for users of blocks; instead size() is the capacity of the + * usable portion. Use and offset() and size() if accessing the block directly through its handle + * to represent the allotted range of the underlying allocation to this block. + */ +class C2Block1D : public _C2LinearRangeAspect { +public: + /** + * Returns the underlying handle for this allocation. + * + * \note that the block and its block pool has shared ownership of the handle + * and if all references to the block are released, the underlying block + * allocation may get reused even if a client keeps a clone of this handle. + */ + const C2Handle *handle() const; + + /** + * Returns the allocator's ID that created the underlying allocation for this block. This + * provides the context for understanding the handle. + */ + C2Allocator::id_t getAllocatorId() const; + +protected: + class Impl; + /** construct a block. */ + C2Block1D(std::shared_ptr<Impl> impl, const _C2LinearRangeAspect &range); + + friend struct _C2BlockFactory; + std::shared_ptr<Impl> mImpl; +}; + +/** + * Read view provides read-only access for a linear memory segment. + * + * This class is copiable. + */ +class C2ReadView : public _C2LinearCapacityAspect { +public: + /** + * \return pointer to the start of the block or nullptr on error. + * This pointer is only valid during the lifetime of this view or until it is released. + */ + const uint8_t *data() const; + + /** + * Returns a portion of this view. + * + * \param offset the start offset of the portion. \note This is clamped to the capacity of this + * view. + * \param size the size of the portion. \note This is clamped to the remaining data from offset. + * + * \return a read view containing a portion of this view + */ + C2ReadView subView(size_t offset, size_t size) const; + + /** + * \return error during the creation/mapping of this view. + */ + c2_status_t error() const; + + /** + * Releases this view. This sets error to C2_NO_INIT. + */ + //void release(); + +protected: + class Impl; + C2ReadView(std::shared_ptr<Impl> impl, uint32_t offset, uint32_t size); + explicit C2ReadView(c2_status_t error); + +private: + friend struct _C2BlockFactory; + std::shared_ptr<Impl> mImpl; + uint32_t mOffset; /**< offset into the linear block backing this read view */ +}; + +/** + * Write view provides read/write access for a linear memory segment. + * + * This class is copiable. \todo movable only? + */ +class C2WriteView : public _C2EditableLinearRangeAspect { +public: + /** + * Start of the block. + * + * \return pointer to the start of the block or nullptr on error. + * This pointer is only valid during the lifetime of this view or until it is released. + */ + uint8_t *base(); + + /** + * \return pointer to the block at the current offset or nullptr on error. + * This pointer is only valid during the lifetime of this view or until it is released. + */ + uint8_t *data(); + + /** + * \return error during the creation/mapping of this view. + */ + c2_status_t error() const; + + /** + * Releases this view. This sets error to C2_NO_INIT. + */ + //void release(); + +protected: + class Impl; + C2WriteView(std::shared_ptr<Impl> impl); + explicit C2WriteView(c2_status_t error); + +private: + friend struct _C2BlockFactory; + std::shared_ptr<Impl> mImpl; +}; + +/** + * A constant (read-only) linear block (portion of an allocation) with an acquire fence. + * Blocks are unmapped when created, and can be mapped into a read view on demand. + * + * This class is copiable and contains a reference to the allocation that it is based on. + */ +class C2ConstLinearBlock : public C2Block1D { +public: + /** + * Maps this block into memory and returns a read view for it. + * + * \return a read view for this block. + */ + C2Acquirable<C2ReadView> map() const; + + /** + * Returns a portion of this block. + * + * \param offset the start offset of the portion. \note This is clamped to the capacity of this + * block. + * \param size the size of the portion. \note This is clamped to the remaining data from offset. + * + * \return a constant linear block containing a portion of this block + */ + C2ConstLinearBlock subBlock(size_t offset, size_t size) const; + + /** + * Returns the acquire fence for this block. + * + * \return a fence that must be waited on before reading the block. + */ + C2Fence fence() const { return mFence; } + +protected: + C2ConstLinearBlock(std::shared_ptr<Impl> impl, const _C2LinearRangeAspect &range, C2Fence mFence); + +private: + friend struct _C2BlockFactory; + C2Fence mFence; +}; + +/** + * Linear block is a writeable 1D block. Once written, it can be shared in whole or in parts with + * consumers/readers as read-only const linear block(s). + */ +class C2LinearBlock : public C2Block1D { +public: + /** + * Maps this block into memory and returns a write view for it. + * + * \return a write view for this block. + */ + C2Acquirable<C2WriteView> map(); + + /** + * Creates a read-only const linear block for a portion of this block; optionally protected + * by an acquire fence. There are two ways to use this: + * + * 1) share ready block after writing data into the block. In this case no fence shall be + * supplied, and the block shall not be modified after calling this method. + * 2) share block metadata before actually (finishing) writing the data into the block. In + * this case a fence must be supplied that will be triggered when the data is written. + * The block shall be modified only until firing the event for the fence. + */ + C2ConstLinearBlock share(size_t offset, size_t size, C2Fence fence); + +protected: + C2LinearBlock(std::shared_ptr<Impl> impl, const _C2LinearRangeAspect &range); + + friend struct _C2BlockFactory; +}; + +/// @} + +/************************************************************************************************** + CIRCULAR BLOCKS AND VIEWS +**************************************************************************************************/ + +/// \defgroup circular Circular buffer support +/// @{ + +/** + * Circular blocks can be used to share data between a writer and a reader (and/or other consumers)- + * in a memory-efficient way by reusing a section of memory. Circular blocks are a bit more complex + * than single reader/single writer schemes to facilitate block-based consuming of data. + * + * They can operate in two modes: + * + * 1) one writer that creates blocks to be consumed (this model can be used by components) + * + * 2) one writer that writes continuously, and one reader that can creates blocks to be consumed + * by further recipients (this model is used by the framework, and cannot be used by components.) + * + * Circular blocks have four segments with running pointers: + * - reserved: data reserved and available for the writer + * - committed: data committed by the writer and available to the reader (if present) + * - used: data used by consumers (if present) + * - available: unused data available to be reserved + */ +class C2CircularBlock : public C2Block1D { + // TODO: add methods + +private: + size_t mReserved __unused; // end of reserved section + size_t mCommitted __unused; // end of committed section + size_t mUsed __unused; // end of used section + size_t mFree __unused; // end of free section +}; + +class _C2CircularBlockSegment : public _C2LinearCapacityAspect { +public: + /** + * Returns the available size for this segment. + * + * \return currently available size for this segment + */ + size_t available() const; + + /** + * Reserve some space for this segment from its current start. + * + * \param size desired space in bytes + * \param fence a pointer to an acquire fence. If non-null, the reservation is asynchronous and + * a fence will be stored here that will be signaled when the reservation is + * complete. If null, the reservation is synchronous. + * + * \retval C2_OK the space was successfully reserved + * \retval C2_NO_MEMORY the space requested cannot be reserved + * \retval C2_TIMED_OUT the reservation timed out \todo when? + * \retval C2_CORRUPTED some unknown error prevented reserving space. (unexpected) + */ + c2_status_t reserve(size_t size, C2Fence *fence /* nullable */); + + /** + * Abandons a portion of this segment. This will move to the beginning of this segment. + * + * \note This methods is only allowed if this segment is producing blocks. + * + * \param size number of bytes to abandon + * + * \retval C2_OK the data was successfully abandoned + * \retval C2_TIMED_OUT the operation timed out (unexpected) + * \retval C2_CORRUPTED some unknown error prevented abandoning the data (unexpected) + */ + c2_status_t abandon(size_t size); + + /** + * Share a portion as block(s) with consumers (these are moved to the used section). + * + * \note This methods is only allowed if this segment is producing blocks. + * \note Share does not move the beginning of the segment. (\todo add abandon/offset?) + * + * \param size number of bytes to share + * \param fence fence to be used for the section + * \param blocks vector where the blocks of the section are appended to + * + * \retval C2_OK the portion was successfully shared + * \retval C2_NO_MEMORY not enough memory to share the portion + * \retval C2_TIMED_OUT the operation timed out (unexpected) + * \retval C2_CORRUPTED some unknown error prevented sharing the data (unexpected) + */ + c2_status_t share(size_t size, C2Fence fence, std::vector<C2ConstLinearBlock> &blocks); + + /** + * Returns the beginning offset of this segment from the start of this circular block. + * + * @return beginning offset + */ + size_t begin(); + + /** + * Returns the end offset of this segment from the start of this circular block. + * + * @return end offset + */ + size_t end(); +}; + +/** + * A circular write-view is a dynamic mapped view for a segment of a circular block. Care must be + * taken when using this view so that only the section owned by the segment is modified. + */ +class C2CircularWriteView : public _C2LinearCapacityAspect { +public: + /** + * Start of the circular block. + * \note the segment does not own this pointer. + * + * \return pointer to the start of the circular block or nullptr on error. + */ + uint8_t *base(); + + /** + * \return error during the creation/mapping of this view. + */ + c2_status_t error() const; +}; + +/** + * The writer of a circular buffer. + * + * Can commit data to a reader (not supported for components) OR share data blocks directly with a + * consumer. + * + * If a component supports outputting data into circular buffers, it must allocate a circular + * block and use a circular writer. + */ +class C2CircularWriter : public _C2CircularBlockSegment { +public: + /** + * Commits a portion of this segment to the next segment. This moves the beginning of the + * segment. + * + * \param size number of bytes to commit to the next segment + * \param fence fence used for the commit (the fence must signal before the data is committed) + */ + c2_status_t commit(size_t size, C2Fence fence); + + /** + * Maps this block into memory and returns a write view for it. + * + * \return a write view for this block. + */ + C2Acquirable<C2CircularWriteView> map(); +}; + +/// @} + +/// \defgroup graphic Graphic Data Blocks +/// @{ + +/** + * C2Rect: rectangle type with non-negative coordinates. + * + * \note This struct has public fields without getters/setters. All methods are inline. + */ +struct C2Rect { +// public: + uint32_t width; + uint32_t height; + uint32_t left; + uint32_t top; + + constexpr inline C2Rect() + : C2Rect(0, 0, 0, 0) { } + + constexpr inline C2Rect(uint32_t width_, uint32_t height_) + : C2Rect(width_, height_, 0, 0) { } + + constexpr C2Rect inline at(uint32_t left_, uint32_t top_) const { + return C2Rect(width, height, left_, top_); + } + + // utility methods + + inline constexpr bool isEmpty() const { + return width == 0 || height == 0; + } + + inline constexpr bool isValid() const { + return left <= ~width && top <= ~height; + } + + inline constexpr operator bool() const { + return isValid() && !isEmpty(); + } + + inline constexpr bool operator!() const { + return !bool(*this); + } + + C2_ALLOW_OVERFLOW + inline constexpr bool contains(const C2Rect &other) const { + if (!isValid() || !other.isValid()) { + return false; + } else { + return left <= other.left && top <= other.top + && left + width >= other.left + other.width + && top + height >= other.top + other.height; + } + } + + inline constexpr bool operator==(const C2Rect &other) const { + if (!isValid()) { + return !other.isValid(); + } else { + return left == other.left && top == other.top + && width == other.width && height == other.height; + } + } + + inline constexpr bool operator!=(const C2Rect &other) const { + return !operator==(other); + } + + inline constexpr bool operator>=(const C2Rect &other) const { + return contains(other); + } + + inline constexpr bool operator>(const C2Rect &other) const { + return contains(other) && !operator==(other); + } + + inline constexpr bool operator<=(const C2Rect &other) const { + return other.contains(*this); + } + + inline constexpr bool operator<(const C2Rect &other) const { + return other.contains(*this) && !operator==(other); + } + + C2_ALLOW_OVERFLOW + inline constexpr uint32_t right() const { + return left + width; + } + + C2_ALLOW_OVERFLOW + inline constexpr uint32_t bottom() const { + return top + height; + } + + C2_ALLOW_OVERFLOW + inline constexpr C2Rect intersect(const C2Rect &other) const { + return C2Rect(c2_min(right(), other.right()) - c2_max(left, other.left), + c2_min(bottom(), other.bottom()) - c2_max(top, other.top), + c2_max(left, other.left), + c2_max(top, other.top)); + } + + /** clamps right and bottom to top, left if they overflow */ + inline constexpr C2Rect normalize() const { + return C2Rect(c2_max(left, right()) - left, c2_max(top, bottom()) - top, left, top); + } + +private: + /// note: potentially unusual argument order + constexpr inline C2Rect(uint32_t width_, uint32_t height_, uint32_t left_, uint32_t top_) + : width(width_), + height(height_), + left(left_), + top(top_) { } +}; + +/** + * Interface for objects that have a width and height (planar capacity). + */ +class _C2PlanarCapacityAspect { +/// \name Planar capacity interface +/// @{ +public: + inline constexpr uint32_t width() const { return _mWidth; } + inline constexpr uint32_t height() const { return _mHeight; } + + inline constexpr operator C2Rect() const { + return C2Rect(_mWidth, _mHeight); + } + +protected: + inline constexpr _C2PlanarCapacityAspect(uint32_t width, uint32_t height) + : _mWidth(width), _mHeight(height) { } + + inline explicit constexpr _C2PlanarCapacityAspect(const _C2PlanarCapacityAspect *parent) + : _mWidth(parent == nullptr ? 0 : parent->width()), + _mHeight(parent == nullptr ? 0 : parent->height()) { } + +private: + uint32_t _mWidth; + uint32_t _mHeight; +/// @} +}; + +/** + * C2PlaneInfo: information on the layout of a singe flexible plane. + * + * Public fields without getters/setters. + */ +struct C2PlaneInfo { +//public: + enum channel_t : uint32_t { + CHANNEL_Y, ///< luma + CHANNEL_R, ///< red + CHANNEL_G, ///< green + CHANNEL_B, ///< blue + CHANNEL_A, ///< alpha + CHANNEL_CR, ///< Cr + CHANNEL_CB, ///< Cb + } channel; + + int32_t colInc; ///< column increment in bytes. may be negative + int32_t rowInc; ///< row increment in bytes. may be negative + + uint32_t colSampling; ///< subsampling compared to width (must be a power of 2) + uint32_t rowSampling; ///< subsampling compared to height (must be a power of 2) + + uint32_t allocatedDepth; ///< size of each sample (must be a multiple of 8) + uint32_t bitDepth; ///< significant bits per sample + /** + * the right shift of the significant bits in the sample. E.g. if a 10-bit significant + * value is laid out in a 16-bit allocation aligned to LSB (values 0-1023), rightShift + * would be 0 as the 16-bit value read from the sample does not need to be right shifted + * and can be used as is (after applying a 10-bit mask of 0x3FF). + * + * +--------+--------+ + * | VV|VVVVVVVV| + * +--------+--------+ + * 15 8 7 0 + * + * If the value is laid out aligned to MSB, rightShift would be 6, as the value read + * from the allocated sample must be right-shifted by 6 to get the actual sample value. + * + * +--------+--------+ + * |VVVVVVVV|VV | + * +--------+--------+ + * 15 8 7 0 + */ + uint32_t rightShift; + + enum endianness_t : uint32_t { + NATIVE, + LITTLE_END, // LITTLE_ENDIAN is reserved macro + BIG_END, // BIG_ENDIAN is a reserved macro + } endianness; ///< endianness of the samples + + /** + * The following two fields define the relation between multiple planes. If multiple planes are + * interleaved, they share a root plane (whichever plane's start address is the lowest), and + * |offset| is the offset of this plane inside the root plane (in bytes). |rootIx| is the index + * of the root plane. If a plane is independent, rootIx is its index and offset is 0. + */ + uint32_t rootIx; ///< index of the root plane + uint32_t offset; ///< offset of this plane inside of the root plane + + inline constexpr ssize_t minOffset(uint32_t width, uint32_t height) const { + ssize_t offs = 0; + if (width > 0 && colInc < 0) { + offs += colInc * (ssize_t)(width - 1); + } + if (height > 0 && rowInc < 0) { + offs += rowInc * (ssize_t)(height - 1); + } + return offs; + } + + inline constexpr ssize_t maxOffset(uint32_t width, uint32_t height) const { + ssize_t offs = (allocatedDepth + 7) >> 3; + if (width > 0 && colInc > 0) { + offs += colInc * (ssize_t)(width - 1); + } + if (height > 0 && rowInc > 0) { + offs += rowInc * (ssize_t)(height - 1); + } + return offs; + } +} C2_PACK; + +struct C2PlanarLayout { +//public: + enum type_t : uint32_t { + TYPE_UNKNOWN = 0, + TYPE_YUV = 0x100, ///< YUV image with 3 planes + TYPE_YUVA, ///< YUVA image with 4 planes + TYPE_RGB, ///< RGB image with 3 planes + TYPE_RGBA, ///< RBGA image with 4 planes + }; + + type_t type; // image type + uint32_t numPlanes; // number of component planes + uint32_t rootPlanes; // number of layout planes (root planes) + + enum plane_index_t : uint32_t { + PLANE_Y = 0, + PLANE_U = 1, + PLANE_V = 2, + PLANE_R = 0, + PLANE_G = 1, + PLANE_B = 2, + PLANE_A = 3, + MAX_NUM_PLANES = 4, + }; + + C2PlaneInfo planes[MAX_NUM_PLANES]; +}; + +/** + * Aspect for objects that have a planar section (crop rectangle). + * + * This class is copiable. + */ +class _C2PlanarSectionAspect : public _C2PlanarCapacityAspect { +/// \name Planar section interface +/// @{ +private: + inline constexpr _C2PlanarSectionAspect(uint32_t width, uint32_t height, const C2Rect &crop) + : _C2PlanarCapacityAspect(width, height), + mCrop(C2Rect(std::min(width - std::min(crop.left, width), crop.width), + std::min(height - std::min(crop.top, height), crop.height)).at( + std::min(crop.left, width), + std::min(crop.height, height))) { + } + +public: + // crop can be an empty rect, does not have to line up with subsampling + // NOTE: we do not support floating-point crop + inline constexpr C2Rect crop() const { return mCrop; } + + /** + * Returns a child planar section for |crop|, where the capacity represents this section. + */ + inline constexpr _C2PlanarSectionAspect childSection(const C2Rect &crop) const { + return _C2PlanarSectionAspect( + mCrop.width, mCrop.height, + // crop and translate |crop| rect + C2Rect(c2_min(mCrop.right() - c2_clamp(mCrop.left, crop.left, mCrop.right()), + crop.width), + c2_min(mCrop.bottom() - c2_clamp(mCrop.top, crop.top, mCrop.bottom()), + crop.height)) + .at(c2_clamp(mCrop.left, crop.left, mCrop.right()) - mCrop.left, + c2_clamp(mCrop.top, crop.top, mCrop.bottom()) - mCrop.top)); + } + +protected: + inline constexpr _C2PlanarSectionAspect(const _C2PlanarCapacityAspect *parent) + : _C2PlanarCapacityAspect(parent), mCrop(width(), height()) {} + + inline constexpr _C2PlanarSectionAspect(const _C2PlanarCapacityAspect *parent, const C2Rect &crop) + : _C2PlanarCapacityAspect(parent), + mCrop(parent == nullptr ? C2Rect() : ((C2Rect)*parent).intersect(crop).normalize()) { } + + inline constexpr _C2PlanarSectionAspect(const _C2PlanarSectionAspect *parent, const C2Rect &crop) + : _C2PlanarCapacityAspect(parent), + mCrop(parent == nullptr ? C2Rect() : parent->crop().intersect(crop).normalize()) { } + +private: + friend class _C2EditablePlanarSectionAspect; + C2Rect mCrop; +/// @} +}; + +/** + * Aspect for objects that have an editable planar section (crop rectangle). + * + * This class is copiable. + */ +class _C2EditablePlanarSectionAspect : public _C2PlanarSectionAspect { +/// \name Planar section interface +/// @{ + using _C2PlanarSectionAspect::_C2PlanarSectionAspect; + +public: + // crop can be an empty rect, does not have to line up with subsampling + // NOTE: we do not support floating-point crop + inline constexpr C2Rect crop() const { return mCrop; } + + /** + * Sets crop to crop intersected with [(0,0) .. (width, height)] + */ + inline void setCrop_be(const C2Rect &crop) { + mCrop.left = std::min(width(), crop.left); + mCrop.top = std::min(height(), crop.top); + // It's guaranteed that mCrop.left <= width() && mCrop.top <= height() + mCrop.width = std::min(width() - mCrop.left, crop.width); + mCrop.height = std::min(height() - mCrop.top, crop.height); + } + + /** + * If crop is within the dimensions of this object, it sets crop to it. + * + * \return true iff crop is within the dimensions of this object + */ + inline bool setCrop(const C2Rect &crop) { + if (width() < crop.width || height() < crop.height + || width() - crop.width < crop.left || height() - crop.height < crop.top) { + return false; + } + mCrop = crop; + return true; + } +/// @} +}; + +/** + * Utility class for safe range calculations using size_t-s. + */ +class C2PlanarSection : public _C2PlanarSectionAspect { +public: + inline constexpr C2PlanarSection(const _C2PlanarCapacityAspect &parent, const C2Rect &crop) + : _C2PlanarSectionAspect(&parent, crop) { } + + inline constexpr C2PlanarSection(const _C2PlanarSectionAspect &parent, const C2Rect &crop) + : _C2PlanarSectionAspect(&parent, crop) { } + + inline constexpr C2PlanarSection intersect(const C2Rect &crop) const { + return C2PlanarSection(*this, crop); + } +}; + +/** + * Utility class for simple and safe planar capacity and section construction. + */ +class C2PlanarCapacity : public _C2PlanarCapacityAspect { +public: + inline constexpr explicit C2PlanarCapacity(size_t width, size_t height) + : _C2PlanarCapacityAspect(c2_min(width, std::numeric_limits<uint32_t>::max()), + c2_min(height, std::numeric_limits<uint32_t>::max())) { } + + inline constexpr C2PlanarSection section(const C2Rect &crop) const { + return C2PlanarSection(*this, crop); + } +}; + + +/** + * \ingroup graphic allocator + * 2D allocation interface. + */ +class C2GraphicAllocation : public _C2PlanarCapacityAspect { +public: + /** + * Maps a rectangular section (as defined by |rect|) of a 2D allocation into local process + * memory for flexible access. On success, it fills out |layout| with the plane specifications + * and fills the |addr| array with pointers to the first byte of the top-left pixel of each + * plane used. Otherwise, it leaves |layout| and |addr| untouched. |fence| will contain + * an acquire sync fence object. If it is already safe to access the + * buffer contents, then it will be an empty (already fired) fence. + * + * Safe regions for the pointer addresses returned can be gotten via C2LayoutInfo.minOffset()/ + * maxOffset(). + * + * \param rect section to be mapped (this does not have to be aligned) + * \param usage the desired usage. \todo this must be kSoftwareRead and/or + * kSoftwareWrite. + * \param fence a pointer to a fence object if an async mapping is requested. If + * not-null, and acquire fence will be stored here on success, or empty + * fence on failure. If null, the mapping will be synchronous. + * \param layout a pointer to where the mapped planes' descriptors will be + * stored. On failure, nullptr will be stored here. + * \param addr pointer to an array with at least C2PlanarLayout::MAX_NUM_PLANES + * elements. Only layout.numPlanes elements will be modified on success. + * + * \retval C2_OK the operation was successful + * \retval C2_REFUSED no permission to map the section + * \retval C2_DUPLICATE there is already a mapped region and this allocation cannot support + * multi-mapping (caller error) + * \retval C2_TIMED_OUT the operation timed out + * \retval C2_NO_MEMORY not enough memory to complete the operation + * \retval C2_BAD_VALUE the parameters (rect) are invalid or outside the allocation, or the + * usage flags are invalid (caller error) + * \retval C2_CORRUPTED some unknown error prevented the operation from completing (unexpected) + + */ + virtual c2_status_t map( + C2Rect rect, C2MemoryUsage usage, C2Fence *fence, + C2PlanarLayout *layout /* nonnull */, uint8_t **addr /* nonnull */) = 0; + + /** + * Unmaps a section of an allocation at |addr| with |rect|. These must be parameters previously + * passed to and returned by |map|; otherwise, this operation is a no-op. + * + * \param addr pointer to an array with at least C2PlanarLayout::MAX_NUM_PLANES + * elements containing the starting addresses of the mapped layers + * \param rect boundaries of the mapped section + * \param fence a pointer to a fence object if an async unmapping is requested. If + * not-null, a release fence will be stored here on success, or empty fence + * on failure. This fence signals when the original allocation contains + * all changes that happened to the mapped section. If null, the unmapping + * will be synchronous. + * + * \retval C2_OK the operation was successful + * \retval C2_TIMED_OUT the operation timed out + * \retval C2_NOT_FOUND there is no such mapped region (caller error) + * \retval C2_CORRUPTED some unknown error prevented the operation from completing (unexpected) + * \retval C2_REFUSED no permission to unmap the section (unexpected - system) + */ + virtual c2_status_t unmap( + uint8_t **addr /* nonnull */, C2Rect rect, C2Fence *fence /* nullable */) = 0; + + /** + * Returns the allocator ID for this allocation. This is useful to put the handle into context. + */ + virtual C2Allocator::id_t getAllocatorId() const = 0; + + /** + * Returns a pointer to the allocation handle. + */ + virtual const C2Handle *handle() const = 0; + + /** + * Returns true if this is the same allocation as |other|. + */ + virtual bool equals(const std::shared_ptr<const C2GraphicAllocation> &other) const = 0; + +protected: + using _C2PlanarCapacityAspect::_C2PlanarCapacityAspect; + virtual ~C2GraphicAllocation() = default; +}; + +class C2GraphicAllocation; + +/** + * A 2D block. + * + * \note width()/height() is not meaningful for users of blocks; instead, crop().width() and + * crop().height() is the capacity of the usable portion. Use and crop() if accessing the block + * directly through its handle to represent the allotted region of the underlying allocation to this + * block. + */ +class C2Block2D : public _C2PlanarSectionAspect { +public: + /** + * Returns the underlying handle for this allocation. + * + * \note that the block and its block pool has shared ownership of the handle + * and if all references to the block are released, the underlying block + * allocation may get reused even if a client keeps a clone of this handle. + */ + const C2Handle *handle() const; + + /** + * Returns the allocator's ID that created the underlying allocation for this block. This + * provides the context for understanding the handle. + */ + C2Allocator::id_t getAllocatorId() const; + +protected: + class Impl; + C2Block2D(std::shared_ptr<Impl> impl, const _C2PlanarSectionAspect §ion); + + friend struct _C2BlockFactory; + std::shared_ptr<Impl> mImpl; +}; + +/** + * Graphic view provides read or read-write access for a graphic block. + * + * This class is copiable. + * + * \note Due to the subsampling of graphic buffers, a read view must still contain a crop rectangle + * to ensure subsampling is followed. This results in nearly identical interface between read and + * write views, so C2GraphicView can encompass both of them. + */ +class C2GraphicView : public _C2EditablePlanarSectionAspect { +public: + /** + * \return array of pointers (of layout().numPlanes elements) to the start of the planes or + * nullptr on error. Regardless of crop rect, they always point to the top-left corner of each + * plane. Access outside of the crop rect results in an undefined behavior. + */ + const uint8_t *const *data() const; + + /** + * \return array of pointers (of layout().numPlanes elements) to the start of the planes or + * nullptr on error. Regardless of crop rect, they always point to the top-left corner of each + * plane. Access outside of the crop rect results in an undefined behavior. + */ + uint8_t *const *data(); + + /** + * \return layout of the graphic block to interpret the returned data. + */ + const C2PlanarLayout layout() const; + + /** + * Returns a section of this view. + * + * \param rect the dimension of the section. \note This is clamped to the crop of this view. + * + * \return a read view containing the requested section of this view + */ + const C2GraphicView subView(const C2Rect &rect) const; + C2GraphicView subView(const C2Rect &rect); + + /** + * \return error during the creation/mapping of this view. + */ + c2_status_t error() const; + +protected: + class Impl; + C2GraphicView(std::shared_ptr<Impl> impl, const _C2PlanarSectionAspect §ion); + explicit C2GraphicView(c2_status_t error); + +private: + friend struct _C2BlockFactory; + std::shared_ptr<Impl> mImpl; +}; + +/** + * A constant (read-only) graphic block (portion of an allocation) with an acquire fence. + * Blocks are unmapped when created, and can be mapped into a read view on demand. + * + * This class is copiable and contains a reference to the allocation that it is based on. + */ +class C2ConstGraphicBlock : public C2Block2D { +public: + /** + * Maps this block into memory and returns a read view for it. + * + * \return a read view for this block. + */ + C2Acquirable<const C2GraphicView> map() const; + + /** + * Returns a section of this block. + * + * \param rect the coordinates of the section. \note This is clamped to the crop rectangle of + * this block. + * + * \return a constant graphic block containing a portion of this block + */ + C2ConstGraphicBlock subBlock(const C2Rect &rect) const; + + /** + * Returns the acquire fence for this block. + * + * \return a fence that must be waited on before reading the block. + */ + C2Fence fence() const { return mFence; } + +protected: + C2ConstGraphicBlock( + std::shared_ptr<Impl> impl, const _C2PlanarSectionAspect §ion, C2Fence fence); + +private: + friend struct _C2BlockFactory; + C2Fence mFence; +}; + +/** + * Graphic block is a writeable 2D block. Once written, it can be shared in whole or in part with + * consumers/readers as read-only const graphic block. + */ +class C2GraphicBlock : public C2Block2D { +public: + /** + * Maps this block into memory and returns a write view for it. + * + * \return a write view for this block. + */ + C2Acquirable<C2GraphicView> map(); + + /** + * Creates a read-only const linear block for a portion of this block; optionally protected + * by an acquire fence. There are two ways to use this: + * + * 1) share ready block after writing data into the block. In this case no fence shall be + * supplied, and the block shall not be modified after calling this method. + * 2) share block metadata before actually (finishing) writing the data into the block. In + * this case a fence must be supplied that will be triggered when the data is written. + * The block shall be modified only until firing the event for the fence. + */ + C2ConstGraphicBlock share(const C2Rect &crop, C2Fence fence); + +protected: + C2GraphicBlock(std::shared_ptr<Impl> impl, const _C2PlanarSectionAspect §ion); + + friend struct _C2BlockFactory; +}; + +/// @} + +/// \defgroup buffer_onj Buffer objects +/// @{ + +// ================================================================================================ +// BUFFERS +// ================================================================================================ + +/// \todo: Do we still need this? +/// +// There are 2 kinds of buffers: linear or graphic. Linear buffers can contain a single block, or +// a list of blocks (LINEAR_CHUNKS). Support for list of blocks is optional, and can allow consuming +// data from circular buffers or scattered data sources without extra memcpy. Currently, list of +// graphic blocks is not supported. + +class C2LinearBuffer; // read-write buffer +class C2GraphicBuffer; // read-write buffer +class C2LinearChunksBuffer; + +/** + * C2BufferData: the main, non-meta data of a buffer. A buffer can contain either linear blocks + * or graphic blocks, and can contain either a single block or multiple blocks. This is determined + * by its type. + */ +class C2BufferData { +public: + /** + * The type of buffer data. + */ + enum type_t : uint32_t { + INVALID, ///< invalid buffer type. Do not use. + LINEAR, ///< the buffer contains a single linear block + LINEAR_CHUNKS, ///< the buffer contains one or more linear blocks + GRAPHIC, ///< the buffer contains a single graphic block + GRAPHIC_CHUNKS, ///< the buffer contains one of more graphic blocks + }; + + /** + * Gets the type of this buffer (data). + * \return the type of this buffer data. + */ + type_t type() const; + + /** + * Gets the linear blocks of this buffer. + * \return a constant list of const linear blocks of this buffer. + * \retval empty list if this buffer does not contain linear block(s). + */ + const std::vector<C2ConstLinearBlock> linearBlocks() const; + + /** + * Gets the graphic blocks of this buffer. + * \return a constant list of const graphic blocks of this buffer. + * \retval empty list if this buffer does not contain graphic block(s). + */ + const std::vector<C2ConstGraphicBlock> graphicBlocks() const; + +private: + class Impl; + std::shared_ptr<Impl> mImpl; + +protected: + // no public constructor + explicit C2BufferData(const std::vector<C2ConstLinearBlock> &blocks); + explicit C2BufferData(const std::vector<C2ConstGraphicBlock> &blocks); +}; + +/** + * C2Buffer: buffer base class. These are always used as shared_ptrs. Though the underlying buffer + * objects (native buffers, ion buffers, or dmabufs) are reference-counted by the system, + * C2Buffers hold only a single reference. + * + * These objects cannot be used on the stack. + */ +class C2Buffer { +public: + /** + * Gets the buffer's data. + * + * \return the buffer's data. + */ + const C2BufferData data() const; + + ///@name Pre-destroy notification handling + ///@{ + + /** + * Register for notification just prior to the destruction of this object. + */ + typedef void (*OnDestroyNotify) (const C2Buffer *buf, void *arg); + + /** + * Registers for a pre-destroy notification. This is called just prior to the destruction of + * this buffer (when this buffer is no longer valid.) + * + * \param onDestroyNotify the notification callback + * \param arg an arbitrary parameter passed to the callback + * + * \retval C2_OK the registration was successful. + * \retval C2_DUPLICATE a notification was already registered for this callback and argument + * \retval C2_NO_MEMORY not enough memory to register for this callback + * \retval C2_CORRUPTED an unknown error prevented the registration (unexpected) + */ + c2_status_t registerOnDestroyNotify(OnDestroyNotify onDestroyNotify, void *arg = nullptr); + + /** + * Unregisters a previously registered pre-destroy notification. + * + * \param onDestroyNotify the notification callback + * \param arg an arbitrary parameter passed to the callback + * + * \retval C2_OK the unregistration was successful. + * \retval C2_NOT_FOUND the notification was not found + * \retval C2_CORRUPTED an unknown error prevented the registration (unexpected) + */ + c2_status_t unregisterOnDestroyNotify(OnDestroyNotify onDestroyNotify, void *arg = nullptr); + + ///@} + + virtual ~C2Buffer() = default; + + ///@name Buffer-specific arbitrary metadata handling + ///@{ + + /** + * Gets the list of metadata associated with this buffer. + * + * \return a constant list of info objects associated with this buffer. + */ + const std::vector<std::shared_ptr<const C2Info>> info() const; + + /** + * Attaches (or updates) an (existing) metadata for this buffer. + * If the metadata is stream specific, the stream information will be reset. + * + * \param info Metadata to update + * + * \retval C2_OK the metadata was successfully attached/updated. + * \retval C2_NO_MEMORY not enough memory to attach the metadata (this return value is not + * used if the same kind of metadata is already attached to the buffer). + */ + c2_status_t setInfo(const std::shared_ptr<C2Info> &info); + + /** + * Checks if there is a certain type of metadata attached to this buffer. + * + * \param index the parameter type of the metadata + * + * \return true iff there is a metadata with the parameter type attached to this buffer. + */ + bool hasInfo(C2Param::Type index) const; + + /** + * Checks if there is a certain type of metadata attached to this buffer, and returns a + * shared pointer to it if there is. Returns an empty shared pointer object (nullptr) if there + * is not. + * + * \param index the parameter type of the metadata + * + * \return shared pointer to the metadata. + */ + std::shared_ptr<const C2Info> getInfo(C2Param::Type index) const; + + /** + * Removes a metadata from the buffer. + */ + std::shared_ptr<C2Info> removeInfo(C2Param::Type index); + ///@} + + /** + * Creates a buffer containing a single linear block. + * + * \param block the content of the buffer. + * + * \return shared pointer to the created buffer. + */ + static std::shared_ptr<C2Buffer> CreateLinearBuffer(const C2ConstLinearBlock &block); + + /** + * Creates a buffer containing a single graphic block. + * + * \param block the content of the buffer. + * + * \return shared pointer to the created buffer. + */ + static std::shared_ptr<C2Buffer> CreateGraphicBuffer(const C2ConstGraphicBlock &block); + +protected: + // no public constructor + explicit C2Buffer(const std::vector<C2ConstLinearBlock> &blocks); + explicit C2Buffer(const std::vector<C2ConstGraphicBlock> &blocks); + +private: + class Impl; + std::shared_ptr<Impl> mImpl; +}; + +/** + * An extension of C2Info objects that can contain arbitrary buffer data. + * + * \note This object is not describable and contains opaque data. + */ +class C2InfoBuffer { +public: + /** + * Gets the index of this info object. + * + * \return the parameter index. + */ + const C2Param::Index index() const; + + /** + * Gets the buffer's data. + * + * \return the buffer's data. + */ + const C2BufferData data() const; +}; + +/// @} + +/// @} + +#endif // C2BUFFER_H_
diff --git a/media/codec2/core/include/C2BufferBase.h b/media/codec2/core/include/C2BufferBase.h new file mode 100644 index 0000000..2bd48f0 --- /dev/null +++ b/media/codec2/core/include/C2BufferBase.h
@@ -0,0 +1,94 @@ +/* + * Copyright (C) 2016 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 C2BUFFER_BASE_H_ +#define C2BUFFER_BASE_H_ + +/// \defgroup allocator Allocation and memory placement +/// @{ + +/** + * Buffer/memory usage bits. These shall be used by the allocators to select optimal memory type/ + * pool and buffer layout. Usage bits are conceptually separated into read and write usage, while + * the buffer use life-cycle is separated into producers (writers) and consumers (readers). + * These two concepts are related but not equivalent: consumers may only read buffers and only + * producers may write to buffers; note, however, that buffer producers may also want or need to + * read the buffers. + * + * Read and write buffer usage bits shall be or-ed to arrive at the full buffer usage. Admittedly, + * this does not account for the amount of reading and writing (e.g. a buffer may have one or more + * readers); however, the proper information necessary to properly weigh the various usages would be + * the amount of data read/written for each usage type. This would result in an integer array of + * size 64 (or the number of distinct usages) for memory usage, and likely such detailed information + * would not always be available. + * + * That platform-agnostic Codec 2.0 API only defines the bare minimum usages. Platforms shall define + * usage bits that are appropriate for the platform. + */ +struct C2MemoryUsage { +// public: + /** + * Buffer read usage. + */ + enum read_t : uint64_t { + /** Buffer is read by the CPU. */ + CPU_READ = 1 << 0, + /** + * Buffer shall only be read by trusted hardware. The definition of trusted hardware is + * platform specific, but this flag is reserved to prevent mapping this block into CPU + * readable memory resulting in bus fault. This flag can be used when buffer access must be + * protected. + */ + READ_PROTECTED = 1 << 1, + }; + + /** + * Buffer write usage. + */ + enum write_t : uint64_t { + /** Buffer is writted to by the CPU. */ + CPU_WRITE = 1 << 2, + /** + * Buffer shall only be written to by trusted hardware. The definition of trusted hardware + * is platform specific, but this flag is reserved to prevent mapping this block into CPU + * writable memory resulting in bus fault. This flag can be used when buffer integrity must + * be protected. + */ + WRITE_PROTECTED = 1 << 3, + }; + + enum : uint64_t { + /** + * Buffer usage bits reserved for the platform. We don't separately reserve read and + * write usages as platforms may have asymmetric distribution between them. + */ + PLATFORM_MASK = ~(CPU_READ | CPU_WRITE | READ_PROTECTED | WRITE_PROTECTED), + }; + + /** Create a usage from separate consumer and producer usage mask. \deprecated */ + inline C2MemoryUsage(uint64_t consumer, uint64_t producer) + : expected(consumer | producer) { } + + inline explicit C2MemoryUsage(uint64_t expected_) + : expected(expected_) { } + + uint64_t expected; // expected buffer usage +}; + +/// @} + +#endif // C2BUFFER_BASE_H_ +
diff --git a/media/codec2/core/include/C2Component.h b/media/codec2/core/include/C2Component.h new file mode 100644 index 0000000..ecf8d2e --- /dev/null +++ b/media/codec2/core/include/C2Component.h
@@ -0,0 +1,976 @@ +/* + * Copyright (C) 2016 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 C2COMPONENT_H_ + +#define C2COMPONENT_H_ + +#include <stdbool.h> +#include <stdint.h> + +#include <list> +#include <memory> +#include <vector> +#include <functional> + +#include <C2Enum.h> +#include <C2Param.h> +#include <C2Work.h> + +/// \defgroup components Components +/// @{ + +struct C2FieldSupportedValuesQuery { + enum type_t : uint32_t { + POSSIBLE, ///< query all possible values regardless of other settings + CURRENT, ///< query currently possible values given dependent settings + }; + +private: + C2ParamField _mField; + type_t _mType; +public: + c2_status_t status; + C2FieldSupportedValues values; + + C2FieldSupportedValuesQuery(const C2ParamField &field_, type_t type_) + : _mField(field_), _mType(type_), status(C2_NO_INIT) { } + + static C2FieldSupportedValuesQuery + Current(const C2ParamField &field_) { + return C2FieldSupportedValuesQuery(field_, CURRENT); + } + + static C2FieldSupportedValuesQuery + Possible(const C2ParamField &field_) { + return C2FieldSupportedValuesQuery(field_, POSSIBLE); + } + + inline C2ParamField field() const { return _mField; }; + + inline type_t type() const { return _mType; } +}; + +/** + * Component interface object. This object contains all of the configuration of a potential or + * actual component. It can be created and used independently of an actual C2Component instance to + * query support and parameters for various component settings and configurations for a potential + * component. Actual components also expose this interface. + */ + +class C2ComponentInterface { +public: + // ALWAYS AVAILABLE METHODS + // ============================================================================================= + + /** + * Returns the name of this component or component interface object. + * This is a unique name for this component or component interface 'class'; however, multiple + * instances of this component SHALL have the same name. + * + * When attached to a component, this method MUST be supported in any component state. + * This call does not change the state nor the internal configuration of the component. + * + * This method MUST be "non-blocking" and return within 1ms. + * + * \return the name of this component or component interface object. + * \retval an empty string if there was not enough memory to allocate the actual name. + */ + virtual C2String getName() const = 0; + + /** + * Returns a unique ID for this component or interface object. + * This ID is used as work targets, unique work IDs, and when configuring tunneling. + * + * When attached to a component, this method MUST be supported in any component state. + * This call does not change the state nor the internal configuration of the component. + * + * This method MUST be "non-blocking" and return within 1ms. + * + * \return a unique node ID for this component or component interface instance. + */ + virtual c2_node_id_t getId() const = 0; + + /** + * Queries a set of parameters from the component or interface object. + * Querying is performed at best effort: the component SHALL query all supported parameters and + * skip unsupported ones, heap allocated parameters that could not be allocated or parameters + * that could not be queried without blocking. Any errors are communicated in the return value. + * Additionally, preallocated (e.g. stack) parameters that could not be queried are invalidated. + * Invalid or blocking parameters to be allocated on the heap are omitted from the result. + * + * \note Parameter values do not depend on the order of query. + * + * \todo This method cannot be used to query info-buffers. Is that a problem? + * + * When attached to a component, this method MUST be supported in any component state except + * released. + * This call does not change the state nor the internal configuration of the component. + * + * This method has a variable blocking behavior based on state. + * In the stopped state this method MUST be "non-blocking" and return within 1ms. + * In the running states this method may be momentarily blocking, but MUST return within 5ms. + * + * \param[in,out] stackParams a list of params queried. These are initialized specific to each + * setting; e.g. size and index are set and rest of the members are + * cleared. + * \note Flexible settings that are of incorrect size will be + * invalidated. + * \param[in] heapParamIndices a vector of param indices for params to be queried and returned + * on the heap. These parameters will be returned in heapParams. + * Unsupported param indices will be ignored. + * \param[in] mayBlock if true (C2_MAY_BLOCK), implementation may momentarily block. + * Otherwise (C2_DONT_BLOCK), it must be "non-blocking". + * \param[out] heapParams a list of params where to which the supported heap parameters + * will be appended in the order they appear in heapParamIndices. + * + * \retval C2_OK all parameters could be queried + * \retval C2_BAD_INDEX all supported parameters could be queried, but some parameters were not + * supported + * \retval C2_BAD_STATE when called in the released component state (user error) + * (this error code is only allowed for interfaces connected to components) + * \retval C2_NO_MEMORY could not allocate memory for a supported parameter + * \retval C2_BLOCKING the operation must block to complete but mayBlock is false + * (this error code is only allowed for interfaces connected to components) + * \retval C2_TIMED_OUT could not query the parameters within the time limit (unexpected) + * (this error code is only allowed for interfaces connected to components + * in the running state) + * \retval C2_CORRUPTED some unknown error prevented the querying of the parameters + * (unexpected) + * (this error code is only allowed for interfaces connected to components) + */ + virtual c2_status_t query_vb( + const std::vector<C2Param*> &stackParams, + const std::vector<C2Param::Index> &heapParamIndices, + c2_blocking_t mayBlock, + std::vector<std::unique_ptr<C2Param>>* const heapParams) const = 0; + + /** + * Sets a set of parameters for the component or interface object. + * + * Tuning is performed at best effort: the component SHALL process the configuration updates in + * the order they appear in |params|. If any parameter update fails, the component shall + * communicate the failure in the return value and in |failures|, and still process the + * remaining parameters. Unsupported parameters are skipped, though they are communicated in + * ther return value. Most parameters are updated at best effort - such that even if client + * specifies an unsupported value for a field, the closest supported value is used. On the + * other hand, strict parameters only accept specific values for their fields, and if the client + * specifies an unsupported value, the parameter setting shall fail for that field. + * If the client tries to change the value of a field that requires momentary blocking without + * setting |mayBlock| to C2_MAY_BLOCK, that parameter shall also be skipped and a specific + * return value shall be used. Final values for all parameters set are propagated back to the + * caller in |params|. + * + * \note Parameter tuning DOES depend on the order of the tuning parameters. E.g. some parameter + * update may allow some subsequent values for further parameter updates. + * + * When attached to a component, this method MUST be supported in any component state except + * released. + * + * This method has a variable blocking behavior based on state. + * In the stopped state this method MUST be "non-blocking" and return within 1ms. + * In the running states this method may be momentarily blocking, but MUST return within 5ms. + * + * \param[in,out] params a list of parameter updates. These will be updated to the actual + * parameter values after the updates (this is because tuning is performed + * at best effort). + * \todo params that could not be updated are not marked here, so are + * confusing - are they "existing" values or intended to be configured + * values? + * \param[in] mayBlock if true (C2_MAY_BLOCK), implementation may momentarily block. + * Otherwise (C2_DONT_BLOCK), it must be "non-blocking". + * \param[out] failures a list of parameter failures and optional guidance + * + * \retval C2_OK all parameters could be updated successfully + * \retval C2_BAD_INDEX all supported parameters could be updated successfully, but some + * parameters were not supported + * \retval C2_BAD_VALUE some supported parameters could not be updated successfully because + * they contained unsupported values. These are returned in |failures|. + * \retval C2_BAD_STATE when called in the released component state (user error) + * (this error code is only allowed for interfaces connected to components) + * \retval C2_NO_MEMORY some supported parameters could not be updated successfully because + * they contained unsupported values, but could not allocate a failure + * object for them. + * \retval C2_TIMED_OUT could not set the parameters within the time limit (unexpected) + * (this error code is only allowed for interfaces connected to components + * in the running state) + * \retval C2_BLOCKING the operation must block to complete but mayBlock is false + * (this error code is only allowed for interfaces connected to components) + * \retval C2_CORRUPTED some unknown error prevented the update of the parameters + * (unexpected) + * (this error code is only allowed for interfaces connected to components) + */ + virtual c2_status_t config_vb( + const std::vector<C2Param*> ¶ms, + c2_blocking_t mayBlock, + std::vector<std::unique_ptr<C2SettingResult>>* const failures) = 0; + + // TUNNELING + // ============================================================================================= + + /** + * Creates a tunnel from this component to the target component. + * + * If the component is successfully created, subsequent work items queued may include a + * tunneled path between these components. + * + * When attached to a component, this method MUST be supported in any component state except + * released. + * + * This method may be momentarily blocking, but MUST return within 5ms. + * + * \retval C2_OK the tunnel was successfully created + * \retval C2_BAD_INDEX the target component does not exist + * \retval C2_DUPLICATE the tunnel already exists + * \retval C2_OMITTED tunneling is not supported by this component + * \retval C2_CANNOT_DO the specific tunnel is not supported + * \retval C2_BAD_STATE when called in the released component state (user error) + * (this error code is only allowed for interfaces connected to components) + * + * \retval C2_TIMED_OUT could not create the tunnel within the time limit (unexpected) + * \retval C2_CORRUPTED some unknown error prevented the creation of the tunnel (unexpected) + * (this error code is only allowed for interfaces connected to components) + */ + virtual c2_status_t createTunnel_sm(c2_node_id_t targetComponent) = 0; + + /** + * Releases a tunnel from this component to the target component. + * + * The release of a tunnel is delayed while there are pending work items for the tunnel. + * After releasing a tunnel, subsequent work items queued MUST NOT include a tunneled + * path between these components. + * + * When attached to a component, this method MUST be supported in any component state except + * released. + * + * This method may be momentarily blocking, but MUST return within 5ms. + * + * \retval C2_OK the tunnel was marked for release successfully + * \retval C2_BAD_INDEX the target component does not exist + * \retval C2_NOT_FOUND the tunnel does not exist + * \retval C2_OMITTED tunneling is not supported by this component + * \retval C2_BAD_STATE when called in the released component state (user error) + * (this error code is only allowed for interfaces connected to components) + * + * \retval C2_TIMED_OUT could not mark the tunnel for release within the time limit (unexpected) + * \retval C2_CORRUPTED some unknown error prevented the release of the tunnel (unexpected) + * (this error code is only allowed for interfaces connected to components) + */ + virtual c2_status_t releaseTunnel_sm(c2_node_id_t targetComponent) = 0; + + // REFLECTION MECHANISM (USED FOR EXTENSION) + // ============================================================================================= + + /** + * Returns the set of supported parameters. + * + * When attached to a component, this method MUST be supported in any component state except + * released. + * + * This method MUST be "non-blocking" and return within 1ms. + * + * \param[out] params a vector of supported parameters will be appended to this vector. + * + * \retval C2_OK the operation completed successfully. + * \retval C2_BAD_STATE when called in the released component state (user error) + * (this error code is only allowed for interfaces connected to components) + * \retval C2_NO_MEMORY not enough memory to complete this method. + */ + virtual c2_status_t querySupportedParams_nb( + std::vector<std::shared_ptr<C2ParamDescriptor>> * const params) const = 0; + + /** + * Retrieves the supported values for the queried fields. + * + * Client SHALL set the parameter-field specifier and the type of supported values query (e.g. + * currently supported values, or potential supported values) in fields. + * Upon return the component SHALL fill in the supported values for the fields listed as well + * as a status for each field. Component shall process all fields queried even if some queries + * fail. + * + * When attached to a component, this method MUST be supported in any component state except + * released. + * + * This method has a variable blocking behavior based on state. + * In the stopped state this method MUST be "non-blocking" and return within 1ms. + * In the running states this method may be momentarily blocking, but MUST return within 5ms. + * + * \param[in out] fields a vector of fields descriptor structures. + * \param[in] mayBlock if true (C2_MAY_BLOCK), implementation may momentarily block. + * Otherwise (C2_DONT_BLOCK), it must be "non-blocking". + * + * \retval C2_OK the operation completed successfully. + * \retval C2_BAD_STATE when called in the released component state (user error) + * (this error code is only allowed for interfaces connected to components) + * \retval C2_BAD_INDEX at least one field was not recognized as a component field + * \retval C2_TIMED_OUT could not query supported values within the time limit (unexpected) + * (this error code is only allowed for interfaces connected to components + * in the running state) + * \retval C2_BLOCKING the operation must block to complete but mayBlock is false + * (this error code is only allowed for interfaces connected to components) + * \retval C2_CORRUPTED some unknown error prevented the operation from completing (unexpected) + * (this error code is only allowed for interfaces connected to components) + */ + virtual c2_status_t querySupportedValues_vb( + std::vector<C2FieldSupportedValuesQuery> &fields, c2_blocking_t mayBlock) const = 0; + + virtual ~C2ComponentInterface() = default; +}; + +class C2Component { +public: + class Listener { + public: + virtual void onWorkDone_nb(std::weak_ptr<C2Component> component, + std::list<std::unique_ptr<C2Work>> workItems) = 0; + + virtual void onTripped_nb(std::weak_ptr<C2Component> component, + std::vector<std::shared_ptr<C2SettingResult>> settingResult) = 0; + + virtual void onError_nb(std::weak_ptr<C2Component> component, + uint32_t errorCode) = 0; + + // virtual void onTunnelReleased(<from>, <to>) = 0; + + // virtual void onComponentReleased(<id>) = 0; + + virtual ~Listener() = default; + }; + + /** + * Sets the listener for this component + * + * This method MUST be supported in all states except released. + * The listener can only be set to non-null value in stopped state (that does not include + * tripped or error). It can be set to nullptr in both stopped and running states. + * Components only use the listener in running state. + * + * If listener is nullptr, the component SHALL guarantee that no more listener callbacks are + * done to the original listener once this method returns. (Any pending listener callbacks will + * need to be completed during this call - hence this call may be temporarily blocking.) + * + * This method has a variable blocking behavior based on state. + * In the stopped state this method MUST be "non-blocking" and return within 1ms. + * In the running states this method may be momentarily blocking, but MUST return within 5ms. + * + * Component SHALL handle listener notifications from the same thread (the thread used is + * at the component's discretion.) + * + * \note This could also be accomplished by passing a weak_ptr to a component-specific listener + * here and requiring the client to always promote the weak_ptr before any callback. This would + * put the burden on the client to clear the listener - wait for its deletion - at which point + * it is guaranteed that no more listener callbacks will occur. + * + * \param[in] listener the component listener object + * \param[in] mayBlock if true (C2_MAY_BLOCK), implementation may momentarily block. + * Otherwise (C2_DONT_BLOCK), it must be "non-blocking". + * + * \retval C2_BAD_STATE attempting to change the listener in the running state to a non-null + * value (user error), or called in the released state + * \retval C2_BLOCKING the operation must block to complete but mayBlock is false + * \retval C2_OK listener was updated successfully. + */ + virtual c2_status_t setListener_vb( + const std::shared_ptr<Listener> &listener, c2_blocking_t mayBlock) = 0; + + /// component domain (e.g. audio or video) + enum domain_t : uint32_t; + + /// component kind (e.g. encoder, decoder or filter) + enum kind_t : uint32_t; + + /// component rank. This number is used to determine component ordering (the lower the sooner) + /// in the component list. + typedef uint32_t rank_t; + + /// component attributes + enum attrib_t : uint64_t; + + /** + * Information about a component. + */ + struct Traits { + // public: + C2String name; ///< name of the component + domain_t domain; ///< component domain + 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. + * \note Multiple components can have the same alias as long as their media-type differs. + */ + std::vector<C2String> aliases; ///< name aliases for backward compatibility + }; + + // METHODS AVAILABLE WHEN RUNNING + // ============================================================================================= + + /** + * Queues up work for the component. + * + * This method MUST be supported in running (including tripped and error) states. + * + * This method MUST be "non-blocking" and return within 1 ms + * + * It is acceptable for this method to return OK and return an error value using the + * onWorkDone() callback. + * + * \retval C2_OK the work was successfully queued + * \retval C2_BAD_INDEX some component(s) in the work do(es) not exist + * \retval C2_CANNOT_DO the components are not tunneled + * \retval C2_BAD_STATE when called in the stopped or released state (user error) + * + * \retval C2_NO_MEMORY not enough memory to queue the work + * \retval C2_CORRUPTED some unknown error prevented queuing the work (unexpected) + */ + virtual c2_status_t queue_nb(std::list<std::unique_ptr<C2Work>>* const items) = 0; + + /** + * Announces a work to be queued later for the component. This reserves a slot for the queue + * to ensure correct work ordering even if the work is queued later. + * + * This method MUST be supported in running (including tripped and error) states. + * + * This method MUST be "non-blocking" and return within 1 ms + * + * \retval C2_OK the work announcement has been successfully recorded + * \retval C2_BAD_INDEX some component(s) in the work outline do(es) not exist + * \retval C2_CANNOT_DO the componentes are not tunneled + * \retval C2_BAD_STATE when called in the stopped or released state (user error) + * + * \retval C2_NO_MEMORY not enough memory to record the work announcement + * \retval C2_CORRUPTED some unknown error prevented recording the announcement (unexpected) + * + * \todo Can this be rolled into queue_nb? + * \todo Expose next work item for each component to detect stalls + */ + virtual c2_status_t announce_nb(const std::vector<C2WorkOutline> &items) = 0; + + enum flush_mode_t : uint32_t { + /// flush work from this component only + FLUSH_COMPONENT, + + /// flush work from this component and all components connected downstream from it via + /// tunneling + FLUSH_CHAIN = (1 << 16), + }; + + /** + * Discards and abandons any pending work for the component, and optionally any component + * downstream. + * + * \todo define this: we could flush all work before last item queued for component across all + * components linked to this; flush only work items that are queued to this + * component + * \todo return work # of last flushed item; or all flushed (but not returned items) + * \todo we could make flush take a work item and flush all work before/after that item to allow + * TBD (slicing/seek?) + * \todo we could simply take a list of numbers and flush those... this is bad for decoders + * also, what would happen to fine grade references? + * + * This method MUST be supported in running (including tripped and error) states. + * + * This method may be momentarily blocking, but must return within 5ms. + * + * Work that could be immediately abandoned/discarded SHALL be returned in |flushedWork|; this + * can be done in an arbitrary order. + * + * Work that could not be abandoned or discarded immediately SHALL be marked to be + * discarded at the earliest opportunity, and SHALL be returned via the onWorkDone() callback. + * This shall be completed within 500ms. + * + * \param mode flush mode + * + * \retval C2_OK the component has been successfully flushed + * \retval C2_BAD_STATE when called in the stopped or released state (user error) + * \retval C2_TIMED_OUT the flush could not be completed within the time limit (unexpected) + * \retval C2_CORRUPTED some unknown error prevented flushing from completion (unexpected) + */ + virtual c2_status_t flush_sm(flush_mode_t mode, std::list<std::unique_ptr<C2Work>>* const flushedWork) = 0; + + enum drain_mode_t : uint32_t { + /// drain component only and add an "end-of-stream" marker. Component shall process all + /// queued work and complete the current stream. If new input is received, it shall start + /// a new stream. \todo define what a stream is. + DRAIN_COMPONENT_WITH_EOS, + /// drain component without setting "end-of-stream" marker. Component shall process all + /// queued work but shall expect more work items for the same stream. + DRAIN_COMPONENT_NO_EOS = (1 << 0), + + /// marks the last work item with a persistent "end-of-stream" marker that will drain + /// downstream components + /// \todo this may confuse work-ordering downstream + DRAIN_CHAIN = (1 << 16), + + /** + * \todo define this; we could place EOS to all upstream components, just this component, or + * all upstream and downstream component. + * \todo should EOS carry over to downstream components? + */ + }; + + /** + * Drains the component, and optionally downstream components. This is a signalling method; + * as such it does not wait for any work completion. + * + * Marks last work item as "drain-till-here", so component is notified not to wait for further + * work before it processes work already queued. This method can also used to set the + * end-of-stream flag after work has been queued. Client can continue to queue further work + * immediately after this method returns. + * + * This method MUST be supported in running (including tripped) states. + * + * This method MUST be "non-blocking" and return within 1ms. + * + * Work that is completed SHALL be returned via the onWorkDone() callback. + * + * \param mode drain mode + * + * \retval C2_OK the drain request has been successfully recorded + * \retval C2_BAD_STATE when called in the stopped or released state (user error) + * \retval C2_BAD_VALUE the drain mode is not supported by the component + * \todo define supported modes discovery + * \retval C2_TIMED_OUT the flush could not be completed within the time limit (unexpected) + * \retval C2_CORRUPTED some unknown error prevented flushing from completion (unexpected) + */ + virtual c2_status_t drain_nb(drain_mode_t mode) = 0; + + // STATE CHANGE METHODS + // ============================================================================================= + + /** + * Starts the component. + * + * This method MUST be supported in stopped state, as well as during the tripped state. + * + * If the return value is C2_OK, the component shall be in the running state. + * If the return value is C2_BAD_STATE or C2_DUPLICATE, no state change is expected as a + * response to this call. + * Otherwise, the component shall be in the stopped state. + * + * \note If a component is in the tripped state and start() is called while the component + * configuration still results in a trip, start shall succeed and a new onTripped callback + * should be used to communicate the configuration conflict that results in the new trip. + * + * \todo This method MUST return within 500ms. Seems this should be able to return quickly, as + * there are no immediate guarantees. Though there are guarantees for responsiveness immediately + * after start returns. + * + * \retval C2_OK the component has started (or resumed) successfully + * \retval C2_DUPLICATE when called during another start call from another thread + * \retval C2_BAD_STATE when called in any state other than the stopped state or tripped state, + * including when called during another state change call from another + * thread (user error) + * \retval C2_NO_MEMORY not enough memory to start the component + * \retval C2_TIMED_OUT the component could not be started within the time limit (unexpected) + * \retval C2_CORRUPTED some unknown error prevented starting the component (unexpected) + */ + virtual c2_status_t start() = 0; + + /** + * Stops the component. + * + * This method MUST be supported in running (including tripped) state. + * + * This method MUST return withing 500ms. + * + * Upon this call, all pending work SHALL be abandoned and all buffer references SHALL be + * released. + * If the return value is C2_BAD_STATE or C2_DUPLICATE, no state change is expected as a + * response to this call. + * For all other return values, the component shall be in the stopped state. + * + * \todo should this return completed work, since client will just free it? Perhaps just to + * verify accounting. + * + * This does not alter any settings and tunings that may have resulted in a tripped state. + * (Is this material given the definition? Perhaps in case we want to start again.) + * + * \retval C2_OK the component has started successfully + * \retval C2_DUPLICATE when called during another stop call from another thread + * \retval C2_BAD_STATE when called in any state other than the running state, including when + * called during another state change call from another thread (user error) + * \retval C2_TIMED_OUT the component could not be stopped within the time limit (unexpected) + * \retval C2_CORRUPTED some unknown error prevented stopping the component (unexpected) + */ + virtual c2_status_t stop() = 0; + + /** + * Resets the component. + * + * This method MUST be supported in all (including tripped) states other than released. + * + * This method MUST be supported during any other blocking call. + * + * This method MUST return withing 500ms. + * + * After this call returns all work SHALL be abandoned, all buffer references SHALL be released. + * If the return value is C2_BAD_STATE or C2_DUPLICATE, no state change is expected as a + * response to this call. + * For all other return values, the component shall be in the stopped state. + * + * \todo should this return completed work, since client will just free it? Also, if it unblocks + * a stop, where should completed work be returned? + * + * This brings settings back to their default - "guaranteeing" no tripped space. + * + * \todo reclaim support - it seems that since ownership is passed, this will allow reclaiming + * stuff. + * + * \retval C2_OK the component has been reset + * \retval C2_DUPLICATE when called during another reset call from another thread + * \retval C2_BAD_STATE when called in the released state + * \retval C2_TIMED_OUT the component could not be reset within the time limit (unexpected) + * \retval C2_CORRUPTED some unknown error prevented resetting the component (unexpected) + */ + virtual c2_status_t reset() = 0; + + /** + * Releases the component. + * + * This method MUST be supported in stopped state. + * + * This method MUST return withing 500ms. Upon return all references shall be abandoned. + * + * \retval C2_OK the component has been released + * \retval C2_DUPLICATE the component is already released + * \retval C2_BAD_STATE the component is running + * \retval C2_TIMED_OUT the component could not be released within the time limit (unexpected) + * \retval C2_CORRUPTED some unknown error prevented releasing the component (unexpected) + */ + virtual c2_status_t release() = 0; + + /** + * Returns the interface for this component. + * + * \return the component interface + */ + virtual std::shared_ptr<C2ComponentInterface> intf() = 0; + + virtual ~C2Component() = default; +}; + +C2ENUM(C2Component::kind_t, uint32_t, + KIND_OTHER, + KIND_DECODER, + KIND_ENCODER +); + +C2ENUM(C2Component::domain_t, uint32_t, + DOMAIN_OTHER, + DOMAIN_VIDEO, + DOMAIN_AUDIO, + DOMAIN_IMAGE +); + +class C2FrameInfoParser { +public: + /** + * \return the content type supported by this info parser. + * + * \todo this may be redundant + */ + virtual C2StringLiteral getType() const = 0; + + /** + * \return a vector of supported parameter indices parsed by this info parser. + * + * This method MUST be "non-blocking" and return within 1ms. + * + * \todo sticky vs. non-sticky params? this may be communicated by param-reflector. + */ + virtual const std::vector<C2Param::Index> getParsedParams() const = 0; + + /** + * Resets this info parser. This brings this parser to its initial state after creation. + * + * This method SHALL return within 5ms. + * + * \retval C2_OK the info parser was reset + * \retval C2_TIMED_OUT could not reset the parser within the time limit (unexpected) + * \retval C2_CORRUPTED some unknown error prevented the resetting of the parser (unexpected) + */ + virtual c2_status_t reset() { return C2_OK; } + + virtual c2_status_t parseFrame(C2FrameData &frame); + + virtual ~C2FrameInfoParser() = default; +}; + +class C2AllocatorStore { +public: + typedef C2Allocator::id_t id_t; + + enum : C2Allocator::id_t { + DEFAULT_LINEAR, ///< basic linear allocator type + DEFAULT_GRAPHIC, ///< basic graphic allocator type + PLATFORM_START = 0x10, + VENDOR_START = 0x100, + BAD_ID = C2Allocator::BAD_ID, ///< DO NOT USE + }; + + /** + * Returns the unique name of this allocator store. + * + * This method MUST be "non-blocking" and return within 1ms. + * + * \return the name of this allocator store. + * \retval an empty string if there was not enough memory to allocate the actual name. + */ + virtual C2String getName() const = 0; + + /** + * Returns the set of allocators supported by this allocator store. + * + * This method MUST be "non-blocking" and return within 1ms. + * + * \retval vector of allocator information (as shared pointers) + * \retval an empty vector if there was not enough memory to allocate the whole vector. + */ + virtual std::vector<std::shared_ptr<const C2Allocator::Traits>> listAllocators_nb() const = 0; + + /** + * Retrieves/creates a shared allocator object. + * + * This method MUST be return within 5ms. + * + * The allocator is created on first use, and the same allocator is returned on subsequent + * concurrent uses in the same process. The allocator is freed when it is no longer referenced. + * + * \param id the ID of the allocator to create. This is defined by the store, but + * the ID of the default linear and graphic allocators is formalized. + * \param allocator shared pointer where the created allocator is stored. Cleared on failure + * and updated on success. + * + * \retval C2_OK the allocator was created successfully + * \retval C2_TIMED_OUT could not create the allocator within the time limit (unexpected) + * \retval C2_CORRUPTED some unknown error prevented the creation of the allocator (unexpected) + * + * \retval C2_NOT_FOUND no such allocator + * \retval C2_NO_MEMORY not enough memory to create the allocator + */ + virtual c2_status_t fetchAllocator(id_t id, std::shared_ptr<C2Allocator>* const allocator) = 0; + + virtual ~C2AllocatorStore() = default; +}; + +class C2ComponentStore { +public: + /** + * Returns the name of this component or component interface object. + * This is a unique name for this component or component interface 'class'; however, multiple + * instances of this component SHALL have the same name. + * + * This method MUST be supported in any state. This call does not change the state nor the + * internal states of the component. + * + * This method MUST be "non-blocking" and return within 1ms. + * + * \return the name of this component or component interface object. + * \retval an empty string if there was not enough memory to allocate the actual name. + */ + virtual C2String getName() const = 0; + + /** + * Creates a component. + * + * This method SHALL return within 100ms. + * + * \param name name of the component to create + * \param component shared pointer where the created component is stored. Cleared on + * failure and updated on success. + * + * \retval C2_OK the component was created successfully + * \retval C2_TIMED_OUT could not create the component within the time limit (unexpected) + * \retval C2_CORRUPTED some unknown error prevented the creation of the component (unexpected) + * + * \retval C2_NOT_FOUND no such component + * \retval C2_NO_MEMORY not enough memory to create the component + */ + virtual c2_status_t createComponent( + C2String name, std::shared_ptr<C2Component>* const component) = 0; + + /** + * Creates a component interface. + * + * This method SHALL return within 100ms. + * + * \param name name of the component interface to create + * \param interface shared pointer where the created interface is stored + * + * \retval C2_OK the component interface was created successfully + * \retval C2_TIMED_OUT could not create the component interface within the time limit + * (unexpected) + * \retval C2_CORRUPTED some unknown error prevented the creation of the component interface + * (unexpected) + * + * \retval C2_NOT_FOUND no such component interface + * \retval C2_NO_MEMORY not enough memory to create the component interface + * + * \todo Do we need an interface, or could this just be a component that is never started? + */ + virtual c2_status_t createInterface( + C2String name, std::shared_ptr<C2ComponentInterface>* const interface) = 0; + + /** + * Returns the list of components supported by this component store. + * + * This method MUST return within 500ms. + * + * \retval vector of component information. + */ + virtual std::vector<std::shared_ptr<const C2Component::Traits>> listComponents() = 0; + + // -------------------------------------- UTILITY METHODS -------------------------------------- + + // on-demand buffer layout conversion (swizzling) + // + virtual c2_status_t copyBuffer( + std::shared_ptr<C2GraphicBuffer> src, std::shared_ptr<C2GraphicBuffer> dst) = 0; + + // -------------------------------------- CONFIGURATION API ----------------------------------- + // e.g. for global settings (system-wide stride, etc.) + + /** + * Queries a set of system-wide parameters. + * Querying is performed at best effort: the store SHALL query all supported parameters and + * skip unsupported ones, or heap allocated parameters that could not be allocated. Any errors + * are communicated in the return value. Additionally, preallocated (e.g. stack) parameters that + * could not be queried are invalidated. Parameters to be allocated on the heap are omitted from + * the result. + * + * \note Parameter values do not depend on the order of query. + * + * This method may be momentarily blocking, but MUST return within 5ms. + * + * \param stackParams a list of params queried. These are initialized specific to each + * setting; e.g. size and index are set and rest of the members are + * cleared. + * NOTE: Flexible settings that are of incorrect size will be invalidated. + * \param heapParamIndices a vector of param indices for params to be queried and returned on the + * heap. These parameters will be returned in heapParams. Unsupported param + * indices will be ignored. + * \param heapParams a list of params where to which the supported heap parameters will be + * appended in the order they appear in heapParamIndices. + * + * \retval C2_OK all parameters could be queried + * \retval C2_BAD_INDEX all supported parameters could be queried, but some parameters were not + * supported + * \retval C2_NO_MEMORY could not allocate memory for a supported parameter + * \retval C2_CORRUPTED some unknown error prevented the querying of the parameters + * (unexpected) + */ + virtual c2_status_t query_sm( + const std::vector<C2Param*> &stackParams, + const std::vector<C2Param::Index> &heapParamIndices, + std::vector<std::unique_ptr<C2Param>>* const heapParams) const = 0; + + /** + * Sets a set of system-wide parameters. + * + * \note There are no settable system-wide parameters defined thus far, but may be added in the + * future. + * + * Tuning is performed at best effort: the store SHALL update all supported configuration at + * best effort (unless configured otherwise) and skip unsupported ones. Any errors are + * communicated in the return value and in |failures|. + * + * \note Parameter tuning DOES depend on the order of the tuning parameters. E.g. some parameter + * update may allow some subsequent parameter update. + * + * This method may be momentarily blocking, but MUST return within 5ms. + * + * \param params a list of parameter updates. These will be updated to the actual + * parameter values after the updates (this is because tuning is performed + * at best effort). + * \todo params that could not be updated are not marked here, so are + * confusing - are they "existing" values or intended to be configured + * values? + * \param failures a list of parameter failures + * + * \retval C2_OK all parameters could be updated successfully + * \retval C2_BAD_INDEX all supported parameters could be updated successfully, but some + * parameters were not supported + * \retval C2_BAD_VALUE some supported parameters could not be updated successfully because + * they contained unsupported values. These are returned in |failures|. + * \retval C2_NO_MEMORY some supported parameters could not be updated successfully because + * they contained unsupported values, but could not allocate a failure + * object for them. + * \retval C2_CORRUPTED some unknown error prevented the update of the parameters + * (unexpected) + */ + virtual c2_status_t config_sm( + const std::vector<C2Param*> ¶ms, + std::vector<std::unique_ptr<C2SettingResult>>* const failures) = 0; + + // REFLECTION MECHANISM (USED FOR EXTENSION) + // ============================================================================================= + + /** + * Returns the parameter reflector. + * + * This is used to describe parameter fields. This is shared for all components created by + * this component store. + * + * This method MUST be "non-blocking" and return within 1ms. + * + * \return a shared parameter reflector object. + */ + virtual std::shared_ptr<C2ParamReflector> getParamReflector() const = 0; + + /** + * Returns the set of supported parameters. + * + * This method MUST be "non-blocking" and return within 1ms. + * + * \param[out] params a vector of supported parameters will be appended to this vector. + * + * \retval C2_OK the operation completed successfully. + * \retval C2_NO_MEMORY not enough memory to complete this method. + */ + virtual c2_status_t querySupportedParams_nb( + std::vector<std::shared_ptr<C2ParamDescriptor>> * const params) const = 0; + + /** + * Retrieves the supported values for the queried fields. + * + * Client SHALL set the parameter-field specifier and the type of supported values query (e.g. + * currently supported values, or potential supported values) in fields. + * Upon return the store SHALL fill in the supported values for the fields listed as well + * as a status for each field. Store shall process all fields queried even if some queries + * fail. + * + * This method may be momentarily blocking, but MUST return within 5ms. + * + * \param[in out] fields a vector of fields descriptor structures. + * + * \retval C2_OK the operation completed successfully. + * \retval C2_BAD_INDEX at least one field was not recognized as a component store field + */ + virtual c2_status_t querySupportedValues_sm( + std::vector<C2FieldSupportedValuesQuery> &fields) const = 0; + + virtual ~C2ComponentStore() = default; +}; + +// ================================================================================================ + +/// @} + +#endif // C2COMPONENT_H_
diff --git a/media/codec2/core/include/C2Config.h b/media/codec2/core/include/C2Config.h new file mode 100644 index 0000000..3820f90 --- /dev/null +++ b/media/codec2/core/include/C2Config.h
@@ -0,0 +1,2149 @@ +/* + * Copyright (C) 2016 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 C2CONFIG_H_ +#define C2CONFIG_H_ + +#include <C2.h> +#include <C2Component.h> +#include <C2Enum.h> +#include <C2ParamDef.h> + +/// \defgroup config Component configuration +/// @{ + +/** + * Enumerated boolean. + */ +C2ENUM(c2_bool_t, uint32_t, + C2_FALSE, ///< true + C2_TRUE, ///< false +) + +typedef C2SimpleValueStruct<c2_bool_t> C2BoolValue; + +typedef C2SimpleValueStruct<C2EasyEnum<c2_bool_t>> C2EasyBoolValue; + +/** + * Enumerated set tri-state. + * + * Used for optional configurations to distinguish between values set by the client, + * default values set by the component, or unset values. + */ +C2ENUM(c2_set_t, uint32_t, + C2_UNSET, // parameter is unset and has no value + C2_SET, // parameter is/has been set by the client + C2_DEFAULT, // parameter has not been set by the client, but is set by the component +) + +/** Enumerations used by configuration parameters */ +struct C2Config { + enum aac_packaging_t : uint32_t; ///< AAC packaging (RAW vs ADTS) + enum aac_sbr_mode_t : uint32_t; ///< AAC SBR mode + enum api_feature_t : uint64_t; ///< API features + enum api_level_t : uint32_t; ///< API level + enum bitrate_mode_t : uint32_t; ///< bitrate control mode + enum drc_compression_mode_t : int32_t; ///< DRC compression mode + enum drc_effect_type_t : int32_t; ///< DRC effect type + enum intra_refresh_mode_t : uint32_t; ///< intra refresh modes + enum level_t : uint32_t; ///< coding level + enum ordinal_key_t : uint32_t; ///< work ordering keys + enum pcm_encoding_t : uint32_t; ///< PCM encoding + enum picture_type_t : uint32_t; ///< picture types + enum platform_feature_t : uint64_t; ///< platform features + enum platform_level_t : uint32_t; ///< platform level + enum prepend_header_mode_t : uint32_t; ///< prepend header operational modes + enum profile_t : uint32_t; ///< coding profile + enum scaling_method_t : uint32_t; ///< scaling methods + enum scan_order_t : uint32_t; ///< scan orders + enum secure_mode_t : uint32_t; ///< secure/protected modes + enum supplemental_info_t : uint32_t; ///< supplemental information types + enum tiling_mode_t : uint32_t; ///< tiling modes +}; + +namespace { + +enum C2ParamIndexKind : C2Param::type_index_t { + C2_PARAM_INDEX_INVALID = 0x0, ///< do not use + C2_PARAM_INDEX_STRUCT_START = 0x1, ///< struct only indices + C2_PARAM_INDEX_PARAM_START = 0x800, ///< regular parameters + C2_PARAM_INDEX_CODER_PARAM_START = 0x1000, ///< en/transcoder parameters + C2_PARAM_INDEX_PICTURE_PARAM_START = 0x1800, ///< image/video parameters + C2_PARAM_INDEX_VIDEO_PARAM_START = 0x2000, ///< video parameters + C2_PARAM_INDEX_IMAGE_PARAM_START = 0x2800, ///< image parameters + C2_PARAM_INDEX_AUDIO_PARAM_START = 0x3000, ///< image parameters + C2_PARAM_INDEX_PLATFORM_START = 0x4000, ///< platform-defined parameters + + /* =================================== structure indices =================================== */ + + kParamIndexColorXy = C2_PARAM_INDEX_STRUCT_START, + kParamIndexMasteringDisplayColorVolume, + kParamIndexChromaOffset, + kParamIndexGopLayer, + + /* =================================== parameter indices =================================== */ + + kParamIndexApiLevel = C2_PARAM_INDEX_PARAM_START, + kParamIndexApiFeatures, + + /* ------------------------------------ all components ------------------------------------ */ + + /* generic component characteristics */ + kParamIndexName, + kParamIndexAliases, + kParamIndexKind, + kParamIndexDomain, + kParamIndexAttributes, + kParamIndexTimeStretch, + + /* coding characteristics */ + kParamIndexProfileLevel, + kParamIndexInitData, + kParamIndexSupplementalData, + kParamIndexSubscribedSupplementalData, + + /* pipeline characteristics */ + kParamIndexMediaType, + __kParamIndexRESERVED_0, + kParamIndexDelay, + kParamIndexMaxReferenceAge, + kParamIndexMaxReferenceCount, + kParamIndexReorderBufferDepth, + kParamIndexReorderKey, + kParamIndexStreamCount, + kParamIndexSubscribedParamIndices, + kParamIndexSuggestedBufferCount, + kParamIndexBatchSize, + kParamIndexCurrentWork, + kParamIndexLastWorkQueued, + + /* memory allocation */ + kParamIndexAllocators, + kParamIndexBlockPools, + kParamIndexBufferType, + kParamIndexUsage, + kParamIndexOutOfMemory, + kParamIndexMaxBufferSize, + + /* misc. state */ + kParamIndexTripped, + kParamIndexConfigCounter, + + /* resources */ + kParamIndexResourcesNeeded, + kParamIndexResourcesReserved, + kParamIndexOperatingRate, + kParamIndexRealTimePriority, + + /* protected content */ + kParamIndexSecureMode, + + // deprecated + kParamIndexDelayRequest = kParamIndexDelay | C2Param::CoreIndex::IS_REQUEST_FLAG, + + /* ------------------------------------ (trans/en)coders ------------------------------------ */ + + kParamIndexBitrate = C2_PARAM_INDEX_CODER_PARAM_START, + kParamIndexBitrateMode, + kParamIndexQuality, + kParamIndexComplexity, + kParamIndexPrependHeaderMode, + + /* --------------------------------- image/video components --------------------------------- */ + + kParamIndexPictureSize = C2_PARAM_INDEX_PICTURE_PARAM_START, + kParamIndexCropRect, + kParamIndexPixelFormat, + kParamIndexRotation, + kParamIndexPixelAspectRatio, + kParamIndexScaledPictureSize, + kParamIndexScaledCropRect, + kParamIndexScalingMethod, + kParamIndexColorInfo, + kParamIndexColorAspects, + kParamIndexHdrStaticMetadata, + kParamIndexDefaultColorAspects, + + kParamIndexBlockSize, + kParamIndexBlockCount, + kParamIndexBlockRate, + + kParamIndexPictureTypeMask, + kParamIndexPictureType, + kParamIndexHdr10PlusMetadata, + + /* ------------------------------------ video components ------------------------------------ */ + + kParamIndexFrameRate = C2_PARAM_INDEX_VIDEO_PARAM_START, + kParamIndexMaxBitrate, + kParamIndexMaxFrameRate, + kParamIndexMaxPictureSize, + kParamIndexGop, + kParamIndexSyncFrameInterval, + kParamIndexRequestSyncFrame, + kParamIndexTemporalLayering, + kParamIndexLayerIndex, + kParamIndexLayerCount, + kParamIndexIntraRefresh, + + /* ------------------------------------ image components ------------------------------------ */ + + kParamIndexTileLayout = C2_PARAM_INDEX_IMAGE_PARAM_START, + kParamIndexTileHandling, + + /* ------------------------------------ audio components ------------------------------------ */ + + kParamIndexSampleRate = C2_PARAM_INDEX_AUDIO_PARAM_START, + kParamIndexChannelCount, + kParamIndexPcmEncoding, + kParamIndexAacPackaging, + kParamIndexMaxChannelCount, + kParamIndexAacSbrMode, // aac encode, enum + kParamIndexDrcEncodedTargetLevel, // drc, float (dBFS) + kParamIndexDrcTargetReferenceLevel, // drc, float (dBFS) + kParamIndexDrcCompression, // drc, enum + kParamIndexDrcBoostFactor, // drc, float (0-1) + kParamIndexDrcAttenuationFactor, // drc, float (0-1) + kParamIndexDrcEffectType, // drc, enum + + /* ============================== platform-defined parameters ============================== */ + + kParamIndexPlatformLevel = C2_PARAM_INDEX_PLATFORM_START, // all, u32 + kParamIndexPlatformFeatures, // all, u64 mask + kParamIndexStoreIonUsage, // store, struct + kParamIndexAspectsToDataSpace, // store, struct + kParamIndexFlexiblePixelFormatDescriptor, // store, struct + kParamIndexFlexiblePixelFormatDescriptors, // store, struct[] + kParamIndexDataSpaceToAspects, // store, struct + kParamIndexDataSpace, // u32 + kParamIndexSurfaceScaling, // u32 + + // input surface + kParamIndexInputSurfaceEos, // input-surface, eos + kParamIndexTimedControl, // struct + kParamIndexStartAt, // input-surface, struct + kParamIndexSuspendAt, // input-surface, struct + kParamIndexResumeAt, // input-surface, struct + kParamIndexStopAt, // input-surface, struct + kParamIndexTimeOffset, // input-surface, struct + kParamIndexMinFrameRate, // input-surface, float + kParamIndexTimestampGapAdjustment, // input-surface, struct + + kParamIndexSurfaceAllocator, // u32 +}; + +} + +/** + * Codec 2.0 parameter types follow the following naming convention: + * + * C2<group><domain><index><type> + * + * E.g. C2StreamPictureSizeInfo: group="" domain="Stream" index="PictureSize" type="Info". + * Group is somewhat arbitrary, but denotes kind of objects the parameter is defined. + * At this point we use Component and Store to distinguish basic component/store parameters. + * + * Parameter keys are named C2_PARAMKEY_[<group>_]<domain>_<index> as type is not expected + * to distinguish parameters. E.g. a component could change the type of the parameter and it + * is not expected users would need to change the key. + */ + +/* ----------------------------------------- API level ----------------------------------------- */ + +enum C2Config::api_level_t : uint32_t { + API_L0_1 = 0, ///< support for API level 0.1 +}; + +// read-only +typedef C2GlobalParam<C2Setting, C2SimpleValueStruct<C2Config::api_level_t>, kParamIndexApiLevel> + C2ApiLevelSetting; +constexpr char C2_PARAMKEY_API_LEVEL[] = "api.level"; + +enum C2Config::api_feature_t : uint64_t { + API_REFLECTION = (1U << 0), ///< ability to list supported parameters + API_VALUES = (1U << 1), ///< ability to list supported values for each parameter + API_CURRENT_VALUES = (1U << 2), ///< ability to list currently supported values for each parameter + API_DEPENDENCY = (1U << 3), ///< have a defined parameter dependency + + API_STREAMS = (1ULL << 32), ///< supporting variable number of streams + + API_TUNNELING = (1ULL << 48), ///< tunneling API +}; + +// read-only +typedef C2GlobalParam<C2Setting, C2SimpleValueStruct<C2Config::api_feature_t>, kParamIndexApiFeatures> + C2ApiFeaturesSetting; +constexpr char C2_PARAMKEY_API_FEATURES[] = "api.features"; + +/* ----------------------------- generic component characteristics ----------------------------- */ + +/** + * The name of the component. + * + * This must contain only alphanumeric characters or dot '.', hyphen '-', plus '+', or + * underline '_'. The name of each component must be unique. + * + * For Android: Component names must start with 'c2.' followed by the company name or abbreviation + * and another dot, e.g. 'c2.android.'. Use of lowercase is preferred but not required. + */ +// read-only +typedef C2GlobalParam<C2Setting, C2StringValue, kParamIndexName> C2ComponentNameSetting; +constexpr char C2_PARAMKEY_COMPONENT_NAME[] = "component.name"; + +/** + * Alternate names (aliases) of the component. + * + * This is a comma ',' separated list of alternate component names. Unlike component names that + * must be unique, multiple components can have the same alias. + */ +// read-only +typedef C2GlobalParam<C2Setting, C2StringValue, kParamIndexAliases> C2ComponentAliasesSetting; +constexpr char C2_PARAMKEY_COMPONENT_ALIASES[] = "component.aliases"; + +/** + * Component kind. + */ +// read-only +typedef C2GlobalParam<C2Setting, C2SimpleValueStruct<C2Component::kind_t>, kParamIndexKind> + C2ComponentKindSetting; +constexpr char C2_PARAMKEY_COMPONENT_KIND[] = "component.kind"; + +/** + * Component domain. + */ +// read-only +typedef C2GlobalParam<C2Setting, C2SimpleValueStruct<C2Component::domain_t>, kParamIndexDomain> + C2ComponentDomainSetting; +constexpr char C2_PARAMKEY_COMPONENT_DOMAIN[] = "component.domain"; + +/** + * Component attributes. + * + * These are a set of flags provided by the component characterizing its processing algorithm. + */ +C2ENUM(C2Component::attrib_t, uint64_t, + ATTRIB_IS_TEMPORAL = 1u << 0, ///< component input ordering matters for processing +) + +// read-only +typedef C2GlobalParam<C2Setting, C2SimpleValueStruct<C2Component::attrib_t>, kParamIndexAttributes> + C2ComponentAttributesSetting; +constexpr char C2_PARAMKEY_COMPONENT_ATTRIBUTES[] = "component.attributes"; + +/** + * Time stretching. + * + * This is the ratio between the rate of the input timestamp, and the rate of the output timestamp. + * E.g. if this is 4.0, for every 1 seconds of input timestamp difference, the output shall differ + * by 4 seconds. + */ +typedef C2GlobalParam<C2Tuning, C2FloatValue, kParamIndexTimeStretch> C2ComponentTimeStretchTuning; +constexpr char C2_PARAMKEY_TIME_STRETCH[] = "algo.time-stretch"; + +/* ----------------------------------- coding characteristics ----------------------------------- */ + +/** + * Profile and level. + * + * Profile determines the tools used by the component. + * Level determines the level of resources used by the component. + */ + +namespace { + +enum : uint32_t { + _C2_PL_MP2V_BASE = 0x1000, + _C2_PL_AAC_BASE = 0x2000, + _C2_PL_H263_BASE = 0x3000, + _C2_PL_MP4V_BASE = 0x4000, + _C2_PL_AVC_BASE = 0x5000, + _C2_PL_HEVC_BASE = 0x6000, + _C2_PL_VP9_BASE = 0x7000, + _C2_PL_DV_BASE = 0x8000, + _C2_PL_AV1_BASE = 0x9000, + + C2_PROFILE_LEVEL_VENDOR_START = 0x70000000, +}; + +} + +enum C2Config::profile_t : uint32_t { + PROFILE_UNUSED = 0, ///< profile is not used by this media type + + // AAC (MPEG-2 Part 7 and MPEG-4 Part 3) profiles + PROFILE_AAC_LC = _C2_PL_AAC_BASE, ///< AAC Low-Complexity + PROFILE_AAC_MAIN, ///< AAC Main + PROFILE_AAC_SSR, ///< AAC Scalable Sampling Rate + PROFILE_AAC_LTP, ///< AAC Long Term Prediction + PROFILE_AAC_HE, ///< AAC High-Efficiency + PROFILE_AAC_SCALABLE, ///< AAC Scalable + PROFILE_AAC_ER_LC, ///< AAC Error Resilient Low-Complexity + PROFILE_AAC_ER_SCALABLE, ///< AAC Error Resilient Scalable + PROFILE_AAC_LD, ///< AAC Low Delay + PROFILE_AAC_HE_PS, ///< AAC High-Efficiency Parametric Stereo + PROFILE_AAC_ELD, ///< AAC Enhanced Low Delay + PROFILE_AAC_XHE, ///< AAC Extended High-Efficiency + + // MPEG-2 Video profiles + PROFILE_MP2V_SIMPLE = _C2_PL_MP2V_BASE, ///< MPEG-2 Video (H.262) Simple + PROFILE_MP2V_MAIN, ///< MPEG-2 Video (H.262) Main + PROFILE_MP2V_SNR_SCALABLE, ///< MPEG-2 Video (H.262) SNR Scalable + PROFILE_MP2V_SPATIALLY_SCALABLE, ///< MPEG-2 Video (H.262) Spatially Scalable + PROFILE_MP2V_HIGH, ///< MPEG-2 Video (H.262) High + PROFILE_MP2V_422, ///< MPEG-2 Video (H.262) 4:2:2 + PROFILE_MP2V_MULTIVIEW, ///< MPEG-2 Video (H.262) Multi-view + + // H.263 profiles + PROFILE_H263_BASELINE = _C2_PL_H263_BASE, ///< H.263 Baseline (Profile 0) + PROFILE_H263_H320, ///< H.263 H.320 Coding Efficiency Version 2 Backward-Compatibility (Profile 1) + PROFILE_H263_V1BC, ///< H.263 Version 1 Backward-Compatibility (Profile 2) + PROFILE_H263_ISWV2, ///< H.263 Version 2 Interactive and Streaming Wireless (Profile 3) + PROFILE_H263_ISWV3, ///< H.263 Version 3 Interactive and Streaming Wireless (Profile 4) + PROFILE_H263_HIGH_COMPRESSION, ///< H.263 Conversational High Compression (Profile 5) + PROFILE_H263_INTERNET, ///< H.263 Conversational Internet (Profile 6) + PROFILE_H263_INTERLACE, ///< H.263 Conversational Interlace (Profile 7) + PROFILE_H263_HIGH_LATENCY, ///< H.263 High Latency (Profile 8) + + // MPEG-4 Part 2 (Video) Natural Visual Profiles + PROFILE_MP4V_SIMPLE, ///< MPEG-4 Video Simple + PROFILE_MP4V_SIMPLE_SCALABLE, ///< MPEG-4 Video Simple Scalable + PROFILE_MP4V_CORE, ///< MPEG-4 Video Core + PROFILE_MP4V_MAIN, ///< MPEG-4 Video Main + PROFILE_MP4V_NBIT, ///< MPEG-4 Video N-Bit + PROFILE_MP4V_ARTS, ///< MPEG-4 Video Advanced Realtime Simple + PROFILE_MP4V_CORE_SCALABLE, ///< MPEG-4 Video Core Scalable + PROFILE_MP4V_ACE, ///< MPEG-4 Video Advanced Coding Efficiency + PROFILE_MP4V_ADVANCED_CORE, ///< MPEG-4 Video Advanced Core + PROFILE_MP4V_SIMPLE_STUDIO, ///< MPEG-4 Video Simple Studio + PROFILE_MP4V_CORE_STUDIO, ///< MPEG-4 Video Core Studio + PROFILE_MP4V_ADVANCED_SIMPLE, ///< MPEG-4 Video Advanced Simple + PROFILE_MP4V_FGS, ///< MPEG-4 Video Fine Granularity Scalable + + // AVC / MPEG-4 Part 10 (H.264) profiles + PROFILE_AVC_BASELINE = _C2_PL_AVC_BASE, ///< AVC (H.264) Baseline + PROFILE_AVC_CONSTRAINED_BASELINE, ///< AVC (H.264) Constrained Baseline + PROFILE_AVC_MAIN, ///< AVC (H.264) Main + PROFILE_AVC_EXTENDED, ///< AVC (H.264) Extended + PROFILE_AVC_HIGH, ///< AVC (H.264) High + PROFILE_AVC_PROGRESSIVE_HIGH, ///< AVC (H.264) Progressive High + PROFILE_AVC_CONSTRAINED_HIGH, ///< AVC (H.264) Constrained High + PROFILE_AVC_HIGH_10, ///< AVC (H.264) High 10 + PROFILE_AVC_PROGRESSIVE_HIGH_10, ///< AVC (H.264) Progressive High 10 + PROFILE_AVC_HIGH_422, ///< AVC (H.264) High 4:2:2 + PROFILE_AVC_HIGH_444_PREDICTIVE, ///< AVC (H.264) High 4:4:4 Predictive + PROFILE_AVC_HIGH_10_INTRA, ///< AVC (H.264) High 10 Intra + PROFILE_AVC_HIGH_422_INTRA, ///< AVC (H.264) High 4:2:2 Intra + PROFILE_AVC_HIGH_444_INTRA, ///< AVC (H.264) High 4:4:4 Intra + PROFILE_AVC_CAVLC_444_INTRA, ///< AVC (H.264) CAVLC 4:4:4 Intra + PROFILE_AVC_SCALABLE_BASELINE = _C2_PL_AVC_BASE + 0x100, ///< AVC (H.264) Scalable Baseline + PROFILE_AVC_SCALABLE_CONSTRAINED_BASELINE, ///< AVC (H.264) Scalable Constrained Baseline + PROFILE_AVC_SCALABLE_HIGH, ///< AVC (H.264) Scalable High + PROFILE_AVC_SCALABLE_CONSTRAINED_HIGH, ///< AVC (H.264) Scalable Constrained High + PROFILE_AVC_SCALABLE_HIGH_INTRA, ///< AVC (H.264) Scalable High Intra + PROFILE_AVC_MULTIVIEW_HIGH = _C2_PL_AVC_BASE + 0x200, ///< AVC (H.264) Multiview High + PROFILE_AVC_STEREO_HIGH, ///< AVC (H.264) Stereo High + PROFILE_AVC_MFC_HIGH, ///< AVC (H.264) MFC High + PROFILE_AVC_MULTIVIEW_DEPTH_HIGH = _C2_PL_AVC_BASE + 0x300, ///< AVC (H.264) Multiview Depth High + PROFILE_AVC_MFC_DEPTH_HIGH, ///< AVC (H.264) MFC Depth High + PROFILE_AVC_ENHANCED_MULTIVIEW_DEPTH_HIGH = _C2_PL_AVC_BASE + 0x400, ///< AVC (H.264) Enhanced Multiview Depth High + + // HEVC profiles + PROFILE_HEVC_MAIN = _C2_PL_HEVC_BASE, ///< HEVC (H.265) Main + PROFILE_HEVC_MAIN_10, ///< HEVC (H.265) Main 10 + PROFILE_HEVC_MAIN_STILL, ///< HEVC (H.265) Main Still Picture + PROFILE_HEVC_MONO = _C2_PL_HEVC_BASE + 0x100, ///< HEVC (H.265) Monochrome + PROFILE_HEVC_MONO_12, ///< HEVC (H.265) Monochrome 12 + PROFILE_HEVC_MONO_16, ///< HEVC (H.265) Monochrome 16 + PROFILE_HEVC_MAIN_12, ///< HEVC (H.265) Main 12 + PROFILE_HEVC_MAIN_422_10, ///< HEVC (H.265) Main 4:2:2 10 + PROFILE_HEVC_MAIN_422_12, ///< HEVC (H.265) Main 4:2:2 12 + PROFILE_HEVC_MAIN_444, ///< HEVC (H.265) Main 4:4:4 + PROFILE_HEVC_MAIN_444_10, ///< HEVC (H.265) Main 4:4:4 10 + PROFILE_HEVC_MAIN_444_12, ///< HEVC (H.265) Main 4:4:4 12 + PROFILE_HEVC_MAIN_INTRA, ///< HEVC (H.265) Main Intra + PROFILE_HEVC_MAIN_10_INTRA, ///< HEVC (H.265) Main 10 Intra + PROFILE_HEVC_MAIN_12_INTRA, ///< HEVC (H.265) Main 12 Intra + PROFILE_HEVC_MAIN_422_10_INTRA, ///< HEVC (H.265) Main 4:2:2 10 Intra + PROFILE_HEVC_MAIN_422_12_INTRA, ///< HEVC (H.265) Main 4:2:2 12 Intra + PROFILE_HEVC_MAIN_444_INTRA, ///< HEVC (H.265) Main 4:4:4 Intra + PROFILE_HEVC_MAIN_444_10_INTRA, ///< HEVC (H.265) Main 4:4:4 10 Intra + PROFILE_HEVC_MAIN_444_12_INTRA, ///< HEVC (H.265) Main 4:4:4 12 Intra + PROFILE_HEVC_MAIN_444_16_INTRA, ///< HEVC (H.265) Main 4:4:4 16 Intra + PROFILE_HEVC_MAIN_444_STILL, ///< HEVC (H.265) Main 4:4:4 Still Picture + PROFILE_HEVC_MAIN_444_16_STILL, ///< HEVC (H.265) Main 4:4:4 16 Still Picture + PROFILE_HEVC_HIGH_444 = _C2_PL_HEVC_BASE + 0x200, ///< HEVC (H.265) High Throughput 4:4:4 + PROFILE_HEVC_HIGH_444_10, ///< HEVC (H.265) High Throughput 4:4:4 10 + PROFILE_HEVC_HIGH_444_14, ///< HEVC (H.265) High Throughput 4:4:4 14 + PROFILE_HEVC_HIGH_444_16_INTRA, ///< HEVC (H.265) High Throughput 4:4:4 16 Intra + PROFILE_HEVC_SX_MAIN = _C2_PL_HEVC_BASE + 0x300, ///< HEVC (H.265) Screen-Extended Main + PROFILE_HEVC_SX_MAIN_10, ///< HEVC (H.265) Screen-Extended Main 10 + PROFILE_HEVC_SX_MAIN_444, ///< HEVC (H.265) Screen-Extended Main 4:4:4 + PROFILE_HEVC_SX_MAIN_444_10, ///< HEVC (H.265) Screen-Extended Main 4:4:4 10 + PROFILE_HEVC_SX_HIGH_444, ///< HEVC (H.265) Screen-Extended High Throughput 4:4:4 + PROFILE_HEVC_SX_HIGH_444_10, ///< HEVC (H.265) Screen-Extended High Throughput 4:4:4 10 + PROFILE_HEVC_SX_HIGH_444_14, ///< HEVC (H.265) Screen-Extended High Throughput 4:4:4 14 + PROFILE_HEVC_MULTIVIEW_MAIN = _C2_PL_HEVC_BASE + 0x400, ///< HEVC (H.265) Multiview Main + PROFILE_HEVC_SCALABLE_MAIN = _C2_PL_HEVC_BASE + 0x500, ///< HEVC (H.265) Scalable Main + PROFILE_HEVC_SCALABLE_MAIN_10, ///< HEVC (H.265) Scalable Main 10 + PROFILE_HEVC_SCALABLE_MONO = _C2_PL_HEVC_BASE + 0x600, ///< HEVC (H.265) Scalable Monochrome + PROFILE_HEVC_SCALABLE_MONO_12, ///< HEVC (H.265) Scalable Monochrome 12 + PROFILE_HEVC_SCALABLE_MONO_16, ///< HEVC (H.265) Scalable Monochrome 16 + PROFILE_HEVC_SCALABLE_MAIN_444, ///< HEVC (H.265) Scalable Main 4:4:4 + PROFILE_HEVC_3D_MAIN = _C2_PL_HEVC_BASE + 0x700, ///< HEVC (H.265) 3D Main + + // VP9 profiles + PROFILE_VP9_0 = _C2_PL_VP9_BASE, ///< VP9 Profile 0 (4:2:0) + PROFILE_VP9_1, ///< VP9 Profile 1 (4:2:2 or 4:4:4) + PROFILE_VP9_2, ///< VP9 Profile 2 (4:2:0, 10 or 12 bit) + PROFILE_VP9_3, ///< VP9 Profile 3 (4:2:2 or 4:4:4, 10 or 12 bit) + + // Dolby Vision profiles + PROFILE_DV_AV_PER = _C2_PL_DV_BASE + 0, ///< Dolby Vision dvav.per profile (deprecated) + PROFILE_DV_AV_PEN, ///< Dolby Vision dvav.pen profile (deprecated) + PROFILE_DV_HE_DER, ///< Dolby Vision dvhe.der profile (deprecated) + PROFILE_DV_HE_DEN, ///< Dolby Vision dvhe.den profile (deprecated) + PROFILE_DV_HE_04 = _C2_PL_DV_BASE + 4, ///< Dolby Vision dvhe.04 profile + PROFILE_DV_HE_05 = _C2_PL_DV_BASE + 5, ///< Dolby Vision dvhe.05 profile + PROFILE_DV_HE_DTH, ///< Dolby Vision dvhe.dth profile (deprecated) + PROFILE_DV_HE_07 = _C2_PL_DV_BASE + 7, ///< Dolby Vision dvhe.07 profile + PROFILE_DV_HE_08 = _C2_PL_DV_BASE + 8, ///< Dolby Vision dvhe.08 profile + PROFILE_DV_AV_09 = _C2_PL_DV_BASE + 9, ///< Dolby Vision dvav.09 profile + + // AV1 profiles + PROFILE_AV1_0 = _C2_PL_AV1_BASE, ///< AV1 Profile 0 (4:2:0, 8 to 10 bit) + PROFILE_AV1_1, ///< AV1 Profile 1 (8 to 10 bit) + PROFILE_AV1_2, ///< AV1 Profile 2 (8 to 12 bit) +}; + +enum C2Config::level_t : uint32_t { + LEVEL_UNUSED = 0, ///< level is not used by this media type + + // MPEG-2 Video levels + LEVEL_MP2V_LOW = _C2_PL_MP2V_BASE, ///< MPEG-2 Video (H.262) Low Level + LEVEL_MP2V_MAIN, ///< MPEG-2 Video (H.262) Main Level + LEVEL_MP2V_HIGH_1440, ///< MPEG-2 Video (H.262) High 1440 Level + LEVEL_MP2V_HIGH, ///< MPEG-2 Video (H.262) High Level + LEVEL_MP2V_HIGHP, ///< MPEG-2 Video (H.262) HighP Level + + // H.263 levels + LEVEL_H263_10 = _C2_PL_H263_BASE, ///< H.263 Level 10 + LEVEL_H263_20, ///< H.263 Level 20 + LEVEL_H263_30, ///< H.263 Level 30 + LEVEL_H263_40, ///< H.263 Level 40 + LEVEL_H263_45, ///< H.263 Level 45 + LEVEL_H263_50, ///< H.263 Level 50 + LEVEL_H263_60, ///< H.263 Level 60 + LEVEL_H263_70, ///< H.263 Level 70 + + // MPEG-4 Part 2 (Video) levels + LEVEL_MP4V_0 = _C2_PL_MP4V_BASE, ///< MPEG-4 Video Level 0 + LEVEL_MP4V_0B, ///< MPEG-4 Video Level 0b + LEVEL_MP4V_1, ///< MPEG-4 Video Level 1 + LEVEL_MP4V_2, ///< MPEG-4 Video Level 2 + LEVEL_MP4V_3, ///< MPEG-4 Video Level 3 + LEVEL_MP4V_3B, ///< MPEG-4 Video Level 3b + LEVEL_MP4V_4, ///< MPEG-4 Video Level 4 + LEVEL_MP4V_4A, ///< MPEG-4 Video Level 4a + LEVEL_MP4V_5, ///< MPEG-4 Video Level 5 + LEVEL_MP4V_6, ///< MPEG-4 Video Level 6 + + // AVC / MPEG-4 Part 10 (H.264) levels + LEVEL_AVC_1 = _C2_PL_AVC_BASE, ///< AVC (H.264) Level 1 + LEVEL_AVC_1B, ///< AVC (H.264) Level 1b + LEVEL_AVC_1_1, ///< AVC (H.264) Level 1.1 + LEVEL_AVC_1_2, ///< AVC (H.264) Level 1.2 + LEVEL_AVC_1_3, ///< AVC (H.264) Level 1.3 + LEVEL_AVC_2, ///< AVC (H.264) Level 2 + LEVEL_AVC_2_1, ///< AVC (H.264) Level 2.1 + LEVEL_AVC_2_2, ///< AVC (H.264) Level 2.2 + LEVEL_AVC_3, ///< AVC (H.264) Level 3 + LEVEL_AVC_3_1, ///< AVC (H.264) Level 3.1 + LEVEL_AVC_3_2, ///< AVC (H.264) Level 3.2 + LEVEL_AVC_4, ///< AVC (H.264) Level 4 + LEVEL_AVC_4_1, ///< AVC (H.264) Level 4.1 + LEVEL_AVC_4_2, ///< AVC (H.264) Level 4.2 + LEVEL_AVC_5, ///< AVC (H.264) Level 5 + LEVEL_AVC_5_1, ///< AVC (H.264) Level 5.1 + LEVEL_AVC_5_2, ///< AVC (H.264) Level 5.2 + LEVEL_AVC_6, ///< AVC (H.264) Level 6 + LEVEL_AVC_6_1, ///< AVC (H.264) Level 6.1 + LEVEL_AVC_6_2, ///< AVC (H.264) Level 6.2 + + // HEVC (H.265) tiers and levels + LEVEL_HEVC_MAIN_1 = _C2_PL_HEVC_BASE, ///< HEVC (H.265) Main Tier Level 1 + LEVEL_HEVC_MAIN_2, ///< HEVC (H.265) Main Tier Level 2 + LEVEL_HEVC_MAIN_2_1, ///< HEVC (H.265) Main Tier Level 2.1 + LEVEL_HEVC_MAIN_3, ///< HEVC (H.265) Main Tier Level 3 + LEVEL_HEVC_MAIN_3_1, ///< HEVC (H.265) Main Tier Level 3.1 + LEVEL_HEVC_MAIN_4, ///< HEVC (H.265) Main Tier Level 4 + LEVEL_HEVC_MAIN_4_1, ///< HEVC (H.265) Main Tier Level 4.1 + LEVEL_HEVC_MAIN_5, ///< HEVC (H.265) Main Tier Level 5 + LEVEL_HEVC_MAIN_5_1, ///< HEVC (H.265) Main Tier Level 5.1 + LEVEL_HEVC_MAIN_5_2, ///< HEVC (H.265) Main Tier Level 5.2 + LEVEL_HEVC_MAIN_6, ///< HEVC (H.265) Main Tier Level 6 + LEVEL_HEVC_MAIN_6_1, ///< HEVC (H.265) Main Tier Level 6.1 + LEVEL_HEVC_MAIN_6_2, ///< HEVC (H.265) Main Tier Level 6.2 + + LEVEL_HEVC_HIGH_4 = _C2_PL_HEVC_BASE + 0x100, ///< HEVC (H.265) High Tier Level 4 + LEVEL_HEVC_HIGH_4_1, ///< HEVC (H.265) High Tier Level 4.1 + LEVEL_HEVC_HIGH_5, ///< HEVC (H.265) High Tier Level 5 + LEVEL_HEVC_HIGH_5_1, ///< HEVC (H.265) High Tier Level 5.1 + LEVEL_HEVC_HIGH_5_2, ///< HEVC (H.265) High Tier Level 5.2 + LEVEL_HEVC_HIGH_6, ///< HEVC (H.265) High Tier Level 6 + LEVEL_HEVC_HIGH_6_1, ///< HEVC (H.265) High Tier Level 6.1 + LEVEL_HEVC_HIGH_6_2, ///< HEVC (H.265) High Tier Level 6.2 + + // VP9 levels + LEVEL_VP9_1 = _C2_PL_VP9_BASE, ///< VP9 Level 1 + LEVEL_VP9_1_1, ///< VP9 Level 1.1 + LEVEL_VP9_2, ///< VP9 Level 2 + LEVEL_VP9_2_1, ///< VP9 Level 2.1 + LEVEL_VP9_3, ///< VP9 Level 3 + LEVEL_VP9_3_1, ///< VP9 Level 3.1 + LEVEL_VP9_4, ///< VP9 Level 4 + LEVEL_VP9_4_1, ///< VP9 Level 4.1 + LEVEL_VP9_5, ///< VP9 Level 5 + LEVEL_VP9_5_1, ///< VP9 Level 5.1 + LEVEL_VP9_5_2, ///< VP9 Level 5.2 + LEVEL_VP9_6, ///< VP9 Level 6 + LEVEL_VP9_6_1, ///< VP9 Level 6.1 + LEVEL_VP9_6_2, ///< VP9 Level 6.2 + + // Dolby Vision levels + LEVEL_DV_MAIN_HD_24 = _C2_PL_DV_BASE, ///< Dolby Vision main tier hd24 + LEVEL_DV_MAIN_HD_30, ///< Dolby Vision main tier hd30 + LEVEL_DV_MAIN_FHD_24, ///< Dolby Vision main tier fhd24 + LEVEL_DV_MAIN_FHD_30, ///< Dolby Vision main tier fhd30 + LEVEL_DV_MAIN_FHD_60, ///< Dolby Vision main tier fhd60 + LEVEL_DV_MAIN_UHD_24, ///< Dolby Vision main tier uhd24 + LEVEL_DV_MAIN_UHD_30, ///< Dolby Vision main tier uhd30 + LEVEL_DV_MAIN_UHD_48, ///< Dolby Vision main tier uhd48 + LEVEL_DV_MAIN_UHD_60, ///< Dolby Vision main tier uhd60 + + LEVEL_DV_HIGH_HD_24 = _C2_PL_DV_BASE + 0x100, ///< Dolby Vision high tier hd24 + LEVEL_DV_HIGH_HD_30, ///< Dolby Vision high tier hd30 + LEVEL_DV_HIGH_FHD_24, ///< Dolby Vision high tier fhd24 + LEVEL_DV_HIGH_FHD_30, ///< Dolby Vision high tier fhd30 + LEVEL_DV_HIGH_FHD_60, ///< Dolby Vision high tier fhd60 + LEVEL_DV_HIGH_UHD_24, ///< Dolby Vision high tier uhd24 + LEVEL_DV_HIGH_UHD_30, ///< Dolby Vision high tier uhd30 + LEVEL_DV_HIGH_UHD_48, ///< Dolby Vision high tier uhd48 + LEVEL_DV_HIGH_UHD_60, ///< Dolby Vision high tier uhd60 + + // AV1 levels + LEVEL_AV1_2 = _C2_PL_AV1_BASE , ///< AV1 Level 2 + LEVEL_AV1_2_1, ///< AV1 Level 2.1 + LEVEL_AV1_2_2, ///< AV1 Level 2.2 + LEVEL_AV1_2_3, ///< AV1 Level 2.3 + LEVEL_AV1_3, ///< AV1 Level 3 + LEVEL_AV1_3_1, ///< AV1 Level 3.1 + LEVEL_AV1_3_2, ///< AV1 Level 3.2 + LEVEL_AV1_3_3, ///< AV1 Level 3.3 + LEVEL_AV1_4, ///< AV1 Level 4 + LEVEL_AV1_4_1, ///< AV1 Level 4.1 + LEVEL_AV1_4_2, ///< AV1 Level 4.2 + LEVEL_AV1_4_3, ///< AV1 Level 4.3 + LEVEL_AV1_5, ///< AV1 Level 5 + LEVEL_AV1_5_1, ///< AV1 Level 5.1 + LEVEL_AV1_5_2, ///< AV1 Level 5.2 + LEVEL_AV1_5_3, ///< AV1 Level 5.3 + LEVEL_AV1_6, ///< AV1 Level 6 + LEVEL_AV1_6_1, ///< AV1 Level 6.1 + LEVEL_AV1_6_2, ///< AV1 Level 6.2 + LEVEL_AV1_6_3, ///< AV1 Level 6.3 + LEVEL_AV1_7, ///< AV1 Level 7 + LEVEL_AV1_7_1, ///< AV1 Level 7.1 + LEVEL_AV1_7_2, ///< AV1 Level 7.2 + LEVEL_AV1_7_3, ///< AV1 Level 7.3 +}; + +struct C2ProfileLevelStruct { + C2Config::profile_t profile; ///< coding profile + C2Config::level_t level; ///< coding level + + C2ProfileLevelStruct( + C2Config::profile_t profile_ = C2Config::PROFILE_UNUSED, + C2Config::level_t level_ = C2Config::LEVEL_UNUSED) + : profile(profile_), level(level_) { } + + DEFINE_AND_DESCRIBE_C2STRUCT(ProfileLevel) + C2FIELD(profile, "profile") + C2FIELD(level, "level") +}; + +// TODO: may need to make this explicit (have .set member) +typedef C2StreamParam<C2Info, C2ProfileLevelStruct, kParamIndexProfileLevel> + C2StreamProfileLevelInfo; +constexpr char C2_PARAMKEY_PROFILE_LEVEL[] = "coded.pl"; + +/** + * Codec-specific initialization data. + * + * This is initialization data for the codec. + * + * For AVC/HEVC, these are the concatenated SPS/PPS/VPS NALs. + * + * TODO: define for other codecs. + */ +typedef C2StreamParam<C2Info, C2BlobValue, kParamIndexInitData> C2StreamInitDataInfo; +constexpr char C2_PARAMKEY_INIT_DATA[] = "coded.init-data"; + +/** + * Supplemental Data. + * + * This is coding-specific supplemental informational data, e.g. SEI for AVC/HEVC. + * This structure is not a configuration so it does not have a parameter key. + * This structure shall be returned in the configuration update, and can be repeated as needed + * in the same update. + */ +C2ENUM(C2Config::supplemental_info_t, uint32_t, + INFO_NONE = 0, + + INFO_PREFIX_SEI_UNIT = 0x10000, ///< prefix SEI payload types add this flag + INFO_SUFFIX_SEI_UNIT = 0x20000, ///< suffix SEI payload types add this flag + + INFO_SEI_USER_DATA = INFO_PREFIX_SEI_UNIT | 4, ///< closed-captioning data (ITU-T T35) + INFO_SEI_MDCV = INFO_PREFIX_SEI_UNIT | 137, ///< mastering display color volume + INFO_SET_USER_DATA_SFX = INFO_SUFFIX_SEI_UNIT | 4, ///< closed-captioning data (ITU-T T35) + + INFO_VENDOR_START = 0x70000000 +) + +struct C2SupplementalDataStruct { + C2SupplementalDataStruct() + : type_(INFO_NONE) { } + + C2SupplementalDataStruct( + size_t flexCount, C2Config::supplemental_info_t type, std::vector<uint8_t> data_) + : type_(type) { + memcpy(data, &data_[0], c2_min(data_.size(), flexCount)); + } + + C2Config::supplemental_info_t type_; + uint8_t data[]; + + DEFINE_AND_DESCRIBE_FLEX_C2STRUCT(SupplementalData, data) + C2FIELD(type_, "type") + C2FIELD(data, "data") +}; +typedef C2StreamParam<C2Info, C2SupplementalDataStruct, kParamIndexSupplementalData> + C2StreamSupplementalDataInfo; + +/** + * Supplemental Data Subscription + */ +typedef C2StreamParam<C2Tuning, C2SimpleArrayStruct<C2Config::supplemental_info_t>, + kParamIndexSubscribedSupplementalData> + C2StreamSubscribedSupplementalDataTuning; +constexpr char C2_PARAMKEY_SUBSCRIBED_SUPPLEMENTAL_DATA[] = "output.subscribed-supplemental"; + +/* ---------------------------------- pipeline characteristics ---------------------------------- */ + +/** + * Media-type. + * + * This is defined for both port and stream, but stream media type may be a subtype of the + * port media type. + */ +typedef C2PortParam<C2Setting, C2StringValue, kParamIndexMediaType> C2PortMediaTypeSetting; +constexpr char C2_PARAMKEY_INPUT_MEDIA_TYPE[] = "input.media-type"; +constexpr char C2_PARAMKEY_OUTPUT_MEDIA_TYPE[] = "output.media-type"; + +typedef C2StreamParam<C2Setting, C2StringValue, kParamIndexMediaType> C2StreamMediaTypeSetting; + +/** + * Pipeline delays. + * + * Input delay is the number of additional input frames requested by the component to process + * an input frame. + * + * Output delay is the number of additional output frames that need to be generated before an + * output can be released by the component. + * + * Pipeline delay is the number of additional frames that are processed at one time by the + * component. + * + * As these may vary from frame to frame, the number is the maximum required value. E.g. if + * input delay is 0, the component is expected to consume each frame queued even if no further + * frames are queued. Similarly, if input delay is 1, as long as there are always exactly 2 + * outstanding input frames queued to the component, it shall produce output. + */ + +typedef C2PortParam<C2Tuning, C2Uint32Value, kParamIndexDelay | C2Param::CoreIndex::IS_REQUEST_FLAG> + C2PortRequestedDelayTuning; +constexpr char C2_PARAMKEY_INPUT_DELAY_REQUEST[] = "input.delay"; // deprecated +constexpr char C2_PARAMKEY_OUTPUT_DELAY_REQUEST[] = "output.delay"; // deprecated + +typedef C2GlobalParam<C2Tuning, C2Uint32Value, + kParamIndexDelay | C2Param::CoreIndex::IS_REQUEST_FLAG> + C2RequestedPipelineDelayTuning; +constexpr char C2_PARAMKEY_PIPELINE_DELAY_REQUEST[] = "algo.delay"; // deprecated + +// read-only +typedef C2PortParam<C2Tuning, C2Uint32Value, kParamIndexDelay> C2PortDelayTuning; +typedef C2PortDelayTuning C2PortActualDelayTuning; // deprecated +constexpr char C2_PARAMKEY_INPUT_DELAY[] = "input.delay"; +constexpr char C2_PARAMKEY_OUTPUT_DELAY[] = "output.delay"; + +// read-only +typedef C2GlobalParam<C2Tuning, C2Uint32Value, kParamIndexDelay> C2PipelineDelayTuning; +typedef C2PipelineDelayTuning C2ActualPipelineDelayTuning; // deprecated +constexpr char C2_PARAMKEY_PIPELINE_DELAY[] = "algo.delay"; + +/** + * Reference characteristics. + * + * The component may hold onto input and output buffers even after completing the corresponding + * work item. + * + * Max reference age is the longest number of additional frame processing that a component may + * hold onto a buffer for. Max reference count is the number of buffers that a component may + * hold onto at the same time at the worst case. These numbers assume single frame per buffers. + * + * Use max-uint32 if there is no limit for the max age or count. + */ +typedef C2StreamParam<C2Tuning, C2Uint32Value, kParamIndexMaxReferenceAge> + C2StreamMaxReferenceAgeTuning; +constexpr char C2_PARAMKEY_INPUT_MAX_REFERENCE_AGE[] = "input.reference.max-age"; +constexpr char C2_PARAMKEY_OUTPUT_MAX_REFERENCE_AGE[] = "output.reference.max-age"; + +typedef C2StreamParam<C2Tuning, C2Uint32Value, kParamIndexMaxReferenceCount> + C2StreamMaxReferenceCountTuning; +constexpr char C2_PARAMKEY_INPUT_MAX_REFERENCE_COUNT[] = "input.reference.max-count"; +constexpr char C2_PARAMKEY_OUTPUT_MAX_REFERENCE_COUNT[] = "output.reference.max-count"; + +/** + * Output reordering. + * + * The size of the window to use for output buffer reordering. 0 is interpreted as 1. + */ +// output only +typedef C2PortParam<C2Tuning, C2Uint32Value, kParamIndexReorderBufferDepth> + C2PortReorderBufferDepthTuning; +constexpr char C2_PARAMKEY_OUTPUT_REORDER_DEPTH[] = "output.reorder.depth"; + +C2ENUM(C2Config::ordinal_key_t, uint32_t, + ORDINAL, + TIMESTAMP, + CUSTOM) + +// read-only, output only +typedef C2PortParam<C2Setting, C2SimpleValueStruct<C2Config::ordinal_key_t>, kParamIndexReorderKey> + C2PortReorderKeySetting; +constexpr char C2_PARAMKEY_OUTPUT_REORDER_KEY[] = "output.reorder.key"; + +/** + * Stream count. + */ +// private +typedef C2PortParam<C2Tuning, C2Uint32Value, kParamIndexStreamCount> C2PortStreamCountTuning; +constexpr char C2_PARAMKEY_INPUT_STREAM_COUNT[] = "input.stream-count"; +constexpr char C2_PARAMKEY_OUTPUT_STREAM_COUNT[] = "output.stream-count"; + +/** + * Config update subscription. + */ +// private +typedef C2GlobalParam<C2Tuning, C2Uint32Array, kParamIndexSubscribedParamIndices> + C2SubscribedParamIndicesTuning; +constexpr char C2_PARAMKEY_SUBSCRIBED_PARAM_INDICES[] = "output.subscribed-indices"; + +/** + * Suggested buffer (C2Frame) count. This is a suggestion by the component for the number of + * input and output frames allocated for the component's use in the buffer pools. + * + * Component shall set the acceptable range of buffers allocated for it. E.g. client shall + * allocate at least the minimum required value. + */ +// read-only +typedef C2PortParam<C2Tuning, C2Uint64Array, kParamIndexSuggestedBufferCount> + C2PortSuggestedBufferCountTuning; +constexpr char C2_PARAMKEY_INPUT_SUGGESTED_BUFFER_COUNT[] = "input.buffers.pool-size"; +constexpr char C2_PARAMKEY_OUTPUT_SUGGESTED_BUFFER_COUNT[] = "output.buffers.pool-size"; + +/** + * Input/output batching. + * + * For input, component requests that client batches work in batches of specified size. For output, + * client requests that the component batches work completion in given batch size. + * Value 0 means don't care. + */ +typedef C2PortParam<C2Tuning, C2Uint64Array, kParamIndexBatchSize> C2PortBatchSizeTuning; +constexpr char C2_PARAMKEY_INPUT_BATCH_SIZE[] = "input.buffers.batch-size"; +constexpr char C2_PARAMKEY_OUTPUT_BATCH_SIZE[] = "output.buffers.batch-size"; + +/** + * Current & last work ordinals. + * + * input port: last work queued to component. + * output port: last work completed by component. + * global: current work. + */ +typedef C2PortParam<C2Tuning, C2WorkOrdinalStruct, kParamIndexLastWorkQueued> C2LastWorkQueuedTuning; +typedef C2GlobalParam<C2Tuning, C2WorkOrdinalStruct, kParamIndexCurrentWork> C2CurrentWorkTuning; + + +/* ------------------------------------- memory allocation ------------------------------------- */ + +/** + * Allocators to use. + * + * These are requested by the component. + * + * If none specified, client will use the default allocator ID based on the component domain and + * kind. + */ +typedef C2PortParam<C2Tuning, C2SimpleArrayStruct<C2Allocator::id_t>, kParamIndexAllocators> + C2PortAllocatorsTuning; +constexpr char C2_PARAMKEY_INPUT_ALLOCATORS[] = "input.buffers.allocator-ids"; +constexpr char C2_PARAMKEY_OUTPUT_ALLOCATORS[] = "output.buffers.allocator-ids"; + +typedef C2GlobalParam<C2Tuning, C2SimpleArrayStruct<C2Allocator::id_t>, kParamIndexAllocators> + C2PrivateAllocatorsTuning; +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 + * component. This is not used for the input port. + */ +typedef C2PortParam<C2Tuning, C2SimpleArrayStruct<C2BlockPool::local_id_t>, kParamIndexBlockPools> + C2PortBlockPoolsTuning; +constexpr char C2_PARAMKEY_OUTPUT_BLOCK_POOLS[] = "output.buffers.pool-ids"; + +typedef C2GlobalParam<C2Tuning, C2SimpleArrayStruct<C2BlockPool::local_id_t>, kParamIndexBlockPools> + C2PrivateBlockPoolsTuning; +constexpr char C2_PARAMKEY_PRIVATE_BLOCK_POOLS[] = "algo.buffers.pool-ids"; + +/** + * The max number of private allocations at any one time by the component. + * (This is an array with a corresponding value for each private allocator) + */ +typedef C2GlobalParam<C2Tuning, C2Uint32Array, kParamIndexMaxReferenceCount> + C2MaxPrivateBufferCountTuning; +constexpr char C2_PARAMKEY_MAX_PRIVATE_BUFFER_COUNT[] = "algo.buffers.max-count"; + +/** + * Buffer type + * + * This is provided by the component for the client to allocate the proper buffer type for the + * input port, and can be provided by the client to control the buffer type for the output. + */ +// private +typedef C2StreamParam<C2Setting, C2SimpleValueStruct<C2EasyEnum<C2BufferData::type_t>>, + kParamIndexBufferType> + C2StreamBufferTypeSetting; +constexpr char C2_PARAMKEY_INPUT_STREAM_BUFFER_TYPE[] = "input.buffers.type"; +constexpr char C2_PARAMKEY_OUTPUT_STREAM_BUFFER_TYPE[] = "output.buffers.type"; + +/** + * Memory usage. + * + * Suggested by component for input and negotiated between client and component for output. + */ +typedef C2StreamParam<C2Tuning, C2Uint64Value, kParamIndexUsage> C2StreamUsageTuning; +constexpr char C2_PARAMKEY_INPUT_STREAM_USAGE[] = "input.buffers.usage"; +constexpr char C2_PARAMKEY_OUTPUT_STREAM_USAGE[] = "output.buffers.usage"; + +/** + * Picture (video or image frame) size. + */ +struct C2PictureSizeStruct { + inline C2PictureSizeStruct() + : width(0), height(0) { } + + inline C2PictureSizeStruct(uint32_t width_, uint32_t height_) + : width(width_), height(height_) { } + + uint32_t width; ///< video width + uint32_t height; ///< video height + + DEFINE_AND_DESCRIBE_C2STRUCT(PictureSize) + C2FIELD(width, "width") + C2FIELD(height, "height") +}; + +/** + * Out of memory signaling + * + * This is a configuration for the client to mark that it cannot allocate necessary private and/ + * or output buffers to continue operation, and to signal the failing configuration. + */ +struct C2OutOfMemoryStruct { + C2BlockPool::local_id_t pool; ///< pool ID that failed the allocation + uint64_t usage; ///< memory usage used + C2PictureSizeStruct planar; ///< buffer dimensions to be allocated if 2D + uint32_t format; ///< pixel format to be used if 2D + uint32_t capacity; ///< buffer capacity to be allocated if 1D + c2_bool_t outOfMemory; ///< true if component is out of memory + + DEFINE_AND_DESCRIBE_C2STRUCT(OutOfMemory) + C2FIELD(pool, "pool") + C2FIELD(usage, "usage") + C2FIELD(planar, "planar") + C2FIELD(format, "format") + C2FIELD(capacity, "capacity") + C2FIELD(outOfMemory, "out-of-memory") +}; + +typedef C2GlobalParam<C2Tuning, C2OutOfMemoryStruct, kParamIndexOutOfMemory> C2OutOfMemoryTuning; +constexpr char C2_PARAMKEY_OUT_OF_MEMORY[] = "algo.oom"; + +/** + * Max buffer size + * + * This is a hint provided by the component for the maximum buffer size expected on a stream for the + * current configuration on its input and output streams. This is communicated to clients so they + * can preallocate input buffers, or configure downstream components that require a maximum size on + * their buffers. + * + * Read-only. Required to be provided by components on all compressed streams. + */ +typedef C2StreamParam<C2Info, C2Uint32Value, kParamIndexMaxBufferSize> C2StreamMaxBufferSizeInfo; +constexpr char C2_PARAMKEY_INPUT_MAX_BUFFER_SIZE[] = "input.buffers.max-size"; +constexpr char C2_PARAMKEY_OUTPUT_MAX_BUFFER_SIZE[] = "output.buffers.max-size"; + +/* ---------------------------------------- misc. state ---------------------------------------- */ + +/** + * Tripped state, + * + * This state exists to be able to provide reasoning for a tripped state during normal + * interface operations, as well as to allow client to trip the component on demand. + */ +typedef C2GlobalParam<C2Tuning, C2BoolValue, kParamIndexTripped> + C2TrippedTuning; +constexpr char C2_PARAMKEY_TRIPPED[] = "algo.tripped"; + +/** + * Configuration counters. + * + * Configurations are tracked using three counters. The input counter is incremented exactly + * once with each work accepted by the component. The output counter is incremented exactly + * once with each work completed by the component (in the order of work completion). The + * global counter is incremented exactly once during to each config() call. These counters + * shall be read-only. + * + * TODO: these should be counters. + */ +typedef C2PortParam<C2Tuning, C2Uint64Value, kParamIndexConfigCounter> C2PortConfigCounterTuning; +typedef C2GlobalParam<C2Tuning, C2Uint64Value, kParamIndexConfigCounter> C2ConfigCounterTuning; +constexpr char C2_PARAMKEY_INPUT_COUNTER[] = "input.buffers.counter"; +constexpr char C2_PARAMKEY_OUTPUT_COUNTER[] = "output.buffers.counter"; +constexpr char C2_PARAMKEY_CONFIG_COUNTER[] = "algo.config.counter"; + +/* ----------------------------------------- resources ----------------------------------------- */ + +/** + * Resources needed and resources reserved for current configuration. + * + * Resources are tracked as a vector of positive numbers. Available resources are defined by + * the vendor. + * + * By default, no resources are reserved for a component. If resource reservation is successful, + * the component shall be able to use those resources exclusively. If however, the component is + * not using all of the reserved resources, those may be shared with other components. + * + * TODO: define some of the resources. + */ +typedef C2GlobalParam<C2Tuning, C2Uint64Array, kParamIndexResourcesNeeded> C2ResourcesNeededTuning; +typedef C2GlobalParam<C2Tuning, C2Uint64Array, kParamIndexResourcesReserved> + C2ResourcesReservedTuning; +constexpr char C2_PARAMKEY_RESOURCES_NEEDED[] = "resources.needed"; +constexpr char C2_PARAMKEY_RESOURCES_RESERVED[] = "resources.reserved"; + +/** + * Operating rate. + * + * Operating rate is the expected rate of work through the component. Negative values is + * invalid. + * + * TODO: this could distinguish set value + */ +typedef C2GlobalParam<C2Tuning, C2FloatValue, kParamIndexOperatingRate> C2OperatingRateTuning; +constexpr char C2_PARAMKEY_OPERATING_RATE[] = "algo.rate"; + +/** + * Realtime / operating point. + * + * Priority value defines the operating point for the component. Operating points are defined by + * the vendor. Priority value of 0 means that the client requires operation at the given operating + * rate. Priority values -1 and below define operating points in decreasing performance. In this + * case client expects best effort without exceeding the specific operating point. This allows + * client to run components deeper in the background by using larger priority values. In these + * cases operating rate is a hint for the maximum rate that the client anticipates. + * + * Operating rate and priority are used in tandem. E.g. if there are components that run at a + * higher operating point (priority) it will make more resources available for components at + * a lower operating point, so operating rate can be used to gate those components. + * + * Positive priority values are not defined at the moment and shall be treated equivalent to 0. + */ +typedef C2GlobalParam<C2Tuning, C2Int32Value, kParamIndexRealTimePriority> + C2RealTimePriorityTuning; +constexpr char C2_PARAMKEY_PRIORITY[] = "algo.priority"; + +/* ------------------------------------- protected content ------------------------------------- */ + +/** + * Secure mode. + */ +C2ENUM(C2Config::secure_mode_t, uint32_t, + SM_UNPROTECTED, ///< no content protection + SM_READ_PROTECTED, ///< input and output buffers shall be protected from reading +) + +typedef C2GlobalParam<C2Tuning, C2SimpleValueStruct<C2Config::secure_mode_t>, kParamIndexSecureMode> + C2SecureModeTuning; +constexpr char C2_PARAMKEY_SECURE_MODE[] = "algo.secure-mode"; + +/* ===================================== ENCODER COMPONENTS ===================================== */ + +/** + * Bitrate + */ +typedef C2StreamParam<C2Info, C2Uint32Value, kParamIndexBitrate> C2StreamBitrateInfo; +constexpr char C2_PARAMKEY_BITRATE[] = "coded.bitrate"; + +/** + * Bitrate mode. + * + * TODO: refine this with bitrate ranges and suggested window + */ +C2ENUM(C2Config::bitrate_mode_t, uint32_t, + BITRATE_CONST_SKIP_ALLOWED = 0, ///< constant bitrate, frame skipping allowed + BITRATE_CONST = 1, ///< constant bitrate, keep all frames + BITRATE_VARIABLE_SKIP_ALLOWED = 2, ///< bitrate can vary, frame skipping allowed + BITRATE_VARIABLE = 3, ///< bitrate can vary, keep all frames + BITRATE_IGNORE = 7, ///< bitrate can be exceeded at will to achieve + ///< quality or other settings + + // bitrate modes are composed of the following flags + BITRATE_FLAG_KEEP_ALL_FRAMES = 1, + BITRATE_FLAG_CAN_VARY = 2, + BITRATE_FLAG_CAN_EXCEED = 4, +) + +typedef C2StreamParam<C2Tuning, C2SimpleValueStruct<C2Config::bitrate_mode_t>, + kParamIndexBitrateMode> + C2StreamBitrateModeTuning; +constexpr char C2_PARAMKEY_BITRATE_MODE[] = "algo.bitrate-mode"; + +/** + * Quality. + * + * This is defined by each component, the higher the better the output quality at the expense of + * less compression efficiency. This setting is defined for the output streams in case the + * component can support varying quality on each stream, or as an output port tuning in case the + * quality is global to all streams. + */ +typedef C2StreamParam<C2Tuning, C2Uint32Value, kParamIndexQuality> C2StreamQualityTuning; +typedef C2PortParam<C2Tuning, C2Uint32Value, kParamIndexQuality> C2QualityTuning; +constexpr char C2_PARAMKEY_QUALITY[] = "algo.quality"; + +/** + * Complexity. + * + * This is defined by each component, this higher the value, the more resources the component + * will use to produce better quality at the same compression efficiency or better compression + * efficiency at the same quality. This setting is defined for the output streams in case the + * component can support varying complexity on each stream, or as an output port tuning in case the + * quality is global to all streams + */ +typedef C2StreamParam<C2Tuning, C2Uint32Value, kParamIndexComplexity> C2StreamComplexityTuning; +typedef C2PortParam<C2Tuning, C2Uint32Value, kParamIndexComplexity> C2ComplexityTuning; +constexpr char C2_PARAMKEY_COMPLEXITY[] = "algo.complexity"; + +/** + * Header (init-data) handling around sync frames. + */ +C2ENUM(C2Config::prepend_header_mode_t, uint32_t, + /** + * don't prepend header. Signal header only through C2StreamInitDataInfo. + */ + PREPEND_HEADER_TO_NONE, + + /** + * prepend header before the first output frame and thereafter before the next sync frame + * if it changes. + */ + PREPEND_HEADER_ON_CHANGE, + + /** + * prepend header before every sync frame. + */ + PREPEND_HEADER_TO_ALL_SYNC, +) + +typedef C2GlobalParam<C2Setting, C2SimpleValueStruct<C2Config::prepend_header_mode_t>, + kParamIndexPrependHeaderMode> + C2PrependHeaderModeSetting; +constexpr char C2_PARAMKEY_PREPEND_HEADER_MODE[] = "output.buffers.prepend-header"; + +/* =================================== IMAGE/VIDEO COMPONENTS =================================== */ + +/* + * Order of transformation is: + * + * crop => (scaling => scaled-crop) => sample-aspect-ratio => flip => rotation + */ + +/** + * Picture (image- and video frame) size. + * + * This is used for the output of the video decoder, and the input of the video encoder. + */ +typedef C2StreamParam<C2Info, C2PictureSizeStruct, kParamIndexPictureSize> C2StreamPictureSizeInfo; +constexpr char C2_PARAMKEY_PICTURE_SIZE[] = "raw.size"; + +/** + * Crop rectangle. + */ +struct C2RectStruct : C2Rect { + C2RectStruct() = default; + C2RectStruct(const C2Rect &rect) : C2Rect(rect) { } + + bool operator==(const C2RectStruct &) = delete; + bool operator!=(const C2RectStruct &) = delete; + + DEFINE_AND_DESCRIBE_BASE_C2STRUCT(Rect) + C2FIELD(width, "width") + C2FIELD(height, "height") + C2FIELD(left, "left") + C2FIELD(top, "top") +}; + +typedef C2StreamParam<C2Info, C2RectStruct, kParamIndexCropRect> C2StreamCropRectInfo; +constexpr char C2_PARAMKEY_CROP_RECT[] = "raw.crop"; +constexpr char C2_PARAMKEY_CODED_CROP_RECT[] = "coded.crop"; + +/** + * Pixel format. + */ +// TODO: define some + +typedef C2StreamParam<C2Info, C2Uint32Value, kParamIndexPixelFormat> C2StreamPixelFormatInfo; +constexpr char C2_PARAMKEY_PIXEL_FORMAT[] = "raw.pixel-format"; + +/** + * Extended rotation information also incorporating a flip. + * + * Rotation is counter clock-wise. + */ +struct C2RotationStruct { + C2RotationStruct(int32_t rotation = 0) + : flip(0), value(rotation) { } + + int32_t flip; ///< horizontal flip (left-right flip applied prior to rotation) + int32_t value; ///< rotation in degrees counter clockwise + + DEFINE_AND_DESCRIBE_BASE_C2STRUCT(Rotation) + C2FIELD(flip, "flip") + C2FIELD(value, "value") +}; + +typedef C2StreamParam<C2Info, C2RotationStruct, kParamIndexRotation> C2StreamRotationInfo; +constexpr char C2_PARAMKEY_ROTATION[] = "raw.rotation"; +constexpr char C2_PARAMKEY_VUI_ROTATION[] = "coded.vui.rotation"; + +/** + * Pixel (sample) aspect ratio. + */ +typedef C2StreamParam<C2Info, C2PictureSizeStruct, kParamIndexPixelAspectRatio> + C2StreamPixelAspectRatioInfo; +constexpr char C2_PARAMKEY_PIXEL_ASPECT_RATIO[] = "raw.sar"; +constexpr char C2_PARAMKEY_VUI_PIXEL_ASPECT_RATIO[] = "coded.vui.sar"; + +/** + * In-line scaling. + * + * Components can optionally support scaling of raw image/video frames. Or scaling only a + * portion of raw image/video frames (scaled-crop). + */ + +C2ENUM(C2Config::scaling_method_t, uint32_t, + SCALING_ARBITRARY, ///< arbitrary, unspecified +) + +typedef C2StreamParam<C2Tuning, C2SimpleValueStruct<C2Config::scaling_method_t>, + kParamIndexScalingMethod> + C2StreamScalingMethodTuning; +constexpr char C2_PARAMKEY_SCALING_MODE[] = "raw.scaling-method"; + +typedef C2StreamParam<C2Tuning, C2PictureSizeStruct, kParamIndexScaledPictureSize> + C2StreamScaledPictureSizeTuning; +constexpr char C2_PARAMKEY_SCALED_PICTURE_SIZE[] = "raw.scaled-size"; + +typedef C2StreamParam<C2Tuning, C2RectStruct, kParamIndexScaledCropRect> + C2StreamScaledCropRectTuning; +constexpr char C2_PARAMKEY_SCALED_CROP_RECT[] = "raw.scaled-crop"; + +/* ------------------------------------- color information ------------------------------------- */ + +/** + * Color Info + * + * Chroma location can vary for top and bottom fields, so use an array, that can have 0 to 2 + * values. Empty array is used for non YUV formats. + */ + +struct C2Color { + enum matrix_t : uint32_t; ///< matrix coefficient (YUV <=> RGB) + enum plane_layout_t : uint32_t; ///< plane layout for flexible formats + enum primaries_t : uint32_t; ///< color primaries and white point + enum range_t : uint32_t; ///< range of color component values + enum subsampling_t : uint32_t; ///< chroma subsampling + enum transfer_t : uint32_t; ///< transfer function +}; + +/// Chroma subsampling +C2ENUM(C2Color::subsampling_t, uint32_t, + MONOCHROME, ///< there are no Cr nor Cb planes + MONOCHROME_ALPHA, ///< there are no Cr nor Cb planes, but there is an alpha plane + RGB, ///< RGB + RGBA, ///< RGBA + YUV_420, ///< Cr and Cb planes are subsampled by 2 both horizontally and vertically + YUV_422, ///< Cr and Cb planes are subsampled horizontally + YUV_444, ///< Cr and Cb planes are not subsampled + YUVA_444, ///< Cr and Cb planes are not subsampled, there is an alpha plane +) + +struct C2ChromaOffsetStruct { + // chroma offsets defined by ITU + constexpr static C2ChromaOffsetStruct ITU_YUV_444() { return { 0.0f, 0.0f }; } + constexpr static C2ChromaOffsetStruct ITU_YUV_422() { return { 0.0f, 0.0f }; } + constexpr static C2ChromaOffsetStruct ITU_YUV_420_0() { return { 0.0f, 0.5f }; } + constexpr static C2ChromaOffsetStruct ITU_YUV_420_1() { return { 0.5f, 0.5f }; } + constexpr static C2ChromaOffsetStruct ITU_YUV_420_2() { return { 0.0f, 0.0f }; } + constexpr static C2ChromaOffsetStruct ITU_YUV_420_3() { return { 0.5f, 0.0f }; } + constexpr static C2ChromaOffsetStruct ITU_YUV_420_4() { return { 0.0f, 1.0f }; } + constexpr static C2ChromaOffsetStruct ITU_YUV_420_5() { return { 0.5f, 1.0f }; } + + float x; ///< x offset in pixels (towards right) + float y; ///< y offset in pixels (towards down) + + DEFINE_AND_DESCRIBE_C2STRUCT(ChromaOffset) + C2FIELD(x, "x") + C2FIELD(y, "y") +}; + +struct C2ColorInfoStruct { + C2ColorInfoStruct() + : bitDepth(8), subsampling(C2Color::YUV_420) { } + + uint32_t bitDepth; + C2Color::subsampling_t subsampling; + C2ChromaOffsetStruct locations[]; // max 2 elements + + C2ColorInfoStruct( + size_t /* flexCount */, uint32_t bitDepth_, C2Color::subsampling_t subsampling_) + : bitDepth(bitDepth_), subsampling(subsampling_) { } + + C2ColorInfoStruct( + size_t flexCount, uint32_t bitDepth_, C2Color::subsampling_t subsampling_, + std::initializer_list<C2ChromaOffsetStruct> locations_) + : bitDepth(bitDepth_), subsampling(subsampling_) { + size_t ix = 0; + for (const C2ChromaOffsetStruct &location : locations_) { + if (ix == flexCount) { + break; + } + locations[ix] = location; + ++ix; + } + } + + DEFINE_AND_DESCRIBE_FLEX_C2STRUCT(ColorInfo, locations) + C2FIELD(bitDepth, "bit-depth") + C2FIELD(subsampling, "subsampling") + C2FIELD(locations, "locations") +}; + +typedef C2StreamParam<C2Info, C2ColorInfoStruct, kParamIndexColorInfo> C2StreamColorInfo; +constexpr char C2_PARAMKEY_COLOR_INFO[] = "raw.color-format"; +constexpr char C2_PARAMKEY_CODED_COLOR_INFO[] = "coded.color-format"; + +/** + * Color Aspects + */ + +/* The meaning of the following enumerators is as described in ITU-T H.273. */ + +/// Range +C2ENUM(C2Color::range_t, uint32_t, + RANGE_UNSPECIFIED, ///< range is unspecified + RANGE_FULL, ///< full range + RANGE_LIMITED, ///< limited range + + RANGE_VENDOR_START = 0x80, ///< vendor-specific range values start here + RANGE_OTHER = 0XFF ///< max value, reserved for undefined values +) + +/// Color primaries +C2ENUM(C2Color::primaries_t, uint32_t, + PRIMARIES_UNSPECIFIED, ///< primaries are unspecified + PRIMARIES_BT709, ///< Rec.ITU-R BT.709-6 or equivalent + PRIMARIES_BT470_M, ///< Rec.ITU-R BT.470-6 System M or equivalent + PRIMARIES_BT601_625, ///< Rec.ITU-R BT.601-6 625 or equivalent + PRIMARIES_BT601_525, ///< Rec.ITU-R BT.601-6 525 or equivalent + PRIMARIES_GENERIC_FILM, ///< Generic Film + PRIMARIES_BT2020, ///< Rec.ITU-R BT.2020 or equivalent + PRIMARIES_RP431, ///< SMPTE RP 431-2 or equivalent + PRIMARIES_EG432, ///< SMPTE EG 432-1 or equivalent + PRIMARIES_EBU3213, ///< EBU Tech.3213-E or equivalent + /// + PRIMARIES_VENDOR_START = 0x80, ///< vendor-specific primaries values start here + PRIMARIES_OTHER = 0xff ///< max value, reserved for undefined values +) + +/// Transfer function +C2ENUM(C2Color::transfer_t, uint32_t, + TRANSFER_UNSPECIFIED, ///< transfer is unspecified + TRANSFER_LINEAR, ///< Linear transfer characteristics + TRANSFER_SRGB, ///< sRGB or equivalent + TRANSFER_170M, ///< SMPTE 170M or equivalent (e.g. BT.601/709/2020) + TRANSFER_GAMMA22, ///< Assumed display gamma 2.2 + TRANSFER_GAMMA28, ///< Assumed display gamma 2.8 + TRANSFER_ST2084, ///< SMPTE ST 2084 for 10/12/14/16 bit systems + TRANSFER_HLG, ///< ARIB STD-B67 hybrid-log-gamma + + TRANSFER_240M = 0x40, ///< SMPTE 240M or equivalent + TRANSFER_XVYCC, ///< IEC 61966-2-4 or equivalent + TRANSFER_BT1361, ///< Rec.ITU-R BT.1361 extended gamut + TRANSFER_ST428, ///< SMPTE ST 428-1 or equivalent + /// + TRANSFER_VENDOR_START = 0x80, ///< vendor-specific transfer values start here + TRANSFER_OTHER = 0xff ///< max value, reserved for undefined values +) + +/// Matrix coefficient +C2ENUM(C2Color::matrix_t, uint32_t, + MATRIX_UNSPECIFIED, ///< matrix coefficients are unspecified + MATRIX_BT709, ///< Rec.ITU-R BT.709-5 or equivalent + MATRIX_FCC47_73_682, ///< FCC Title 47 CFR 73.682 or equivalent (KR=0.30, KB=0.11) + MATRIX_BT601, ///< Rec.ITU-R BT.470, BT.601-6 625 or equivalent + MATRIX_240M, ///< SMPTE 240M or equivalent + MATRIX_BT2020, ///< Rec.ITU-R BT.2020 non-constant luminance + MATRIX_BT2020_CONSTANT, ///< Rec.ITU-R BT.2020 constant luminance + MATRIX_VENDOR_START = 0x80, ///< vendor-specific matrix coefficient values start here + MATRIX_OTHER = 0xff, ///< max value, reserved for undefined values +) + +struct C2ColorAspectsStruct { + C2Color::range_t range; + C2Color::primaries_t primaries; + C2Color::transfer_t transfer; + C2Color::matrix_t matrix; + + C2ColorAspectsStruct() + : range(C2Color::RANGE_UNSPECIFIED), + primaries(C2Color::PRIMARIES_UNSPECIFIED), + transfer(C2Color::TRANSFER_UNSPECIFIED), + matrix(C2Color::MATRIX_UNSPECIFIED) { } + + C2ColorAspectsStruct(C2Color::range_t range_, C2Color::primaries_t primaries_, + C2Color::transfer_t transfer_, C2Color::matrix_t matrix_) + : range(range_), primaries(primaries_), transfer(transfer_), matrix(matrix_) {} + + DEFINE_AND_DESCRIBE_C2STRUCT(ColorAspects) + C2FIELD(range, "range") + C2FIELD(primaries, "primaries") + C2FIELD(transfer, "transfer") + C2FIELD(matrix, "matrix") +}; + +typedef C2StreamParam<C2Info, C2ColorAspectsStruct, kParamIndexColorAspects> + C2StreamColorAspectsInfo; +constexpr char C2_PARAMKEY_COLOR_ASPECTS[] = "raw.color"; +constexpr char C2_PARAMKEY_VUI_COLOR_ASPECTS[] = "coded.vui.color"; + +/** + * Default color aspects to use. These come from the container or client and shall be handled + * according to the coding standard. + */ +typedef C2StreamParam<C2Tuning, C2ColorAspectsStruct, kParamIndexDefaultColorAspects> + C2StreamColorAspectsTuning; +constexpr char C2_PARAMKEY_DEFAULT_COLOR_ASPECTS[] = "default.color"; + +/** + * HDR Static Metadata Info. + */ +struct C2ColorXyStruct { + float x; ///< x color coordinate in xyY space [0-1] + float y; ///< y color coordinate in xyY space [0-1] + + DEFINE_AND_DESCRIBE_C2STRUCT(ColorXy) + C2FIELD(x, "x") + C2FIELD(y, "y") +}; + +struct C2MasteringDisplayColorVolumeStruct { + C2ColorXyStruct red; ///< coordinates of red display primary + C2ColorXyStruct green; ///< coordinates of green display primary + C2ColorXyStruct blue; ///< coordinates of blue display primary + C2ColorXyStruct white; ///< coordinates of white point + + float maxLuminance; ///< max display mastering luminance in cd/m^2 + float minLuminance; ///< min display mastering luminance in cd/m^2 + + DEFINE_AND_DESCRIBE_C2STRUCT(MasteringDisplayColorVolume) + C2FIELD(red, "red") + C2FIELD(green, "green") + C2FIELD(blue, "blue") + C2FIELD(white, "white") + + C2FIELD(maxLuminance, "max-luminance") + C2FIELD(minLuminance, "min-luminance") +}; + +struct C2HdrStaticMetadataStruct { + C2MasteringDisplayColorVolumeStruct mastering; + + // content descriptors + float maxCll; ///< max content light level (pixel luminance) in cd/m^2 + float maxFall; ///< max frame average light level (frame luminance) in cd/m^2 + + DEFINE_AND_DESCRIBE_BASE_C2STRUCT(HdrStaticMetadata) + C2FIELD(mastering, "mastering") + C2FIELD(maxCll, "max-cll") + C2FIELD(maxFall, "max-fall") +}; +typedef C2StreamParam<C2Info, C2HdrStaticMetadataStruct, kParamIndexHdrStaticMetadata> + 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 ----------------------------------- */ + +/** + * Block-size, block count and block rate. Used to determine or communicate profile-level + * requirements. + */ +typedef C2StreamParam<C2Info, C2PictureSizeStruct, kParamIndexBlockSize> C2StreamBlockSizeInfo; +constexpr char C2_PARAMKEY_BLOCK_SIZE[] = "coded.block-size"; + +typedef C2StreamParam<C2Info, C2Uint32Value, kParamIndexBlockCount> C2StreamBlockCountInfo; +constexpr char C2_PARAMKEY_BLOCK_COUNT[] = "coded.block-count"; + +typedef C2StreamParam<C2Info, C2FloatValue, kParamIndexBlockRate> C2StreamBlockRateInfo; +constexpr char C2_PARAMKEY_BLOCK_RATE[] = "coded.block-rate"; + +/* ====================================== VIDEO COMPONENTS ====================================== */ + +/** + * Frame rate (coded and port for raw data) + * + * Coded frame rates are what is represented in the compressed bitstream and should correspond to + * the timestamp. + * + * Frame rates on raw ports should still correspond to the timestamps. + * + * For slow motion or timelapse recording, the timestamp shall be adjusted prior to feeding an + * encoder, and the time stretch parameter should be used to signal the relationship between + * timestamp and real-world time. + */ +typedef C2StreamParam<C2Info, C2FloatValue, kParamIndexFrameRate> C2StreamFrameRateInfo; +constexpr char C2_PARAMKEY_FRAME_RATE[] = "coded.frame-rate"; + +typedef C2PortParam<C2Info, C2FloatValue, kParamIndexFrameRate> C2PortFrameRateInfo; +constexpr char C2_PARAMKEY_INPUT_FRAME_RATE[] = "input.frame-rate"; +constexpr char C2_PARAMKEY_OUTPUT_FRAME_RATE[] = "output.frame-rate"; + +/** + * Time stretch. Ratio between real-world time and timestamp. E.g. time stretch of 4.0 means that + * timestamp grows 1/4 the speed of real-world time (e.g. 4x slo-mo input). This can be used to + * optimize encoding. + */ +typedef C2PortParam<C2Info, C2FloatValue, kParamIndexTimeStretch> C2PortTimeStretchInfo; +constexpr char C2_PARAMKEY_INPUT_TIME_STRETCH[] = "input.time-stretch"; +constexpr char C2_PARAMKEY_OUTPUT_TIME_STRETCH[] = "output.time-stretch"; + +/** + * Max video frame size. + */ +typedef C2StreamParam<C2Tuning, C2PictureSizeStruct, kParamIndexMaxPictureSize> + C2StreamMaxPictureSizeTuning; +typedef C2StreamMaxPictureSizeTuning C2MaxVideoSizeHintPortSetting; +constexpr char C2_PARAMKEY_MAX_PICTURE_SIZE[] = "raw.max-size"; + +/** + * Picture type mask. + */ +C2ENUM(C2Config::picture_type_t, uint32_t, + SYNC_FRAME = (1 << 0), ///< sync frame, e.g. IDR + I_FRAME = (1 << 1), ///< intra frame that is completely encoded + P_FRAME = (1 << 2), ///< inter predicted frame from previous frames + B_FRAME = (1 << 3), ///< backward predicted (out-of-order) frame +) + +/** + * Allowed picture types. + */ +typedef C2StreamParam<C2Tuning, C2SimpleValueStruct<C2EasyEnum<C2Config::picture_type_t>>, + kParamIndexPictureTypeMask> + C2StreamPictureTypeMaskTuning; +constexpr char C2_PARAMKEY_PICTURE_TYPE_MASK[] = "coding.picture-type-mask"; + +/** + * Resulting picture type + */ +typedef C2StreamParam<C2Info, C2SimpleValueStruct<C2EasyEnum<C2Config::picture_type_t>>, + kParamIndexPictureType> + C2StreamPictureTypeInfo; +typedef C2StreamPictureTypeInfo C2StreamPictureTypeMaskInfo; +constexpr char C2_PARAMKEY_PICTURE_TYPE[] = "coded.picture-type"; + +/** + * GOP specification. + * + * GOP is specified in layers between sync frames, by specifying the number of specific type of + * frames between the previous type (starting with sync frames for the first layer): + * + * E.g. + * - 4 I frames between each sync frame + * - 2 P frames between each I frame + * - 1 B frame between each P frame + * + * [ { I, 4 }, { P, 2 }, { B, 1 } ] ==> (Sync)BPBPB IBPBPB IBPBPB IBPBPB IBPBPB (Sync)BPBPB + * + * For infinite GOP, I layer can be omitted (as the first frame is always a sync frame.): + * + * [ { P, MAX_UINT } ] ==> (Sync)PPPPPPPPPPPPPPPPPP... + * + * Sync frames can also be requested on demand, and as a time-based interval. For time-based + * interval, if there hasn't been a sync frame in at least the given time, the next I frame shall + * be encoded as a sync frame. For sync request, the next I frame shall be encoded as a sync frame. + * + * Temporal layering will determine GOP structure other than the I frame count between sync + * frames. + */ +struct C2GopLayerStruct { + C2GopLayerStruct() : type_((C2Config::picture_type_t)0), count(0) {} + C2GopLayerStruct(C2Config::picture_type_t type, uint32_t count_) + : type_(type), count(count_) { } + + C2Config::picture_type_t type_; + uint32_t count; + + DEFINE_AND_DESCRIBE_C2STRUCT(GopLayer) + C2FIELD(type_, "type") + C2FIELD(count, "count") +}; + +typedef C2StreamParam<C2Tuning, C2SimpleArrayStruct<C2GopLayerStruct>, kParamIndexGop> + C2StreamGopTuning; +constexpr char C2_PARAMKEY_GOP[] = "coding.gop"; + +/** + * Sync frame can be requested on demand by the client. + * + * If true, the next I frame shall be encoded as a sync frame. This config can be passed + * synchronously with the work, or directly to the component - leading to different result. + * If it is passed with work, it shall take effect when that work item is being processed (so + * the first I frame at or after that work item shall be a sync frame). + */ +typedef C2StreamParam<C2Tuning, C2EasyBoolValue, kParamIndexRequestSyncFrame> + C2StreamRequestSyncFrameTuning; +constexpr char C2_PARAMKEY_REQUEST_SYNC_FRAME[] = "coding.request-sync-frame"; + +/** + * Sync frame interval in time domain (timestamp). + * + * If there hasn't been a sync frame in at least this value, the next intra frame shall be encoded + * as a sync frame. The value of MAX_I64 or a negative value means no sync frames after the first + * frame. A value of 0 means all sync frames. + */ +typedef C2StreamParam<C2Tuning, C2Int64Value, kParamIndexSyncFrameInterval> + C2StreamSyncFrameIntervalTuning; +constexpr char C2_PARAMKEY_SYNC_FRAME_INTERVAL[] = "coding.sync-frame-interval"; + +/** + * Temporal layering + * + * Layer index is a value between 0 and layer count - 1. Layers with higher index have higher + * frequency: + * 0 + * 1 1 + * 2 2 2 2 + */ +typedef C2StreamParam<C2Info, C2Uint32Value, kParamIndexLayerIndex> C2StreamLayerIndexInfo; +constexpr char C2_PARAMKEY_LAYER_INDEX[] = "coded.layer-index"; + +typedef C2StreamParam<C2Info, C2Uint32Value, kParamIndexLayerCount> C2StreamLayerCountInfo; +constexpr char C2_PARAMKEY_LAYER_COUNT[] = "coded.layer-count"; + +struct C2TemporalLayeringStruct { + C2TemporalLayeringStruct() + : layerCount(0), bLayerCount(0) { } + + C2TemporalLayeringStruct(size_t /* flexCount */, uint32_t layerCount_, uint32_t bLayerCount_) + : layerCount(layerCount_), bLayerCount(c2_min(layerCount_, bLayerCount_)) { } + + C2TemporalLayeringStruct(size_t flexCount, uint32_t layerCount_, uint32_t bLayerCount_, + std::initializer_list<float> ratios) + : layerCount(layerCount_), bLayerCount(c2_min(layerCount_, bLayerCount_)) { + size_t ix = 0; + for (float ratio : ratios) { + if (ix == flexCount) { + break; + } + bitrateRatios[ix++] = ratio; + } + } + + uint32_t layerCount; ///< total number of layers (0 means no temporal layering) + uint32_t bLayerCount; ///< total number of bidirectional layers (<= num layers) + /** + * Bitrate budgets for each layer and the layers below, given as a ratio of the total + * stream bitrate. This can be omitted or partially specififed by the client while configuring, + * in which case the component shall fill in appropriate values for the missing layers. + * This must be provided by the component when queried for at least layer count - 1 (as the + * last layer's budget is always 1.0). + */ + float bitrateRatios[]; ///< 1.0-based + + DEFINE_AND_DESCRIBE_FLEX_C2STRUCT(TemporalLayering, bitrateRatios) + C2FIELD(layerCount, "layer-count") + C2FIELD(bLayerCount, "b-layer-count") + C2FIELD(bitrateRatios, "bitrate-ratios") +}; + +typedef C2StreamParam<C2Tuning, C2TemporalLayeringStruct, kParamIndexTemporalLayering> + C2StreamTemporalLayeringTuning; +constexpr char C2_PARAMKEY_TEMPORAL_LAYERING[] = "coding.temporal-layering"; + +/** + * Intra-refresh. + */ + +C2ENUM(C2Config::intra_refresh_mode_t, uint32_t, + INTRA_REFRESH_DISABLED, ///< no intra refresh + INTRA_REFRESH_ARBITRARY, ///< arbitrary, unspecified +) + +struct C2IntraRefreshStruct { + C2IntraRefreshStruct() + : mode(C2Config::INTRA_REFRESH_DISABLED), period(0.) { } + + C2IntraRefreshStruct(C2Config::intra_refresh_mode_t mode_, float period_) + : mode(mode_), period(period_) { } + + C2Config::intra_refresh_mode_t mode; ///< refresh mode + float period; ///< intra refresh period in frames (must be >= 1), 0 means disabled + + DEFINE_AND_DESCRIBE_C2STRUCT(IntraRefresh) + C2FIELD(mode, "mode") + C2FIELD(period, "period") +}; + +typedef C2StreamParam<C2Tuning, C2IntraRefreshStruct, kParamIndexIntraRefresh> + C2StreamIntraRefreshTuning; +constexpr char C2_PARAMKEY_INTRA_REFRESH[] = "coding.intra-refresh"; + +/* ====================================== IMAGE COMPONENTS ====================================== */ + +/** + * Tile layout. + * + * This described how the image is decomposed into tiles. + */ +C2ENUM(C2Config::scan_order_t, uint32_t, + SCAN_LEFT_TO_RIGHT_THEN_DOWN +) + +struct C2TileLayoutStruct { + C2PictureSizeStruct tile; ///< tile size + uint32_t columnCount; ///< number of tiles horizontally + uint32_t rowCount; ///< number of tiles vertically + C2Config::scan_order_t order; ///< tile order + + DEFINE_AND_DESCRIBE_C2STRUCT(TileLayout) + C2FIELD(tile, "tile") + C2FIELD(columnCount, "columns") + C2FIELD(rowCount, "rows") + C2FIELD(order, "order") +}; + +typedef C2StreamParam<C2Info, C2TileLayoutStruct, kParamIndexTileLayout> C2StreamTileLayoutInfo; +constexpr char C2_PARAMKEY_TILE_LAYOUT[] = "coded.tile-layout"; + +/** + * Tile handling. + * + * Whether to concatenate tiles or output them each. + */ +C2ENUM(C2Config::tiling_mode_t, uint32_t, + TILING_SEPARATE, ///< output each tile in a separate onWorkDone + TILING_CONCATENATE ///< output one work completion per frame (concatenate tiles) +) + +typedef C2StreamParam<C2Tuning, C2TileLayoutStruct, kParamIndexTileHandling> + C2StreamTileHandlingTuning; +constexpr char C2_PARAMKEY_TILE_HANDLING[] = "coding.tile-handling"; + +/* ====================================== AUDIO COMPONENTS ====================================== */ + +/** + * Sample rate + */ +typedef C2StreamParam<C2Info, C2Uint32Value, kParamIndexSampleRate> C2StreamSampleRateInfo; +constexpr char C2_PARAMKEY_SAMPLE_RATE[] = "raw.sample-rate"; +constexpr char C2_PARAMKEY_CODED_SAMPLE_RATE[] = "coded.sample-rate"; + +/** + * Channel count. + */ +typedef C2StreamParam<C2Info, C2Uint32Value, kParamIndexChannelCount> C2StreamChannelCountInfo; +constexpr char C2_PARAMKEY_CHANNEL_COUNT[] = "raw.channel-count"; +constexpr char C2_PARAMKEY_CODED_CHANNEL_COUNT[] = "coded.channel-count"; + +/** + * Max channel count. Used to limit the number of coded or decoded channels. + */ +typedef C2StreamParam<C2Info, C2Uint32Value, kParamIndexMaxChannelCount> C2StreamMaxChannelCountInfo; +constexpr char C2_PARAMKEY_MAX_CHANNEL_COUNT[] = "raw.max-channel-count"; +constexpr char C2_PARAMKEY_MAX_CODED_CHANNEL_COUNT[] = "coded.max-channel-count"; + +/** + * Audio sample format (PCM encoding) + */ +C2ENUM(C2Config::pcm_encoding_t, uint32_t, + PCM_16, + PCM_8, + PCM_FLOAT +) + +typedef C2StreamParam<C2Info, C2SimpleValueStruct<C2Config::pcm_encoding_t>, kParamIndexPcmEncoding> + C2StreamPcmEncodingInfo; +constexpr char C2_PARAMKEY_PCM_ENCODING[] = "raw.pcm-encoding"; +constexpr char C2_PARAMKEY_CODED_PCM_ENCODING[] = "coded.pcm-encoding"; + +/** + * AAC SBR Mode. Used during encoding. + */ +C2ENUM(C2Config::aac_sbr_mode_t, uint32_t, + AAC_SBR_OFF, + AAC_SBR_SINGLE_RATE, + AAC_SBR_DUAL_RATE, + AAC_SBR_AUTO ///< let the codec decide +) + +typedef C2StreamParam<C2Info, C2SimpleValueStruct<C2Config::aac_sbr_mode_t>, kParamIndexAacSbrMode> + C2StreamAacSbrModeTuning; +constexpr char C2_PARAMKEY_AAC_SBR_MODE[] = "coding.aac-sbr-mode"; + +/** + * DRC Compression. Used during decoding. + */ +C2ENUM(C2Config::drc_compression_mode_t, int32_t, + DRC_COMPRESSION_ODM_DEFAULT, ///< odm's default + DRC_COMPRESSION_NONE, + DRC_COMPRESSION_LIGHT, + DRC_COMPRESSION_HEAVY ///< +) + +typedef C2StreamParam<C2Info, C2SimpleValueStruct<C2Config::drc_compression_mode_t>, + kParamIndexDrcCompression> + C2StreamDrcCompressionModeTuning; +constexpr char C2_PARAMKEY_DRC_COMPRESSION_MODE[] = "coding.drc.compression-mode"; + +/** + * DRC target reference level in dBFS. Used during decoding. + */ +typedef C2StreamParam<C2Info, C2FloatValue, kParamIndexDrcTargetReferenceLevel> + C2StreamDrcTargetReferenceLevelTuning; +constexpr char C2_PARAMKEY_DRC_TARGET_REFERENCE_LEVEL[] = "coding.drc.reference-level"; + +/** + * DRC target reference level in dBFS. Used during decoding. + */ +typedef C2StreamParam<C2Info, C2FloatValue, kParamIndexDrcEncodedTargetLevel> + C2StreamDrcEncodedTargetLevelTuning; +constexpr char C2_PARAMKEY_DRC_ENCODED_TARGET_LEVEL[] = "coding.drc.encoded-level"; + +/** + * DRC target reference level in dBFS. Used during decoding. + */ +typedef C2StreamParam<C2Info, C2FloatValue, kParamIndexDrcBoostFactor> + C2StreamDrcBoostFactorTuning; +constexpr char C2_PARAMKEY_DRC_BOOST_FACTOR[] = "coding.drc.boost-factor"; + +/** + * DRC target reference level in dBFS. Used during decoding. + */ +typedef C2StreamParam<C2Info, C2FloatValue, kParamIndexDrcAttenuationFactor> + C2StreamDrcAttenuationFactorTuning; +constexpr char C2_PARAMKEY_DRC_ATTENUATION_FACTOR[] = "coding.drc.attenuation-factor"; + +/** + * DRC Effect Type (see ISO 23003-4) Uniform Dynamic Range Control. Used during decoding. + */ +C2ENUM(C2Config::drc_effect_type_t, int32_t, + DRC_EFFECT_ODM_DEFAULT = -2, ///< odm's default + DRC_EFFECT_OFF = -1, ///< no DRC + DRC_EFFECT_NONE = 0, ///< no DRC except to prevent clipping + DRC_EFFECT_LATE_NIGHT, + DRC_EFFECT_NOISY_ENVIRONMENT, + DRC_EFFECT_LIMITED_PLAYBACK_RANGE, + DRC_EFFECT_LOW_PLAYBACK_LEVEL, + DRC_EFFECT_DIALOG_ENHANCEMENT, + DRC_EFFECT_GENERAL_COMPRESSION +) + +typedef C2StreamParam<C2Info, C2SimpleValueStruct<C2Config::drc_effect_type_t>, + kParamIndexDrcEffectType> + C2StreamDrcEffectTypeTuning; +constexpr char C2_PARAMKEY_DRC_EFFECT_TYPE[] = "coding.drc.effect-type"; + +/* --------------------------------------- AAC components --------------------------------------- */ + +/** + * AAC stream format + */ +C2ENUM(C2Config::aac_packaging_t, uint32_t, + AAC_PACKAGING_RAW, + AAC_PACKAGING_ADTS +) + +typedef C2StreamParam<C2Info, C2SimpleValueStruct<C2EasyEnum<C2Config::aac_packaging_t>>, + kParamIndexAacPackaging> C2StreamAacPackagingInfo; +typedef C2StreamAacPackagingInfo C2StreamAacFormatInfo; +constexpr char C2_PARAMKEY_AAC_PACKAGING[] = "coded.aac-packaging"; + +/* ================================ PLATFORM-DEFINED PARAMETERS ================================ */ + +/** + * Platform level and features. + */ +enum C2Config::platform_level_t : uint32_t { + PLATFORM_P, ///< support for Android 9.0 feature set +}; + +// read-only +typedef C2GlobalParam<C2Setting, C2SimpleValueStruct<C2Config::platform_level_t>, + kParamIndexPlatformLevel> + C2PlatformLevelSetting; +constexpr char C2_PARAMKEY_PLATFORM_LEVEL[] = "api.platform-level"; + +enum C2Config::platform_feature_t : uint64_t { + // no platform-specific features have been defined +}; + +// read-only +typedef C2GlobalParam<C2Setting, C2SimpleValueStruct<C2Config::platform_feature_t>, + kParamIndexPlatformFeatures> + C2PlatformFeaturesSetting; +constexpr char C2_PARAMKEY_PLATFORM_FEATURES[] = "api.platform-features"; + +/** + * This structure describes the preferred ion allocation parameters for a given memory usage. + */ +struct C2StoreIonUsageStruct { + inline C2StoreIonUsageStruct() { + memset(this, 0, sizeof(*this)); + } + + inline C2StoreIonUsageStruct(uint64_t usage_, uint32_t capacity_) + : usage(usage_), capacity(capacity_), heapMask(0), allocFlags(0), minAlignment(0) { } + + uint64_t usage; ///< C2MemoryUsage + uint32_t capacity; ///< capacity + int32_t heapMask; ///< ion heapMask + int32_t allocFlags; ///< ion allocation flags + uint32_t minAlignment; ///< minimum alignment + + DEFINE_AND_DESCRIBE_C2STRUCT(StoreIonUsage) + C2FIELD(usage, "usage") + C2FIELD(capacity, "capacity") + C2FIELD(heapMask, "heap-mask") + C2FIELD(allocFlags, "alloc-flags") + C2FIELD(minAlignment, "min-alignment") +}; + +// store, private +typedef C2GlobalParam<C2Info, C2StoreIonUsageStruct, kParamIndexStoreIonUsage> + C2StoreIonUsageInfo; + +/** + * Flexible pixel format descriptors + */ +struct C2FlexiblePixelFormatDescriptorStruct { + uint32_t pixelFormat; + uint32_t bitDepth; + C2Color::subsampling_t subsampling; + C2Color::plane_layout_t layout; + + DEFINE_AND_DESCRIBE_C2STRUCT(FlexiblePixelFormatDescriptor) + C2FIELD(pixelFormat, "pixel-format") + C2FIELD(bitDepth, "bit-depth") + C2FIELD(subsampling, "subsampling") + C2FIELD(layout, "layout") +}; + +/** + * Plane layout of flexible pixel formats. + * + * bpp: bytes per color component, e.g. 1 for 8-bit formats, and 2 for 10-16-bit formats. + */ +C2ENUM(C2Color::plane_layout_t, uint32_t, + /** Unknown layout */ + UNKNOWN_LAYOUT, + + /** Planar layout with rows of each plane packed (colInc = bpp) */ + PLANAR_PACKED, + + /** Semiplanar layout with rows of each plane packed (colInc_Y/A = bpp (planar), + * colInc_Cb/Cr = 2*bpp (interleaved). Used only for YUV(A) formats. */ + SEMIPLANAR_PACKED, + + /** Interleaved packed. colInc = N*bpp (N are the number of color components) */ + INTERLEAVED_PACKED, + + /** Interleaved aligned. colInc = smallest power of 2 >= N*bpp (N are the number of color + * components) */ + INTERLEAVED_ALIGNED +) + +typedef C2GlobalParam<C2Info, C2SimpleArrayStruct<C2FlexiblePixelFormatDescriptorStruct>, + kParamIndexFlexiblePixelFormatDescriptors> + C2StoreFlexiblePixelFormatDescriptorsInfo; + +/** + * This structure describes the android dataspace for a raw video/image frame. + */ +typedef C2StreamParam<C2Info, C2Uint32Value, kParamIndexDataSpace> C2StreamDataSpaceInfo; +constexpr char C2_PARAMKEY_DATA_SPACE[] = "raw.data-space"; + +/** + * This structure describes the android surface scaling mode for a raw video/image frame. + */ +typedef C2StreamParam<C2Info, C2Uint32Value, kParamIndexSurfaceScaling> C2StreamSurfaceScalingInfo; +constexpr char C2_PARAMKEY_SURFACE_SCALING_MODE[] = "raw.surface-scaling"; + +/* ======================================= INPUT SURFACE ======================================= */ + +/** + * Input surface EOS + */ +typedef C2GlobalParam<C2Tuning, C2EasyBoolValue, kParamIndexInputSurfaceEos> + C2InputSurfaceEosTuning; +constexpr char C2_PARAMKEY_INPUT_SURFACE_EOS[] = "input-surface.eos"; + +/** + * Start/suspend/resume/stop controls and timestamps for input surface. + * + * TODO: make these counters + */ + +struct C2TimedControlStruct { + c2_bool_t enabled; ///< control is enabled + int64_t timestamp; ///< if enabled, time the control should take effect + + C2TimedControlStruct() + : enabled(C2_FALSE), timestamp(0) { } + + /* implicit */ C2TimedControlStruct(uint64_t timestamp_) + : enabled(C2_TRUE), timestamp(timestamp_) { } + + DEFINE_AND_DESCRIBE_C2STRUCT(TimedControl) + C2FIELD(enabled, "enabled") + C2FIELD(timestamp, "timestamp") +}; + +typedef C2PortParam<C2Tuning, C2TimedControlStruct, kParamIndexStartAt> + C2PortStartTimestampTuning; +constexpr char C2_PARAMKEY_INPUT_SURFACE_START_AT[] = "input-surface.start"; +typedef C2PortParam<C2Tuning, C2TimedControlStruct, kParamIndexSuspendAt> + C2PortSuspendTimestampTuning; +constexpr char C2_PARAMKEY_INPUT_SURFACE_SUSPEND_AT[] = "input-surface.suspend"; +typedef C2PortParam<C2Tuning, C2TimedControlStruct, kParamIndexResumeAt> + C2PortResumeTimestampTuning; +constexpr char C2_PARAMKEY_INPUT_SURFACE_RESUME_AT[] = "input-surface.resume"; +typedef C2PortParam<C2Tuning, C2TimedControlStruct, kParamIndexStopAt> + C2PortStopTimestampTuning; +constexpr char C2_PARAMKEY_INPUT_SURFACE_STOP_AT[] = "input-surface.stop"; + +/** + * Time offset for input surface. Input timestamp to codec is surface buffer timestamp plus this + * time offset. + */ +typedef C2GlobalParam<C2Tuning, C2Int64Value, kParamIndexTimeOffset> C2ComponentTimeOffsetTuning; +constexpr char C2_PARAMKEY_INPUT_SURFACE_TIME_OFFSET[] = "input-surface.time-offset"; + +/** + * Minimum fps for input surface. + * + * Repeat frame to meet this. + */ +typedef C2PortParam<C2Tuning, C2FloatValue, kParamIndexMinFrameRate> C2PortMinFrameRateTuning; +constexpr char C2_PARAMKEY_INPUT_SURFACE_MIN_FRAME_RATE[] = "input-surface.min-frame-rate"; + +/** + * Timestamp adjustment (override) for input surface buffers. These control the input timestamp + * fed to the codec, but do not impact the output timestamp. + */ +struct C2TimestampGapAdjustmentStruct { + /// control modes + enum mode_t : uint32_t; + + inline C2TimestampGapAdjustmentStruct(); + + inline C2TimestampGapAdjustmentStruct(mode_t mode_, uint64_t value_) + : mode(mode_), value(value_) { } + + mode_t mode; ///< control mode + uint64_t value; ///< control value for gap between two timestamp + + DEFINE_AND_DESCRIBE_C2STRUCT(TimestampGapAdjustment) + C2FIELD(mode, "mode") + C2FIELD(value, "value") +}; + +C2ENUM(C2TimestampGapAdjustmentStruct::mode_t, uint32_t, + NONE, + MIN_GAP, + FIXED_GAP, +); + +inline C2TimestampGapAdjustmentStruct::C2TimestampGapAdjustmentStruct() + : mode(C2TimestampGapAdjustmentStruct::NONE), value(0) { } + +typedef C2PortParam<C2Tuning, C2TimestampGapAdjustmentStruct> C2PortTimestampGapTuning; +constexpr char C2_PARAMKEY_INPUT_SURFACE_TIMESTAMP_ADJUSTMENT[] = "input-surface.timestamp-adjustment"; + +/// @} + +#endif // C2CONFIG_H_
diff --git a/media/codec2/core/include/C2Enum.h b/media/codec2/core/include/C2Enum.h new file mode 100644 index 0000000..b0fad8f --- /dev/null +++ b/media/codec2/core/include/C2Enum.h
@@ -0,0 +1,228 @@ +/* + * Copyright (C) 2016 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 C2ENUM_H_ +#define C2ENUM_H_ + +#include <C2Param.h> +#include <_C2MacroUtils.h> + +#include <utility> +#include <vector> + +/** \file + * Tools for easier enum support. + */ + +/// \cond INTERNAL + +/* ---------------------------- UTILITIES FOR ENUMERATION REFLECTION ---------------------------- */ + +/** + * Utility class that allows ignoring enum value assignment (e.g. both '(_C2EnumConst)kValue = x' + * and '(_C2EnumConst)kValue' will eval to kValue. + */ +template<typename T> +class _C2EnumConst { +public: + // implicit conversion from T + inline _C2EnumConst(T value) : _mValue(value) {} + // implicit conversion to T + inline operator T() { return _mValue; } + // implicit conversion to C2Value::Primitive + inline operator C2Value::Primitive() { return (T)_mValue; } + // ignore assignment and return T here to avoid implicit conversion to T later + inline T &operator =(T value __unused) { return _mValue; } +private: + T _mValue; +}; + +/// mapper to get name of enum +/// \note this will contain any initialization, which we will remove when converting to lower-case +#define _C2_GET_ENUM_NAME(x, y) #x +/// mapper to get value of enum +#define _C2_GET_ENUM_VALUE(x, type) (_C2EnumConst<type>)x + +/// \endcond + +class _C2EnumUtils { + static C2String camelCaseToDashed(C2String name); + + static std::vector<C2String> sanitizeEnumValueNames( + const std::vector<C2StringLiteral> names, + C2StringLiteral _prefix = nullptr); + + friend class C2UtilTest_EnumUtilsTest_Test; + +public: + // this may not be used... + static C2_HIDE std::vector<C2String> parseEnumValuesFromString(C2StringLiteral value); + + template<typename T> + static C2_HIDE C2FieldDescriptor::NamedValuesType sanitizeEnumValues( + std::vector<T> values, + std::vector<C2StringLiteral> names, + 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) { + namedValues.emplace_back(sanitizedNames[i], values[i]); + } + return namedValues; + } + + template<typename E> + static C2_HIDE C2FieldDescriptor::NamedValuesType customEnumValues( + std::vector<std::pair<C2StringLiteral, E>> items) { + C2FieldDescriptor::NamedValuesType namedValues; + for (auto &item : items) { + namedValues.emplace_back(item.first, item.second); + } + return namedValues; + } +}; + +#define DEFINE_C2_ENUM_VALUE_AUTO_HELPER(name, type, prefix, ...) \ + _DEFINE_C2_ENUM_VALUE_AUTO_HELPER(__C2_GENERATE_GLOBAL_VARS__, name, type, prefix, \ + ##__VA_ARGS__) +#define _DEFINE_C2_ENUM_VALUE_AUTO_HELPER(enabled, name, type, prefix, ...) \ + __DEFINE_C2_ENUM_VALUE_AUTO_HELPER(enabled, name, type, prefix, ##__VA_ARGS__) +#define __DEFINE_C2_ENUM_VALUE_AUTO_HELPER(enabled, name, type, prefix, ...) \ + ___DEFINE_C2_ENUM_VALUE_AUTO_HELPER##enabled(name, type, prefix, ##__VA_ARGS__) +#define ___DEFINE_C2_ENUM_VALUE_AUTO_HELPER(name, type, prefix, ...) \ +template<> \ +C2FieldDescriptor::NamedValuesType C2FieldDescriptor::namedValuesFor(const name &r __unused) { \ + return _C2EnumUtils::sanitizeEnumValues( \ + std::vector<C2Value::Primitive> { _C2_MAP(_C2_GET_ENUM_VALUE, type, __VA_ARGS__) }, \ + { _C2_MAP(_C2_GET_ENUM_NAME, type, __VA_ARGS__) }, \ + prefix); \ +} +#define ___DEFINE_C2_ENUM_VALUE_AUTO_HELPER__C2_GENERATE_GLOBAL_VARS__(name, type, prefix, ...) + +#define DEFINE_C2_ENUM_VALUE_CUSTOM_HELPER(name, names) \ + _DEFINE_C2_ENUM_VALUE_CUSTOM_HELPER(__C2_GENERATE_GLOBAL_VARS__, name, names) +#define _DEFINE_C2_ENUM_VALUE_CUSTOM_HELPER(enabled, name, names) \ + __DEFINE_C2_ENUM_VALUE_CUSTOM_HELPER(enabled, name, names) +#define __DEFINE_C2_ENUM_VALUE_CUSTOM_HELPER(enabled, name, names) \ + ___DEFINE_C2_ENUM_VALUE_CUSTOM_HELPER##enabled(name, names) +#define ___DEFINE_C2_ENUM_VALUE_CUSTOM_HELPER(name, names) \ +template<> \ +C2FieldDescriptor::NamedValuesType C2FieldDescriptor::namedValuesFor(const name &r __unused) { \ + return _C2EnumUtils::customEnumValues( \ + std::vector<std::pair<C2StringLiteral, name>> names); \ +} +#define ___DEFINE_C2_ENUM_VALUE_CUSTOM_HELPER__C2_GENERATE_GLOBAL_VARS__(name, names) + +/** + * Defines an enum type with the default named value mapper. The default mapper + * finds and removes the longest common prefix across all of the enum value names, and + * replaces camel-case separators with dashes ('-'). + * + * This macro must be used in the global scope and namespace. + * + * ~~~~~~~~~~~~~ (.cpp) + * C2ENUM(c2_enum_t, uint32_t, + * C2_VALUE1, + * C2_VALUE2 = 5, + * C2_VALUE3 = C2_VALUE1 + 1) + * // named values are: C2_VALUE1 => "1", C2_VALUE2 => "2", ... + * // longest common prefix is "C2_VALUE" + * ~~~~~~~~~~~~~ + * + * \param name name of the enum type (This can be an inner class enum.) + * \param type underlying type + */ +#define C2ENUM(name, type, ...) \ +enum name : type { __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 + * mapper removes the prefix from all of the enum value names (if present), and + * inserts dashes at camel-case separators (lowHigh becomes low-high) and also replaces + * non-leading underscores with dashes ('-'). + * + * This macro must be used in the global scope and namespace. + * + * ~~~~~~~~~~~~~ (.cpp) + * C2ENUM_CUSTOM_PREFIX(c2_enum_t, uint32_t, "C2_", + * C2_VALUE1, + * C2_VALUE2 = 5, + * C2_VALUE3 = C2_VALUE1 + 1) + * // named values are: C2_VALUE1 => "VALUE1", C2_VALUE2 => "VALUE2", ... + * ~~~~~~~~~~~~~ + * + * \param name name of the enum type (This can be an inner class enum.) + * \param type underlying type + * \param prefix prefix to remove + */ +#define C2ENUM_CUSTOM_PREFIX(name, type, prefix, ...) \ +enum name : type { __VA_ARGS__ }; \ +DEFINE_C2_ENUM_VALUE_AUTO_HELPER(name, type, prefix, __VA_ARGS__) + +/** + * Defines an enum type with custom names. + * + * This macro must be used in the global scope and namespace. + * + * ~~~~~~~~~~~~~ (.cpp) + * C2ENUM_CUSTOM_NAMES(SomeStruct::c2_enum_t, uint32_t, ({ + * { "One", SomeStruct::C2_VALUE1 }, + * { "Two", SomeStruct::C2_VALUE2 }, + * { "Three", SomeStruct::C2_VALUE3 } }), + * C2_VALUE1, + * C2_VALUE2 = 5, + * C2_VALUE3 = C2_VALUE1 + 1) + * + * // named values are: C2_VALUE1 => "One", C2_VALUE2 => "Two", ... + * ~~~~~~~~~~~~~ + */ +#define C2ENUM_CUSTOM_NAMES(name, type, names, ...) \ +enum name : type { __VA_ARGS__ }; \ +DEFINE_C2_ENUM_VALUE_CUSTOM_HELPER(name, names) + +/** + * Make enums usable as their integral types. + * + * Note: this makes them not usable in printf() + */ +template<class E> +struct C2EasyEnum { + using U = typename std::underlying_type<E>::type; + E value; + // define conversion functions + inline constexpr operator E() const { return value; } + inline constexpr C2EasyEnum(E value_) : value(value_) { } + inline constexpr C2EasyEnum(U value_) : value(E(value_)) { } + inline constexpr C2EasyEnum() = default; +}; + +// make C2EasyEnum behave like a regular enum + +namespace std { + template<typename E> + struct underlying_type<C2EasyEnum<E>> { + typedef typename underlying_type<E>::type type; + }; + + template<typename E> + struct is_enum<C2EasyEnum<E>> { + constexpr static bool value = true; + }; +} + +#endif // C2ENUM_H_ +
diff --git a/media/codec2/core/include/C2Param.h b/media/codec2/core/include/C2Param.h new file mode 100644 index 0000000..51d417a --- /dev/null +++ b/media/codec2/core/include/C2Param.h
@@ -0,0 +1,1687 @@ +/* + * Copyright (C) 2016 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 C2PARAM_H_ +#define C2PARAM_H_ + +#include <C2.h> + +#include <stdbool.h> +#include <stdint.h> + +#include <algorithm> +#include <string> +#include <type_traits> +#include <utility> +#include <vector> + +/// \addtogroup Parameters +/// @{ + +/// \defgroup internal Internal helpers. + +/*! + * \file + * PARAMETERS: SETTINGs, TUNINGs, and INFOs + * === + * + * These represent miscellaneous control and metadata information and are likely copied into + * kernel space. Therefore, these are C-like structures designed to carry just a small amount of + * information. We are using C++ to be able to add constructors, as well as non-virtual and class + * methods. + * + * ==Specification details: + * + * Restrictions: + * - must be POD struct, e.g. no vtable (no virtual destructor) + * - must have the same size in 64-bit and 32-bit mode (no size_t) + * - as such, no pointer members + * - some common member field names are reserved as they are defined as methods for all + * parameters: + * they are: size, type, kind, index and stream + * + * Behavior: + * - Params can be global (not related to input or output), related to input or output, + * or related to an input/output stream. + * - All params are queried/set using a unique param index, which incorporates a potential stream + * index and/or port. + * - Querying (supported) params MUST never fail. + * - All params MUST have default values. + * - If some fields have "unsupported" or "invalid" values during setting, this SHOULD be + * communicated to the app. + * a) Ideally, this should be avoided. When setting parameters, in general, component should do + * "best effort" to apply all settings. It should change "invalid/unsupported" values to the + * nearest supported values. + * - This is communicated to the client by changing the source values in tune()/ + * configure(). + * b) If falling back to a supported value is absolutely impossible, the component SHALL return + * an error for the specific setting, but should continue to apply other settings. + * TODO: this currently may result in unintended results. + * + * **NOTE:** unlike OMX, params are not versioned. Instead, a new struct with new param index + * SHALL be added as new versions are required. + * + * The proper subtype (Setting, Info or Param) is incorporated into the class type. Define structs + * to define multiple subtyped versions of related parameters. + * + * ==Implementation details: + * + * - Use macros to define parameters + * - All parameters must have a default constructor + * - This is only used for instantiating the class in source (e.g. will not be used + * when building a parameter by the framework from key/value pairs.) + */ + +/// \ingroup internal + +/** + * Parameter base class. + */ +struct C2Param { + // param index encompasses the following: + // + // - kind (setting, tuning, info, struct) + // - scope + // - direction (global, input, output) + // - stream flag + // - stream ID (usually 0) + // - and the parameter's type (core index) + // - flexible parameter flag + // - vendor extension flag + // - type index (this includes the vendor extension flag) + // + // layout: + // + // kind : <------- scope -------> : <----- core index -----> + // +------+-----+---+------+--------+----|------+--------------+ + // | kind | dir | - |stream|streamID|flex|vendor| type index | + // +------+-----+---+------+--------+----+------+--------------+ + // bit: 31..30 29.28 25 24 .. 17 16 15 14 .. 0 + // +public: + /** + * C2Param kinds, usable as bitmaps. + */ + enum kind_t : uint32_t { + NONE = 0, + STRUCT = (1 << 0), + INFO = (1 << 1), + SETTING = (1 << 2), + TUNING = (1 << 3) | SETTING, // tunings are settings + }; + + /** + * The parameter type index specifies the underlying parameter type of a parameter as + * an integer value. + * + * Parameter types are divided into two groups: platform types and vendor types. + * + * Platform types are defined by the platform and are common for all implementations. + * + * Vendor types are defined by each vendors, so they may differ between implementations. + * It is recommended that vendor types be the same for all implementations by a specific + * vendor. + */ + typedef uint32_t type_index_t; + enum : uint32_t { + TYPE_INDEX_VENDOR_START = 0x00008000, ///< vendor indices SHALL start after this + }; + + /** + * Core index is the underlying parameter type for a parameter. It is used to describe the + * layout of the parameter structure regardless of the component or parameter kind/scope. + * + * It is used to identify and distinguish global parameters, and also parameters on a given + * port or stream. They must be unique for the set of global parameters, as well as for the + * set of parameters on each port or each stream, but the same core index can be used for + * parameters on different streams or ports, as well as for global parameters and port/stream + * parameters. + * + * Multiple parameter types can share the same layout. + * + * \note The layout for all parameters with the same core index across all components must + * be identical. + */ + struct CoreIndex { + //public: + enum : uint32_t { + IS_FLEX_FLAG = 0x00010000, + IS_REQUEST_FLAG = 0x00020000, + }; + + protected: + enum : uint32_t { + KIND_MASK = 0xC0000000, + KIND_STRUCT = 0x00000000, + KIND_TUNING = 0x40000000, + KIND_SETTING = 0x80000000, + KIND_INFO = 0xC0000000, + + DIR_MASK = 0x30000000, + DIR_GLOBAL = 0x20000000, + DIR_UNDEFINED = DIR_MASK, // MUST have all bits set + DIR_INPUT = 0x00000000, + DIR_OUTPUT = 0x10000000, + + IS_STREAM_FLAG = 0x02000000, + STREAM_ID_MASK = 0x01F00000, + STREAM_ID_SHIFT = 20, + MAX_STREAM_ID = STREAM_ID_MASK >> STREAM_ID_SHIFT, + STREAM_MASK = IS_STREAM_FLAG | STREAM_ID_MASK, + + IS_VENDOR_FLAG = 0x00008000, + TYPE_INDEX_MASK = 0x0000FFFF, + CORE_MASK = TYPE_INDEX_MASK | IS_FLEX_FLAG, + }; + + public: + /// constructor/conversion from uint32_t + inline CoreIndex(uint32_t index) : mIndex(index) { } + + // no conversion from uint64_t + inline CoreIndex(uint64_t index) = delete; + + /// returns true iff this is a vendor extension parameter + inline bool isVendor() const { return mIndex & IS_VENDOR_FLAG; } + + /// returns true iff this is a flexible parameter (with variable size) + inline bool isFlexible() const { return mIndex & IS_FLEX_FLAG; } + + /// returns the core index + /// This is the combination of the parameter type index and the flexible flag. + inline uint32_t coreIndex() const { return mIndex & CORE_MASK; } + + /// returns the parameter type index + inline type_index_t typeIndex() const { return mIndex & TYPE_INDEX_MASK; } + + DEFINE_FIELD_AND_MASK_BASED_COMPARISON_OPERATORS(CoreIndex, mIndex, CORE_MASK) + + protected: + uint32_t mIndex; + }; + + /** + * Type encompasses the parameter's kind (tuning, setting, info), its scope (whether the + * parameter is global, input or output, and whether it is for a stream) and the its base + * index (which also determines its layout). + */ + struct Type : public CoreIndex { + //public: + /// returns true iff this is a global parameter (not for input nor output) + inline bool isGlobal() const { return (mIndex & DIR_MASK) == DIR_GLOBAL; } + /// returns true iff this is an input or input stream parameter + inline bool forInput() const { return (mIndex & DIR_MASK) == DIR_INPUT; } + /// returns true iff this is an output or output stream parameter + inline bool forOutput() const { return (mIndex & DIR_MASK) == DIR_OUTPUT; } + + /// returns true iff this is a stream parameter + inline bool forStream() const { return mIndex & IS_STREAM_FLAG; } + /// returns true iff this is a port (input or output) parameter + inline bool forPort() const { return !forStream() && !isGlobal(); } + + /// returns the parameter type: the parameter index without the stream ID + inline uint32_t type() const { return mIndex & (~STREAM_ID_MASK); } + + /// return the kind (struct, info, setting or tuning) of this param + inline kind_t kind() const { + switch (mIndex & KIND_MASK) { + case KIND_STRUCT: return STRUCT; + case KIND_INFO: return INFO; + case KIND_SETTING: return SETTING; + case KIND_TUNING: return TUNING; + default: return NONE; // should not happen + } + } + + /// constructor/conversion from uint32_t + inline Type(uint32_t index) : CoreIndex(index) { } + + // no conversion from uint64_t + inline Type(uint64_t index) = delete; + + DEFINE_FIELD_AND_MASK_BASED_COMPARISON_OPERATORS(Type, mIndex, ~STREAM_ID_MASK) + + private: + friend struct C2Param; // for setPort() + friend struct C2Tuning; // for KIND_TUNING + friend struct C2Setting; // for KIND_SETTING + friend struct C2Info; // for KIND_INFO + // for DIR_GLOBAL + template<typename T, typename S, int I, class F> friend struct C2GlobalParam; + template<typename T, typename S, int I, class F> friend struct C2PortParam; // for kDir* + template<typename T, typename S, int I, class F> friend struct C2StreamParam; // for kDir* + friend struct _C2ParamInspector; // for testing + + /** + * Sets the port/stream direction. + * @return true on success, false if could not set direction (e.g. it is global param). + */ + inline bool setPort(bool output) { + if (isGlobal()) { + return false; + } else { + mIndex = (mIndex & ~DIR_MASK) | (output ? DIR_OUTPUT : DIR_INPUT); + return true; + } + } + }; + + /** + * index encompasses all remaining information: basically the stream ID. + */ + struct Index : public Type { + /// returns the index as uint32_t + inline operator uint32_t() const { return mIndex; } + + /// constructor/conversion from uint32_t + inline Index(uint32_t index) : Type(index) { } + + /// copy constructor + inline Index(const Index &index) = default; + + // no conversion from uint64_t + inline Index(uint64_t index) = delete; + + /// returns the stream ID or ~0 if not a stream + inline unsigned stream() const { + return forStream() ? rawStream() : ~0U; + } + + /// Returns an index with stream field set to given stream. + inline Index withStream(unsigned stream) const { + Index ix = mIndex; + (void)ix.setStream(stream); + return ix; + } + + /// sets the port (direction). Returns true iff successful. + inline Index withPort(bool output) const { + Index ix = mIndex; + (void)ix.setPort(output); + return ix; + } + + DEFINE_FIELD_BASED_COMPARISON_OPERATORS(Index, mIndex) + + private: + friend struct C2Param; // for setStream, MakeStreamId, isValid + friend struct _C2ParamInspector; // for testing + + /** + * @return true if the type is valid, e.g. direction is not undefined AND + * stream is 0 if not a stream param. + */ + inline bool isValid() const { + // there is no Type::isValid (even though some of this check could be + // performed on types) as this is only used on index... + return (forStream() ? rawStream() < MAX_STREAM_ID : rawStream() == 0) + && (mIndex & DIR_MASK) != DIR_UNDEFINED; + } + + /// returns the raw stream ID field + inline unsigned rawStream() const { + return (mIndex & STREAM_ID_MASK) >> STREAM_ID_SHIFT; + } + + /// returns the streamId bitfield for a given |stream|. If stream is invalid, + /// returns an invalid bitfield. + inline static uint32_t MakeStreamId(unsigned stream) { + // saturate stream ID (max value is invalid) + if (stream > MAX_STREAM_ID) { + stream = MAX_STREAM_ID; + } + return (stream << STREAM_ID_SHIFT) & STREAM_ID_MASK; + } + + inline bool convertToStream(bool output, unsigned stream) { + mIndex = (mIndex & ~DIR_MASK) | IS_STREAM_FLAG; + (void)setPort(output); + return setStream(stream); + } + + inline void convertToPort(bool output) { + mIndex = (mIndex & ~(DIR_MASK | IS_STREAM_FLAG)); + (void)setPort(output); + } + + inline void convertToGlobal() { + mIndex = (mIndex & ~(DIR_MASK | IS_STREAM_FLAG)) | DIR_GLOBAL; + } + + inline void convertToRequest() { + mIndex = mIndex | IS_REQUEST_FLAG; + } + + /** + * Sets the stream index. + * \return true on success, false if could not set index (e.g. not a stream param). + */ + inline bool setStream(unsigned stream) { + if (forStream()) { + mIndex = (mIndex & ~STREAM_ID_MASK) | MakeStreamId(stream); + return this->stream() < MAX_STREAM_ID; + } + return false; + } + }; + +public: + // public getters for Index methods + + /// returns true iff this is a vendor extension parameter + inline bool isVendor() const { return _mIndex.isVendor(); } + /// returns true iff this is a flexible parameter + inline bool isFlexible() const { return _mIndex.isFlexible(); } + /// returns true iff this is a global parameter (not for input nor output) + inline bool isGlobal() const { return _mIndex.isGlobal(); } + /// returns true iff this is an input or input stream parameter + inline bool forInput() const { return _mIndex.forInput(); } + /// returns true iff this is an output or output stream parameter + inline bool forOutput() const { return _mIndex.forOutput(); } + + /// returns true iff this is a stream parameter + inline bool forStream() const { return _mIndex.forStream(); } + /// returns true iff this is a port (input or output) parameter + inline bool forPort() const { return _mIndex.forPort(); } + + /// returns the stream ID or ~0 if not a stream + inline unsigned stream() const { return _mIndex.stream(); } + + /// returns the parameter type: the parameter index without the stream ID + inline Type type() const { return _mIndex.type(); } + + /// returns the index of this parameter + /// \todo: should we restrict this to C2ParamField? + inline uint32_t index() const { return (uint32_t)_mIndex; } + + /// returns the core index of this parameter + inline CoreIndex coreIndex() const { return _mIndex.coreIndex(); } + + /// returns the kind of this parameter + inline kind_t kind() const { return _mIndex.kind(); } + + /// returns the size of the parameter or 0 if the parameter is invalid + inline size_t size() const { return _mSize; } + + /// returns true iff the parameter is valid + inline operator bool() const { return _mIndex.isValid() && _mSize > 0; } + + /// returns true iff the parameter is invalid + inline bool operator!() const { return !operator bool(); } + + // equality is done by memcmp (use equals() to prevent any overread) + inline bool operator==(const C2Param &o) const { + return equals(o) && memcmp(this, &o, _mSize) == 0; + } + inline bool operator!=(const C2Param &o) const { return !operator==(o); } + + /// safe(r) type cast from pointer and size + inline static C2Param* From(void *addr, size_t len) { + // _mSize must fit into size, but really C2Param must also to be a valid param + if (len < sizeof(C2Param)) { + return nullptr; + } + // _mSize must match length + C2Param *param = (C2Param*)addr; + if (param->_mSize != len) { + return nullptr; + } + return param; + } + + /// Returns managed clone of |orig| at heap. + inline static std::unique_ptr<C2Param> Copy(const C2Param &orig) { + if (orig.size() == 0) { + return nullptr; + } + void *mem = ::operator new (orig.size()); + C2Param *param = new (mem) C2Param(orig.size(), orig._mIndex); + param->updateFrom(orig); + return std::unique_ptr<C2Param>(param); + } + + /// Returns managed clone of |orig| as a stream parameter at heap. + inline static std::unique_ptr<C2Param> CopyAsStream( + const C2Param &orig, bool output, unsigned stream) { + std::unique_ptr<C2Param> copy = Copy(orig); + if (copy) { + copy->_mIndex.convertToStream(output, stream); + } + return copy; + } + + /// Returns managed clone of |orig| as a port parameter at heap. + inline static std::unique_ptr<C2Param> CopyAsPort(const C2Param &orig, bool output) { + std::unique_ptr<C2Param> copy = Copy(orig); + if (copy) { + copy->_mIndex.convertToPort(output); + } + return copy; + } + + /// Returns managed clone of |orig| as a global parameter at heap. + inline static std::unique_ptr<C2Param> CopyAsGlobal(const C2Param &orig) { + std::unique_ptr<C2Param> copy = Copy(orig); + if (copy) { + copy->_mIndex.convertToGlobal(); + } + return copy; + } + + /// Returns managed clone of |orig| as a stream parameter at heap. + inline static std::unique_ptr<C2Param> CopyAsRequest(const C2Param &orig) { + std::unique_ptr<C2Param> copy = Copy(orig); + if (copy) { + copy->_mIndex.convertToRequest(); + } + return copy; + } + +#if 0 + template<typename P, class=decltype(C2Param(P()))> + P *As() { return P::From(this); } + template<typename P> + const P *As() const { return const_cast<const P*>(P::From(const_cast<C2Param*>(this))); } +#endif + +protected: + /// sets the stream field. Returns true iff successful. + inline bool setStream(unsigned stream) { + return _mIndex.setStream(stream); + } + + /// sets the port (direction). Returns true iff successful. + inline bool setPort(bool output) { + return _mIndex.setPort(output); + } + +public: + /// invalidate this parameter. There is no recovery from this call; e.g. parameter + /// cannot be 'corrected' to be valid. + inline void invalidate() { _mSize = 0; } + + // if other is the same kind of (valid) param as this, copy it into this and return true. + // otherwise, do not copy anything, and return false. + inline bool updateFrom(const C2Param &other) { + if (other._mSize <= _mSize && other._mIndex == _mIndex && _mSize > 0) { + memcpy(this, &other, other._mSize); + return true; + } + return false; + } + +protected: + // returns |o| if it is a null ptr, or if can suitably be a param of given |type| (e.g. has + // same type (ignoring stream ID), and size). Otherwise, returns null. If |checkDir| is false, + // allow undefined or different direction (e.g. as constructed from C2PortParam() vs. + // C2PortParam::input), but still require equivalent type (stream, port or global); otherwise, + // return null. + inline static const C2Param* IfSuitable( + const C2Param* o, size_t size, Type type, size_t flexSize = 0, bool checkDir = true) { + if (o == nullptr || o->_mSize < size || (flexSize && ((o->_mSize - size) % flexSize))) { + return nullptr; + } else if (checkDir) { + return o->_mIndex.type() == type.mIndex ? o : nullptr; + } else if (o->_mIndex.isGlobal()) { + return nullptr; + } else { + return ((o->_mIndex.type() ^ type.mIndex) & ~Type::DIR_MASK) ? nullptr : o; + } + } + + /// base constructor + inline C2Param(uint32_t paramSize, Index paramIndex) + : _mSize(paramSize), + _mIndex(paramIndex) { + if (paramSize > sizeof(C2Param)) { + memset(this + 1, 0, paramSize - sizeof(C2Param)); + } + } + + /// base constructor with stream set + inline C2Param(uint32_t paramSize, Index paramIndex, unsigned stream) + : _mSize(paramSize), + _mIndex(paramIndex | Index::MakeStreamId(stream)) { + if (paramSize > sizeof(C2Param)) { + memset(this + 1, 0, paramSize - sizeof(C2Param)); + } + if (!forStream()) { + invalidate(); + } + } + +private: + friend struct _C2ParamInspector; // for testing + + /// returns true iff |o| has the same size and index as this. This performs the + /// basic check for equality. + inline bool equals(const C2Param &o) const { + return _mSize == o._mSize && _mIndex == o._mIndex; + } + + uint32_t _mSize; + Index _mIndex; +}; + +/// \ingroup internal +/// allow C2Params access to private methods, e.g. constructors +#define C2PARAM_MAKE_FRIENDS \ + template<typename U, typename S, int I, class F> friend struct C2GlobalParam; \ + template<typename U, typename S, int I, class F> friend struct C2PortParam; \ + template<typename U, typename S, int I, class F> friend struct C2StreamParam; \ + +/** + * Setting base structure for component method signatures. Wrap constructors. + */ +struct C2Setting : public C2Param { +protected: + template<typename ...Args> + inline C2Setting(const Args(&... args)) : C2Param(args...) { } +public: // TODO + enum : uint32_t { PARAM_KIND = Type::KIND_SETTING }; +}; + +/** + * Tuning base structure for component method signatures. Wrap constructors. + */ +struct C2Tuning : public C2Setting { +protected: + template<typename ...Args> + inline C2Tuning(const Args(&... args)) : C2Setting(args...) { } +public: // TODO + enum : uint32_t { PARAM_KIND = Type::KIND_TUNING }; +}; + +/** + * Info base structure for component method signatures. Wrap constructors. + */ +struct C2Info : public C2Param { +protected: + template<typename ...Args> + inline C2Info(const Args(&... args)) : C2Param(args...) { } +public: // TODO + enum : uint32_t { PARAM_KIND = Type::KIND_INFO }; +}; + +/** + * Structure uniquely specifying a field in an arbitrary structure. + * + * \note This structure is used differently in C2FieldDescriptor to + * identify array fields, such that _mSize is the size of each element. This is + * because the field descriptor contains the array-length, and we want to keep + * a relevant element size for variable length arrays. + */ +struct _C2FieldId { +//public: + /** + * Constructor used for C2FieldDescriptor that removes the array extent. + * + * \param[in] offset pointer to the field in an object at address 0. + */ + template<typename T, class B=typename std::remove_extent<T>::type> + inline _C2FieldId(T* offset) + : // offset is from "0" so will fit on 32-bits + _mOffset((uint32_t)(uintptr_t)(offset)), + _mSize(sizeof(B)) { } + + /** + * Direct constructor from offset and size. + * + * \param[in] offset offset of the field. + * \param[in] size size of the field. + */ + inline _C2FieldId(size_t offset, size_t size) + : _mOffset(offset), _mSize(size) {} + + /** + * Constructor used to identify a field in an object. + * + * \param U[type] pointer to the object that contains this field. This is needed in case the + * field is in an (inherited) base class, in which case T will be that base class. + * \param pm[im] member pointer to the field + */ + template<typename R, typename T, typename U, typename B=typename std::remove_extent<R>::type> + inline _C2FieldId(U *, R T::* pm) + : _mOffset((uint32_t)(uintptr_t)(&(((U*)256)->*pm)) - 256u), + _mSize(sizeof(B)) { } + + /** + * Constructor used to identify a field in an object. + * + * \param pm[im] member pointer to the field + */ + template<typename R, typename T, typename B=typename std::remove_extent<R>::type> + inline _C2FieldId(R T::* pm) + : _mOffset((uint32_t)(uintptr_t)(&(((T*)0)->*pm))), + _mSize(sizeof(B)) { } + + inline bool operator==(const _C2FieldId &other) const { + return _mOffset == other._mOffset && _mSize == other._mSize; + } + + inline bool operator<(const _C2FieldId &other) const { + return _mOffset < other._mOffset || + // NOTE: order parent structure before sub field + (_mOffset == other._mOffset && _mSize > other._mSize); + } + + DEFINE_OTHER_COMPARISON_OPERATORS(_C2FieldId) + +#if 0 + inline uint32_t offset() const { return _mOffset; } + inline uint32_t size() const { return _mSize; } +#endif + +#if defined(FRIEND_TEST) + friend void PrintTo(const _C2FieldId &d, ::std::ostream*); +#endif + +private: + friend struct _C2ParamInspector; + friend struct C2FieldDescriptor; + + uint32_t _mOffset; // offset of field + uint32_t _mSize; // size of field +}; + +/** + * Structure uniquely specifying a 'field' in a configuration. The field + * can be a field of a configuration, a subfield of a field of a configuration, + * and even the whole configuration. Moreover, if the field can point to an + * element in a array field, or to the entire array field. + * + * This structure is used for querying supported values for a field, as well + * as communicating configuration failures and conflicts when trying to change + * a configuration for a component/interface or a store. + */ +struct C2ParamField { +//public: + /** + * Create a field identifier using a configuration parameter (variable), + * and a pointer to member. + * + * ~~~~~~~~~~~~~ (.cpp) + * + * struct C2SomeParam { + * uint32_t mField; + * uint32_t mArray[2]; + * C2OtherStruct mStruct; + * uint32_t mFlexArray[]; + * } *mParam; + * + * C2ParamField(mParam, &mParam->mField); + * C2ParamField(mParam, &mParam->mArray); + * C2ParamField(mParam, &mParam->mArray[0]); + * C2ParamField(mParam, &mParam->mStruct.mSubField); + * C2ParamField(mParam, &mParam->mFlexArray); + * C2ParamField(mParam, &mParam->mFlexArray[2]); + * + * ~~~~~~~~~~~~~ + * + * \todo fix what this is for T[] (for now size becomes T[1]) + * + * \note this does not work for 64-bit members as it triggers a + * 'taking address of packed member' warning. + * + * \param param pointer to parameter + * \param offset member pointer + */ + template<typename S, typename T> + inline C2ParamField(S* param, T* offset) + : _mIndex(param->index()), + _mFieldId((T*)((uintptr_t)offset - (uintptr_t)param)) {} + + template<typename S, typename T> + inline static C2ParamField Make(S& param, T& offset) { + return C2ParamField(param.index(), (uintptr_t)&offset - (uintptr_t)¶m, sizeof(T)); + } + + /** + * Create a field identifier using a configuration parameter (variable), + * and a member pointer. This method cannot be used to refer to an + * array element or a subfield. + * + * ~~~~~~~~~~~~~ (.cpp) + * + * C2SomeParam mParam; + * C2ParamField(&mParam, &C2SomeParam::mMemberField); + * + * ~~~~~~~~~~~~~ + * + * \param p pointer to parameter + * \param T member pointer to the field member + */ + template<typename R, typename T, typename U> + inline C2ParamField(U *p, R T::* pm) : _mIndex(p->index()), _mFieldId(p, pm) { } + + /** + * Create a field identifier to a configuration parameter (variable). + * + * ~~~~~~~~~~~~~ (.cpp) + * + * C2SomeParam mParam; + * C2ParamField(&mParam); + * + * ~~~~~~~~~~~~~ + * + * \param param pointer to parameter + */ + template<typename S> + inline C2ParamField(S* param) + : _mIndex(param->index()), _mFieldId(0u, param->size()) { } + + /** Copy constructor. */ + inline C2ParamField(const C2ParamField &other) = default; + + /** + * Equality operator. + */ + inline bool operator==(const C2ParamField &other) const { + return _mIndex == other._mIndex && _mFieldId == other._mFieldId; + } + + /** + * Ordering operator. + */ + inline bool operator<(const C2ParamField &other) const { + return _mIndex < other._mIndex || + (_mIndex == other._mIndex && _mFieldId < other._mFieldId); + } + + DEFINE_OTHER_COMPARISON_OPERATORS(C2ParamField) + +protected: + inline C2ParamField(C2Param::Index index, uint32_t offset, uint32_t size) + : _mIndex(index), _mFieldId(offset, size) {} + +private: + friend struct _C2ParamInspector; + + C2Param::Index _mIndex; ///< parameter index + _C2FieldId _mFieldId; ///< field identifier +}; + +/** + * A shared (union) representation of numeric values + */ +class C2Value { +public: + /// A union of supported primitive types. + union Primitive { + // first member is always zero initialized so it must be the largest + uint64_t u64; ///< uint64_t value + int64_t i64; ///< int64_t value + c2_cntr64_t c64; ///< c2_cntr64_t value + uint32_t u32; ///< uint32_t value + int32_t i32; ///< int32_t value + c2_cntr32_t c32; ///< c2_cntr32_t value + float fp; ///< float value + + // constructors - implicit + Primitive(uint64_t value) : u64(value) { } + Primitive(int64_t value) : i64(value) { } + Primitive(c2_cntr64_t value) : c64(value) { } + Primitive(uint32_t value) : u32(value) { } + Primitive(int32_t value) : i32(value) { } + Primitive(c2_cntr32_t value) : c32(value) { } + Primitive(uint8_t value) : u32(value) { } + Primitive(char value) : i32(value) { } + Primitive(float value) : fp(value) { } + + // allow construction from enum type + template<typename E, typename = typename std::enable_if<std::is_enum<E>::value>::type> + Primitive(E value) + : Primitive(static_cast<typename std::underlying_type<E>::type>(value)) { } + + Primitive() : u64(0) { } + + /** gets value out of the union */ + template<typename T> const T &ref() const; + + // verify that we can assume standard aliasing + static_assert(sizeof(u64) == sizeof(i64), ""); + static_assert(sizeof(u64) == sizeof(c64), ""); + static_assert(sizeof(u32) == sizeof(i32), ""); + static_assert(sizeof(u32) == sizeof(c32), ""); + }; + // verify that we can assume standard aliasing + static_assert(offsetof(Primitive, u64) == offsetof(Primitive, i64), ""); + static_assert(offsetof(Primitive, u64) == offsetof(Primitive, c64), ""); + static_assert(offsetof(Primitive, u32) == offsetof(Primitive, i32), ""); + static_assert(offsetof(Primitive, u32) == offsetof(Primitive, c32), ""); + + enum type_t : uint32_t { + NO_INIT, + INT32, + UINT32, + CNTR32, + INT64, + UINT64, + CNTR64, + FLOAT, + }; + + template<typename T, bool = std::is_enum<T>::value> + inline static constexpr type_t TypeFor() { + using U = typename std::underlying_type<T>::type; + return TypeFor<U>(); + } + + // deprectated + template<typename T, bool B = std::is_enum<T>::value> + inline static constexpr type_t typeFor() { + return TypeFor<T, B>(); + } + + // constructors - implicit + template<typename T> + C2Value(T value) : _mType(typeFor<T>()), _mValue(value) { } + + C2Value() : _mType(NO_INIT) { } + + inline type_t type() const { return _mType; } + + template<typename T> + inline bool get(T *value) const { + if (_mType == typeFor<T>()) { + *value = _mValue.ref<T>(); + return true; + } + return false; + } + + /// returns the address of the value + void *get() const { + return _mType == NO_INIT ? nullptr : (void*)&_mValue; + } + + /// returns the size of the contained value + size_t inline sizeOf() const { + return SizeFor(_mType); + } + + static size_t SizeFor(type_t type) { + switch (type) { + case INT32: + case UINT32: + case CNTR32: return sizeof(_mValue.i32); + case INT64: + case UINT64: + case CNTR64: return sizeof(_mValue.i64); + case FLOAT: return sizeof(_mValue.fp); + default: return 0; + } + } + +private: + type_t _mType; + Primitive _mValue; +}; + +template<> inline const int32_t &C2Value::Primitive::ref<int32_t>() const { return i32; } +template<> inline const int64_t &C2Value::Primitive::ref<int64_t>() const { return i64; } +template<> inline const uint32_t &C2Value::Primitive::ref<uint32_t>() const { return u32; } +template<> inline const uint64_t &C2Value::Primitive::ref<uint64_t>() const { return u64; } +template<> inline const c2_cntr32_t &C2Value::Primitive::ref<c2_cntr32_t>() const { return c32; } +template<> inline const c2_cntr64_t &C2Value::Primitive::ref<c2_cntr64_t>() const { return c64; } +template<> inline const float &C2Value::Primitive::ref<float>() const { return fp; } + +// provide types for enums and uint8_t, char even though we don't provide reading as them +template<> constexpr C2Value::type_t C2Value::TypeFor<char, false>() { return INT32; } +template<> constexpr C2Value::type_t C2Value::TypeFor<int32_t, false>() { return INT32; } +template<> constexpr C2Value::type_t C2Value::TypeFor<int64_t, false>() { return INT64; } +template<> constexpr C2Value::type_t C2Value::TypeFor<uint8_t, false>() { return UINT32; } +template<> constexpr C2Value::type_t C2Value::TypeFor<uint32_t, false>() { return UINT32; } +template<> constexpr C2Value::type_t C2Value::TypeFor<uint64_t, false>() { return UINT64; } +template<> constexpr C2Value::type_t C2Value::TypeFor<c2_cntr32_t, false>() { return CNTR32; } +template<> constexpr C2Value::type_t C2Value::TypeFor<c2_cntr64_t, false>() { return CNTR64; } +template<> constexpr C2Value::type_t C2Value::TypeFor<float, false>() { return FLOAT; } + +// forward declare easy enum template +template<typename E> struct C2EasyEnum; + +/** + * field descriptor. A field is uniquely defined by an index into a parameter. + * (Note: Stream-id is not captured as a field.) + * + * Ordering of fields is by offset. In case of structures, it is depth first, + * with a structure taking an index just before and in addition to its members. + */ +struct C2FieldDescriptor { +//public: + /** field types and flags + * \note: only 32-bit and 64-bit fields are supported (e.g. no boolean, as that + * is represented using INT32). + */ + enum type_t : uint32_t { + // primitive types + INT32 = C2Value::INT32, ///< 32-bit signed integer + UINT32 = C2Value::UINT32, ///< 32-bit unsigned integer + CNTR32 = C2Value::CNTR32, ///< 32-bit counter + INT64 = C2Value::INT64, ///< 64-bit signed integer + UINT64 = C2Value::UINT64, ///< 64-bit signed integer + CNTR64 = C2Value::CNTR64, ///< 64-bit counter + FLOAT = C2Value::FLOAT, ///< 32-bit floating point + + // array types + STRING = 0x100, ///< fixed-size string (POD) + BLOB, ///< blob. Blobs have no sub-elements and can be thought of as byte arrays; + ///< however, bytes cannot be individually addressed by clients. + + // complex types + STRUCT_FLAG = 0x20000, ///< structs. Marked with this flag in addition to their coreIndex. + }; + + typedef std::pair<C2String, C2Value::Primitive> NamedValueType; + typedef std::vector<NamedValueType> NamedValuesType; + //typedef std::pair<std::vector<C2String>, std::vector<C2Value::Primitive>> NamedValuesType; + + /** + * Template specialization that returns the named values for a type. + * + * \todo hide from client. + * + * \return a vector of name-value pairs. + */ + template<typename B> + static NamedValuesType namedValuesFor(const B &); + + /** specialization for easy enums */ + template<typename E> + inline static NamedValuesType namedValuesFor(const C2EasyEnum<E> &) { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnull-dereference" + return namedValuesFor(*(E*)nullptr); +#pragma GCC diagnostic pop + } + +private: + template<typename B, bool enabled=std::is_arithmetic<B>::value || std::is_enum<B>::value> + struct C2_HIDE _NamedValuesGetter; + +public: + inline C2FieldDescriptor(uint32_t type, uint32_t extent, C2String name, size_t offset, size_t size) + : _mType((type_t)type), _mExtent(extent), _mName(name), _mFieldId(offset, size) { } + + inline C2FieldDescriptor(const C2FieldDescriptor &) = default; + + template<typename T, class B=typename std::remove_extent<T>::type> + inline C2FieldDescriptor(const T* offset, const char *name) + : _mType(this->GetType((B*)nullptr)), + _mExtent(std::is_array<T>::value ? std::extent<T>::value : 1), + _mName(name), + _mNamedValues(_NamedValuesGetter<B>::getNamedValues()), + _mFieldId(offset) {} + + /// \deprecated + template<typename T, typename S, class B=typename std::remove_extent<T>::type> + inline C2FieldDescriptor(S*, T S::* field, const char *name) + : _mType(this->GetType((B*)nullptr)), + _mExtent(std::is_array<T>::value ? std::extent<T>::value : 1), + _mName(name), + _mFieldId(&(((S*)0)->*field)) {} + + /// returns the type of this field + inline type_t type() const { return _mType; } + /// returns the length of the field in case it is an array. Returns 0 for + /// T[] arrays, returns 1 for T[1] arrays as well as if the field is not an array. + inline size_t extent() const { return _mExtent; } + /// returns the name of the field + inline C2String name() const { return _mName; } + + const NamedValuesType &namedValues() const { return _mNamedValues; } + +#if defined(FRIEND_TEST) + friend void PrintTo(const C2FieldDescriptor &, ::std::ostream*); + friend bool operator==(const C2FieldDescriptor &, const C2FieldDescriptor &); + FRIEND_TEST(C2ParamTest_ParamFieldList, VerifyStruct); +#endif + +private: + /** + * Construct an offseted field descriptor. + */ + inline C2FieldDescriptor(const C2FieldDescriptor &desc, size_t offset) + : _mType(desc._mType), _mExtent(desc._mExtent), + _mName(desc._mName), _mNamedValues(desc._mNamedValues), + _mFieldId(desc._mFieldId._mOffset + offset, desc._mFieldId._mSize) { } + + type_t _mType; + uint32_t _mExtent; // the last member can be arbitrary length if it is T[] array, + // extending to the end of the parameter (this is marked with + // 0). T[0]-s are not fields. + C2String _mName; + NamedValuesType _mNamedValues; + + _C2FieldId _mFieldId; // field identifier (offset and size) + + // NOTE: We do not capture default value(s) here as that may depend on the component. + // NOTE: We also do not capture bestEffort, as 1) this should be true for most fields, + // 2) this is at parameter granularity. + + // type resolution + inline static type_t GetType(int32_t*) { return INT32; } + inline static type_t GetType(uint32_t*) { return UINT32; } + inline static type_t GetType(c2_cntr32_t*) { return CNTR32; } + inline static type_t GetType(int64_t*) { return INT64; } + inline static type_t GetType(uint64_t*) { return UINT64; } + inline static type_t GetType(c2_cntr64_t*) { return CNTR64; } + inline static type_t GetType(float*) { return FLOAT; } + inline static type_t GetType(char*) { return STRING; } + inline static type_t GetType(uint8_t*) { return BLOB; } + + template<typename T, + class=typename std::enable_if<std::is_enum<T>::value>::type> + inline static type_t GetType(T*) { + typename std::underlying_type<T>::type underlying(0); + return GetType(&underlying); + } + + // verify C2Struct by having a FieldList() and a CORE_INDEX. + template<typename T, + class=decltype(T::CORE_INDEX + 1), class=decltype(T::FieldList())> + inline static type_t GetType(T*) { + static_assert(!std::is_base_of<C2Param, T>::value, "cannot use C2Params as fields"); + return (type_t)(T::CORE_INDEX | STRUCT_FLAG); + } + + friend struct _C2ParamInspector; +}; + +// no named values for compound types +template<typename B> +struct C2FieldDescriptor::_NamedValuesGetter<B, false> { + inline static C2FieldDescriptor::NamedValuesType getNamedValues() { + return NamedValuesType(); + } +}; + +template<typename B> +struct C2FieldDescriptor::_NamedValuesGetter<B, true> { + inline static C2FieldDescriptor::NamedValuesType getNamedValues() { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnull-dereference" + return C2FieldDescriptor::namedValuesFor(*(B*)nullptr); +#pragma GCC diagnostic pop + } +}; + +#define DEFINE_NO_NAMED_VALUES_FOR(type) \ +template<> inline C2FieldDescriptor::NamedValuesType C2FieldDescriptor::namedValuesFor(const type &) { \ + return NamedValuesType(); \ +} + +// We cannot subtype constructor for enumerated types so insted define no named values for +// non-enumerated integral types. +DEFINE_NO_NAMED_VALUES_FOR(int32_t) +DEFINE_NO_NAMED_VALUES_FOR(uint32_t) +DEFINE_NO_NAMED_VALUES_FOR(c2_cntr32_t) +DEFINE_NO_NAMED_VALUES_FOR(int64_t) +DEFINE_NO_NAMED_VALUES_FOR(uint64_t) +DEFINE_NO_NAMED_VALUES_FOR(c2_cntr64_t) +DEFINE_NO_NAMED_VALUES_FOR(uint8_t) +DEFINE_NO_NAMED_VALUES_FOR(char) +DEFINE_NO_NAMED_VALUES_FOR(float) + +/** + * Describes the fields of a structure. + */ +struct C2StructDescriptor { +public: + /// Returns the core index of the struct + inline C2Param::CoreIndex coreIndex() const { return _mType.coreIndex(); } + + // Returns the number of fields in this struct (not counting any recursive fields). + // Must be at least 1 for valid structs. + inline size_t numFields() const { return _mFields.size(); } + + // Returns the list of direct fields (not counting any recursive fields). + typedef std::vector<C2FieldDescriptor>::const_iterator field_iterator; + inline field_iterator cbegin() const { return _mFields.cbegin(); } + inline field_iterator cend() const { return _mFields.cend(); } + + // only supplying const iterator - but these names are needed for range based loops + inline field_iterator begin() const { return _mFields.cbegin(); } + inline field_iterator end() const { return _mFields.cend(); } + + template<typename T> + inline C2StructDescriptor(T*) + : C2StructDescriptor(T::CORE_INDEX, T::FieldList()) { } + + inline C2StructDescriptor( + C2Param::CoreIndex type, + const std::vector<C2FieldDescriptor> &fields) + : _mType(type), _mFields(fields) { } + +private: + friend struct _C2ParamInspector; + + inline C2StructDescriptor( + C2Param::CoreIndex type, + std::vector<C2FieldDescriptor> &&fields) + : _mType(type), _mFields(std::move(fields)) { } + + const C2Param::CoreIndex _mType; + const std::vector<C2FieldDescriptor> _mFields; +}; + +/** + * Describes parameters for a component. + */ +struct C2ParamDescriptor { +public: + /** + * Returns whether setting this param is required to configure this component. + * This can only be true for builtin params for platform-defined components (e.g. video and + * audio encoders/decoders, video/audio filters). + * For vendor-defined components, it can be true even for vendor-defined params, + * but it is not recommended, in case the component becomes platform-defined. + */ + inline bool isRequired() const { return _mAttrib & IS_REQUIRED; } + + /** + * Returns whether this parameter is persistent. This is always true for C2Tuning and C2Setting, + * but may be false for C2Info. If true, this parameter persists across frames and applies to + * the current and subsequent frames. If false, this C2Info parameter only applies to the + * current frame and is not assumed to have the same value (or even be present) on subsequent + * frames, unless it is specified for those frames. + */ + inline bool isPersistent() const { return _mAttrib & IS_PERSISTENT; } + + inline bool isStrict() const { return _mAttrib & IS_STRICT; } + + inline bool isReadOnly() const { return _mAttrib & IS_READ_ONLY; } + + inline bool isVisible() const { return !(_mAttrib & IS_HIDDEN); } + + inline bool isPublic() const { return !(_mAttrib & IS_INTERNAL); } + + /// Returns the name of this param. + /// This defaults to the underlying C2Struct's name, but could be altered for a component. + inline C2String name() const { return _mName; } + + /// Returns the parameter index + inline C2Param::Index index() const { return _mIndex; } + + /// Returns the indices of parameters that this parameter has a dependency on + inline const std::vector<C2Param::Index> &dependencies() const { return _mDependencies; } + + /// \deprecated + template<typename T> + inline C2ParamDescriptor(bool isRequired, C2StringLiteral name, const T*) + : _mIndex(T::PARAM_TYPE), + _mAttrib(IS_PERSISTENT | (isRequired ? IS_REQUIRED : 0)), + _mName(name) { } + + /// \deprecated + inline C2ParamDescriptor( + bool isRequired, C2StringLiteral name, C2Param::Index index) + : _mIndex(index), + _mAttrib(IS_PERSISTENT | (isRequired ? IS_REQUIRED : 0)), + _mName(name) { } + + enum attrib_t : uint32_t { + // flags that default on + IS_REQUIRED = 1u << 0, ///< parameter is required to be specified + IS_PERSISTENT = 1u << 1, ///< parameter retains its value + // flags that default off + IS_STRICT = 1u << 2, ///< parameter is strict + IS_READ_ONLY = 1u << 3, ///< parameter is publicly read-only + IS_HIDDEN = 1u << 4, ///< parameter shall not be visible to clients + IS_INTERNAL = 1u << 5, ///< parameter shall not be used by framework (other than testing) + IS_CONST = 1u << 6 | IS_READ_ONLY, ///< parameter is publicly const (hence read-only) + }; + + inline C2ParamDescriptor( + C2Param::Index index, attrib_t attrib, C2StringLiteral name) + : _mIndex(index), + _mAttrib(attrib), + _mName(name) { } + + inline C2ParamDescriptor( + C2Param::Index index, attrib_t attrib, C2String &&name, + std::vector<C2Param::Index> &&dependencies) + : _mIndex(index), + _mAttrib(attrib), + _mName(name), + _mDependencies(std::move(dependencies)) { } + +private: + const C2Param::Index _mIndex; + const uint32_t _mAttrib; + const C2String _mName; + std::vector<C2Param::Index> _mDependencies; + + friend struct _C2ParamInspector; +}; + +DEFINE_ENUM_OPERATORS(::C2ParamDescriptor::attrib_t) + + +/// \ingroup internal +/// Define a structure without CORE_INDEX. +/// \note _FIELD_LIST is used only during declaration so that C2Struct declarations can end with +/// a simple list of C2FIELD-s and closing bracket. Mark it unused as it is not used in templated +/// structs. +#define DEFINE_BASE_C2STRUCT(name) \ +private: \ + const static std::vector<C2FieldDescriptor> _FIELD_LIST __unused; /**< structure fields */ \ +public: \ + typedef C2##name##Struct _type; /**< type name shorthand */ \ + static const std::vector<C2FieldDescriptor> FieldList(); /**< structure fields factory */ + +/// Define a structure with matching CORE_INDEX. +#define DEFINE_C2STRUCT(name) \ +public: \ + enum : uint32_t { CORE_INDEX = kParamIndex##name }; \ + DEFINE_BASE_C2STRUCT(name) + +/// Define a flexible structure without CORE_INDEX. +#define DEFINE_BASE_FLEX_C2STRUCT(name, flexMember) \ +public: \ + FLEX(C2##name##Struct, flexMember) \ + DEFINE_BASE_C2STRUCT(name) + +/// Define a flexible structure with matching CORE_INDEX. +#define DEFINE_FLEX_C2STRUCT(name, flexMember) \ +public: \ + FLEX(C2##name##Struct, flexMember) \ + enum : uint32_t { CORE_INDEX = kParamIndex##name | C2Param::CoreIndex::IS_FLEX_FLAG }; \ + DEFINE_BASE_C2STRUCT(name) + +/// \ingroup internal +/// Describe a structure of a templated structure. +// Use list... as the argument gets resubsitituted and it contains commas. Alternative would be +// to wrap list in an expression, e.g. ({ std::vector<C2FieldDescriptor> list; })) which converts +// it from an initializer list to a vector. +#define DESCRIBE_TEMPLATED_C2STRUCT(strukt, list...) \ + _DESCRIBE_TEMPLATABLE_C2STRUCT(template<>, strukt, __C2_GENERATE_GLOBAL_VARS__, list) + +/// \deprecated +/// Describe the fields of a structure using an initializer list. +#define DESCRIBE_C2STRUCT(name, list...) \ + _DESCRIBE_TEMPLATABLE_C2STRUCT(, C2##name##Struct, __C2_GENERATE_GLOBAL_VARS__, list) + +/// \ingroup internal +/// Macro layer to get value of enabled that is passed in as a macro variable +#define _DESCRIBE_TEMPLATABLE_C2STRUCT(template, strukt, enabled, list...) \ + __DESCRIBE_TEMPLATABLE_C2STRUCT(template, strukt, enabled, list) + +/// \ingroup internal +/// Macro layer to resolve to the specific macro based on macro variable +#define __DESCRIBE_TEMPLATABLE_C2STRUCT(template, strukt, enabled, list...) \ + ___DESCRIBE_TEMPLATABLE_C2STRUCT##enabled(template, strukt, list) + +#define ___DESCRIBE_TEMPLATABLE_C2STRUCT(template, strukt, list...) \ + template \ + const std::vector<C2FieldDescriptor> strukt::FieldList() { return list; } + +#define ___DESCRIBE_TEMPLATABLE_C2STRUCT__C2_GENERATE_GLOBAL_VARS__(template, strukt, list...) + +/** + * Describe a field of a structure. + * These must be in order. + * + * There are two ways to use this macro: + * + * ~~~~~~~~~~~~~ (.cpp) + * struct C2VideoWidthStruct { + * int32_t width; + * C2VideoWidthStruct() {} // optional default constructor + * C2VideoWidthStruct(int32_t _width) : width(_width) {} + * + * DEFINE_AND_DESCRIBE_C2STRUCT(VideoWidth) + * C2FIELD(width, "width") + * }; + * ~~~~~~~~~~~~~ + * + * ~~~~~~~~~~~~~ (.cpp) + * struct C2VideoWidthStruct { + * int32_t width; + * C2VideoWidthStruct() = default; // optional default constructor + * C2VideoWidthStruct(int32_t _width) : width(_width) {} + * + * DEFINE_C2STRUCT(VideoWidth) + * } C2_PACK; + * + * DESCRIBE_C2STRUCT(VideoWidth, { + * C2FIELD(width, "width") + * }) + * ~~~~~~~~~~~~~ + * + * For flexible structures (those ending in T[]), use the flexible macros: + * + * ~~~~~~~~~~~~~ (.cpp) + * struct C2VideoFlexWidthsStruct { + * int32_t widths[]; + * C2VideoFlexWidthsStruct(); // must have a default constructor + * + * private: + * // may have private constructors taking number of widths as the first argument + * // This is used by the C2Param factory methods, e.g. + * // C2VideoFlexWidthsGlobalParam::AllocUnique(size_t, int32_t); + * C2VideoFlexWidthsStruct(size_t flexCount, int32_t value) { + * for (size_t i = 0; i < flexCount; ++i) { + * widths[i] = value; + * } + * } + * + * // If the last argument is T[N] or std::initializer_list<T>, the flexCount will + * // be automatically calculated and passed by the C2Param factory methods, e.g. + * // int widths[] = { 1, 2, 3 }; + * // C2VideoFlexWidthsGlobalParam::AllocUnique(widths); + * template<unsigned N> + * C2VideoFlexWidthsStruct(size_t flexCount, const int32_t(&init)[N]) { + * for (size_t i = 0; i < flexCount; ++i) { + * widths[i] = init[i]; + * } + * } + * + * DEFINE_AND_DESCRIBE_FLEX_C2STRUCT(VideoFlexWidths, widths) + * C2FIELD(widths, "widths") + * }; + * ~~~~~~~~~~~~~ + * + * ~~~~~~~~~~~~~ (.cpp) + * struct C2VideoFlexWidthsStruct { + * int32_t mWidths[]; + * C2VideoFlexWidthsStruct(); // must have a default constructor + * + * DEFINE_FLEX_C2STRUCT(VideoFlexWidths, mWidths) + * } C2_PACK; + * + * DESCRIBE_C2STRUCT(VideoFlexWidths, { + * C2FIELD(mWidths, "widths") + * }) + * ~~~~~~~~~~~~~ + * + */ +#define DESCRIBE_C2FIELD(member, name) \ + C2FieldDescriptor(&((_type*)(nullptr))->member, name), + +#define C2FIELD(member, name) _C2FIELD(member, name, __C2_GENERATE_GLOBAL_VARS__) +/// \if 0 +#define _C2FIELD(member, name, enabled) __C2FIELD(member, name, enabled) +#define __C2FIELD(member, name, enabled) DESCRIBE_C2FIELD##enabled(member, name) +#define DESCRIBE_C2FIELD__C2_GENERATE_GLOBAL_VARS__(member, name) +/// \endif + +/// Define a structure with matching CORE_INDEX and start describing its fields. +/// This must be at the end of the structure definition. +#define DEFINE_AND_DESCRIBE_C2STRUCT(name) \ + _DEFINE_AND_DESCRIBE_C2STRUCT(name, DEFINE_C2STRUCT, __C2_GENERATE_GLOBAL_VARS__) + +/// Define a base structure (with no CORE_INDEX) and start describing its fields. +/// This must be at the end of the structure definition. +#define DEFINE_AND_DESCRIBE_BASE_C2STRUCT(name) \ + _DEFINE_AND_DESCRIBE_C2STRUCT(name, DEFINE_BASE_C2STRUCT, __C2_GENERATE_GLOBAL_VARS__) + +/// Define a flexible structure with matching CORE_INDEX and start describing its fields. +/// This must be at the end of the structure definition. +#define DEFINE_AND_DESCRIBE_FLEX_C2STRUCT(name, flexMember) \ + _DEFINE_AND_DESCRIBE_FLEX_C2STRUCT( \ + name, flexMember, DEFINE_FLEX_C2STRUCT, __C2_GENERATE_GLOBAL_VARS__) + +/// Define a flexible base structure (with no CORE_INDEX) and start describing its fields. +/// This must be at the end of the structure definition. +#define DEFINE_AND_DESCRIBE_BASE_FLEX_C2STRUCT(name, flexMember) \ + _DEFINE_AND_DESCRIBE_FLEX_C2STRUCT( \ + name, flexMember, DEFINE_BASE_FLEX_C2STRUCT, __C2_GENERATE_GLOBAL_VARS__) + +/// \if 0 +/* + Alternate declaration of field definitions in case no field list is to be generated. + The specific macro is chosed based on the value of __C2_GENERATE_GLOBAL_VARS__ (whether it is + defined (to be empty) or not. This requires two level of macro substitution. + TRICKY: use namespace declaration to handle closing bracket that is normally after + these macros. +*/ + +#define _DEFINE_AND_DESCRIBE_C2STRUCT(name, defineMacro, enabled) \ + __DEFINE_AND_DESCRIBE_C2STRUCT(name, defineMacro, enabled) +#define __DEFINE_AND_DESCRIBE_C2STRUCT(name, defineMacro, enabled) \ + ___DEFINE_AND_DESCRIBE_C2STRUCT##enabled(name, defineMacro) +#define ___DEFINE_AND_DESCRIBE_C2STRUCT__C2_GENERATE_GLOBAL_VARS__(name, defineMacro) \ + defineMacro(name) } C2_PACK; namespace { +#define ___DEFINE_AND_DESCRIBE_C2STRUCT(name, defineMacro) \ + defineMacro(name) } C2_PACK; \ + const std::vector<C2FieldDescriptor> C2##name##Struct::FieldList() { return _FIELD_LIST; } \ + const std::vector<C2FieldDescriptor> C2##name##Struct::_FIELD_LIST = { + +#define _DEFINE_AND_DESCRIBE_FLEX_C2STRUCT(name, flexMember, defineMacro, enabled) \ + __DEFINE_AND_DESCRIBE_FLEX_C2STRUCT(name, flexMember, defineMacro, enabled) +#define __DEFINE_AND_DESCRIBE_FLEX_C2STRUCT(name, flexMember, defineMacro, enabled) \ + ___DEFINE_AND_DESCRIBE_FLEX_C2STRUCT##enabled(name, flexMember, defineMacro) +#define ___DEFINE_AND_DESCRIBE_FLEX_C2STRUCT__C2_GENERATE_GLOBAL_VARS__(name, flexMember, defineMacro) \ + defineMacro(name, flexMember) } C2_PACK; namespace { +#define ___DEFINE_AND_DESCRIBE_FLEX_C2STRUCT(name, flexMember, defineMacro) \ + defineMacro(name, flexMember) } C2_PACK; \ + const std::vector<C2FieldDescriptor> C2##name##Struct::FieldList() { return _FIELD_LIST; } \ + const std::vector<C2FieldDescriptor> C2##name##Struct::_FIELD_LIST = { +/// \endif + + +/** + * Parameter reflector class. + * + * This class centralizes the description of parameter structures. This can be shared + * by multiple components as describing a parameter does not imply support of that + * parameter. However, each supported parameter and any dependent structures within + * must be described by the parameter reflector provided by a component. + */ +class C2ParamReflector { +public: + /** + * Describes a parameter structure. + * + * \param[in] coreIndex the core index of the parameter structure containing at least the + * core index + * + * \return the description of the parameter structure + * \retval nullptr if the parameter is not supported by this reflector + * + * This methods shall not block and return immediately. + * + * \note this class does not take a set of indices because we would then prefer + * to also return any dependent structures, and we don't want this logic to be + * repeated in each reflector. Alternately, this could just return a map of all + * descriptions, but we want to conserve memory if client only wants the description + * of a few indices. + */ + virtual std::unique_ptr<C2StructDescriptor> describe(C2Param::CoreIndex coreIndex) const = 0; + +protected: + virtual ~C2ParamReflector() = default; +}; + +/** + * Generic supported values for a field. + * + * This can be either a range or a set of values. The range can be a simple range, an arithmetic, + * geometric or multiply-accumulate series with a clear minimum and maximum value. Values can + * be discrete values, or can optionally represent flags to be or-ed. + * + * \note Do not use flags to represent bitfields. Use individual values or separate fields instead. + */ +struct C2FieldSupportedValues { +//public: + enum type_t { + EMPTY, ///< no supported values + RANGE, ///< a numeric range that can be continuous or discrete + VALUES, ///< a list of values + FLAGS ///< a list of flags that can be OR-ed + }; + + type_t type; /** Type of values for this field. */ + + typedef C2Value::Primitive Primitive; + + /** + * Range specifier for supported value. Used if type is RANGE. + * + * If step is 0 and num and denom are both 1, the supported values are any value, for which + * min <= value <= max. + * + * Otherwise, the range represents a geometric/arithmetic/multiply-accumulate series, where + * successive supported values can be derived from previous values (starting at min), using the + * following formula: + * v[0] = min + * v[i] = v[i-1] * num / denom + step for i >= 1, while min < v[i] <= max. + */ + struct { + /** Lower end of the range (inclusive). */ + Primitive min; + /** Upper end of the range (inclusive if permitted by series). */ + Primitive max; + /** Step between supported values. */ + Primitive step; + /** Numerator of a geometric series. */ + Primitive num; + /** Denominator of a geometric series. */ + Primitive denom; + } range; + + /** + * List of values. Used if type is VALUES or FLAGS. + * + * If type is VALUES, this is the list of supported values in decreasing preference. + * + * If type is FLAGS, this vector contains { min-mask, flag1, flag2... }. Basically, the first + * value is the required set of flags to be set, and the rest of the values are flags that can + * be set independently. FLAGS is only supported for integral types. Supported flags should + * not overlap, as it can make validation non-deterministic. The standard validation method + * is that starting from the original value, if each flag is removed when fully present (the + * min-mask must be fully present), we shall arrive at 0. + */ + std::vector<Primitive> values; + + C2FieldSupportedValues() + : type(EMPTY) { + } + + template<typename T> + C2FieldSupportedValues(T min, T max, T step = T(std::is_floating_point<T>::value ? 0 : 1)) + : type(RANGE), + range{min, max, step, (T)1, (T)1} { } + + template<typename T> + C2FieldSupportedValues(T min, T max, T num, T den) : + type(RANGE), + range{min, max, (T)0, num, den} { } + + template<typename T> + C2FieldSupportedValues(T min, T max, T step, T num, T den) + : type(RANGE), + range{min, max, step, num, den} { } + + /// \deprecated + template<typename T> + C2FieldSupportedValues(bool flags, std::initializer_list<T> list) + : type(flags ? FLAGS : VALUES), + range{(T)0, (T)0, (T)0, (T)0, (T)0} { + for (T value : list) { + values.emplace_back(value); + } + } + + /// \deprecated + template<typename T> + C2FieldSupportedValues(bool flags, const std::vector<T>& list) + : type(flags ? FLAGS : VALUES), + range{(T)0, (T)0, (T)0, (T)0, (T)0} { + for(T value : list) { + values.emplace_back(value); + } + } + + /// \internal + /// \todo: create separate values vs. flags initializer as for flags we want + /// to list both allowed and required flags +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnull-dereference" + template<typename T, typename E=decltype(C2FieldDescriptor::namedValuesFor(*(T*)nullptr))> + C2FieldSupportedValues(bool flags, const T*) + : type(flags ? FLAGS : VALUES), + range{(T)0, (T)0, (T)0, (T)0, (T)0} { + C2FieldDescriptor::NamedValuesType named = C2FieldDescriptor::namedValuesFor(*(T*)nullptr); + if (flags) { + values.emplace_back(0); // min-mask defaults to 0 + } + for (const C2FieldDescriptor::NamedValueType &item : named){ + values.emplace_back(item.second); + } + } +}; +#pragma GCC diagnostic pop + +/** + * Supported values for a specific field. + * + * This is a pair of the field specifier together with an optional supported values object. + * This structure is used when reporting parameter configuration failures and conflicts. + */ +struct C2ParamFieldValues { + C2ParamField paramOrField; ///< the field or parameter + /// optional supported values for the field if paramOrField specifies an actual field that is + /// numeric (non struct, blob or string). Supported values for arrays (including string and + /// blobs) describe the supported values for each element (character for string, and bytes for + /// blobs). It is optional for read-only strings and blobs. + std::unique_ptr<C2FieldSupportedValues> values; + + // This struct is meant to be move constructed. + C2_DEFAULT_MOVE(C2ParamFieldValues); + + // Copy constructor/assignment is also provided as this object may get copied. + C2ParamFieldValues(const C2ParamFieldValues &other) + : paramOrField(other.paramOrField), + values(other.values ? std::make_unique<C2FieldSupportedValues>(*other.values) : nullptr) { } + + C2ParamFieldValues& operator=(const C2ParamFieldValues &other) { + paramOrField = other.paramOrField; + values = other.values ? std::make_unique<C2FieldSupportedValues>(*other.values) : nullptr; + return *this; + } + + + /** + * Construct with no values. + */ + C2ParamFieldValues(const C2ParamField ¶mOrField_) + : paramOrField(paramOrField_) { } + + /** + * Construct with values. + */ + C2ParamFieldValues(const C2ParamField ¶mOrField_, const C2FieldSupportedValues &values_) + : paramOrField(paramOrField_), + values(std::make_unique<C2FieldSupportedValues>(values_)) { } + + /** + * Construct from fields. + */ + C2ParamFieldValues(const C2ParamField ¶mOrField_, std::unique_ptr<C2FieldSupportedValues> &&values_) + : paramOrField(paramOrField_), + values(std::move(values_)) { } +}; + +/// @} + +// include debug header for C2Params.h if C2Debug.h was already included +#ifdef C2UTILS_DEBUG_H_ +#include <util/C2Debug-param.h> +#endif + +#endif // C2PARAM_H_
diff --git a/media/codec2/core/include/C2ParamDef.h b/media/codec2/core/include/C2ParamDef.h new file mode 100644 index 0000000..0a33283 --- /dev/null +++ b/media/codec2/core/include/C2ParamDef.h
@@ -0,0 +1,931 @@ +/* + * Copyright (C) 2016 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. + */ + +/** \file + * Templates used to declare parameters. + */ +#ifndef C2PARAM_DEF_H_ +#define C2PARAM_DEF_H_ + +#include <type_traits> + +#include <C2Param.h> + +/// \addtogroup Parameters +/// @{ + +/* ======================== UTILITY TEMPLATES FOR PARAMETER DEFINITIONS ======================== */ + +/// \addtogroup internal +/// @{ + +/// Helper class that checks if a type has equality and inequality operators. +struct C2_HIDE _C2Comparable_impl +{ + template<typename S, typename=decltype(S() == S())> + static std::true_type TestEqual(int); + template<typename> + static std::false_type TestEqual(...); + + template<typename S, typename=decltype(S() != S())> + static std::true_type TestNotEqual(int); + template<typename> + static std::false_type TestNotEqual(...); +}; + +/** + * Helper template that returns if a type has equality and inequality operators. + * + * Use as _C2Comparable<typename S>::value. + */ +template<typename S> +struct C2_HIDE _C2Comparable + : public std::integral_constant<bool, decltype(_C2Comparable_impl::TestEqual<S>(0))::value + || decltype(_C2Comparable_impl::TestNotEqual<S>(0))::value> { +}; + +/// Helper class that checks if a type has a CORE_INDEX constant. +struct C2_HIDE _C2CoreIndexHelper_impl +{ + template<typename S, int=S::CORE_INDEX> + static std::true_type TestCoreIndex(int); + template<typename> + static std::false_type TestCoreIndex(...); +}; + +/// Macro that defines and thus overrides a type's CORE_INDEX for a setting +#define _C2_CORE_INDEX_OVERRIDE(coreIndex) \ +public: \ + enum : uint32_t { CORE_INDEX = coreIndex }; + + +/// Helper template that adds a CORE_INDEX to a type if it does not have one (for testing) +template<typename S, int CoreIndex> +struct C2_HIDE _C2AddCoreIndex : public S { + _C2_CORE_INDEX_OVERRIDE(CoreIndex) +}; + +/** + * \brief Helper class to check struct requirements for parameters. + * + * Features: + * - verify default constructor, no virtual methods, and no equality operators. + * - expose PARAM_TYPE, and non-flex FLEX_SIZE. + */ +template<typename S, int CoreIndex, unsigned TypeFlags> +struct C2_HIDE _C2StructCheck { + static_assert( + std::is_default_constructible<S>::value, "C2 structure must have default constructor"); + static_assert(!std::is_polymorphic<S>::value, "C2 structure must not have virtual methods"); + static_assert(!_C2Comparable<S>::value, "C2 structure must not have operator== or !="); + +public: + enum : uint32_t { + PARAM_TYPE = CoreIndex | TypeFlags + }; + +protected: + enum : uint32_t { + FLEX_SIZE = 0, + }; +}; + +/// Helper class that checks if a type has an integer FLEX_SIZE member. +struct C2_HIDE _C2Flexible_impl { + /// specialization for types that have a FLEX_SIZE member + template<typename S, unsigned=S::FLEX_SIZE> + static std::true_type TestFlexSize(int); + template<typename> + static std::false_type TestFlexSize(...); +}; + +/// Helper template that returns if a type has an integer FLEX_SIZE member. +template<typename S> +struct C2_HIDE _C2Flexible + : public std::integral_constant<bool, decltype(_C2Flexible_impl::TestFlexSize<S>(0))::value> { +}; + +/// Macro to test if a type is flexible (has a FLEX_SIZE member). +#define IF_FLEXIBLE(S) ENABLE_IF(_C2Flexible<S>::value) +/// Shorthand for std::enable_if +#define ENABLE_IF(cond) typename std::enable_if<cond>::type + +template<typename T, typename V=void> +struct C2_HIDE _c2_enable_if_type { + typedef V type; +}; + +/// Helper template that exposes the flexible subtype of a struct. +template<typename S, typename E=void> +struct C2_HIDE _C2FlexHelper { + typedef void FlexType; + enum : uint32_t { FLEX_SIZE = 0 }; +}; + +/// Specialization for flexible types. This only works if _FlexMemberType is public. +template<typename S> +struct C2_HIDE _C2FlexHelper<S, + typename _c2_enable_if_type<typename S::_FlexMemberType>::type> { + typedef typename _C2FlexHelper<typename S::_FlexMemberType>::FlexType FlexType; + enum : uint32_t { FLEX_SIZE = _C2FlexHelper<typename S::_FlexMemberType>::FLEX_SIZE }; +}; + +/// Specialization for flex arrays. +template<typename S> +struct C2_HIDE _C2FlexHelper<S[], + typename std::enable_if<std::is_void<typename _C2FlexHelper<S>::FlexType>::value>::type> { + typedef S FlexType; + enum : uint32_t { FLEX_SIZE = sizeof(S) }; +}; + +/** + * \brief Helper class to check flexible struct requirements and add common operations. + * + * Features: + * - expose CORE_INDEX and FieldList (this is normally inherited from the struct, but flexible + * structs cannot be base classes and thus inherited from) + * - disable copy assignment and construction (TODO: this is already done in the FLEX macro for the + * flexible struct, so may not be needed here) + */ +template<typename S, int ParamIndex, unsigned TypeFlags> +struct C2_HIDE _C2FlexStructCheck : +// add flexible flag as _C2StructCheck defines PARAM_TYPE + public _C2StructCheck<S, ParamIndex | C2Param::CoreIndex::IS_FLEX_FLAG, TypeFlags> { +public: + enum : uint32_t { + /// \hideinitializer + CORE_INDEX = ParamIndex | C2Param::CoreIndex::IS_FLEX_FLAG, ///< flexible struct core-index + }; + + inline static const std::vector<C2FieldDescriptor> FieldList() { return S::FieldList(); } + + // default constructor needed because of the disabled copy constructor + inline _C2FlexStructCheck() = default; + +protected: + // cannot copy flexible params + _C2FlexStructCheck(const _C2FlexStructCheck<S, ParamIndex, TypeFlags> &) = delete; + _C2FlexStructCheck& operator= (const _C2FlexStructCheck<S, ParamIndex, TypeFlags> &) = delete; + + // constants used for helper methods + enum : uint32_t { + /// \hideinitializer + FLEX_SIZE = _C2FlexHelper<S>::FLEX_SIZE, ///< size of flexible type + /// \hideinitializer + MAX_SIZE = (uint32_t)std::min((size_t)UINT32_MAX, SIZE_MAX), // TODO: is this always u32 max? + /// \hideinitializer + BASE_SIZE = sizeof(S) + sizeof(C2Param), ///< size of the base param + }; + + /// returns the allocated size of this param with flexCount, or 0 if it would overflow. + inline static size_t CalcSize(size_t flexCount, size_t size = BASE_SIZE) { + if (flexCount <= (MAX_SIZE - size) / S::FLEX_SIZE) { + return size + S::FLEX_SIZE * flexCount; + } + return 0; + } + + /// dynamic new operator usable for params of type S + inline void* operator new(size_t size, size_t flexCount) noexcept { + // TODO: assert(size == BASE_SIZE); + size = CalcSize(flexCount, size); + if (size > 0) { + return ::operator new(size); + } + return nullptr; + } +}; + +/// Define From() cast operators for params. +#define DEFINE_CAST_OPERATORS(_Type) \ + inline static _Type* From(C2Param *other) { \ + return (_Type*)C2Param::IfSuitable( \ + other, sizeof(_Type), _Type::PARAM_TYPE, _Type::FLEX_SIZE, \ + (_Type::PARAM_TYPE & T::Index::DIR_UNDEFINED) != T::Index::DIR_UNDEFINED); \ + } \ + inline static const _Type* From(const C2Param *other) { \ + return const_cast<const _Type*>(From(const_cast<C2Param *>(other))); \ + } \ + inline static _Type* From(std::nullptr_t) { return nullptr; } \ + +/** + * Define flexible allocators (AllocShared or AllocUnique) for flexible params. + * - P::AllocXyz(flexCount, args...): allocate for given flex-count. This maps to + * T(flexCount, args...)\ + * + * Clang does not support args... followed by templated param as args... eats it. Hence, + * provide specializations where the initializer replaces the flexCount. + * + * Specializations that deduce flexCount: + * - P::AllocXyz(T[], args...): allocate for size of (and with) init array. + * - P::AllocXyz(std::initializer_list<T>, args...): allocate for size of (and with) initializer + * list. + * - P::AllocXyz(std::vector<T>, args...): allocate for size of (and with) init vector. + * These specializations map to T(flexCount = size-of-init, args..., init) + */ +#define DEFINE_FLEXIBLE_ALLOC(_Type, S, ptr, Ptr) \ + template<typename ...Args> \ + inline static std::ptr##_ptr<_Type> Alloc##Ptr(size_t flexCount, const Args(&... args)) { \ + return std::ptr##_ptr<_Type>(new(flexCount) _Type(flexCount, args...)); \ + } \ + template<typename ...Args, typename U=typename S::FlexType> \ + inline static std::ptr##_ptr<_Type> Alloc##Ptr( \ + const std::initializer_list<U> &init, const Args(&... args)) { \ + return std::ptr##_ptr<_Type>(new(init.size()) _Type(init.size(), args..., init)); \ + } \ + template<typename ...Args, typename U=typename S::FlexType> \ + inline static std::ptr##_ptr<_Type> Alloc##Ptr( \ + const std::vector<U> &init, const Args(&... args)) { \ + return std::ptr##_ptr<_Type>(new(init.size()) _Type(init.size(), args..., init)); \ + } \ + template<typename ...Args, typename U=typename S::FlexType, unsigned N> \ + inline static std::ptr##_ptr<_Type> Alloc##Ptr(const U(&init)[N], const Args(&... args)) { \ + return std::ptr##_ptr<_Type>(new(N) _Type(N, args..., init)); \ + } \ + +/** + * Define flexible methods AllocShared, AllocUnique and flexCount. + */ +#define DEFINE_FLEXIBLE_METHODS(_Type, S) \ + DEFINE_FLEXIBLE_ALLOC(_Type, S, shared, Shared) \ + DEFINE_FLEXIBLE_ALLOC(_Type, S, unique, Unique) \ + inline size_t flexCount() const { \ + static_assert(sizeof(_Type) == _Type::BASE_SIZE, "incorrect BASE_SIZE"); \ + size_t sz = this->size(); \ + if (sz >= sizeof(_Type)) { \ + return (sz - sizeof(_Type)) / _Type::FLEX_SIZE; \ + } \ + return 0; \ + } \ + +/// Mark flexible member variable and make structure flexible. +#define FLEX(cls, m) \ + C2_DO_NOT_COPY(cls) \ +private: \ + C2PARAM_MAKE_FRIENDS \ + /** \if 0 */ \ + template<typename, typename> friend struct _C2FlexHelper; \ +public: \ + typedef decltype(m) _FlexMemberType; \ + /* default constructor with flexCount */ \ + inline cls(size_t) : cls() {} \ + /* constexpr static _FlexMemberType cls::* flexMember = &cls::m; */ \ + typedef typename _C2FlexHelper<_FlexMemberType>::FlexType FlexType; \ + static_assert(\ + !std::is_void<FlexType>::value, \ + "member is not flexible, or a flexible array of a flexible type"); \ + enum : uint32_t { FLEX_SIZE = _C2FlexHelper<_FlexMemberType>::FLEX_SIZE }; \ + /** \endif */ \ + +/// @} + +/** + * Global-parameter template. + * + * Base template to define a global setting/tuning or info based on a structure and + * an optional ParamIndex. Global parameters are not tied to a port (input or output). + * + * Parameters wrap structures by prepending a (parameter) header. The fields of the wrapped + * structure can be accessed directly, and constructors and potential public methods are also + * wrapped. + * + * \tparam T param type C2Setting, C2Tuning or C2Info + * \tparam S wrapped structure + * \tparam ParamIndex optional parameter index override. Must be specified for base/reused + * structures. + */ +template<typename T, typename S, int ParamIndex=S::CORE_INDEX, class Flex=void> +struct C2_HIDE C2GlobalParam : public T, public S, + public _C2StructCheck<S, ParamIndex, T::PARAM_KIND | T::Type::DIR_GLOBAL> { + _C2_CORE_INDEX_OVERRIDE(ParamIndex) +private: + typedef C2GlobalParam<T, S, ParamIndex> _Type; + +public: + /// Wrapper around base structure's constructor. + template<typename ...Args> + inline C2GlobalParam(const Args(&... args)) : T(sizeof(_Type), _Type::PARAM_TYPE), S(args...) { } + + DEFINE_CAST_OPERATORS(_Type) +}; + +/** + * Global-parameter template for flexible structures. + * + * Base template to define a global setting/tuning or info based on a flexible structure and + * an optional ParamIndex. Global parameters are not tied to a port (input or output). + * + * \tparam T param type C2Setting, C2Tuning or C2Info + * \tparam S wrapped flexible structure + * \tparam ParamIndex optional parameter index override. Must be specified for base/reused + * structures. + * + * Parameters wrap structures by prepending a (parameter) header. The fields and methods of flexible + * structures can be accessed via the m member variable; however, the constructors of the structure + * are wrapped directly. (This is because flexible types cannot be subclassed.) + */ +template<typename T, typename S, int ParamIndex> +struct C2_HIDE C2GlobalParam<T, S, ParamIndex, IF_FLEXIBLE(S)> + : public T, public _C2FlexStructCheck<S, ParamIndex, T::PARAM_KIND | T::Type::DIR_GLOBAL> { +private: + typedef C2GlobalParam<T, S, ParamIndex> _Type; + + /// Wrapper around base structure's constructor. + template<typename ...Args> + inline C2GlobalParam(size_t flexCount, const Args(&... args)) + : T(_Type::CalcSize(flexCount), _Type::PARAM_TYPE), m(flexCount, args...) { } + +public: + S m; ///< wrapped flexible structure + + DEFINE_FLEXIBLE_METHODS(_Type, S) + DEFINE_CAST_OPERATORS(_Type) +}; + +/** + * Port-parameter template. + * + * Base template to define a port setting/tuning or info based on a structure and + * an optional ParamIndex. Port parameters are tied to a port (input or output), but not to a + * specific stream. + * + * \tparam T param type C2Setting, C2Tuning or C2Info + * \tparam S wrapped structure + * \tparam ParamIndex optional parameter index override. Must be specified for base/reused + * structures. + * + * Parameters wrap structures by prepending a (parameter) header. The fields of the wrapped + * structure can be accessed directly, and constructors and potential public methods are also + * wrapped. + * + * There are 3 flavors of port parameters: unspecified, input and output. Parameters with + * unspecified port expose a setPort method, and add an initial port parameter to the constructor. + */ +template<typename T, typename S, int ParamIndex=S::CORE_INDEX, class Flex=void> +struct C2_HIDE C2PortParam : public T, public S, + private _C2StructCheck<S, ParamIndex, T::PARAM_KIND | T::Index::DIR_UNDEFINED> { + _C2_CORE_INDEX_OVERRIDE(ParamIndex) +private: + typedef C2PortParam<T, S, ParamIndex> _Type; + +public: + /// Default constructor. + inline C2PortParam() : T(sizeof(_Type), _Type::PARAM_TYPE) { } + template<typename ...Args> + /// Wrapper around base structure's constructor while specifying port/direction. + inline C2PortParam(bool _output, const Args(&... args)) + : T(sizeof(_Type), _output ? output::PARAM_TYPE : input::PARAM_TYPE), S(args...) { } + /// Set port/direction. + inline void setPort(bool output) { C2Param::setPort(output); } + + DEFINE_CAST_OPERATORS(_Type) + + /// Specialization for an input port parameter. + struct input : public T, public S, + public _C2StructCheck<S, ParamIndex, T::PARAM_KIND | T::Index::DIR_INPUT> { + _C2_CORE_INDEX_OVERRIDE(ParamIndex) + /// Wrapper around base structure's constructor. + template<typename ...Args> + inline input(const Args(&... args)) : T(sizeof(_Type), input::PARAM_TYPE), S(args...) { } + + DEFINE_CAST_OPERATORS(input) + + }; + + /// Specialization for an output port parameter. + struct output : public T, public S, + public _C2StructCheck<S, ParamIndex, T::PARAM_KIND | T::Index::DIR_OUTPUT> { + _C2_CORE_INDEX_OVERRIDE(ParamIndex) + /// Wrapper around base structure's constructor. + template<typename ...Args> + inline output(const Args(&... args)) : T(sizeof(_Type), output::PARAM_TYPE), S(args...) { } + + DEFINE_CAST_OPERATORS(output) + }; +}; + +/** + * Port-parameter template for flexible structures. + * + * Base template to define a port setting/tuning or info based on a flexible structure and + * an optional ParamIndex. Port parameters are tied to a port (input or output), but not to a + * specific stream. + * + * \tparam T param type C2Setting, C2Tuning or C2Info + * \tparam S wrapped flexible structure + * \tparam ParamIndex optional parameter index override. Must be specified for base/reused + * structures. + * + * Parameters wrap structures by prepending a (parameter) header. The fields and methods of flexible + * structures can be accessed via the m member variable; however, the constructors of the structure + * are wrapped directly. (This is because flexible types cannot be subclassed.) + * + * There are 3 flavors of port parameters: unspecified, input and output. Parameters with + * unspecified port expose a setPort method, and add an initial port parameter to the constructor. + */ +template<typename T, typename S, int ParamIndex> +struct C2_HIDE C2PortParam<T, S, ParamIndex, IF_FLEXIBLE(S)> + : public T, public _C2FlexStructCheck<S, ParamIndex, T::PARAM_KIND | T::Type::DIR_UNDEFINED> { +private: + typedef C2PortParam<T, S, ParamIndex> _Type; + + /// Default constructor for basic allocation: new(flexCount) P. + inline C2PortParam(size_t flexCount) : T(_Type::CalcSize(flexCount), _Type::PARAM_TYPE) { } + template<typename ...Args> + /// Wrapper around base structure's constructor while also specifying port/direction. + inline C2PortParam(size_t flexCount, bool _output, const Args(&... args)) + : T(_Type::CalcSize(flexCount), _output ? output::PARAM_TYPE : input::PARAM_TYPE), + m(flexCount, args...) { } + +public: + /// Set port/direction. + inline void setPort(bool output) { C2Param::setPort(output); } + + S m; ///< wrapped flexible structure + + DEFINE_FLEXIBLE_METHODS(_Type, S) + DEFINE_CAST_OPERATORS(_Type) + + /// Specialization for an input port parameter. + struct input : public T, + public _C2FlexStructCheck<S, ParamIndex, T::PARAM_KIND | T::Index::DIR_INPUT> { + private: + /// Wrapper around base structure's constructor while also specifying port/direction. + template<typename ...Args> + inline input(size_t flexCount, const Args(&... args)) + : T(_Type::CalcSize(flexCount), input::PARAM_TYPE), m(flexCount, args...) { } + + public: + S m; ///< wrapped flexible structure + + DEFINE_FLEXIBLE_METHODS(input, S) + DEFINE_CAST_OPERATORS(input) + }; + + /// Specialization for an output port parameter. + struct output : public T, + public _C2FlexStructCheck<S, ParamIndex, T::PARAM_KIND | T::Index::DIR_OUTPUT> { + private: + /// Wrapper around base structure's constructor while also specifying port/direction. + template<typename ...Args> + inline output(size_t flexCount, const Args(&... args)) + : T(_Type::CalcSize(flexCount), output::PARAM_TYPE), m(flexCount, args...) { } + + public: + S m; ///< wrapped flexible structure + + DEFINE_FLEXIBLE_METHODS(output, S) + DEFINE_CAST_OPERATORS(output) + }; +}; + +/** + * Stream-parameter template. + * + * Base template to define a stream setting/tuning or info based on a structure and + * an optional ParamIndex. Stream parameters are tied to a specific stream on a port (input or + * output). + * + * \tparam T param type C2Setting, C2Tuning or C2Info + * \tparam S wrapped structure + * \tparam ParamIndex optional paramter index override. Must be specified for base/reused + * structures. + * + * Parameters wrap structures by prepending a (parameter) header. The fields of the wrapped + * structure can be accessed directly, and constructors and potential public methods are also + * wrapped. + * + * There are 3 flavors of stream parameters: unspecified port, input and output. All of these expose + * a setStream method and an extra initial streamID parameter for the constructor. Moreover, + * parameters with unspecified port expose a setPort method, and add an additional initial port + * parameter to the constructor. + */ +template<typename T, typename S, int ParamIndex=S::CORE_INDEX, class Flex=void> +struct C2_HIDE C2StreamParam : public T, public S, + private _C2StructCheck<S, ParamIndex, + T::PARAM_KIND | T::Index::IS_STREAM_FLAG | T::Index::DIR_UNDEFINED> { + _C2_CORE_INDEX_OVERRIDE(ParamIndex) +private: + typedef C2StreamParam<T, S, ParamIndex> _Type; + +public: + /// Default constructor. Port/direction and stream-ID is undefined. + inline C2StreamParam() : T(sizeof(_Type), _Type::PARAM_TYPE) { } + /// Wrapper around base structure's constructor while also specifying port/direction and + /// stream-ID. + template<typename ...Args> + inline C2StreamParam(bool _output, unsigned stream, const Args(&... args)) + : T(sizeof(_Type), _output ? output::PARAM_TYPE : input::PARAM_TYPE, stream), + S(args...) { } + /// Set port/direction. + inline void setPort(bool output) { C2Param::setPort(output); } + /// Set stream-id. \retval true if the stream-id was successfully set. + inline bool setStream(unsigned stream) { return C2Param::setStream(stream); } + + DEFINE_CAST_OPERATORS(_Type) + + /// Specialization for an input stream parameter. + struct input : public T, public S, + public _C2StructCheck<S, ParamIndex, + T::PARAM_KIND | T::Index::IS_STREAM_FLAG | T::Type::DIR_INPUT> { + _C2_CORE_INDEX_OVERRIDE(ParamIndex) + + /// Default constructor. Stream-ID is undefined. + inline input() : T(sizeof(_Type), input::PARAM_TYPE) { } + /// Wrapper around base structure's constructor while also specifying stream-ID. + template<typename ...Args> + inline input(unsigned stream, const Args(&... args)) + : T(sizeof(_Type), input::PARAM_TYPE, stream), S(args...) { } + /// Set stream-id. \retval true if the stream-id was successfully set. + inline bool setStream(unsigned stream) { return C2Param::setStream(stream); } + + DEFINE_CAST_OPERATORS(input) + }; + + /// Specialization for an output stream parameter. + struct output : public T, public S, + public _C2StructCheck<S, ParamIndex, + T::PARAM_KIND | T::Index::IS_STREAM_FLAG | T::Type::DIR_OUTPUT> { + _C2_CORE_INDEX_OVERRIDE(ParamIndex) + + /// Default constructor. Stream-ID is undefined. + inline output() : T(sizeof(_Type), output::PARAM_TYPE) { } + /// Wrapper around base structure's constructor while also specifying stream-ID. + template<typename ...Args> + inline output(unsigned stream, const Args(&... args)) + : T(sizeof(_Type), output::PARAM_TYPE, stream), S(args...) { } + /// Set stream-id. \retval true if the stream-id was successfully set. + inline bool setStream(unsigned stream) { return C2Param::setStream(stream); } + + DEFINE_CAST_OPERATORS(output) + }; +}; + +/** + * Stream-parameter template for flexible structures. + * + * Base template to define a stream setting/tuning or info based on a flexible structure and + * an optional ParamIndex. Stream parameters are tied to a specific stream on a port (input or + * output). + * + * \tparam T param type C2Setting, C2Tuning or C2Info + * \tparam S wrapped flexible structure + * \tparam ParamIndex optional parameter index override. Must be specified for base/reused + * structures. + * + * Parameters wrap structures by prepending a (parameter) header. The fields and methods of flexible + * structures can be accessed via the m member variable; however, the constructors of the structure + * are wrapped directly. (This is because flexible types cannot be subclassed.) + * + * There are 3 flavors of stream parameters: unspecified port, input and output. All of these expose + * a setStream method and an extra initial streamID parameter for the constructor. Moreover, + * parameters with unspecified port expose a setPort method, and add an additional initial port + * parameter to the constructor. + */ +template<typename T, typename S, int ParamIndex> +struct C2_HIDE C2StreamParam<T, S, ParamIndex, IF_FLEXIBLE(S)> + : public T, + public _C2FlexStructCheck<S, ParamIndex, + T::PARAM_KIND | T::Index::IS_STREAM_FLAG | T::Index::DIR_UNDEFINED> { +private: + typedef C2StreamParam<T, S, ParamIndex> _Type; + /// Default constructor. Port/direction and stream-ID is undefined. + inline C2StreamParam(size_t flexCount) : T(_Type::CalcSize(flexCount), _Type::PARAM_TYPE, 0u) { } + /// Wrapper around base structure's constructor while also specifying port/direction and + /// stream-ID. + template<typename ...Args> + inline C2StreamParam(size_t flexCount, bool _output, unsigned stream, const Args(&... args)) + : T(_Type::CalcSize(flexCount), _output ? output::PARAM_TYPE : input::PARAM_TYPE, stream), + m(flexCount, args...) { } + +public: + S m; ///< wrapped flexible structure + + /// Set port/direction. + inline void setPort(bool output) { C2Param::setPort(output); } + /// Set stream-id. \retval true if the stream-id was successfully set. + inline bool setStream(unsigned stream) { return C2Param::setStream(stream); } + + DEFINE_FLEXIBLE_METHODS(_Type, S) + DEFINE_CAST_OPERATORS(_Type) + + /// Specialization for an input stream parameter. + struct input : public T, + public _C2FlexStructCheck<S, ParamIndex, + T::PARAM_KIND | T::Index::IS_STREAM_FLAG | T::Type::DIR_INPUT> { + private: + /// Default constructor. Stream-ID is undefined. + inline input(size_t flexCount) : T(_Type::CalcSize(flexCount), input::PARAM_TYPE) { } + /// Wrapper around base structure's constructor while also specifying stream-ID. + template<typename ...Args> + inline input(size_t flexCount, unsigned stream, const Args(&... args)) + : T(_Type::CalcSize(flexCount), input::PARAM_TYPE, stream), m(flexCount, args...) { } + + public: + S m; ///< wrapped flexible structure + + /// Set stream-id. \retval true if the stream-id was successfully set. + inline bool setStream(unsigned stream) { return C2Param::setStream(stream); } + + DEFINE_FLEXIBLE_METHODS(input, S) + DEFINE_CAST_OPERATORS(input) + }; + + /// Specialization for an output stream parameter. + struct output : public T, + public _C2FlexStructCheck<S, ParamIndex, + T::PARAM_KIND | T::Index::IS_STREAM_FLAG | T::Type::DIR_OUTPUT> { + private: + /// Default constructor. Stream-ID is undefined. + inline output(size_t flexCount) : T(_Type::CalcSize(flexCount), output::PARAM_TYPE) { } + /// Wrapper around base structure's constructor while also specifying stream-ID. + template<typename ...Args> + inline output(size_t flexCount, unsigned stream, const Args(&... args)) + : T(_Type::CalcSize(flexCount), output::PARAM_TYPE, stream), m(flexCount, args...) { } + + public: + S m; ///< wrapped flexible structure + + /// Set stream-id. \retval true if the stream-id was successfully set. + inline bool setStream(unsigned stream) { return C2Param::setStream(stream); } + + DEFINE_FLEXIBLE_METHODS(output, S) + DEFINE_CAST_OPERATORS(output) + }; +}; + +/* ======================== SIMPLE VALUE PARAMETERS ======================== */ + +/** + * \ingroup internal + * A structure template encapsulating a single element with default constructors and no core-index. + */ +template<typename T> +struct C2SimpleValueStruct { + T value; ///< simple value of the structure + // Default constructor. + inline C2SimpleValueStruct() = default; + // Constructor with an initial value. + inline C2SimpleValueStruct(T value) : value(value) {} + DEFINE_BASE_C2STRUCT(SimpleValue) +}; + +// TODO: move this and next to some generic place +/** + * Interface to a block of (mapped) memory containing an array of some type (T). + */ +template<typename T> +struct C2MemoryBlock { + /// \returns the number of elements in this block. + virtual size_t size() const = 0; + /// \returns a const pointer to the start of this block. Care must be taken to not read outside + /// the block. + virtual const T *data() const = 0; // TODO: should this be friend access only in some C2Memory module? + /// \returns a pointer to the start of this block. Care must be taken to not read or write + /// outside the block. + inline T *data() { return const_cast<T*>(const_cast<const C2MemoryBlock*>(this)->data()); } + +protected: + // TODO: for now it should never be deleted as C2MemoryBlock + virtual ~C2MemoryBlock() = default; +}; + +/** + * Interface to a block of memory containing a constant (constexpr) array of some type (T). + */ +template<typename T> +struct C2ConstMemoryBlock : public C2MemoryBlock<T> { + virtual const T * data() const { return _mData; } + virtual size_t size() const { return _mSize; } + + /// Constructor. + template<unsigned N> + inline constexpr C2ConstMemoryBlock(const T(&init)[N]) : _mData(init), _mSize(N) {} + +private: + const T *_mData; + const size_t _mSize; +}; + +/// \addtogroup internal +/// @{ + +/// Helper class to initialize flexible arrays with various initalizers. +struct _C2ValueArrayHelper { + // char[]-s are used as null terminated strings, so the last element is never inited. + + /// Initialize a flexible array using a constexpr memory block. + template<typename T> + static void init(T(&array)[], size_t arrayLen, const C2MemoryBlock<T> &block) { + // reserve last element for terminal 0 for strings + if (arrayLen && std::is_same<T, char>::value) { + --arrayLen; + } + if (block.data()) { + memcpy(array, block.data(), std::min(arrayLen, block.size()) * sizeof(T)); + } + } + + /// Initialize a flexible array using an initializer list. + template<typename T> + static void init(T(&array)[], size_t arrayLen, const std::initializer_list<T> &init) { + size_t ix = 0; + // reserve last element for terminal 0 for strings + if (arrayLen && std::is_same<T, char>::value) { + --arrayLen; + } + for (const T &item : init) { + if (ix == arrayLen) { + break; + } + array[ix++] = item; + } + } + + /// Initialize a flexible array using a vector. + template<typename T> + static void init(T(&array)[], size_t arrayLen, const std::vector<T> &init) { + size_t ix = 0; + // reserve last element for terminal 0 for strings + if (arrayLen && std::is_same<T, char>::value) { + --arrayLen; + } + for (const T &item : init) { + if (ix == arrayLen) { + break; + } + array[ix++] = item; + } + } + + /// Initialize a flexible array using another flexible array. + template<typename T, unsigned N> + static void init(T(&array)[], size_t arrayLen, const T(&str)[N]) { + // reserve last element for terminal 0 for strings + if (arrayLen && std::is_same<T, char>::value) { + --arrayLen; + } + if (arrayLen) { + memcpy(array, str, std::min(arrayLen, (size_t)N) * sizeof(T)); + } + } +}; + +/** + * Specialization for a flexible blob and string arrays. A structure template encapsulating a single + * flexible array member with default flexible constructors and no core-index. This type cannot be + * constructed on its own as it's size is 0. + * + * \internal This is different from C2SimpleArrayStruct<T[]> simply because its member has the name + * as value to reflect this is a single value. + */ +template<typename T> +struct C2SimpleValueStruct<T[]> { + static_assert(std::is_same<T, char>::value || std::is_same<T, uint8_t>::value, + "C2SimpleValueStruct<T[]> is only for BLOB or STRING"); + T value[]; + + inline C2SimpleValueStruct() = default; + DEFINE_BASE_C2STRUCT(SimpleValue) + FLEX(C2SimpleValueStruct, value) + +private: + inline C2SimpleValueStruct(size_t flexCount, const C2MemoryBlock<T> &block) { + _C2ValueArrayHelper::init(value, flexCount, block); + } + + inline C2SimpleValueStruct(size_t flexCount, const std::initializer_list<T> &init) { + _C2ValueArrayHelper::init(value, flexCount, init); + } + + inline C2SimpleValueStruct(size_t flexCount, const std::vector<T> &init) { + _C2ValueArrayHelper::init(value, flexCount, init); + } + + template<unsigned N> + inline C2SimpleValueStruct(size_t flexCount, const T(&init)[N]) { + _C2ValueArrayHelper::init(value, flexCount, init); + } +}; + +/// @} + +/** + * A structure template encapsulating a single flexible array element of a specific type (T) with + * default constructors and no core-index. This type cannot be constructed on its own as it's size + * is 0. Instead, it is meant to be used as a parameter, e.g. + * + * typedef C2StreamParam<C2Info, C2SimpleArrayStruct<C2MyFancyStruct>, + * kParamIndexMyFancyArrayStreamParam> C2MyFancyArrayStreamInfo; + */ +template<typename T> +struct C2SimpleArrayStruct { + static_assert(!std::is_same<T, char>::value && !std::is_same<T, uint8_t>::value, + "use C2SimpleValueStruct<T[]> is for BLOB or STRING"); + + T values[]; ///< array member + /// Default constructor + inline C2SimpleArrayStruct() = default; + DEFINE_BASE_FLEX_C2STRUCT(SimpleArray, values) + //FLEX(C2SimpleArrayStruct, values) + +private: + /// Construct from a C2MemoryBlock. + /// Used only by the flexible parameter allocators (AllocUnique & AllocShared). + inline C2SimpleArrayStruct(size_t flexCount, const C2MemoryBlock<T> &block) { + _C2ValueArrayHelper::init(values, flexCount, block); + } + + /// Construct from an initializer list. + /// Used only by the flexible parameter allocators (AllocUnique & AllocShared). + inline C2SimpleArrayStruct(size_t flexCount, const std::initializer_list<T> &init) { + _C2ValueArrayHelper::init(values, flexCount, init); + } + + /// Construct from an vector. + /// Used only by the flexible parameter allocators (AllocUnique & AllocShared). + inline C2SimpleArrayStruct(size_t flexCount, const std::vector<T> &init) { + _C2ValueArrayHelper::init(values, flexCount, init); + } + + /// Construct from another flexible array. + /// Used only by the flexible parameter allocators (AllocUnique & AllocShared). + template<unsigned N> + inline C2SimpleArrayStruct(size_t flexCount, const T(&init)[N]) { + _C2ValueArrayHelper::init(values, flexCount, init); + } +}; + +/** + * \addtogroup simplevalue Simple value and array structures. + * @{ + * + * Simple value structures. + * + * Structures containing a single simple value. These can be reused to easily define simple + * parameters of various types: + * + * typedef C2PortParam<C2Tuning, C2Int32Value, kParamIndexMyIntegerPortParam> + * C2MyIntegerPortParamTuning; + * + * They contain a single member (value or values) that is described as "value" or "values". + * + * These structures don't define a core index, and as such, they cannot be used in structure + * declarations. Use type[] instead, such as int32_t field[]. + */ +/// A 32-bit signed integer parameter in value, described as "value" +typedef C2SimpleValueStruct<int32_t> C2Int32Value; +/// A 32-bit signed integer array parameter in values, described as "values" +typedef C2SimpleArrayStruct<int32_t> C2Int32Array; +/// A 32-bit unsigned integer parameter in value, described as "value" +typedef C2SimpleValueStruct<uint32_t> C2Uint32Value; +/// A 32-bit unsigned integer array parameter in values, described as "values" +typedef C2SimpleArrayStruct<uint32_t> C2Uint32Array; +/// A 64-bit signed integer parameter in value, described as "value" +typedef C2SimpleValueStruct<int64_t> C2Int64Value; +/// A 64-bit signed integer array parameter in values, described as "values" +typedef C2SimpleArrayStruct<int64_t> C2Int64Array; +/// A 64-bit unsigned integer parameter in value, described as "value" +typedef C2SimpleValueStruct<uint64_t> C2Uint64Value; +/// A 64-bit unsigned integer array parameter in values, described as "values" +typedef C2SimpleArrayStruct<uint64_t> C2Uint64Array; +/// A float parameter in value, described as "value" +typedef C2SimpleValueStruct<float> C2FloatValue; +/// A float array parameter in values, described as "values" +typedef C2SimpleArrayStruct<float> C2FloatArray; +/// A blob flexible parameter in value, described as "value" +typedef C2SimpleValueStruct<uint8_t[]> C2BlobValue; +/// A string flexible parameter in value, described as "value" +typedef C2SimpleValueStruct<char[]> C2StringValue; + +template<typename T> +const std::vector<C2FieldDescriptor> C2SimpleValueStruct<T>::FieldList() { + return { DESCRIBE_C2FIELD(value, "value") }; +} +template<typename T> +const std::vector<C2FieldDescriptor> C2SimpleValueStruct<T[]>::FieldList() { + return { DESCRIBE_C2FIELD(value, "value") }; +} +template<typename T> +const std::vector<C2FieldDescriptor> C2SimpleArrayStruct<T>::FieldList() { + return { DESCRIBE_C2FIELD(values, "values") }; +} + +/// @} + +/// @} + +#endif // C2PARAM_DEF_H_
diff --git a/media/codec2/core/include/C2Work.h b/media/codec2/core/include/C2Work.h new file mode 100644 index 0000000..6923f3e --- /dev/null +++ b/media/codec2/core/include/C2Work.h
@@ -0,0 +1,235 @@ +/* + * Copyright (C) 2016 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 C2WORK_H_ + +#define C2WORK_H_ + +#include <C2Buffer.h> +#include <C2Param.h> + +#include <memory> +#include <list> +#include <vector> + +#include <stdint.h> +#include <stdbool.h> + +/// \defgroup work Work and data processing +/// @{ + +/** + * Information describing the reason a parameter settings may fail, or + * may be overriden. + */ +struct C2SettingResult { + enum Failure : uint32_t { + /* parameter failures below */ + BAD_TYPE, ///< parameter is not supported + BAD_PORT, ///< parameter is not supported on the specific port + BAD_INDEX, ///< parameter is not supported on the specific stream + READ_ONLY, ///< parameter is read-only and cannot be set + MISMATCH, ///< parameter mismatches input data + + /* field failures below */ + BAD_VALUE, ///< strict parameter does not accept value for the field at all + CONFLICT, ///< strict parameter field value is in conflict with an/other setting(s) + + /// parameter field is out of range due to other settings (this failure mode + /// can only be used for strict calculated parameters) + UNSUPPORTED, + + /// field does not access the requested parameter value at all. It has been corrected to + /// the closest supported value. This failure mode is provided to give guidance as to what + /// are the currently supported values for this field (which may be a subset of the at-all- + /// potential values) + INFO_BAD_VALUE, + + /// requested parameter value is in conflict with an/other setting(s) + /// and has been corrected to the closest supported value. This failure + /// mode is given to provide guidance as to what are the currently supported values as well + /// as to optionally provide suggestion to the client as to how to enable the requested + /// parameter value. + INFO_CONFLICT, + }; + + Failure failure; ///< failure code + + /// Failing (or corrected) field or parameterand optionally, currently supported values for the + /// field. Values must only be set for field failures other than BAD_VALUE, and only if they are + /// different from the globally supported values (e.g. due to restrictions by another param or + /// input data). + C2ParamFieldValues field; + + /// Conflicting parameters or fields with optional suggestions with (optional) suggested values + /// for any conflicting fields to avoid the conflict. Must only be set for CONFLICT, UNSUPPORTED + /// and INFO_CONFLICT failure codes. + std::vector<C2ParamFieldValues> conflicts; +}; + +// ================================================================================================ +// WORK +// ================================================================================================ + +/** Unique ID for a processing node. */ +typedef uint32_t c2_node_id_t; + +enum { + kParamIndexWorkOrdinal, +}; + +/** + * Information for ordering work items on a component port. + */ +struct C2WorkOrdinalStruct { +//public: + c2_cntr64_t timestamp; /** frame timestamp in microseconds */ + c2_cntr64_t frameIndex; /** submission ordinal on the initial component */ + c2_cntr64_t customOrdinal; /** can be given by the component, e.g. decode order */ + + DEFINE_AND_DESCRIBE_C2STRUCT(WorkOrdinal) + C2FIELD(timestamp, "timestamp") + C2FIELD(frameIndex, "frame-index") + C2FIELD(customOrdinal, "custom-ordinal") +}; + +/** + * This structure represents a Codec 2.0 frame with its metadata. + * + * A frame basically consists of an ordered sets of buffers, configuration changes and info buffers + * along with some non-configuration metadata. + */ +struct C2FrameData { +//public: + enum flags_t : uint32_t { + /** + * For input frames: no output frame shall be generated when processing this frame, but + * metadata shall still be processed. + * For output frames: this frame shall be discarded and but metadata is still valid. + */ + FLAG_DROP_FRAME = (1 << 0), + /** + * This frame is the last frame of the current stream. Further frames are part of a new + * stream. + */ + FLAG_END_OF_STREAM = (1 << 1), + /** + * This frame shall be discarded with its metadata. + * This flag is only set by components - e.g. as a response to the flush command. + */ + FLAG_DISCARD_FRAME = (1 << 2), + /** + * This frame is not the last frame produced for the input. + * + * This flag is normally set by the component - e.g. when an input frame results in multiple + * output frames, this flag is set on all but the last output frame. + * + * Also, when components are chained, this flag should be propagated down the + * work chain. That is, if set on an earlier frame of a work-chain, it should be propagated + * to all later frames in that chain. Additionally, components down the chain could set + * this flag even if not set earlier, e.g. if multiple output frame is generated at that + * component for the input frame. + */ + FLAG_INCOMPLETE = (1 << 3), + /** + * This frame contains only codec-specific configuration data, and no actual access unit. + * + * \deprecated pass codec configuration with using the \todo codec-specific configuration + * info together with the access unit. + */ + FLAG_CODEC_CONFIG = (1u << 31), + }; + + /** + * Frame flags */ + flags_t flags; + C2WorkOrdinalStruct ordinal; + std::vector<std::shared_ptr<C2Buffer>> buffers; + //< for initial work item, these may also come from the parser - if provided + //< for output buffers, these are the responses to requestedInfos + std::vector<std::unique_ptr<C2Param>> configUpdate; + std::vector<std::shared_ptr<C2InfoBuffer>> infoBuffers; +}; + +struct C2Worklet { +//public: + // IN + c2_node_id_t component; + + /** Configuration changes to be applied before processing this worklet. */ + std::vector<std::unique_ptr<C2Tuning>> tunings; + std::vector<std::unique_ptr<C2SettingResult>> failures; + + // OUT + C2FrameData output; +}; + +/** + * Information about partial work-chains not part of the current work items. + * + * To be defined later. + */ +struct C2WorkChainInfo; + +/** + * This structure holds information about all a single work item. + * + * This structure shall be passed by the client to the component for the first worklet. As such, + * worklets must not be empty. The ownership of this object is passed. + */ +struct C2Work { +//public: + /// additional work chain info not part of this work + std::shared_ptr<C2WorkChainInfo> chainInfo; + + /// The input data to be processed as part of this work/work-chain. This is provided by the + /// client with ownership. When the work is returned (via onWorkDone), the input buffer-pack's + /// buffer vector shall contain nullptrs. + C2FrameData input; + + /// The chain of components, tunings (including output buffer pool IDs) and info requests that the + /// data must pass through. If this has more than a single element, the tunnels between successive + /// components of the worklet chain must have been (successfully) pre-registered at the time that + /// the work is submitted. Allocating the output buffers in the worklets is the responsibility of + /// each component. Upon work submission, each output buffer-pack shall be an appropriately sized + /// vector containing nullptrs. When the work is completed/returned to the client, output buffers + /// pointers from all but the final worklet shall be nullptrs. + std::list<std::unique_ptr<C2Worklet>> worklets; + + /// Number of worklets successfully processed in this chain. This shall be initialized to 0 by the + /// client when the work is submitted. It shall contain the number of worklets that were + /// successfully processed when the work is returned to the client. If this is less then the number + /// of worklets, result must not be success. It must be in the range of [0, worklets.size()]. + uint32_t workletsProcessed; + + /// The final outcome of the work (corresponding to the current workletsProcessed). If 0 when + /// work is returned, it is assumed that all worklets have been processed. + c2_status_t result; +}; + +/** + * Information about a future work to be submitted to the component. The information is used to + * reserve the work for work ordering purposes. + */ +struct C2WorkOutline { +//public: + C2WorkOrdinalStruct ordinal; + std::vector<c2_node_id_t> chain; +}; + +/// @} + +#endif // C2WORK_H_
diff --git a/media/codec2/core/include/_C2MacroUtils.h b/media/codec2/core/include/_C2MacroUtils.h new file mode 100644 index 0000000..04e9ba5 --- /dev/null +++ b/media/codec2/core/include/_C2MacroUtils.h
@@ -0,0 +1,162 @@ +/* + * Copyright (C) 2016 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 C2UTILS_MACRO_UTILS_H_ +#define C2UTILS_MACRO_UTILS_H_ + +/** \file + * Macro utilities for the utils library used by Codec2 implementations. + */ + +/// \if 0 + +/* --------------------------------- VARIABLE ARGUMENT COUNTING --------------------------------- */ + +// remove empty arguments - _C2_ARG() expands to '', while _C2_ARG(x) expands to ', x' +// _C2_ARGn(...) does the same for n arguments +#define _C2_ARG(...) , ##__VA_ARGS__ +#define _C2_ARG2(_1, _2) _C2_ARG(_1) _C2_ARG(_2) +#define _C2_ARG4(_1, _2, _3, _4) _C2_ARG2(_1, _2) _C2_ARG2(_3, _4) +#define _C2_ARG8(_1, _2, _3, _4, _5, _6, _7, _8) _C2_ARG4(_1, _2, _3, _4) _C2_ARG4(_5, _6, _7, _8) +#define _C2_ARG16(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16) \ + _C2_ARG8(_1, _2, _3, _4, _5, _6, _7, _8) _C2_ARG8(_9, _10, _11, _12, _13, _14, _15, _16) + +// return the 65th argument +#define _C2_ARGC_3(_, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, \ + _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, \ + _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, \ + _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, _62, _63, _64, ...) _64 + +/// \endif + +/** + * Returns the number of arguments. + */ +// We do this by prepending 1 and appending 65 designed values such that the 65th element +// will be the number of arguments. +#define _C2_ARGC(...) _C2_ARGC_1(0, ##__VA_ARGS__, \ + 64, 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, \ + 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, \ + 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + +/// \if 0 + +// step 1. remove empty arguments - this is needed to allow trailing comma in enum definitions +// (NOTE: we don't know which argument will have this trailing comma so we have to try all) +#define _C2_ARGC_1(_, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, \ + _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, \ + _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, \ + _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, _62, _63, _64, ...) \ + _C2_ARGC_2(_ _C2_ARG(_0) \ + _C2_ARG16(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16) \ + _C2_ARG16(_17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32) \ + _C2_ARG16(_33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48) \ + _C2_ARG16(_49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, _62, _63, _64), \ + ##__VA_ARGS__) + +// step 2. this is needed as removed arguments cannot be passed directly as empty into a macro +#define _C2_ARGC_2(...) _C2_ARGC_3(__VA_ARGS__) + +/// \endif + +/* -------------------------------- VARIABLE ARGUMENT CONVERSION -------------------------------- */ + +/// \if 0 + +// macros that convert _1, _2, _3, ... to fn(_1, arg), fn(_2, arg), fn(_3, arg), ... +#define _C2_MAP_64(fn, arg, head, ...) fn(head, arg), _C2_MAP_63(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_63(fn, arg, head, ...) fn(head, arg), _C2_MAP_62(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_62(fn, arg, head, ...) fn(head, arg), _C2_MAP_61(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_61(fn, arg, head, ...) fn(head, arg), _C2_MAP_60(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_60(fn, arg, head, ...) fn(head, arg), _C2_MAP_59(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_59(fn, arg, head, ...) fn(head, arg), _C2_MAP_58(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_58(fn, arg, head, ...) fn(head, arg), _C2_MAP_57(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_57(fn, arg, head, ...) fn(head, arg), _C2_MAP_56(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_56(fn, arg, head, ...) fn(head, arg), _C2_MAP_55(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_55(fn, arg, head, ...) fn(head, arg), _C2_MAP_54(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_54(fn, arg, head, ...) fn(head, arg), _C2_MAP_53(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_53(fn, arg, head, ...) fn(head, arg), _C2_MAP_52(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_52(fn, arg, head, ...) fn(head, arg), _C2_MAP_51(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_51(fn, arg, head, ...) fn(head, arg), _C2_MAP_50(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_50(fn, arg, head, ...) fn(head, arg), _C2_MAP_49(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_49(fn, arg, head, ...) fn(head, arg), _C2_MAP_48(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_48(fn, arg, head, ...) fn(head, arg), _C2_MAP_47(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_47(fn, arg, head, ...) fn(head, arg), _C2_MAP_46(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_46(fn, arg, head, ...) fn(head, arg), _C2_MAP_45(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_45(fn, arg, head, ...) fn(head, arg), _C2_MAP_44(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_44(fn, arg, head, ...) fn(head, arg), _C2_MAP_43(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_43(fn, arg, head, ...) fn(head, arg), _C2_MAP_42(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_42(fn, arg, head, ...) fn(head, arg), _C2_MAP_41(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_41(fn, arg, head, ...) fn(head, arg), _C2_MAP_40(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_40(fn, arg, head, ...) fn(head, arg), _C2_MAP_39(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_39(fn, arg, head, ...) fn(head, arg), _C2_MAP_38(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_38(fn, arg, head, ...) fn(head, arg), _C2_MAP_37(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_37(fn, arg, head, ...) fn(head, arg), _C2_MAP_36(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_36(fn, arg, head, ...) fn(head, arg), _C2_MAP_35(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_35(fn, arg, head, ...) fn(head, arg), _C2_MAP_34(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_34(fn, arg, head, ...) fn(head, arg), _C2_MAP_33(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_33(fn, arg, head, ...) fn(head, arg), _C2_MAP_32(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_32(fn, arg, head, ...) fn(head, arg), _C2_MAP_31(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_31(fn, arg, head, ...) fn(head, arg), _C2_MAP_30(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_30(fn, arg, head, ...) fn(head, arg), _C2_MAP_29(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_29(fn, arg, head, ...) fn(head, arg), _C2_MAP_28(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_28(fn, arg, head, ...) fn(head, arg), _C2_MAP_27(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_27(fn, arg, head, ...) fn(head, arg), _C2_MAP_26(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_26(fn, arg, head, ...) fn(head, arg), _C2_MAP_25(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_25(fn, arg, head, ...) fn(head, arg), _C2_MAP_24(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_24(fn, arg, head, ...) fn(head, arg), _C2_MAP_23(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_23(fn, arg, head, ...) fn(head, arg), _C2_MAP_22(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_22(fn, arg, head, ...) fn(head, arg), _C2_MAP_21(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_21(fn, arg, head, ...) fn(head, arg), _C2_MAP_20(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_20(fn, arg, head, ...) fn(head, arg), _C2_MAP_19(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_19(fn, arg, head, ...) fn(head, arg), _C2_MAP_18(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_18(fn, arg, head, ...) fn(head, arg), _C2_MAP_17(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_17(fn, arg, head, ...) fn(head, arg), _C2_MAP_16(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_16(fn, arg, head, ...) fn(head, arg), _C2_MAP_15(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_15(fn, arg, head, ...) fn(head, arg), _C2_MAP_14(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_14(fn, arg, head, ...) fn(head, arg), _C2_MAP_13(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_13(fn, arg, head, ...) fn(head, arg), _C2_MAP_12(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_12(fn, arg, head, ...) fn(head, arg), _C2_MAP_11(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_11(fn, arg, head, ...) fn(head, arg), _C2_MAP_10(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_10(fn, arg, head, ...) fn(head, arg), _C2_MAP_9(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_9(fn, arg, head, ...) fn(head, arg), _C2_MAP_8(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_8(fn, arg, head, ...) fn(head, arg), _C2_MAP_7(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_7(fn, arg, head, ...) fn(head, arg), _C2_MAP_6(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_6(fn, arg, head, ...) fn(head, arg), _C2_MAP_5(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_5(fn, arg, head, ...) fn(head, arg), _C2_MAP_4(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_4(fn, arg, head, ...) fn(head, arg), _C2_MAP_3(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_3(fn, arg, head, ...) fn(head, arg), _C2_MAP_2(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_2(fn, arg, head, ...) fn(head, arg), _C2_MAP_1(fn, arg, ##__VA_ARGS__) +#define _C2_MAP_1(fn, arg, head, ...) fn(head, arg) + +/// \endif + +/** + * Maps each argument using another macro x -> fn(x, arg) + */ +// use wrapper to call the proper mapper based on the number of arguments +#define _C2_MAP(fn, arg, ...) _C2_MAP__(_C2_ARGC(__VA_ARGS__), fn, arg, ##__VA_ARGS__) + +/// \if 0 + +// evaluate _n so it becomes a number +#define _C2_MAP__(_n, fn, arg, ...) _C2_MAP_(_n, fn, arg, __VA_ARGS__) +// call the proper mapper +#define _C2_MAP_(_n, fn, arg, ...) _C2_MAP_##_n (fn, arg, __VA_ARGS__) + +/// \endif + +#endif // C2UTILS_MACRO_UTILS_H_
diff --git a/media/codec2/core/include/android-C2Buffer.h b/media/codec2/core/include/android-C2Buffer.h new file mode 100644 index 0000000..26beab7 --- /dev/null +++ b/media/codec2/core/include/android-C2Buffer.h
@@ -0,0 +1,71 @@ +/* + * Copyright (C) 2016 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_C2BUFFER_H_ +#define ANDROID_C2BUFFER_H_ + +#include <cutils/native_handle.h> +#include <hardware/gralloc.h> + +/* Use android native handle for C2Handle */ +typedef ::native_handle_t C2Handle; + +namespace android { + +/** + * Android platform buffer/memory usage bits. + */ +struct C2AndroidMemoryUsage : public C2MemoryUsage { + inline C2AndroidMemoryUsage(const C2MemoryUsage &usage) : C2MemoryUsage(usage) { } + +// public: + /** + * Reuse gralloc flags where possible, as Codec 2.0 API only uses bits 0 and 1. + */ + enum consumer_t : uint64_t { + RENDERSCRIPT_READ = GRALLOC_USAGE_RENDERSCRIPT, + HW_TEXTURE_READ = GRALLOC_USAGE_HW_TEXTURE, + HW_COMPOSER_READ = GRALLOC_USAGE_HW_COMPOSER, + // gralloc does not define a video decoder read usage flag, so use encoder for + // now + HW_CODEC_READ = GRALLOC_USAGE_HW_VIDEO_ENCODER, + READ_PROTECTED = GRALLOC_USAGE_PROTECTED, + }; + + enum producer_t : uint64_t { + RENDERSCRIPT_WRITE = GRALLOC_USAGE_RENDERSCRIPT, + HW_TEXTURE_WRITE = GRALLOC_USAGE_HW_RENDER, + HW_COMPOSER_WRITE = GRALLOC_USAGE_HW_COMPOSER | GRALLOC_USAGE_HW_RENDER, + HW_CODEC_WRITE = GRALLOC_USAGE_HW_VIDEO_ENCODER, + // gralloc does not define a write protected usage flag, so use read protected + // now + WRITE_PROTECTED = GRALLOC_USAGE_PROTECTED, + }; + + /** + * Convert from gralloc usage. + */ + static C2MemoryUsage FromGrallocUsage(uint64_t usage); + + /** + * Convert to gralloc usage. + */ + uint64_t asGrallocUsage() const; +}; + +} // namespace android + +#endif // ANDROID_C2BUFFER_H_
diff --git a/media/codec2/core/include/media/stagefright/codec2/1.0/InputSurface.h b/media/codec2/core/include/media/stagefright/codec2/1.0/InputSurface.h new file mode 100644 index 0000000..0a82a68 --- /dev/null +++ b/media/codec2/core/include/media/stagefright/codec2/1.0/InputSurface.h
@@ -0,0 +1,70 @@ +/* + * Copyright 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_C2_V1_0_INPUT_SURFACE_H +#define ANDROID_HARDWARE_MEDIA_C2_V1_0_INPUT_SURFACE_H + +#include <memory> + +#include <C2Component.h> +#include <media/stagefright/codec2/1.0/InputSurfaceConnection.h> + +namespace android { + +class GraphicBufferSource; + +namespace hardware { +namespace media { +namespace c2 { +namespace V1_0 { +namespace implementation { + +using ::android::sp; + +typedef ::android::hardware::graphics::bufferqueue::V1_0::IGraphicBufferProducer + HGraphicBufferProducer; +typedef ::android::IGraphicBufferProducer BGraphicBufferProducer; + +// TODO: ::android::TWGraphicBufferProducer<IInputSurface> +typedef ::android::TWGraphicBufferProducer<HGraphicBufferProducer> InputSurfaceBase; + +class InputSurface : public InputSurfaceBase { +public: + virtual ~InputSurface() = default; + + // Methods from IInputSurface + sp<InputSurfaceConnection> connectToComponent( + const std::shared_ptr<::C2Component> &comp); + // TODO: intf() + + static sp<InputSurface> Create(); + +private: + InputSurface( + const sp<BGraphicBufferProducer> &base, + const sp<::android::GraphicBufferSource> &source); + + sp<::android::GraphicBufferSource> mSource; +}; + +} // namespace implementation +} // namespace V1_0 +} // namespace c2 +} // namespace media +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_MEDIA_C2_V1_0_INPUT_SURFACE_H
diff --git a/media/codec2/core/include/media/stagefright/codec2/1.0/InputSurfaceConnection.h b/media/codec2/core/include/media/stagefright/codec2/1.0/InputSurfaceConnection.h new file mode 100644 index 0000000..5eae3af --- /dev/null +++ b/media/codec2/core/include/media/stagefright/codec2/1.0/InputSurfaceConnection.h
@@ -0,0 +1,66 @@ +/* + * Copyright 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_C2_V1_0_INPUT_SURFACE_CONNECTION_H +#define ANDROID_HARDWARE_MEDIA_C2_V1_0_INPUT_SURFACE_CONNECTION_H + +#include <memory> + +#include <C2Component.h> +#include <media/stagefright/bqhelper/GraphicBufferSource.h> +#include <media/stagefright/codec2/1.0/InputSurfaceConnection.h> + +namespace android { +namespace hardware { +namespace media { +namespace c2 { +namespace V1_0 { +namespace implementation { + +// TODO: inherit from IInputSurfaceConnection +class InputSurfaceConnection : public RefBase { +public: + virtual ~InputSurfaceConnection(); + + // From IInputSurfaceConnection + void disconnect(); + +private: + friend class InputSurface; + + // For InputSurface + InputSurfaceConnection( + const sp<GraphicBufferSource> &source, const std::shared_ptr<C2Component> &comp); + bool init(); + + InputSurfaceConnection() = delete; + + class Impl; + + sp<GraphicBufferSource> mSource; + sp<Impl> mImpl; + + DISALLOW_EVIL_CONSTRUCTORS(InputSurfaceConnection); +}; + +} // namespace implementation +} // namespace V1_0 +} // namespace c2 +} // namespace media +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_MEDIA_C2_V1_0_INPUT_SURFACE_CONNECTION_H
diff --git a/media/codec2/docs/doxyfilter.sh b/media/codec2/docs/doxyfilter.sh new file mode 100755 index 0000000..d813153 --- /dev/null +++ b/media/codec2/docs/doxyfilter.sh
@@ -0,0 +1,100 @@ +#!/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/media/codec2/docs/doxygen.config b/media/codec2/docs/doxygen.config new file mode 100644 index 0000000..5c3bea3 --- /dev/null +++ b/media/codec2/docs/doxygen.config
@@ -0,0 +1,2446 @@ +# 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++14 + +#--------------------------------------------------------------------------- +# 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/media/codec2/faultinjection/Android.bp b/media/codec2/faultinjection/Android.bp new file mode 100644 index 0000000..a0ad3ce --- /dev/null +++ b/media/codec2/faultinjection/Android.bp
@@ -0,0 +1,29 @@ +cc_library_shared { + name: "libcodec2_component_wrapper", + vendor_available: true, + + srcs: [ + "C2ComponentWrapper.cpp", + "SimpleMethodState.cpp", + ], + + shared_libs: [ + "libcodec2", + "libcodec2_vndk", + "libcutils", + "liblog", + "libstagefright_foundation", + "libutils", + ], + + sanitize: { + misc_undefined: [ + "unsigned-integer-overflow", + "signed-integer-overflow", + ], + cfi: true, + }, + + ldflags: ["-Wl,-Bsymbolic"], +} +
diff --git a/media/codec2/faultinjection/C2ComponentWrapper.cpp b/media/codec2/faultinjection/C2ComponentWrapper.cpp new file mode 100644 index 0000000..c45f8bf --- /dev/null +++ b/media/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/media/codec2/faultinjection/C2ComponentWrapper.h b/media/codec2/faultinjection/C2ComponentWrapper.h new file mode 100644 index 0000000..737350d --- /dev/null +++ b/media/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/media/codec2/faultinjection/SimpleMethodState.cpp b/media/codec2/faultinjection/SimpleMethodState.cpp new file mode 100644 index 0000000..179d64e --- /dev/null +++ b/media/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/media/codec2/faultinjection/SimpleMethodState.h b/media/codec2/faultinjection/SimpleMethodState.h new file mode 100644 index 0000000..dc0459d --- /dev/null +++ b/media/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/media/codec2/hidl/1.0/utils/Android.bp b/media/codec2/hidl/1.0/utils/Android.bp new file mode 100644 index 0000000..63fe36b --- /dev/null +++ b/media/codec2/hidl/1.0/utils/Android.bp
@@ -0,0 +1,126 @@ +// DO NOT DEPEND ON THIS DIRECTLY +// use libcodec2-hidl-client-defaults instead +cc_library { + name: "libcodec2_hidl_client@1.0", + + defaults: ["hidl_defaults"], + + srcs: [ + "ClientBlockHelper.cpp", + "types.cpp", + ], + + header_libs: [ + "libcodec2_internal", // private + ], + + shared_libs: [ + "android.hardware.media.bufferpool@2.0", + "android.hardware.media.c2@1.0", + "libbase", + "libcodec2", + "libcodec2_vndk", + "libcutils", + "libgui", + "libhidlbase", + "liblog", + "libstagefright_bufferpool@2.0", + "libui", + "libutils", + ], + + export_include_dirs: [ + "include", + ], + + export_shared_lib_headers: [ + "android.hardware.media.c2@1.0", + "libcodec2", + "libgui", + "libstagefright_bufferpool@2.0", + "libui", + ], +} + + +// DO NOT DEPEND ON THIS DIRECTLY +// use libcodec2-hidl-defaults instead +cc_library { + name: "libcodec2_hidl@1.0", + vendor_available: true, + + defaults: ["hidl_defaults"], + + srcs: [ + "Component.cpp", + "ComponentInterface.cpp", + "ComponentStore.cpp", + "Configurable.cpp", + "InputBufferManager.cpp", + "InputSurface.cpp", + "InputSurfaceConnection.cpp", + "types.cpp", + ], + + header_libs: [ + "libsystem_headers", + "libcodec2_internal", // private + ], + + shared_libs: [ + "android.hardware.graphics.bufferqueue@1.0", + "android.hardware.graphics.bufferqueue@2.0", + "android.hardware.graphics.common@1.0", + "android.hardware.media@1.0", + "android.hardware.media.bufferpool@2.0", + "android.hardware.media.c2@1.0", + "android.hardware.media.omx@1.0", + "libbase", + "libcodec2", + "libcodec2_vndk", + "libcutils", + "libhidlbase", + "libhidltransport", + "libhwbinder", + "liblog", + "libstagefright_bufferpool@2.0", + "libstagefright_bufferqueue_helper", + "libui", + "libutils", + ], + + export_include_dirs: [ + "include", + ], + + export_shared_lib_headers: [ + "android.hardware.media.c2@1.0", + "libcodec2", + "libcodec2_vndk", + "libhidlbase", + "libstagefright_bufferpool@2.0", + "libui", + ], +} + +// public dependency for Codec 2.0 HAL service implementations +cc_defaults { + name: "libcodec2-hidl-defaults", + defaults: ["libcodec2-impl-defaults"], + + shared_libs: [ + "android.hardware.media.c2@1.0", + "libcodec2_hidl@1.0", + ], +} + +// public dependency for Codec 2.0 HAL client +cc_defaults { + name: "libcodec2-hidl-client-defaults", + defaults: ["libcodec2-impl-defaults"], + + shared_libs: [ + "android.hardware.media.c2@1.0", + "libcodec2_hidl_client@1.0", + ], +}
diff --git a/media/codec2/hidl/1.0/utils/ClientBlockHelper.cpp b/media/codec2/hidl/1.0/utils/ClientBlockHelper.cpp new file mode 100644 index 0000000..50790bc --- /dev/null +++ b/media/codec2/hidl/1.0/utils/ClientBlockHelper.cpp
@@ -0,0 +1,371 @@ +/* + * Copyright 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 "Codec2-block_helper" +#include <android-base/logging.h> + +#include <android/hardware/graphics/bufferqueue/2.0/IGraphicBufferProducer.h> +#include <codec2/hidl/1.0/ClientBlockHelper.h> +#include <gui/bufferqueue/2.0/B2HGraphicBufferProducer.h> + +#include <C2AllocatorGralloc.h> +#include <C2BlockInternal.h> +#include <C2Buffer.h> +#include <C2PlatformSupport.h> + +#include <iomanip> + +namespace android { +namespace hardware { +namespace media { +namespace c2 { +namespace V1_0 { +namespace utils { + +using HGraphicBufferProducer = ::android::hardware::graphics::bufferqueue:: + V2_0::IGraphicBufferProducer; +using B2HGraphicBufferProducer = ::android::hardware::graphics::bufferqueue:: + V2_0::utils::B2HGraphicBufferProducer; + +namespace /* unnamed */ { + +// Create a GraphicBuffer object from a graphic block. +sp<GraphicBuffer> createGraphicBuffer(const C2ConstGraphicBlock& block) { + uint32_t width; + uint32_t height; + uint32_t format; + uint64_t usage; + uint32_t stride; + uint32_t generation; + uint64_t bqId; + int32_t bqSlot; + _UnwrapNativeCodec2GrallocMetadata( + block.handle(), &width, &height, &format, &usage, + &stride, &generation, &bqId, reinterpret_cast<uint32_t*>(&bqSlot)); + native_handle_t *grallocHandle = + UnwrapNativeCodec2GrallocHandle(block.handle()); + sp<GraphicBuffer> graphicBuffer = + new GraphicBuffer(grallocHandle, + GraphicBuffer::CLONE_HANDLE, + width, height, format, + 1, usage, stride); + native_handle_delete(grallocHandle); + return graphicBuffer; +} + +template <typename BlockProcessor> +void forEachBlock(C2FrameData& frameData, + BlockProcessor process) { + for (const std::shared_ptr<C2Buffer>& buffer : frameData.buffers) { + if (buffer) { + for (const C2ConstGraphicBlock& block : + buffer->data().graphicBlocks()) { + process(block); + } + } + } +} + +template <typename BlockProcessor> +void forEachBlock(const std::list<std::unique_ptr<C2Work>>& workList, + BlockProcessor process) { + for (const std::unique_ptr<C2Work>& work : workList) { + if (!work) { + continue; + } + for (const std::unique_ptr<C2Worklet>& worklet : work->worklets) { + if (worklet) { + forEachBlock(worklet->output, process); + } + } + } +} + +sp<HGraphicBufferProducer> getHgbp(const sp<IGraphicBufferProducer>& igbp) { + sp<HGraphicBufferProducer> hgbp = + igbp->getHalInterface<HGraphicBufferProducer>(); + return hgbp ? hgbp : + new B2HGraphicBufferProducer(igbp); +} + +status_t attachToBufferQueue(const C2ConstGraphicBlock& block, + const sp<IGraphicBufferProducer>& igbp, + uint32_t generation, + int32_t* bqSlot) { + if (!igbp) { + LOG(WARNING) << "attachToBufferQueue -- null producer."; + return NO_INIT; + } + + sp<GraphicBuffer> graphicBuffer = createGraphicBuffer(block); + graphicBuffer->setGenerationNumber(generation); + + LOG(VERBOSE) << "attachToBufferQueue -- attaching buffer:" + << " block dimension " << block.width() << "x" + << block.height() + << ", graphicBuffer dimension " << graphicBuffer->getWidth() << "x" + << graphicBuffer->getHeight() + << std::hex << std::setfill('0') + << ", format 0x" << std::setw(8) << graphicBuffer->getPixelFormat() + << ", usage 0x" << std::setw(16) << graphicBuffer->getUsage() + << std::dec << std::setfill(' ') + << ", stride " << graphicBuffer->getStride() + << ", generation " << graphicBuffer->getGenerationNumber(); + + status_t result = igbp->attachBuffer(bqSlot, graphicBuffer); + if (result != OK) { + LOG(WARNING) << "attachToBufferQueue -- attachBuffer failed: " + "status = " << result << "."; + return result; + } + LOG(VERBOSE) << "attachToBufferQueue -- attachBuffer returned slot #" + << *bqSlot << "."; + return OK; +} + +bool getBufferQueueAssignment(const C2ConstGraphicBlock& block, + uint32_t* generation, + uint64_t* bqId, + int32_t* bqSlot) { + return _C2BlockFactory::GetBufferQueueData( + _C2BlockFactory::GetGraphicBlockPoolData(block), + generation, bqId, bqSlot); +} +} // unnamed namespace + +class OutputBufferQueue::Impl { + std::mutex mMutex; + sp<IGraphicBufferProducer> mIgbp; + uint32_t mGeneration; + uint64_t mBqId; + std::shared_ptr<int> mOwner; + // To migrate existing buffers + sp<GraphicBuffer> mBuffers[BufferQueueDefs::NUM_BUFFER_SLOTS]; // find a better way + std::weak_ptr<_C2BlockPoolData> + mPoolDatas[BufferQueueDefs::NUM_BUFFER_SLOTS]; + +public: + Impl(): mGeneration(0), mBqId(0) {} + + bool configure(const sp<IGraphicBufferProducer>& igbp, + uint32_t generation, + uint64_t bqId) { + size_t tryNum = 0; + size_t success = 0; + sp<GraphicBuffer> buffers[BufferQueueDefs::NUM_BUFFER_SLOTS]; + std::weak_ptr<_C2BlockPoolData> + poolDatas[BufferQueueDefs::NUM_BUFFER_SLOTS]; + { + std::scoped_lock<std::mutex> l(mMutex); + if (generation == mGeneration) { + return false; + } + mIgbp = igbp; + mGeneration = generation; + mBqId = bqId; + mOwner = std::make_shared<int>(0); + for (int i = 0; i < BufferQueueDefs::NUM_BUFFER_SLOTS; ++i) { + if (mBqId == 0 || !mBuffers[i]) { + continue; + } + std::shared_ptr<_C2BlockPoolData> data = mPoolDatas[i].lock(); + if (!data || + !_C2BlockFactory::BeginAttachBlockToBufferQueue(data)) { + continue; + } + ++tryNum; + int bqSlot; + mBuffers[i]->setGenerationNumber(generation); + status_t result = igbp->attachBuffer(&bqSlot, mBuffers[i]); + if (result != OK) { + continue; + } + bool attach = + _C2BlockFactory::EndAttachBlockToBufferQueue( + data, mOwner, getHgbp(mIgbp), + generation, bqId, bqSlot); + if (!attach) { + igbp->cancelBuffer(bqSlot, Fence::NO_FENCE); + continue; + } + buffers[bqSlot] = mBuffers[i]; + poolDatas[bqSlot] = data; + ++success; + } + for (int i = 0; i < BufferQueueDefs::NUM_BUFFER_SLOTS; ++i) { + mBuffers[i] = buffers[i]; + mPoolDatas[i] = poolDatas[i]; + } + } + ALOGD("remote graphic buffer migration %zu/%zu", success, tryNum); + return true; + } + + bool registerBuffer(const C2ConstGraphicBlock& block) { + std::shared_ptr<_C2BlockPoolData> data = + _C2BlockFactory::GetGraphicBlockPoolData(block); + if (!data) { + return false; + } + std::scoped_lock<std::mutex> l(mMutex); + + if (!mIgbp) { + return false; + } + + uint32_t oldGeneration; + uint64_t oldId; + int32_t oldSlot; + // If the block is not bufferqueue-based, do nothing. + if (!_C2BlockFactory::GetBufferQueueData( + data, &oldGeneration, &oldId, &oldSlot) || (oldId == 0)) { + return false; + } + // If the block's bqId is the same as the desired bqId, just hold. + if ((oldId == mBqId) && (oldGeneration == mGeneration)) { + LOG(VERBOSE) << "holdBufferQueueBlock -- import without attaching:" + << " bqId " << oldId + << ", bqSlot " << oldSlot + << ", generation " << mGeneration + << "."; + _C2BlockFactory::HoldBlockFromBufferQueue(data, mOwner, getHgbp(mIgbp)); + mPoolDatas[oldSlot] = data; + mBuffers[oldSlot] = createGraphicBuffer(block); + mBuffers[oldSlot]->setGenerationNumber(mGeneration); + return true; + } + int32_t d = (int32_t) mGeneration - (int32_t) oldGeneration; + LOG(WARNING) << "receiving stale buffer: generation " + << mGeneration << " , diff " << d << " : slot " + << oldSlot; + return false; + } + + status_t outputBuffer( + const C2ConstGraphicBlock& block, + const BnGraphicBufferProducer::QueueBufferInput& input, + BnGraphicBufferProducer::QueueBufferOutput* output) { + uint32_t generation; + uint64_t bqId; + int32_t bqSlot; + bool display = displayBufferQueueBlock(block); + if (!getBufferQueueAssignment(block, &generation, &bqId, &bqSlot) || + bqId == 0) { + // Block not from bufferqueue -- it must be attached before queuing. + + mMutex.lock(); + sp<IGraphicBufferProducer> outputIgbp = mIgbp; + uint32_t outputGeneration = mGeneration; + mMutex.unlock(); + + status_t status = attachToBufferQueue( + block, outputIgbp, outputGeneration, &bqSlot); + if (status != OK) { + LOG(WARNING) << "outputBuffer -- attaching failed."; + return INVALID_OPERATION; + } + + status = outputIgbp->queueBuffer(static_cast<int>(bqSlot), + input, output); + if (status != OK) { + LOG(ERROR) << "outputBuffer -- queueBuffer() failed " + "on non-bufferqueue-based block. " + "Error = " << status << "."; + return status; + } + return OK; + } + + mMutex.lock(); + sp<IGraphicBufferProducer> outputIgbp = mIgbp; + uint32_t outputGeneration = mGeneration; + uint64_t outputBqId = mBqId; + mMutex.unlock(); + + if (!outputIgbp) { + LOG(VERBOSE) << "outputBuffer -- output surface is null."; + return NO_INIT; + } + + if (!display) { + LOG(WARNING) << "outputBuffer -- cannot display " + "bufferqueue-based block to the bufferqueue."; + return UNKNOWN_ERROR; + } + if (bqId != outputBqId || generation != outputGeneration) { + int32_t diff = (int32_t) outputGeneration - (int32_t) generation; + LOG(WARNING) << "outputBuffer -- buffers from old generation to " + << outputGeneration << " , diff: " << diff + << " , slot: " << bqSlot; + return DEAD_OBJECT; + } + + status_t status = outputIgbp->queueBuffer(static_cast<int>(bqSlot), + input, output); + if (status != OK) { + LOG(ERROR) << "outputBuffer -- queueBuffer() failed " + "on bufferqueue-based block. " + "Error = " << status << "."; + return status; + } + return OK; + } + + Impl *getPtr() { + return this; + } + + ~Impl() {} +}; + +OutputBufferQueue::OutputBufferQueue(): mImpl(new Impl()) {} + +OutputBufferQueue::~OutputBufferQueue() {} + +bool OutputBufferQueue::configure(const sp<IGraphicBufferProducer>& igbp, + uint32_t generation, + uint64_t bqId) { + return mImpl && mImpl->configure(igbp, generation, bqId); +} + +status_t OutputBufferQueue::outputBuffer( + const C2ConstGraphicBlock& block, + const BnGraphicBufferProducer::QueueBufferInput& input, + BnGraphicBufferProducer::QueueBufferOutput* output) { + if (mImpl) { + return mImpl->outputBuffer(block, input, output); + } + return DEAD_OBJECT; +} + +void OutputBufferQueue::holdBufferQueueBlocks( + const std::list<std::unique_ptr<C2Work>>& workList) { + if (!mImpl) { + return; + } + forEachBlock(workList, + std::bind(&OutputBufferQueue::Impl::registerBuffer, + mImpl->getPtr(), std::placeholders::_1)); +} + +} // namespace utils +} // namespace V1_0 +} // namespace c2 +} // namespace media +} // namespace hardware +} // namespace android +
diff --git a/media/codec2/hidl/1.0/utils/Component.cpp b/media/codec2/hidl/1.0/utils/Component.cpp new file mode 100644 index 0000000..a9f20a4 --- /dev/null +++ b/media/codec2/hidl/1.0/utils/Component.cpp
@@ -0,0 +1,486 @@ +/* + * Copyright 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 "Codec2-Component" +#include <android-base/logging.h> + +#include <codec2/hidl/1.0/Component.h> +#include <codec2/hidl/1.0/ComponentStore.h> +#include <codec2/hidl/1.0/InputBufferManager.h> + +#include <hidl/HidlBinderSupport.h> +#include <utils/Timers.h> + +#include <C2BqBufferPriv.h> +#include <C2Debug.h> +#include <C2PlatformSupport.h> + +#include <chrono> +#include <thread> + +namespace android { +namespace hardware { +namespace media { +namespace c2 { +namespace V1_0 { +namespace utils { + +using namespace ::android; + +// ComponentListener wrapper +struct Component::Listener : public C2Component::Listener { + + Listener(const sp<Component>& component) : + mComponent(component), + mListener(component->mListener) { + } + + virtual void onError_nb( + std::weak_ptr<C2Component> /* c2component */, + uint32_t errorCode) override { + sp<IComponentListener> listener = mListener.promote(); + if (listener) { + Return<void> transStatus = listener->onError(Status::OK, errorCode); + if (!transStatus.isOk()) { + LOG(ERROR) << "Component::Listener::onError_nb -- " + << "transaction failed."; + } + } + } + + virtual void onTripped_nb( + std::weak_ptr<C2Component> /* c2component */, + std::vector<std::shared_ptr<C2SettingResult>> c2settingResult + ) override { + sp<IComponentListener> listener = mListener.promote(); + if (listener) { + hidl_vec<SettingResult> settingResults(c2settingResult.size()); + size_t ix = 0; + for (const std::shared_ptr<C2SettingResult> &c2result : + c2settingResult) { + if (c2result) { + if (!objcpy(&settingResults[ix++], *c2result)) { + break; + } + } + } + settingResults.resize(ix); + Return<void> transStatus = listener->onTripped(settingResults); + if (!transStatus.isOk()) { + LOG(ERROR) << "Component::Listener::onTripped_nb -- " + << "transaction failed."; + } + } + } + + virtual void onWorkDone_nb( + std::weak_ptr<C2Component> /* c2component */, + std::list<std::unique_ptr<C2Work>> c2workItems) override { + for (const std::unique_ptr<C2Work>& work : c2workItems) { + if (work) { + if (work->worklets.empty() + || !work->worklets.back() + || (work->worklets.back()->output.flags & + C2FrameData::FLAG_INCOMPLETE) == 0) { + InputBufferManager:: + unregisterFrameData(mListener, work->input); + } + } + } + + sp<IComponentListener> listener = mListener.promote(); + if (listener) { + WorkBundle workBundle; + + sp<Component> strongComponent = mComponent.promote(); + beginTransferBufferQueueBlocks(c2workItems, true); + if (!objcpy(&workBundle, c2workItems, strongComponent ? + &strongComponent->mBufferPoolSender : nullptr)) { + LOG(ERROR) << "Component::Listener::onWorkDone_nb -- " + << "received corrupted work items."; + endTransferBufferQueueBlocks(c2workItems, false, true); + return; + } + Return<void> transStatus = listener->onWorkDone(workBundle); + if (!transStatus.isOk()) { + LOG(ERROR) << "Component::Listener::onWorkDone_nb -- " + << "transaction failed."; + endTransferBufferQueueBlocks(c2workItems, false, true); + return; + } + endTransferBufferQueueBlocks(c2workItems, true, true); + } + } + +protected: + wp<Component> mComponent; + wp<IComponentListener> mListener; +}; + +// Component::Sink +struct Component::Sink : public IInputSink { + std::shared_ptr<Component> mComponent; + sp<IConfigurable> mConfigurable; + + virtual Return<Status> queue(const WorkBundle& workBundle) override { + return mComponent->queue(workBundle); + } + + virtual Return<sp<IConfigurable>> getConfigurable() override { + return mConfigurable; + } + + Sink(const std::shared_ptr<Component>& component); + virtual ~Sink() override; + + // Process-wide map: Component::Sink -> C2Component. + static std::mutex sSink2ComponentMutex; + static std::map<IInputSink*, std::weak_ptr<C2Component>> sSink2Component; + + static std::shared_ptr<C2Component> findLocalComponent( + const sp<IInputSink>& sink); +}; + +std::mutex + Component::Sink::sSink2ComponentMutex{}; +std::map<IInputSink*, std::weak_ptr<C2Component>> + Component::Sink::sSink2Component{}; + +Component::Sink::Sink(const std::shared_ptr<Component>& component) + : mComponent{component}, + mConfigurable{[&component]() -> sp<IConfigurable> { + Return<sp<IComponentInterface>> ret1 = component->getInterface(); + if (!ret1.isOk()) { + LOG(ERROR) << "Sink::Sink -- component's transaction failed."; + return nullptr; + } + Return<sp<IConfigurable>> ret2 = + static_cast<sp<IComponentInterface>>(ret1)-> + getConfigurable(); + if (!ret2.isOk()) { + LOG(ERROR) << "Sink::Sink -- interface's transaction failed."; + return nullptr; + } + return static_cast<sp<IConfigurable>>(ret2); + }()} { + std::lock_guard<std::mutex> lock(sSink2ComponentMutex); + sSink2Component.emplace(this, component->mComponent); +} + +Component::Sink::~Sink() { + std::lock_guard<std::mutex> lock(sSink2ComponentMutex); + sSink2Component.erase(this); +} + +std::shared_ptr<C2Component> Component::Sink::findLocalComponent( + const sp<IInputSink>& sink) { + std::lock_guard<std::mutex> lock(sSink2ComponentMutex); + auto i = sSink2Component.find(sink.get()); + if (i == sSink2Component.end()) { + return nullptr; + } + return i->second.lock(); +} + +// Component +Component::Component( + const std::shared_ptr<C2Component>& component, + const sp<IComponentListener>& listener, + const sp<ComponentStore>& store, + const sp<::android::hardware::media::bufferpool::V2_0:: + IClientManager>& clientPoolManager) + : mComponent{component}, + mInterface{new ComponentInterface(component->intf(), store.get())}, + mListener{listener}, + mStore{store}, + mBufferPoolSender{clientPoolManager} { + // Retrieve supported parameters from store + // TODO: We could cache this per component/interface type + mInit = mInterface->status(); +} + +c2_status_t Component::status() const { + return mInit; +} + +// Methods from ::android::hardware::media::c2::V1_0::IComponent +Return<Status> Component::queue(const WorkBundle& workBundle) { + std::list<std::unique_ptr<C2Work>> c2works; + + if (!objcpy(&c2works, workBundle)) { + return Status::CORRUPTED; + } + + // Register input buffers. + for (const std::unique_ptr<C2Work>& work : c2works) { + if (work) { + InputBufferManager:: + registerFrameData(mListener, work->input); + } + } + + return static_cast<Status>(mComponent->queue_nb(&c2works)); +} + +Return<void> Component::flush(flush_cb _hidl_cb) { + std::list<std::unique_ptr<C2Work>> c2flushedWorks; + c2_status_t c2res = mComponent->flush_sm( + C2Component::FLUSH_COMPONENT, + &c2flushedWorks); + + // Unregister input buffers. + for (const std::unique_ptr<C2Work>& work : c2flushedWorks) { + if (work) { + if (work->worklets.empty() + || !work->worklets.back() + || (work->worklets.back()->output.flags & + C2FrameData::FLAG_INCOMPLETE) == 0) { + InputBufferManager:: + unregisterFrameData(mListener, work->input); + } + } + } + + WorkBundle flushedWorkBundle; + Status res = static_cast<Status>(c2res); + beginTransferBufferQueueBlocks(c2flushedWorks, true); + if (c2res == C2_OK) { + if (!objcpy(&flushedWorkBundle, c2flushedWorks, &mBufferPoolSender)) { + res = Status::CORRUPTED; + } + } + _hidl_cb(res, flushedWorkBundle); + endTransferBufferQueueBlocks(c2flushedWorks, true, true); + return Void(); +} + +Return<Status> Component::drain(bool withEos) { + return static_cast<Status>(mComponent->drain_nb(withEos ? + C2Component::DRAIN_COMPONENT_WITH_EOS : + C2Component::DRAIN_COMPONENT_NO_EOS)); +} + +Return<Status> Component::setOutputSurface( + uint64_t blockPoolId, + const sp<HGraphicBufferProducer2>& surface) { + std::shared_ptr<C2BlockPool> pool; + GetCodec2BlockPool(blockPoolId, mComponent, &pool); + if (pool && pool->getAllocatorId() == C2PlatformAllocatorStore::BUFFERQUEUE) { + std::shared_ptr<C2BufferQueueBlockPool> bqPool = + std::static_pointer_cast<C2BufferQueueBlockPool>(pool); + C2BufferQueueBlockPool::OnRenderCallback cb = + [this](uint64_t producer, int32_t slot, int64_t nsecs) { + // TODO: batch this + hidl_vec<IComponentListener::RenderedFrame> rendered; + rendered.resize(1); + rendered[0] = { producer, slot, nsecs }; + (void)mListener->onFramesRendered(rendered).isOk(); + }; + if (bqPool) { + bqPool->setRenderCallback(cb); + bqPool->configureProducer(surface); + } + } + return Status::OK; +} + +Return<void> Component::connectToInputSurface( + const sp<IInputSurface>& inputSurface, + connectToInputSurface_cb _hidl_cb) { + Status status; + sp<IInputSurfaceConnection> connection; + auto transStatus = inputSurface->connect( + asInputSink(), + [&status, &connection]( + Status s, const sp<IInputSurfaceConnection>& c) { + status = s; + connection = c; + } + ); + _hidl_cb(status, connection); + return Void(); +} + +Return<void> Component::connectToOmxInputSurface( + const sp<HGraphicBufferProducer1>& producer, + const sp<::android::hardware::media::omx::V1_0:: + IGraphicBufferSource>& source, + connectToOmxInputSurface_cb _hidl_cb) { + (void)producer; + (void)source; + (void)_hidl_cb; + return Void(); +} + +Return<Status> Component::disconnectFromInputSurface() { + // TODO implement + return Status::OK; +} + +namespace /* unnamed */ { + +struct BlockPoolIntf : public ConfigurableC2Intf { + BlockPoolIntf(const std::shared_ptr<C2BlockPool>& pool) + : ConfigurableC2Intf{ + "C2BlockPool:" + + (pool ? std::to_string(pool->getLocalId()) : "null"), + 0}, + mPool{pool} { + } + + virtual c2_status_t config( + const std::vector<C2Param*>& params, + c2_blocking_t mayBlock, + std::vector<std::unique_ptr<C2SettingResult>>* const failures + ) override { + (void)params; + (void)mayBlock; + (void)failures; + return C2_OK; + } + + virtual c2_status_t query( + const std::vector<C2Param::Index>& indices, + c2_blocking_t mayBlock, + std::vector<std::unique_ptr<C2Param>>* const params + ) const override { + (void)indices; + (void)mayBlock; + (void)params; + return C2_OK; + } + + virtual c2_status_t querySupportedParams( + std::vector<std::shared_ptr<C2ParamDescriptor>>* const params + ) const override { + (void)params; + return C2_OK; + } + + virtual c2_status_t querySupportedValues( + std::vector<C2FieldSupportedValuesQuery>& fields, + c2_blocking_t mayBlock) const override { + (void)fields; + (void)mayBlock; + return C2_OK; + } + +protected: + std::shared_ptr<C2BlockPool> mPool; +}; + +} // unnamed namespace + +Return<void> Component::createBlockPool( + uint32_t allocatorId, + createBlockPool_cb _hidl_cb) { + std::shared_ptr<C2BlockPool> blockPool; + c2_status_t status = CreateCodec2BlockPool( + static_cast<C2PlatformAllocatorStore::id_t>(allocatorId), + mComponent, + &blockPool); + if (status != C2_OK) { + blockPool = nullptr; + } + if (blockPool) { + mBlockPoolsMutex.lock(); + mBlockPools.emplace(blockPool->getLocalId(), blockPool); + mBlockPoolsMutex.unlock(); + } else if (status == C2_OK) { + status = C2_CORRUPTED; + } + + _hidl_cb(static_cast<Status>(status), + blockPool ? blockPool->getLocalId() : 0, + new CachedConfigurable( + std::make_unique<BlockPoolIntf>(blockPool))); + return Void(); +} + +Return<Status> Component::destroyBlockPool(uint64_t blockPoolId) { + std::lock_guard<std::mutex> lock(mBlockPoolsMutex); + return mBlockPools.erase(blockPoolId) == 1 ? + Status::OK : Status::CORRUPTED; +} + +Return<Status> Component::start() { + return static_cast<Status>(mComponent->start()); +} + +Return<Status> Component::stop() { + InputBufferManager::unregisterFrameData(mListener); + return static_cast<Status>(mComponent->stop()); +} + +Return<Status> Component::reset() { + Status status = static_cast<Status>(mComponent->reset()); + { + std::lock_guard<std::mutex> lock(mBlockPoolsMutex); + mBlockPools.clear(); + } + InputBufferManager::unregisterFrameData(mListener); + return status; +} + +Return<Status> Component::release() { + Status status = static_cast<Status>(mComponent->release()); + { + std::lock_guard<std::mutex> lock(mBlockPoolsMutex); + mBlockPools.clear(); + } + InputBufferManager::unregisterFrameData(mListener); + return status; +} + +Return<sp<IComponentInterface>> Component::getInterface() { + return sp<IComponentInterface>(mInterface); +} + +Return<sp<IInputSink>> Component::asInputSink() { + std::lock_guard<std::mutex> lock(mSinkMutex); + if (!mSink) { + mSink = new Sink(shared_from_this()); + } + return {mSink}; +} + +std::shared_ptr<C2Component> Component::findLocalComponent( + const sp<IInputSink>& sink) { + return Component::Sink::findLocalComponent(sink); +} + +void Component::initListener(const sp<Component>& self) { + std::shared_ptr<C2Component::Listener> c2listener = + std::make_shared<Listener>(self); + c2_status_t res = mComponent->setListener_vb(c2listener, C2_DONT_BLOCK); + if (res != C2_OK) { + mInit = res; + } +} + +Component::~Component() { + InputBufferManager::unregisterFrameData(mListener); + mStore->reportComponentDeath(this); +} + +} // namespace utils +} // namespace V1_0 +} // namespace c2 +} // namespace media +} // namespace hardware +} // namespace android +
diff --git a/media/codec2/hidl/1.0/utils/ComponentInterface.cpp b/media/codec2/hidl/1.0/utils/ComponentInterface.cpp new file mode 100644 index 0000000..39e5357 --- /dev/null +++ b/media/codec2/hidl/1.0/utils/ComponentInterface.cpp
@@ -0,0 +1,110 @@ +/* + * Copyright 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 "Codec2-ComponentInterface" +#include <android-base/logging.h> + +#include <codec2/hidl/1.0/Component.h> +#include <codec2/hidl/1.0/ComponentInterface.h> +#include <codec2/hidl/1.0/ComponentStore.h> + +#include <hidl/HidlBinderSupport.h> +#include <utils/Timers.h> + +#include <C2BqBufferPriv.h> +#include <C2Debug.h> +#include <C2PlatformSupport.h> + +#include <chrono> +#include <thread> + +namespace android { +namespace hardware { +namespace media { +namespace c2 { +namespace V1_0 { +namespace utils { + +using namespace ::android; + +namespace /* unnamed */ { + +// Implementation of ConfigurableC2Intf based on C2ComponentInterface +struct CompIntf : public ConfigurableC2Intf { + CompIntf(const std::shared_ptr<C2ComponentInterface>& intf) : + ConfigurableC2Intf{intf->getName(), intf->getId()}, + mIntf{intf} { + } + + virtual c2_status_t config( + const std::vector<C2Param*>& params, + c2_blocking_t mayBlock, + std::vector<std::unique_ptr<C2SettingResult>>* const failures + ) override { + return mIntf->config_vb(params, mayBlock, failures); + } + + virtual c2_status_t query( + const std::vector<C2Param::Index>& indices, + c2_blocking_t mayBlock, + std::vector<std::unique_ptr<C2Param>>* const params + ) const override { + return mIntf->query_vb({}, indices, mayBlock, params); + } + + virtual c2_status_t querySupportedParams( + std::vector<std::shared_ptr<C2ParamDescriptor>>* const params + ) const override { + return mIntf->querySupportedParams_nb(params); + } + + virtual c2_status_t querySupportedValues( + std::vector<C2FieldSupportedValuesQuery>& fields, + c2_blocking_t mayBlock) const override { + return mIntf->querySupportedValues_vb(fields, mayBlock); + } + +protected: + std::shared_ptr<C2ComponentInterface> mIntf; +}; + +} // unnamed namespace + +// ComponentInterface +ComponentInterface::ComponentInterface( + const std::shared_ptr<C2ComponentInterface>& intf, + ComponentStore* store) + : mInterface{intf}, + mConfigurable{new CachedConfigurable(std::make_unique<CompIntf>(intf))} { + mInit = mConfigurable->init(store); +} + +c2_status_t ComponentInterface::status() const { + return mInit; +} + +Return<sp<IConfigurable>> ComponentInterface::getConfigurable() { + return mConfigurable; +} + +} // namespace utils +} // namespace V1_0 +} // namespace c2 +} // namespace media +} // namespace hardware +} // namespace android +
diff --git a/media/codec2/hidl/1.0/utils/ComponentStore.cpp b/media/codec2/hidl/1.0/utils/ComponentStore.cpp new file mode 100644 index 0000000..1e0a190 --- /dev/null +++ b/media/codec2/hidl/1.0/utils/ComponentStore.cpp
@@ -0,0 +1,437 @@ +/* + * Copyright 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 "Codec2-ComponentStore" +#include <android-base/logging.h> + +#include <codec2/hidl/1.0/ComponentStore.h> +#include <codec2/hidl/1.0/InputSurface.h> +#include <codec2/hidl/1.0/types.h> + +#include <android-base/file.h> +#include <media/stagefright/bqhelper/GraphicBufferSource.h> +#include <utils/Errors.h> + +#include <C2PlatformSupport.h> +#include <util/C2InterfaceHelper.h> + +#include <chrono> +#include <ctime> +#include <iomanip> +#include <ostream> +#include <sstream> + +namespace android { +namespace hardware { +namespace media { +namespace c2 { +namespace V1_0 { +namespace utils { + +using namespace ::android; +using ::android::GraphicBufferSource; +using namespace ::android::hardware::media::bufferpool::V2_0::implementation; + +namespace /* unnamed */ { + +struct StoreIntf : public ConfigurableC2Intf { + StoreIntf(const std::shared_ptr<C2ComponentStore>& store) + : ConfigurableC2Intf{store ? store->getName() : "", 0}, + mStore{store} { + } + + virtual c2_status_t config( + const std::vector<C2Param*> ¶ms, + c2_blocking_t mayBlock, + std::vector<std::unique_ptr<C2SettingResult>> *const failures + ) override { + // Assume all params are blocking + // TODO: Filter for supported params + if (mayBlock == C2_DONT_BLOCK && params.size() != 0) { + return C2_BLOCKING; + } + return mStore->config_sm(params, failures); + } + + virtual c2_status_t query( + const std::vector<C2Param::Index> &indices, + c2_blocking_t mayBlock, + std::vector<std::unique_ptr<C2Param>> *const params) const override { + // Assume all params are blocking + // TODO: Filter for supported params + if (mayBlock == C2_DONT_BLOCK && indices.size() != 0) { + return C2_BLOCKING; + } + return mStore->query_sm({}, indices, params); + } + + virtual c2_status_t querySupportedParams( + std::vector<std::shared_ptr<C2ParamDescriptor>> *const params + ) const override { + return mStore->querySupportedParams_nb(params); + } + + virtual c2_status_t querySupportedValues( + std::vector<C2FieldSupportedValuesQuery> &fields, + c2_blocking_t mayBlock) const override { + // Assume all params are blocking + // TODO: Filter for supported params + if (mayBlock == C2_DONT_BLOCK && fields.size() != 0) { + return C2_BLOCKING; + } + return mStore->querySupportedValues_sm(fields); + } + +protected: + std::shared_ptr<C2ComponentStore> mStore; +}; + +} // unnamed namespace + +ComponentStore::ComponentStore(const std::shared_ptr<C2ComponentStore>& store) + : mConfigurable{new CachedConfigurable(std::make_unique<StoreIntf>(store))}, + mStore{store} { + + std::shared_ptr<C2ComponentStore> platformStore = android::GetCodec2PlatformComponentStore(); + SetPreferredCodec2ComponentStore(store); + + // Retrieve struct descriptors + mParamReflector = mStore->getParamReflector(); + + // Retrieve supported parameters from store + mInit = mConfigurable->init(this); +} + +c2_status_t ComponentStore::status() const { + return mInit; +} + +c2_status_t ComponentStore::validateSupportedParams( + const std::vector<std::shared_ptr<C2ParamDescriptor>>& params) { + c2_status_t res = C2_OK; + + for (const std::shared_ptr<C2ParamDescriptor> &desc : params) { + if (!desc) { + // All descriptors should be valid + res = res ? res : C2_BAD_VALUE; + continue; + } + C2Param::CoreIndex coreIndex = desc->index().coreIndex(); + std::lock_guard<std::mutex> lock(mStructDescriptorsMutex); + auto it = mStructDescriptors.find(coreIndex); + if (it == mStructDescriptors.end()) { + std::shared_ptr<C2StructDescriptor> structDesc = + mParamReflector->describe(coreIndex); + if (!structDesc) { + // All supported params must be described + res = C2_BAD_INDEX; + } + mStructDescriptors.insert({ coreIndex, structDesc }); + } + } + return res; +} + +// Methods from ::android::hardware::media::c2::V1_0::IComponentStore +Return<void> ComponentStore::createComponent( + const hidl_string& name, + const sp<IComponentListener>& listener, + const sp<IClientManager>& pool, + createComponent_cb _hidl_cb) { + + sp<Component> component; + std::shared_ptr<C2Component> c2component; + Status status = static_cast<Status>( + mStore->createComponent(name, &c2component)); + + if (status == Status::OK) { + onInterfaceLoaded(c2component->intf()); + component = new Component(c2component, listener, this, pool); + if (!component) { + status = Status::CORRUPTED; + } else { + reportComponentBirth(component.get()); + if (component->status() != C2_OK) { + status = static_cast<Status>(component->status()); + } else { + component->initListener(component); + if (component->status() != C2_OK) { + status = static_cast<Status>(component->status()); + } + } + } + } + _hidl_cb(status, component); + return Void(); +} + +Return<void> ComponentStore::createInterface( + const hidl_string& name, + createInterface_cb _hidl_cb) { + std::shared_ptr<C2ComponentInterface> c2interface; + c2_status_t res = mStore->createInterface(name, &c2interface); + sp<IComponentInterface> interface; + if (res == C2_OK) { + onInterfaceLoaded(c2interface); + interface = new ComponentInterface(c2interface, this); + } + _hidl_cb(static_cast<Status>(res), interface); + return Void(); +} + +Return<void> ComponentStore::listComponents(listComponents_cb _hidl_cb) { + std::vector<std::shared_ptr<const C2Component::Traits>> c2traits = + mStore->listComponents(); + hidl_vec<IComponentStore::ComponentTraits> traits(c2traits.size()); + size_t ix = 0; + for (const std::shared_ptr<const C2Component::Traits> &c2trait : c2traits) { + if (c2trait) { + if (objcpy(&traits[ix], *c2trait)) { + ++ix; + } else { + break; + } + } + } + traits.resize(ix); + _hidl_cb(Status::OK, traits); + return Void(); +} + +Return<void> ComponentStore::createInputSurface(createInputSurface_cb _hidl_cb) { + sp<GraphicBufferSource> source = new GraphicBufferSource(); + if (source->initCheck() != OK) { + _hidl_cb(Status::CORRUPTED, nullptr); + return Void(); + } + sp<InputSurface> inputSurface = new InputSurface( + this, + std::make_shared<C2ReflectorHelper>(), + source->getHGraphicBufferProducer(), + source); + _hidl_cb(inputSurface ? Status::OK : Status::NO_MEMORY, + inputSurface); + return Void(); +} + +void ComponentStore::onInterfaceLoaded(const std::shared_ptr<C2ComponentInterface> &intf) { + // invalidate unsupported struct descriptors if a new interface is loaded as it may have + // exposed new descriptors + std::lock_guard<std::mutex> lock(mStructDescriptorsMutex); + if (!mLoadedInterfaces.count(intf->getName())) { + mUnsupportedStructDescriptors.clear(); + mLoadedInterfaces.emplace(intf->getName()); + } +} + +Return<void> ComponentStore::getStructDescriptors( + const hidl_vec<uint32_t>& indices, + getStructDescriptors_cb _hidl_cb) { + hidl_vec<StructDescriptor> descriptors(indices.size()); + size_t dstIx = 0; + Status res = Status::OK; + for (size_t srcIx = 0; srcIx < indices.size(); ++srcIx) { + std::lock_guard<std::mutex> lock(mStructDescriptorsMutex); + const C2Param::CoreIndex coreIndex = C2Param::CoreIndex(indices[srcIx]).coreIndex(); + const auto item = mStructDescriptors.find(coreIndex); + if (item == mStructDescriptors.end()) { + // not in the cache, and not known to be unsupported, query local reflector + if (!mUnsupportedStructDescriptors.count(coreIndex)) { + std::shared_ptr<C2StructDescriptor> structDesc = + mParamReflector->describe(coreIndex); + if (!structDesc) { + mUnsupportedStructDescriptors.emplace(coreIndex); + } else { + mStructDescriptors.insert({ coreIndex, structDesc }); + if (objcpy(&descriptors[dstIx], *structDesc)) { + ++dstIx; + continue; + } + res = Status::CORRUPTED; + break; + } + } + res = Status::NOT_FOUND; + } else if (item->second) { + if (objcpy(&descriptors[dstIx], *item->second)) { + ++dstIx; + continue; + } + res = Status::CORRUPTED; + break; + } else { + res = Status::NO_MEMORY; + break; + } + } + descriptors.resize(dstIx); + _hidl_cb(res, descriptors); + return Void(); +} + +Return<sp<IClientManager>> ComponentStore::getPoolClientManager() { + return ClientManager::getInstance(); +} + +Return<Status> ComponentStore::copyBuffer(const Buffer& src, const Buffer& dst) { + // TODO implement + (void)src; + (void)dst; + return Status::OMITTED; +} + +Return<sp<IConfigurable>> ComponentStore::getConfigurable() { + return mConfigurable; +} + +// Called from createComponent() after a successful creation of `component`. +void ComponentStore::reportComponentBirth(Component* component) { + ComponentStatus componentStatus; + componentStatus.c2Component = component->mComponent; + componentStatus.birthTime = std::chrono::system_clock::now(); + + std::lock_guard<std::mutex> lock(mComponentRosterMutex); + mComponentRoster.emplace(component, componentStatus); +} + +// Called from within the destructor of `component`. No virtual function calls +// are made on `component` here. +void ComponentStore::reportComponentDeath(Component* component) { + std::lock_guard<std::mutex> lock(mComponentRosterMutex); + mComponentRoster.erase(component); +} + +// Dumps component traits. +std::ostream& ComponentStore::dump( + std::ostream& out, + const std::shared_ptr<const C2Component::Traits>& comp) { + + constexpr const char indent[] = " "; + + out << indent << "name: " << comp->name << std::endl; + out << indent << "domain: " << comp->domain << std::endl; + out << indent << "kind: " << comp->kind << std::endl; + out << indent << "rank: " << comp->rank << std::endl; + out << indent << "mediaType: " << comp->mediaType << std::endl; + out << indent << "aliases:"; + for (const auto& alias : comp->aliases) { + out << ' ' << alias; + } + out << std::endl; + + return out; +} + +// Dumps component status. +std::ostream& ComponentStore::dump( + std::ostream& out, + ComponentStatus& compStatus) { + + constexpr const char indent[] = " "; + + // Print birth time. + std::chrono::milliseconds ms = + std::chrono::duration_cast<std::chrono::milliseconds>( + compStatus.birthTime.time_since_epoch()); + std::time_t birthTime = std::chrono::system_clock::to_time_t( + compStatus.birthTime); + std::tm tm = *std::localtime(&birthTime); + out << indent << "Creation time: " + << std::put_time(&tm, "%Y-%m-%d %H:%M:%S") + << '.' << std::setfill('0') << std::setw(3) << ms.count() % 1000 + << std::endl; + + // Print name and id. + std::shared_ptr<C2ComponentInterface> intf = compStatus.c2Component->intf(); + if (!intf) { + out << indent << "Unknown component -- null interface" << std::endl; + return out; + } + out << indent << "Name: " << intf->getName() << std::endl; + out << indent << "Id: " << intf->getId() << std::endl; + + return out; +} + +// Dumps information when lshal is called. +Return<void> ComponentStore::debug( + const hidl_handle& handle, + const hidl_vec<hidl_string>& /* args */) { + LOG(INFO) << "debug -- dumping..."; + const native_handle_t *h = handle.getNativeHandle(); + if (!h || h->numFds != 1) { + LOG(ERROR) << "debug -- dumping failed -- " + "invalid file descriptor to dump to"; + return Void(); + } + std::ostringstream out; + + { // Populate "out". + + constexpr const char indent[] = " "; + + // Show name. + out << "Beginning of dump -- C2ComponentStore: " + << mStore->getName() << std::endl << std::endl; + + // Retrieve the list of supported components. + std::vector<std::shared_ptr<const C2Component::Traits>> traitsList = + mStore->listComponents(); + + // Dump the traits of supported components. + out << indent << "Supported components:" << std::endl << std::endl; + if (traitsList.size() == 0) { + out << indent << indent << "NONE" << std::endl << std::endl; + } else { + for (const auto& traits : traitsList) { + dump(out, traits) << std::endl; + } + } + + // Dump active components. + { + out << indent << "Active components:" << std::endl << std::endl; + std::lock_guard<std::mutex> lock(mComponentRosterMutex); + if (mComponentRoster.size() == 0) { + out << indent << indent << "NONE" << std::endl << std::endl; + } else { + for (auto& pair : mComponentRoster) { + dump(out, pair.second) << std::endl; + } + } + } + + out << "End of dump -- C2ComponentStore: " + << mStore->getName() << std::endl; + } + + if (!android::base::WriteStringToFd(out.str(), h->data[0])) { + PLOG(WARNING) << "debug -- dumping failed -- write()"; + } else { + LOG(INFO) << "debug -- dumping succeeded"; + } + return Void(); +} + + +} // namespace utils +} // namespace V1_0 +} // namespace c2 +} // namespace media +} // namespace hardware +} // namespace android +
diff --git a/media/codec2/hidl/1.0/utils/Configurable.cpp b/media/codec2/hidl/1.0/utils/Configurable.cpp new file mode 100644 index 0000000..ec9c170 --- /dev/null +++ b/media/codec2/hidl/1.0/utils/Configurable.cpp
@@ -0,0 +1,195 @@ +/* + * Copyright 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 "Codec2-Configurable" +#include <android-base/logging.h> + +#include <codec2/hidl/1.0/Configurable.h> +#include <codec2/hidl/1.0/ComponentStore.h> +#include <codec2/hidl/1.0/types.h> + +#include <C2ParamInternal.h> + +namespace android { +namespace hardware { +namespace media { +namespace c2 { +namespace V1_0 { +namespace utils { + +using namespace ::android; + +CachedConfigurable::CachedConfigurable( + std::unique_ptr<ConfigurableC2Intf>&& intf) + : mIntf{std::move(intf)} { +} + +c2_status_t CachedConfigurable::init(ComponentStore* store) { + // Retrieve supported parameters from store + c2_status_t init = mIntf->querySupportedParams(&mSupportedParams); + c2_status_t validate = store->validateSupportedParams(mSupportedParams); + return init == C2_OK ? C2_OK : validate; +} + +// Methods from ::android::hardware::media::c2::V1_0::IConfigurable follow. +Return<uint32_t> CachedConfigurable::getId() { + return mIntf->getId(); +} + +Return<void> CachedConfigurable::getName(getName_cb _hidl_cb) { + _hidl_cb(mIntf->getName()); + return Void(); +} + +Return<void> CachedConfigurable::query( + const hidl_vec<uint32_t>& indices, + bool mayBlock, + query_cb _hidl_cb) { + typedef C2Param::Index Index; + std::vector<Index> c2heapParamIndices( + (Index*)indices.data(), + (Index*)indices.data() + indices.size()); + std::vector<std::unique_ptr<C2Param>> c2heapParams; + c2_status_t c2res = mIntf->query( + c2heapParamIndices, + mayBlock ? C2_MAY_BLOCK : C2_DONT_BLOCK, + &c2heapParams); + + hidl_vec<uint8_t> params; + if (!createParamsBlob(¶ms, c2heapParams)) { + LOG(WARNING) << "query -- invalid output params."; + } + _hidl_cb(static_cast<Status>(c2res), params); + return Void(); +} + +Return<void> CachedConfigurable::config( + const hidl_vec<uint8_t>& inParams, + bool mayBlock, + config_cb _hidl_cb) { + // inParams is not writable, so create a copy as config modifies the parameters + hidl_vec<uint8_t> inParamsCopy = inParams; + std::vector<C2Param*> c2params; + if (!parseParamsBlob(&c2params, inParamsCopy)) { + LOG(WARNING) << "config -- invalid input params."; + _hidl_cb(Status::CORRUPTED, + hidl_vec<SettingResult>(), + hidl_vec<uint8_t>()); + return Void(); + } + // TODO: check if blob was invalid + std::vector<std::unique_ptr<C2SettingResult>> c2failures; + c2_status_t c2res = mIntf->config( + c2params, + mayBlock ? C2_MAY_BLOCK : C2_DONT_BLOCK, + &c2failures); + hidl_vec<SettingResult> failures(c2failures.size()); + { + size_t ix = 0; + for (const std::unique_ptr<C2SettingResult>& c2result : c2failures) { + if (c2result) { + if (objcpy(&failures[ix], *c2result)) { + ++ix; + } else { + LOG(DEBUG) << "config -- invalid setting results."; + break; + } + } + } + failures.resize(ix); + } + hidl_vec<uint8_t> outParams; + if (!createParamsBlob(&outParams, c2params)) { + LOG(DEBUG) << "config -- invalid output params."; + } + _hidl_cb((Status)c2res, failures, outParams); + return Void(); +} + +Return<void> CachedConfigurable::querySupportedParams( + uint32_t start, + uint32_t count, + querySupportedParams_cb _hidl_cb) { + C2LinearRange request = C2LinearCapacity(mSupportedParams.size()).range( + start, count); + hidl_vec<ParamDescriptor> params(request.size()); + Status res = Status::OK; + size_t dstIx = 0; + for (size_t srcIx = request.offset(); srcIx < request.endOffset(); ++srcIx) { + if (mSupportedParams[srcIx]) { + if (objcpy(¶ms[dstIx], *mSupportedParams[srcIx])) { + ++dstIx; + } else { + res = Status::CORRUPTED; + LOG(WARNING) << "querySupportedParams -- invalid output params."; + break; + } + } else { + res = Status::BAD_INDEX; + } + } + params.resize(dstIx); + _hidl_cb(res, params); + return Void(); +} + +Return<void> CachedConfigurable::querySupportedValues( + const hidl_vec<FieldSupportedValuesQuery>& inFields, + bool mayBlock, + querySupportedValues_cb _hidl_cb) { + std::vector<C2FieldSupportedValuesQuery> c2fields; + { + // C2FieldSupportedValuesQuery objects are restricted in that some + // members are const. + // C2ParamField - required for its constructor - has no constructors + // from fields. Use C2ParamInspector. + for (const FieldSupportedValuesQuery &query : inFields) { + c2fields.emplace_back(_C2ParamInspector::CreateParamField( + query.field.index, + query.field.fieldId.offset, + query.field.fieldId.size), + query.type == FieldSupportedValuesQuery::Type::POSSIBLE ? + C2FieldSupportedValuesQuery::POSSIBLE : + C2FieldSupportedValuesQuery::CURRENT); + } + } + c2_status_t c2res = mIntf->querySupportedValues( + c2fields, + mayBlock ? C2_MAY_BLOCK : C2_DONT_BLOCK); + hidl_vec<FieldSupportedValuesQueryResult> outFields(inFields.size()); + size_t dstIx = 0; + for (const C2FieldSupportedValuesQuery &result : c2fields) { + if (objcpy(&outFields[dstIx], result)) { + ++dstIx; + } else { + outFields.resize(dstIx); + c2res = C2_CORRUPTED; + LOG(WARNING) << "querySupportedValues -- invalid output params."; + break; + } + } + _hidl_cb((Status)c2res, outFields); + return Void(); +} + +} // namespace utils +} // namespace V1_0 +} // namespace c2 +} // namespace media +} // namespace hardware +} // namespace android +
diff --git a/media/codec2/hidl/1.0/utils/InputBufferManager.cpp b/media/codec2/hidl/1.0/utils/InputBufferManager.cpp new file mode 100644 index 0000000..a023a05 --- /dev/null +++ b/media/codec2/hidl/1.0/utils/InputBufferManager.cpp
@@ -0,0 +1,461 @@ +/* + * Copyright 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 "Codec2-InputBufferManager" +#include <android-base/logging.h> + +#include <codec2/hidl/1.0/InputBufferManager.h> +#include <codec2/hidl/1.0/types.h> + +#include <android/hardware/media/c2/1.0/IComponentListener.h> +#include <android-base/logging.h> + +#include <C2Buffer.h> +#include <C2Work.h> + +#include <chrono> + +namespace android { +namespace hardware { +namespace media { +namespace c2 { +namespace V1_0 { +namespace utils { + +using namespace ::android; + +void InputBufferManager::registerFrameData( + const sp<IComponentListener>& listener, + const C2FrameData& input) { + getInstance()._registerFrameData(listener, input); +} + +void InputBufferManager::unregisterFrameData( + const wp<IComponentListener>& listener, + const C2FrameData& input) { + getInstance()._unregisterFrameData(listener, input); +} + +void InputBufferManager::unregisterFrameData( + const wp<IComponentListener>& listener) { + getInstance()._unregisterFrameData(listener); +} + +void InputBufferManager::setNotificationInterval( + nsecs_t notificationIntervalNs) { + getInstance()._setNotificationInterval(notificationIntervalNs); +} + +void InputBufferManager::_registerFrameData( + const sp<IComponentListener>& listener, + const C2FrameData& input) { + uint64_t frameIndex = input.ordinal.frameIndex.peeku(); + LOG(VERBOSE) << "InputBufferManager::_registerFrameData -- called with " + << "listener @ 0x" << std::hex << listener.get() + << ", frameIndex = " << std::dec << frameIndex + << "."; + std::lock_guard<std::mutex> lock(mMutex); + + std::set<TrackedBuffer> &bufferIds = + mTrackedBuffersMap[listener][frameIndex]; + + for (size_t i = 0; i < input.buffers.size(); ++i) { + if (!input.buffers[i]) { + LOG(VERBOSE) << "InputBufferManager::_registerFrameData -- " + << "Input buffer at index " << i << " is null."; + continue; + } + const TrackedBuffer &bufferId = + *bufferIds.emplace(listener, frameIndex, i, input.buffers[i]). + first; + + c2_status_t status = input.buffers[i]->registerOnDestroyNotify( + onBufferDestroyed, + const_cast<void*>(reinterpret_cast<const void*>(&bufferId))); + if (status != C2_OK) { + LOG(DEBUG) << "InputBufferManager::_registerFrameData -- " + << "registerOnDestroyNotify() failed " + << "(listener @ 0x" << std::hex << listener.get() + << ", frameIndex = " << std::dec << frameIndex + << ", bufferIndex = " << i + << ") => status = " << status + << "."; + } + } + + mDeathNotifications.emplace( + listener, + DeathNotifications( + mNotificationIntervalNs.load(std::memory_order_relaxed))); +} + +// Remove a pair (listener, frameIndex) from mTrackedBuffersMap and +// mDeathNotifications. This implies all bufferIndices are removed. +// +// This is called from onWorkDone() and flush(). +void InputBufferManager::_unregisterFrameData( + const wp<IComponentListener>& listener, + const C2FrameData& input) { + uint64_t frameIndex = input.ordinal.frameIndex.peeku(); + LOG(VERBOSE) << "InputBufferManager::_unregisterFrameData -- called with " + << "listener @ 0x" << std::hex << listener.unsafe_get() + << ", frameIndex = " << std::dec << frameIndex + << "."; + std::lock_guard<std::mutex> lock(mMutex); + + auto findListener = mTrackedBuffersMap.find(listener); + if (findListener != mTrackedBuffersMap.end()) { + std::map<uint64_t, std::set<TrackedBuffer>> &frameIndex2BufferIds + = findListener->second; + auto findFrameIndex = frameIndex2BufferIds.find(frameIndex); + if (findFrameIndex != frameIndex2BufferIds.end()) { + std::set<TrackedBuffer> &bufferIds = findFrameIndex->second; + for (const TrackedBuffer& bufferId : bufferIds) { + std::shared_ptr<C2Buffer> buffer = bufferId.buffer.lock(); + if (buffer) { + c2_status_t status = buffer->unregisterOnDestroyNotify( + onBufferDestroyed, + const_cast<void*>( + reinterpret_cast<const void*>(&bufferId))); + if (status != C2_OK) { + LOG(DEBUG) << "InputBufferManager::_unregisterFrameData " + << "-- unregisterOnDestroyNotify() failed " + << "(listener @ 0x" + << std::hex + << bufferId.listener.unsafe_get() + << ", frameIndex = " + << std::dec << bufferId.frameIndex + << ", bufferIndex = " << bufferId.bufferIndex + << ") => status = " << status + << "."; + } + } + } + + frameIndex2BufferIds.erase(findFrameIndex); + if (frameIndex2BufferIds.empty()) { + mTrackedBuffersMap.erase(findListener); + } + } + } + + auto findListenerD = mDeathNotifications.find(listener); + if (findListenerD != mDeathNotifications.end()) { + DeathNotifications &deathNotifications = findListenerD->second; + auto findFrameIndex = deathNotifications.indices.find(frameIndex); + if (findFrameIndex != deathNotifications.indices.end()) { + std::vector<size_t> &bufferIndices = findFrameIndex->second; + deathNotifications.count -= bufferIndices.size(); + deathNotifications.indices.erase(findFrameIndex); + } + } +} + +// Remove listener from mTrackedBuffersMap and mDeathNotifications. This implies +// all frameIndices and bufferIndices are removed. +// +// This is called when the component cleans up all input buffers, i.e., when +// reset(), release(), stop() or ~Component() is called. +void InputBufferManager::_unregisterFrameData( + const wp<IComponentListener>& listener) { + LOG(VERBOSE) << "InputBufferManager::_unregisterFrameData -- called with " + << "listener @ 0x" << std::hex << listener.unsafe_get() + << std::dec << "."; + std::lock_guard<std::mutex> lock(mMutex); + + auto findListener = mTrackedBuffersMap.find(listener); + if (findListener != mTrackedBuffersMap.end()) { + std::map<uint64_t, std::set<TrackedBuffer>> &frameIndex2BufferIds = + findListener->second; + for (auto findFrameIndex = frameIndex2BufferIds.begin(); + findFrameIndex != frameIndex2BufferIds.end(); + ++findFrameIndex) { + std::set<TrackedBuffer> &bufferIds = findFrameIndex->second; + for (const TrackedBuffer& bufferId : bufferIds) { + std::shared_ptr<C2Buffer> buffer = bufferId.buffer.lock(); + if (buffer) { + c2_status_t status = buffer->unregisterOnDestroyNotify( + onBufferDestroyed, + const_cast<void*>( + reinterpret_cast<const void*>(&bufferId))); + if (status != C2_OK) { + LOG(DEBUG) << "InputBufferManager::_unregisterFrameData " + << "-- unregisterOnDestroyNotify() failed " + << "(listener @ 0x" + << std::hex + << bufferId.listener.unsafe_get() + << ", frameIndex = " + << std::dec << bufferId.frameIndex + << ", bufferIndex = " << bufferId.bufferIndex + << ") => status = " << status + << "."; + } + } + } + } + mTrackedBuffersMap.erase(findListener); + } + + mDeathNotifications.erase(listener); +} + +// Set mNotificationIntervalNs. +void InputBufferManager::_setNotificationInterval( + nsecs_t notificationIntervalNs) { + mNotificationIntervalNs.store( + notificationIntervalNs, + std::memory_order_relaxed); +} + +// Move a buffer from mTrackedBuffersMap to mDeathNotifications. +// This is called when a registered C2Buffer object is destroyed. +void InputBufferManager::onBufferDestroyed(const C2Buffer* buf, void* arg) { + getInstance()._onBufferDestroyed(buf, arg); +} + +void InputBufferManager::_onBufferDestroyed(const C2Buffer* buf, void* arg) { + if (!buf || !arg) { + LOG(WARNING) << "InputBufferManager::_onBufferDestroyed -- called with " + << "null argument (s): " + << "buf @ 0x" << std::hex << buf + << ", arg @ 0x" << std::hex << arg + << std::dec << "."; + return; + } + TrackedBuffer id(*reinterpret_cast<TrackedBuffer*>(arg)); + LOG(VERBOSE) << "InputBufferManager::_onBufferDestroyed -- called with " + << "buf @ 0x" << std::hex << buf + << ", arg @ 0x" << std::hex << arg + << std::dec << " -- " + << "listener @ 0x" << std::hex << id.listener.unsafe_get() + << ", frameIndex = " << std::dec << id.frameIndex + << ", bufferIndex = " << id.bufferIndex + << "."; + + std::lock_guard<std::mutex> lock(mMutex); + + auto findListener = mTrackedBuffersMap.find(id.listener); + if (findListener == mTrackedBuffersMap.end()) { + LOG(DEBUG) << "InputBufferManager::_onBufferDestroyed -- " + << "received invalid listener: " + << "listener @ 0x" << std::hex << id.listener.unsafe_get() + << " (frameIndex = " << std::dec << id.frameIndex + << ", bufferIndex = " << id.bufferIndex + << ")."; + return; + } + + std::map<uint64_t, std::set<TrackedBuffer>> &frameIndex2BufferIds + = findListener->second; + auto findFrameIndex = frameIndex2BufferIds.find(id.frameIndex); + if (findFrameIndex == frameIndex2BufferIds.end()) { + LOG(DEBUG) << "InputBufferManager::_onBufferDestroyed -- " + << "received invalid frame index: " + << "frameIndex = " << id.frameIndex + << " (listener @ 0x" << std::hex << id.listener.unsafe_get() + << ", bufferIndex = " << std::dec << id.bufferIndex + << ")."; + return; + } + + std::set<TrackedBuffer> &bufferIds = findFrameIndex->second; + auto findBufferId = bufferIds.find(id); + if (findBufferId == bufferIds.end()) { + LOG(DEBUG) << "InputBufferManager::_onBufferDestroyed -- " + << "received invalid buffer index: " + << "bufferIndex = " << id.bufferIndex + << " (frameIndex = " << id.frameIndex + << ", listener @ 0x" << std::hex << id.listener.unsafe_get() + << std::dec << ")."; + return; + } + + bufferIds.erase(findBufferId); + if (bufferIds.empty()) { + frameIndex2BufferIds.erase(findFrameIndex); + if (frameIndex2BufferIds.empty()) { + mTrackedBuffersMap.erase(findListener); + } + } + + DeathNotifications &deathNotifications = mDeathNotifications[id.listener]; + deathNotifications.indices[id.frameIndex].emplace_back(id.bufferIndex); + ++deathNotifications.count; + mOnBufferDestroyed.notify_one(); +} + +// Notify the clients about buffer destructions. +// Return false if all destructions have been notified. +// Return true and set timeToRetry to the time point to wait for before +// retrying if some destructions have not been notified. +bool InputBufferManager::processNotifications(nsecs_t* timeToRetryNs) { + + struct Notification { + sp<IComponentListener> listener; + hidl_vec<IComponentListener::InputBuffer> inputBuffers; + Notification(const sp<IComponentListener>& l, size_t s) + : listener(l), inputBuffers(s) {} + }; + std::list<Notification> notifications; + nsecs_t notificationIntervalNs = + mNotificationIntervalNs.load(std::memory_order_relaxed); + + bool retry = false; + { + std::lock_guard<std::mutex> lock(mMutex); + *timeToRetryNs = notificationIntervalNs; + nsecs_t timeNowNs = systemTime(); + for (auto it = mDeathNotifications.begin(); + it != mDeathNotifications.end(); ) { + sp<IComponentListener> listener = it->first.promote(); + if (!listener) { + ++it; + continue; + } + DeathNotifications &deathNotifications = it->second; + + nsecs_t timeSinceLastNotifiedNs = + timeNowNs - deathNotifications.lastSentNs; + // If not enough time has passed since the last callback, leave the + // notifications for this listener untouched for now and retry + // later. + if (timeSinceLastNotifiedNs < notificationIntervalNs) { + retry = true; + *timeToRetryNs = std::min(*timeToRetryNs, + notificationIntervalNs - timeSinceLastNotifiedNs); + LOG(VERBOSE) << "InputBufferManager::processNotifications -- " + << "Notifications for listener @ " + << std::hex << listener.get() + << " will be postponed."; + ++it; + continue; + } + + // If enough time has passed since the last notification to this + // listener but there are currently no pending notifications, the + // listener can be removed from mDeathNotifications---there is no + // need to keep track of the last notification time anymore. + if (deathNotifications.count == 0) { + it = mDeathNotifications.erase(it); + continue; + } + + // Create the argument for the callback. + notifications.emplace_back(listener, deathNotifications.count); + hidl_vec<IComponentListener::InputBuffer> &inputBuffers = + notifications.back().inputBuffers; + size_t i = 0; + for (std::pair<const uint64_t, std::vector<size_t>>& p : + deathNotifications.indices) { + uint64_t frameIndex = p.first; + const std::vector<size_t> &bufferIndices = p.second; + for (const size_t& bufferIndex : bufferIndices) { + IComponentListener::InputBuffer &inputBuffer + = inputBuffers[i++]; + inputBuffer.arrayIndex = bufferIndex; + inputBuffer.frameIndex = frameIndex; + } + } + + // Clear deathNotifications for this listener and set retry to true + // so processNotifications will be called again. This will + // guarantee that a listener with no pending notifications will + // eventually be removed from mDeathNotifications after + // mNotificationIntervalNs nanoseconds has passed. + retry = true; + deathNotifications.indices.clear(); + deathNotifications.count = 0; + deathNotifications.lastSentNs = timeNowNs; + ++it; + } + } + + // Call onInputBuffersReleased() outside the lock to avoid deadlock. + for (const Notification& notification : notifications) { + if (!notification.listener->onInputBuffersReleased( + notification.inputBuffers).isOk()) { + // This may trigger if the client has died. + LOG(DEBUG) << "InputBufferManager::processNotifications -- " + << "failed to send death notifications to " + << "listener @ 0x" << std::hex + << notification.listener.get() + << std::dec << "."; + } else { +#if LOG_NDEBUG == 0 + std::stringstream inputBufferLog; + for (const IComponentListener::InputBuffer& inputBuffer : + notification.inputBuffers) { + inputBufferLog << " (" << inputBuffer.frameIndex + << ", " << inputBuffer.arrayIndex + << ")"; + } + LOG(VERBOSE) << "InputBufferManager::processNotifications -- " + << "death notifications sent to " + << "listener @ 0x" << std::hex + << notification.listener.get() + << std::dec + << " with these (frameIndex, bufferIndex) pairs:" + << inputBufferLog.str(); +#endif + } + } +#if LOG_NDEBUG == 0 + if (retry) { + LOG(VERBOSE) << "InputBufferManager::processNotifications -- " + << "will retry again in " << *timeToRetryNs << "ns."; + } else { + LOG(VERBOSE) << "InputBufferManager::processNotifications -- " + << "no pending death notifications."; + } +#endif + return retry; +} + +void InputBufferManager::main() { + LOG(VERBOSE) << "InputBufferManager main -- started."; + nsecs_t timeToRetryNs; + while (true) { + std::unique_lock<std::mutex> lock(mMutex); + while (mDeathNotifications.empty()) { + mOnBufferDestroyed.wait(lock); + } + lock.unlock(); + while (processNotifications(&timeToRetryNs)) { + std::this_thread::sleep_for( + std::chrono::nanoseconds(timeToRetryNs)); + } + } +} + +InputBufferManager::InputBufferManager() + : mMainThread{&InputBufferManager::main, this} { +} + +InputBufferManager& InputBufferManager::getInstance() { + static InputBufferManager instance{}; + return instance; +} + +} // namespace utils +} // namespace V1_0 +} // namespace c2 +} // namespace media +} // namespace hardware +} // namespace android + + +
diff --git a/media/codec2/hidl/1.0/utils/InputSurface.cpp b/media/codec2/hidl/1.0/utils/InputSurface.cpp new file mode 100644 index 0000000..2b4ca85 --- /dev/null +++ b/media/codec2/hidl/1.0/utils/InputSurface.cpp
@@ -0,0 +1,177 @@ +/* + * Copyright 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 "Codec2-InputSurface" +#include <android-base/logging.h> + +#include <codec2/hidl/1.0/InputSurface.h> +#include <codec2/hidl/1.0/InputSurfaceConnection.h> + +#include <C2Component.h> +#include <C2Config.h> + +#include <memory> + +namespace android { +namespace hardware { +namespace media { +namespace c2 { +namespace V1_0 { +namespace utils { + +using namespace ::android; + +// Derived class of C2InterfaceHelper +class InputSurface::Interface : public C2InterfaceHelper { +public: + explicit Interface( + const std::shared_ptr<C2ReflectorHelper> &helper) + : C2InterfaceHelper(helper) { + + setDerivedInstance(this); + + addParameter( + DefineParam(mEos, C2_PARAMKEY_INPUT_SURFACE_EOS) + .withDefault(new C2InputSurfaceEosTuning(false)) + .withFields({C2F(mEos, value).oneOf({true, false})}) + .withSetter(EosSetter) + .build()); + } + + static C2R EosSetter(bool mayBlock, C2P<C2InputSurfaceEosTuning> &me) { + (void)mayBlock; + return me.F(me.v.value).validatePossible(me.v.value); + } + + bool eos() const { return mEos->value; } + +private: + std::shared_ptr<C2InputSurfaceEosTuning> mEos; +}; + +// Derived class of ConfigurableC2Intf +class InputSurface::ConfigurableIntf : public ConfigurableC2Intf { +public: + ConfigurableIntf( + const std::shared_ptr<InputSurface::Interface> &intf, + const sp<GraphicBufferSource> &source) + : ConfigurableC2Intf("input-surface", 0), + mIntf(intf), + mSource(source) { + } + + virtual ~ConfigurableIntf() override = default; + + virtual c2_status_t query( + const std::vector<C2Param::Index> &indices, + c2_blocking_t mayBlock, + std::vector<std::unique_ptr<C2Param>>* const params + ) const override { + return mIntf->query({}, indices, mayBlock, params); + } + + virtual c2_status_t config( + const std::vector<C2Param*> ¶ms, + c2_blocking_t mayBlock, + std::vector<std::unique_ptr<C2SettingResult>>* const failures + ) override { + c2_status_t err = mIntf->config(params, mayBlock, failures); + if (mIntf->eos()) { + sp<GraphicBufferSource> source = mSource.promote(); + if (source == nullptr || source->signalEndOfInputStream() != OK) { + // TODO: put something in |failures| + err = C2_BAD_VALUE; + } + // TODO: reset eos? + } + return err; + } + + virtual c2_status_t querySupportedParams( + std::vector<std::shared_ptr<C2ParamDescriptor>>* const params + ) const override { + return mIntf->querySupportedParams(params); + } + + virtual c2_status_t querySupportedValues( + std::vector<C2FieldSupportedValuesQuery>& fields, + c2_blocking_t mayBlock) const override { + return mIntf->querySupportedValues(fields, mayBlock); + } + +private: + const std::shared_ptr<InputSurface::Interface> mIntf; + wp<GraphicBufferSource> mSource; +}; + +Return<sp<InputSurface::HGraphicBufferProducer>> InputSurface::getGraphicBufferProducer() { + return mProducer; +} + +Return<sp<IConfigurable>> InputSurface::getConfigurable() { + return mConfigurable; +} + +Return<void> InputSurface::connect( + const sp<IInputSink>& sink, + connect_cb _hidl_cb) { + Status status; + sp<InputSurfaceConnection> connection; + if (!sink) { + _hidl_cb(Status::BAD_VALUE, nullptr); + return Void(); + } + std::shared_ptr<C2Component> comp = Component::findLocalComponent(sink); + if (comp) { + connection = new InputSurfaceConnection(mSource, comp, mStore); + } else { + connection = new InputSurfaceConnection(mSource, sink, mStore); + } + if (!connection->init()) { + connection = nullptr; + status = Status::BAD_VALUE; + } else { + status = Status::OK; + } + _hidl_cb(status, connection); + return Void(); +} + +// Constructor is exclusive to ComponentStore. +InputSurface::InputSurface( + const sp<ComponentStore>& store, + const std::shared_ptr<C2ReflectorHelper>& reflector, + const sp<HGraphicBufferProducer>& producer, + const sp<GraphicBufferSource>& source) + : mStore{store}, + mProducer{producer}, + mSource{source}, + mIntf{std::make_shared<Interface>(reflector)}, + mConfigurable{new CachedConfigurable( + std::make_unique<ConfigurableIntf>( + mIntf, source))} { + + mConfigurable->init(store.get()); +} + +} // namespace utils +} // namespace V1_0 +} // namespace c2 +} // namespace media +} // namespace hardware +} // namespace android +
diff --git a/media/codec2/hidl/1.0/utils/InputSurfaceConnection.cpp b/media/codec2/hidl/1.0/utils/InputSurfaceConnection.cpp new file mode 100644 index 0000000..c9932ef --- /dev/null +++ b/media/codec2/hidl/1.0/utils/InputSurfaceConnection.cpp
@@ -0,0 +1,529 @@ +/* + * Copyright 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 "Codec2-InputSurfaceConnection" +#include <android-base/logging.h> + +#include <codec2/hidl/1.0/InputSurfaceConnection.h> +#include <codec2/hidl/1.0/InputSurfaceConnection.h> + +#include <memory> +#include <list> +#include <mutex> +#include <atomic> + +#include <hidl/HidlSupport.h> +#include <media/stagefright/bqhelper/ComponentWrapper.h> +#include <system/graphics.h> +#include <ui/GraphicBuffer.h> +#include <utils/Errors.h> + +#include <C2.h> +#include <C2AllocatorGralloc.h> +#include <C2BlockInternal.h> +#include <C2Buffer.h> +#include <C2Component.h> +#include <C2Config.h> +#include <C2Debug.h> +#include <C2PlatformSupport.h> +#include <C2Work.h> + +namespace android { +namespace hardware { +namespace media { +namespace c2 { +namespace V1_0 { +namespace utils { + +constexpr int32_t kBufferCount = 16; + +using namespace ::android; +using ::android::hardware::hidl_string; +using ::android::hardware::hidl_vec; +using ::android::hardware::Return; + +namespace /* unnamed */ { + +class Buffer2D : public C2Buffer { +public: + explicit Buffer2D(C2ConstGraphicBlock block) : C2Buffer({ block }) { + } +}; + +} // unnamed namespace + +// Derived class of ComponentWrapper for use with +// GraphicBufferSource::configure(). +// +struct InputSurfaceConnection::Impl : public ComponentWrapper { + + Impl(const sp<GraphicBufferSource>& source, + const std::shared_ptr<C2Component>& localComp) + : mSource{source}, mLocalComp{localComp}, mSink{}, mFrameIndex{0} { + std::shared_ptr<C2ComponentInterface> intf = localComp->intf(); + mSinkName = intf ? intf->getName() : ""; + } + + Impl(const sp<GraphicBufferSource>& source, + const sp<IInputSink>& sink) + : mSource{source}, mLocalComp{}, mSink{sink}, mFrameIndex{0} { + Return<sp<IConfigurable>> transResult = sink->getConfigurable(); + if (!transResult.isOk()) { + LOG(ERROR) << "Remote sink is dead."; + return; + } + mSinkConfigurable = + static_cast<sp<IConfigurable>>(transResult); + if (!mSinkConfigurable) { + LOG(ERROR) << "Remote sink is not configurable."; + mSinkName = ""; + return; + } + + hidl_string name; + Return<void> transStatus = mSinkConfigurable->getName( + [&name](const hidl_string& n) { + name = n; + }); + if (!transStatus.isOk()) { + LOG(ERROR) << "Remote sink's configurable is dead."; + mSinkName = ""; + return; + } + mSinkName = name.c_str(); + } + + virtual ~Impl() { + mSource->stop(); + mSource->release(); + } + + bool init() { + if (mSource == nullptr) { + return false; + } + status_t err = mSource->initCheck(); + if (err != OK) { + LOG(WARNING) << "Impl::init -- GraphicBufferSource init failed: " + << "status = " << err << "."; + return false; + } + + // TODO: read settings properly from the interface + C2StreamPictureSizeInfo::input inputSize; + C2StreamUsageTuning::input usage; + c2_status_t c2Status = queryFromSink({ &inputSize, &usage }, + {}, + C2_MAY_BLOCK, + nullptr); + if (c2Status != C2_OK) { + LOG(WARNING) << "Impl::init -- cannot query information from " + "the component interface: " + << "status = " << asString(c2Status) << "."; + return false; + } + + // TODO: proper color aspect & dataspace + android_dataspace dataSpace = HAL_DATASPACE_BT709; + + // TODO: use the usage read from intf + // uint32_t grallocUsage = + // C2AndroidMemoryUsage(C2MemoryUsage(usage.value)). + // asGrallocUsage(); + + uint32_t grallocUsage = + mSinkName.compare(0, 11, "c2.android.") == 0 ? + GRALLOC_USAGE_SW_READ_OFTEN : + GRALLOC_USAGE_HW_VIDEO_ENCODER; + + err = mSource->configure( + this, dataSpace, kBufferCount, + inputSize.width, inputSize.height, + grallocUsage); + if (err != OK) { + LOG(WARNING) << "Impl::init -- GBS configure failed: " + << "status = " << err << "."; + return false; + } + for (int32_t i = 0; i < kBufferCount; ++i) { + if (!mSource->onInputBufferAdded(i).isOk()) { + LOG(WARNING) << "Impl::init: failed to populate GBS slots."; + return false; + } + } + if (!mSource->start().isOk()) { + LOG(WARNING) << "Impl::init -- GBS failed to start."; + return false; + } + mAllocatorMutex.lock(); + c2_status_t c2err = GetCodec2PlatformAllocatorStore()->fetchAllocator( + C2AllocatorStore::PLATFORM_START + 1, // GRALLOC + &mAllocator); + mAllocatorMutex.unlock(); + if (c2err != OK) { + LOG(WARNING) << "Impl::init -- failed to fetch gralloc allocator: " + << "status = " << asString(c2err) << "."; + return false; + } + return true; + } + + // From ComponentWrapper + virtual status_t submitBuffer( + int32_t bufferId, + const sp<GraphicBuffer>& buffer, + int64_t timestamp, + int fenceFd) override { + LOG(VERBOSE) << "Impl::submitBuffer -- bufferId = " << bufferId << "."; + // TODO: Use fd to construct fence + (void)fenceFd; + + std::shared_ptr<C2GraphicAllocation> alloc; + C2Handle* handle = WrapNativeCodec2GrallocHandle( + buffer->handle, + buffer->width, buffer->height, + buffer->format, buffer->usage, buffer->stride); + mAllocatorMutex.lock(); + c2_status_t err = mAllocator->priorGraphicAllocation(handle, &alloc); + mAllocatorMutex.unlock(); + if (err != OK) { + return UNKNOWN_ERROR; + } + std::shared_ptr<C2GraphicBlock> block = + _C2BlockFactory::CreateGraphicBlock(alloc); + + std::unique_ptr<C2Work> work(new C2Work); + work->input.flags = (C2FrameData::flags_t)0; + work->input.ordinal.timestamp = timestamp; + work->input.ordinal.frameIndex = mFrameIndex.fetch_add( + 1, std::memory_order_relaxed); + work->input.buffers.clear(); + std::shared_ptr<C2Buffer> c2Buffer( + // TODO: fence + new Buffer2D(block->share( + C2Rect(block->width(), block->height()), ::C2Fence())), + [bufferId, source = mSource](C2Buffer* ptr) { + delete ptr; + if (source != nullptr) { + // TODO: fence + (void)source->onInputBufferEmptied(bufferId, -1); + } + }); + work->input.buffers.push_back(c2Buffer); + work->worklets.clear(); + work->worklets.emplace_back(new C2Worklet); + std::list<std::unique_ptr<C2Work>> items; + items.push_back(std::move(work)); + + err = queueToSink(&items); + return (err == C2_OK) ? OK : UNKNOWN_ERROR; + } + + virtual status_t submitEos(int32_t bufferId) override { + LOG(VERBOSE) << "Impl::submitEos -- bufferId = " << bufferId << "."; + (void)bufferId; + + std::unique_ptr<C2Work> work(new C2Work); + work->input.flags = (C2FrameData::flags_t)0; + work->input.ordinal.frameIndex = mFrameIndex.fetch_add( + 1, std::memory_order_relaxed); + 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)); + + c2_status_t err = queueToSink(&items); + return (err == C2_OK) ? OK : UNKNOWN_ERROR; + } + + virtual void dispatchDataSpaceChanged( + int32_t dataSpace, int32_t aspects, int32_t pixelFormat) override { + // TODO + (void)dataSpace; + (void)aspects; + (void)pixelFormat; + } + + // Configurable interface for InputSurfaceConnection::Impl. + // + // This class is declared as an inner class so that it will have access to + // all Impl's members. + struct ConfigurableIntf : public ConfigurableC2Intf { + sp<Impl> mConnection; + ConfigurableIntf(const sp<Impl>& connection) + : ConfigurableC2Intf{"input-surface-connection", 0}, + mConnection{connection} {} + virtual c2_status_t config( + const std::vector<C2Param*> ¶ms, + c2_blocking_t mayBlock, + std::vector<std::unique_ptr<C2SettingResult>> *const failures + ) override; + virtual c2_status_t query( + const std::vector<C2Param::Index> &indices, + c2_blocking_t mayBlock, + std::vector<std::unique_ptr<C2Param>> *const params) const override; + virtual c2_status_t querySupportedParams( + std::vector<std::shared_ptr<C2ParamDescriptor>> *const params + ) const override; + virtual c2_status_t querySupportedValues( + std::vector<C2FieldSupportedValuesQuery> &fields, + c2_blocking_t mayBlock) const override; + }; + +private: + c2_status_t queryFromSink( + const std::vector<C2Param*> &stackParams, + const std::vector<C2Param::Index> &heapParamIndices, + c2_blocking_t mayBlock, + std::vector<std::unique_ptr<C2Param>>* const heapParams) { + if (mLocalComp) { + std::shared_ptr<C2ComponentInterface> intf = mLocalComp->intf(); + if (intf) { + return intf->query_vb(stackParams, + heapParamIndices, + mayBlock, + heapParams); + } else { + LOG(ERROR) << "queryFromSink -- " + << "component does not have an interface."; + return C2_BAD_STATE; + } + } + + CHECK(mSink) << "-- queryFromSink " + << "-- connection has no sink."; + CHECK(mSinkConfigurable) << "-- queryFromSink " + << "-- sink has no configurable."; + + hidl_vec<ParamIndex> indices( + stackParams.size() + heapParamIndices.size()); + size_t numIndices = 0; + for (C2Param* const& stackParam : stackParams) { + if (!stackParam) { + LOG(DEBUG) << "queryFromSink -- null stack param encountered."; + continue; + } + indices[numIndices++] = static_cast<ParamIndex>(stackParam->index()); + } + size_t numStackIndices = numIndices; + for (const C2Param::Index& index : heapParamIndices) { + indices[numIndices++] = + static_cast<ParamIndex>(static_cast<uint32_t>(index)); + } + indices.resize(numIndices); + if (heapParams) { + heapParams->reserve(heapParams->size() + numIndices); + } + c2_status_t status; + Return<void> transStatus = mSinkConfigurable->query( + indices, + mayBlock == C2_MAY_BLOCK, + [&status, &numStackIndices, &stackParams, heapParams]( + Status s, const Params& p) { + status = static_cast<c2_status_t>(s); + if (status != C2_OK && status != C2_BAD_INDEX) { + LOG(DEBUG) << "queryFromSink -- call failed: " + << "status = " << asString(status) << "."; + return; + } + std::vector<C2Param*> paramPointers; + if (!parseParamsBlob(¶mPointers, p)) { + LOG(DEBUG) << "queryFromSink -- error while " + << "parsing params."; + status = C2_CORRUPTED; + return; + } + size_t i = 0; + for (auto it = paramPointers.begin(); + it != paramPointers.end(); ) { + C2Param* paramPointer = *it; + if (numStackIndices > 0) { + --numStackIndices; + if (!paramPointer) { + LOG(DEBUG) << "queryFromSink -- " + "null stack param."; + ++it; + continue; + } + for (; i < stackParams.size() && + !stackParams[i]; ) { + ++i; + } + CHECK(i < stackParams.size()); + if (stackParams[i]->index() != + paramPointer->index()) { + LOG(DEBUG) << "queryFromSink -- " + "param skipped (index = " + << stackParams[i]->index() << ")."; + stackParams[i++]->invalidate(); + continue; + } + if (!stackParams[i++]->updateFrom(*paramPointer)) { + LOG(DEBUG) << "queryFromSink -- " + "param update failed (index = " + << paramPointer->index() << ")."; + } + } else { + if (!paramPointer) { + LOG(DEBUG) << "queryFromSink -- " + "null heap param."; + ++it; + continue; + } + if (!heapParams) { + LOG(WARNING) << "queryFromSink -- " + "too many stack params."; + break; + } + heapParams->emplace_back(C2Param::Copy(*paramPointer)); + } + ++it; + } + }); + if (!transStatus.isOk()) { + LOG(ERROR) << "queryFromSink -- transaction failed."; + return C2_CORRUPTED; + } + return status; + } + + c2_status_t queueToSink(std::list<std::unique_ptr<C2Work>>* const items) { + if (mLocalComp) { + return mLocalComp->queue_nb(items); + } + + CHECK(mSink) << "-- queueToSink " + << "-- connection has no sink."; + + WorkBundle workBundle; + if (!objcpy(&workBundle, *items, nullptr)) { + LOG(ERROR) << "queueToSink -- bad input."; + return C2_CORRUPTED; + } + Return<Status> transStatus = mSink->queue(workBundle); + if (!transStatus.isOk()) { + LOG(ERROR) << "queueToSink -- transaction failed."; + return C2_CORRUPTED; + } + c2_status_t status = + static_cast<c2_status_t>(static_cast<Status>(transStatus)); + if (status != C2_OK) { + LOG(DEBUG) << "queueToSink -- call failed: " + << asString(status); + } + return status; + } + + sp<GraphicBufferSource> mSource; + std::shared_ptr<C2Component> mLocalComp; + sp<IInputSink> mSink; + sp<IConfigurable> mSinkConfigurable; + std::string mSinkName; + + // Needed for ComponentWrapper implementation + std::mutex mAllocatorMutex; + std::shared_ptr<C2Allocator> mAllocator; + std::atomic_uint64_t mFrameIndex; + +}; + +InputSurfaceConnection::InputSurfaceConnection( + const sp<GraphicBufferSource>& source, + const std::shared_ptr<C2Component>& comp, + const sp<ComponentStore>& store) + : mImpl{new Impl(source, comp)}, + mConfigurable{new CachedConfigurable( + std::make_unique<Impl::ConfigurableIntf>(mImpl))} { + mConfigurable->init(store.get()); +} + +InputSurfaceConnection::InputSurfaceConnection( + const sp<GraphicBufferSource>& source, + const sp<IInputSink>& sink, + const sp<ComponentStore>& store) + : mImpl{new Impl(source, sink)}, + mConfigurable{new CachedConfigurable( + std::make_unique<Impl::ConfigurableIntf>(mImpl))} { + mConfigurable->init(store.get()); +} + +Return<Status> InputSurfaceConnection::disconnect() { + std::lock_guard<std::mutex> lock(mImplMutex); + mImpl = nullptr; + return Status::OK; +} + +InputSurfaceConnection::~InputSurfaceConnection() { + mImpl = nullptr; +} + +bool InputSurfaceConnection::init() { + std::lock_guard<std::mutex> lock(mImplMutex); + return mImpl->init(); +} + +Return<sp<IConfigurable>> InputSurfaceConnection::getConfigurable() { + return mConfigurable; +} + +// Configurable interface for InputSurfaceConnection::Impl +c2_status_t InputSurfaceConnection::Impl::ConfigurableIntf::config( + const std::vector<C2Param*> ¶ms, + c2_blocking_t mayBlock, + std::vector<std::unique_ptr<C2SettingResult>> *const failures) { + // TODO: implement + (void)params; + (void)mayBlock; + (void)failures; + return C2_OK; +} + +c2_status_t InputSurfaceConnection::Impl::ConfigurableIntf::query( + const std::vector<C2Param::Index> &indices, + c2_blocking_t mayBlock, + std::vector<std::unique_ptr<C2Param>> *const params) const { + // TODO: implement + (void)indices; + (void)mayBlock; + (void)params; + return C2_OK; +} + +c2_status_t InputSurfaceConnection::Impl::ConfigurableIntf::querySupportedParams( + std::vector<std::shared_ptr<C2ParamDescriptor>> *const params) const { + // TODO: implement + (void)params; + return C2_OK; +} + +c2_status_t InputSurfaceConnection::Impl::ConfigurableIntf::querySupportedValues( + std::vector<C2FieldSupportedValuesQuery> &fields, + c2_blocking_t mayBlock) const { + // TODO: implement + (void)fields; + (void)mayBlock; + return C2_OK; +} + +} // namespace utils +} // namespace V1_0 +} // namespace c2 +} // namespace media +} // namespace hardware +} // namespace android +
diff --git a/media/codec2/hidl/1.0/utils/include/codec2/hidl/1.0/ClientBlockHelper.h b/media/codec2/hidl/1.0/utils/include/codec2/hidl/1.0/ClientBlockHelper.h new file mode 100644 index 0000000..0a2298c --- /dev/null +++ b/media/codec2/hidl/1.0/utils/include/codec2/hidl/1.0/ClientBlockHelper.h
@@ -0,0 +1,75 @@ +/* + * Copyright 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 CLIENT_BLOCK_HELPER_H +#define CLIENT_BLOCK_HELPER_H + +#include <gui/IGraphicBufferProducer.h> +#include <codec2/hidl/1.0/types.h> +#include <C2Work.h> + +namespace android { +namespace hardware { +namespace media { +namespace c2 { +namespace V1_0 { +namespace utils { + +// BufferQueue-Based Block Operations +// ================================== + +// Manage BufferQueue and graphic blocks for both component and codec. +// Manage graphic blocks ownership consistently during surface change. +struct OutputBufferQueue { + + OutputBufferQueue(); + + ~OutputBufferQueue(); + + // Configure a new surface to render graphic blocks. + // Graphic blocks from older surface will be migrated to new surface. + bool configure(const sp<IGraphicBufferProducer>& igbp, + uint32_t generation, + uint64_t bqId); + + // Render a graphic block to current surface. + status_t outputBuffer( + const C2ConstGraphicBlock& block, + const BnGraphicBufferProducer::QueueBufferInput& input, + BnGraphicBufferProducer::QueueBufferOutput* output); + + // Call holdBufferQueueBlock() on output blocks in the given workList. + // The OutputBufferQueue will take the ownership of output blocks. + // + // Note: This function should be called after WorkBundle has been received + // from another process. + void holdBufferQueueBlocks( + const std::list<std::unique_ptr<C2Work>>& workList); + +private: + + class Impl; + std::unique_ptr<Impl> mImpl; +}; + +} // namespace utils +} // namespace V1_0 +} // namespace c2 +} // namespace media +} // namespace hardware +} // namespace android + +#endif // CLIENT_BLOCK_HELPER_H
diff --git a/media/codec2/hidl/1.0/utils/include/codec2/hidl/1.0/Component.h b/media/codec2/hidl/1.0/utils/include/codec2/hidl/1.0/Component.h new file mode 100644 index 0000000..86dccd0 --- /dev/null +++ b/media/codec2/hidl/1.0/utils/include/codec2/hidl/1.0/Component.h
@@ -0,0 +1,144 @@ +/* + * Copyright 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 CODEC2_HIDL_V1_0_UTILS_COMPONENT_H +#define CODEC2_HIDL_V1_0_UTILS_COMPONENT_H + +#include <codec2/hidl/1.0/ComponentInterface.h> +#include <codec2/hidl/1.0/Configurable.h> +#include <codec2/hidl/1.0/types.h> + +#include <android/hardware/media/bufferpool/2.0/IClientManager.h> +#include <android/hardware/media/c2/1.0/IComponent.h> +#include <android/hardware/media/c2/1.0/IComponentInterface.h> +#include <android/hardware/media/c2/1.0/IComponentListener.h> +#include <android/hardware/media/c2/1.0/IComponentStore.h> +#include <android/hardware/media/c2/1.0/IInputSink.h> +#include <hidl/Status.h> +#include <hwbinder/IBinder.h> + +#include <C2Component.h> +#include <C2Buffer.h> +#include <C2.h> + +#include <map> +#include <memory> +#include <mutex> + +namespace android { +namespace hardware { +namespace media { +namespace c2 { +namespace V1_0 { +namespace utils { + +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::hardware::IBinder; +using ::android::sp; +using ::android::wp; + +struct ComponentStore; + +struct Component : public IComponent, + public std::enable_shared_from_this<Component> { + Component( + const std::shared_ptr<C2Component>&, + const sp<IComponentListener>& listener, + const sp<ComponentStore>& store, + const sp<::android::hardware::media::bufferpool::V2_0:: + IClientManager>& clientPoolManager); + c2_status_t status() const; + + typedef ::android::hardware::graphics::bufferqueue::V1_0:: + IGraphicBufferProducer HGraphicBufferProducer1; + typedef ::android::hardware::graphics::bufferqueue::V2_0:: + IGraphicBufferProducer HGraphicBufferProducer2; + + // Methods from IComponent follow. + virtual Return<Status> queue(const WorkBundle& workBundle) override; + virtual Return<void> flush(flush_cb _hidl_cb) override; + virtual Return<Status> drain(bool withEos) override; + virtual Return<Status> setOutputSurface( + uint64_t blockPoolId, + const sp<HGraphicBufferProducer2>& surface) override; + virtual Return<void> connectToInputSurface( + const sp<IInputSurface>& inputSurface, + connectToInputSurface_cb _hidl_cb) override; + virtual Return<void> connectToOmxInputSurface( + const sp<HGraphicBufferProducer1>& producer, + const sp<::android::hardware::media::omx::V1_0:: + IGraphicBufferSource>& source, + connectToOmxInputSurface_cb _hidl_cb) override; + virtual Return<Status> disconnectFromInputSurface() override; + virtual Return<void> createBlockPool( + uint32_t allocatorId, + createBlockPool_cb _hidl_cb) override; + virtual Return<Status> destroyBlockPool(uint64_t blockPoolId) override; + virtual Return<Status> start() override; + virtual Return<Status> stop() override; + virtual Return<Status> reset() override; + virtual Return<Status> release() override; + virtual Return<sp<IComponentInterface>> getInterface() override; + virtual Return<sp<IInputSink>> asInputSink() override; + + // Returns a C2Component associated to the given sink if the sink is indeed + // a local component. Returns nullptr otherwise. + // + // This function is used by InputSurface::connect(). + static std::shared_ptr<C2Component> findLocalComponent( + const sp<IInputSink>& sink); + +protected: + c2_status_t mInit; + std::shared_ptr<C2Component> mComponent; + sp<ComponentInterface> mInterface; + sp<IComponentListener> mListener; + sp<ComponentStore> mStore; + ::android::hardware::media::c2::V1_0::utils::DefaultBufferPoolSender + mBufferPoolSender; + + struct Sink; + std::mutex mSinkMutex; + sp<Sink> mSink; + + std::mutex mBlockPoolsMutex; + // This map keeps C2BlockPool objects that are created by createBlockPool() + // alive. These C2BlockPool objects can be deleted by calling + // destroyBlockPool(), reset() or release(), or by destroying the component. + std::map<uint64_t, std::shared_ptr<C2BlockPool>> mBlockPools; + + void initListener(const sp<Component>& self); + + virtual ~Component() override; + + friend struct ComponentStore; + + struct Listener; +}; + +} // namespace utils +} // namespace V1_0 +} // namespace c2 +} // namespace media +} // namespace hardware +} // namespace android + +#endif // CODEC2_HIDL_V1_0_UTILS_COMPONENT_H
diff --git a/media/codec2/hidl/1.0/utils/include/codec2/hidl/1.0/ComponentInterface.h b/media/codec2/hidl/1.0/utils/include/codec2/hidl/1.0/ComponentInterface.h new file mode 100644 index 0000000..a5d235e --- /dev/null +++ b/media/codec2/hidl/1.0/utils/include/codec2/hidl/1.0/ComponentInterface.h
@@ -0,0 +1,66 @@ +/* + * Copyright 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 CODEC2_HIDL_V1_0_UTILS_COMPONENT_INTERFACE_H +#define CODEC2_HIDL_V1_0_UTILS_COMPONENT_INTERFACE_H + +#include <codec2/hidl/1.0/Configurable.h> +#include <codec2/hidl/1.0/types.h> + +#include <android/hardware/media/c2/1.0/IComponentInterface.h> +#include <hidl/Status.h> + +#include <C2Component.h> +#include <C2Buffer.h> +#include <C2.h> + +#include <memory> + +namespace android { +namespace hardware { +namespace media { +namespace c2 { +namespace V1_0 { +namespace utils { + +using ::android::hardware::Return; +using ::android::hardware::Void; +using ::android::sp; + +struct ComponentStore; + +struct ComponentInterface : public IComponentInterface { + ComponentInterface( + const std::shared_ptr<C2ComponentInterface>& interface, + ComponentStore* store); + c2_status_t status() const; + virtual Return<sp<IConfigurable>> getConfigurable() override; + +protected: + std::shared_ptr<C2ComponentInterface> mInterface; + sp<CachedConfigurable> mConfigurable; + c2_status_t mInit; +}; + + +} // namespace utils +} // namespace V1_0 +} // namespace c2 +} // namespace media +} // namespace hardware +} // namespace android + +#endif // CODEC2_HIDL_V1_0_UTILS_COMPONENT_INTERFACE_H
diff --git a/media/codec2/hidl/1.0/utils/include/codec2/hidl/1.0/ComponentStore.h b/media/codec2/hidl/1.0/utils/include/codec2/hidl/1.0/ComponentStore.h new file mode 100644 index 0000000..be80c62 --- /dev/null +++ b/media/codec2/hidl/1.0/utils/include/codec2/hidl/1.0/ComponentStore.h
@@ -0,0 +1,150 @@ +/* + * Copyright 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 CODEC2_HIDL_V1_0_UTILS_COMPONENTSTORE_H +#define CODEC2_HIDL_V1_0_UTILS_COMPONENTSTORE_H + +#include <codec2/hidl/1.0/Component.h> +#include <codec2/hidl/1.0/ComponentInterface.h> +#include <codec2/hidl/1.0/Configurable.h> + +#include <android/hardware/media/bufferpool/2.0/IClientManager.h> +#include <android/hardware/media/c2/1.0/IComponentStore.h> +#include <hidl/Status.h> + +#include <C2Component.h> +#include <C2Param.h> +#include <C2.h> + +#include <chrono> +#include <map> +#include <memory> +#include <mutex> +#include <set> +#include <vector> + +namespace android { +namespace hardware { +namespace media { +namespace c2 { +namespace V1_0 { +namespace utils { + +using ::android::hardware::media::bufferpool::V2_0::IClientManager; + +using ::android::hardware::hidl_handle; +using ::android::hardware::hidl_string; +using ::android::hardware::hidl_vec; +using ::android::hardware::Return; +using ::android::hardware::Void; +using ::android::sp; + +struct ComponentStore : public IComponentStore { + ComponentStore(const std::shared_ptr<C2ComponentStore>& store); + virtual ~ComponentStore() = default; + + /** + * Returns the status of the construction of this object. + */ + c2_status_t status() const; + + /** + * This function is called by CachedConfigurable::init() to validate + * supported parameters. + */ + c2_status_t validateSupportedParams( + const std::vector<std::shared_ptr<C2ParamDescriptor>>& params); + + // Methods from ::android::hardware::media::c2::V1_0::IComponentStore. + virtual Return<void> createComponent( + const hidl_string& name, + const sp<IComponentListener>& listener, + const sp<IClientManager>& pool, + createComponent_cb _hidl_cb) override; + virtual Return<void> createInterface( + const hidl_string& name, + createInterface_cb _hidl_cb) override; + virtual Return<void> listComponents(listComponents_cb _hidl_cb) override; + virtual Return<void> createInputSurface( + createInputSurface_cb _hidl_cb) override; + virtual Return<void> getStructDescriptors( + const hidl_vec<uint32_t>& indices, + getStructDescriptors_cb _hidl_cb) override; + virtual Return<sp<IClientManager>> getPoolClientManager() override; + virtual Return<Status> copyBuffer( + const Buffer& src, + const Buffer& dst) override; + virtual Return<sp<IConfigurable>> getConfigurable() override; + + /** + * Dumps information when lshal is called. + */ + virtual Return<void> debug( + const hidl_handle& handle, + const hidl_vec<hidl_string>& args) override; + +protected: + sp<CachedConfigurable> mConfigurable; + + // Does bookkeeping for an interface that has been loaded. + void onInterfaceLoaded(const std::shared_ptr<C2ComponentInterface> &intf); + + c2_status_t mInit; + std::shared_ptr<C2ComponentStore> mStore; + std::shared_ptr<C2ParamReflector> mParamReflector; + + std::map<C2Param::CoreIndex, std::shared_ptr<C2StructDescriptor>> mStructDescriptors; + std::set<C2Param::CoreIndex> mUnsupportedStructDescriptors; + std::set<C2String> mLoadedInterfaces; + mutable std::mutex mStructDescriptorsMutex; + + // ComponentStore keeps track of live Components. + + struct ComponentStatus { + std::shared_ptr<C2Component> c2Component; + std::chrono::system_clock::time_point birthTime; + }; + + mutable std::mutex mComponentRosterMutex; + std::map<Component*, ComponentStatus> mComponentRoster; + + // Called whenever Component is created. + void reportComponentBirth(Component* component); + // Called only from the destructor of Component. + void reportComponentDeath(Component* component); + + friend Component; + + // Helper functions for dumping. + + std::ostream& dump( + std::ostream& out, + const std::shared_ptr<const C2Component::Traits>& comp); + + std::ostream& dump( + std::ostream& out, + ComponentStatus& compStatus); + +}; + +} // namespace utils +} // namespace V1_0 +} // namespace c2 +} // namespace media +} // namespace hardware +} // namespace android + +#endif // CODEC2_HIDL_V1_0_UTILS_COMPONENTSTORE_H
diff --git a/media/codec2/hidl/1.0/utils/include/codec2/hidl/1.0/Configurable.h b/media/codec2/hidl/1.0/utils/include/codec2/hidl/1.0/Configurable.h new file mode 100644 index 0000000..8095185 --- /dev/null +++ b/media/codec2/hidl/1.0/utils/include/codec2/hidl/1.0/Configurable.h
@@ -0,0 +1,138 @@ +/* + * Copyright 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 CODEC2_HIDL_V1_0_UTILS_CONFIGURABLE_H +#define CODEC2_HIDL_V1_0_UTILS_CONFIGURABLE_H + +#include <android/hardware/media/c2/1.0/IConfigurable.h> +#include <hidl/Status.h> + +#include <C2Component.h> +#include <C2Param.h> +#include <C2.h> + +#include <memory> + +namespace android { +namespace hardware { +namespace media { +namespace c2 { +namespace V1_0 { +namespace utils { + +using ::android::hardware::hidl_vec; +using ::android::hardware::Return; +using ::android::hardware::Void; +using ::android::sp; + +struct ComponentStore; + +/** + * Codec2 objects of different types may have different querying and configuring + * functions, but across the Treble boundary, they share the same HIDL + * interface, IConfigurable. + * + * ConfigurableC2Intf is an abstract class that a Codec2 object can implement to + * easily expose an IConfigurable instance. See CachedConfigurable below. + */ +struct ConfigurableC2Intf { + C2String getName() const { return mName; } + uint32_t getId() const { return mId; } + /** C2ComponentInterface::query_vb sans stack params */ + virtual c2_status_t query( + const std::vector<C2Param::Index> &indices, + c2_blocking_t mayBlock, + std::vector<std::unique_ptr<C2Param>>* const params) const = 0; + /** C2ComponentInterface::config_vb */ + virtual c2_status_t config( + const std::vector<C2Param*> ¶ms, + c2_blocking_t mayBlock, + std::vector<std::unique_ptr<C2SettingResult>>* const failures) = 0; + /** C2ComponentInterface::querySupportedParams_nb */ + virtual c2_status_t querySupportedParams( + std::vector<std::shared_ptr<C2ParamDescriptor>>* const params) const = 0; + /** C2ComponentInterface::querySupportedParams_nb */ + virtual c2_status_t querySupportedValues( + std::vector<C2FieldSupportedValuesQuery>& fields, c2_blocking_t mayBlock) const = 0; + + virtual ~ConfigurableC2Intf() = default; + + ConfigurableC2Intf(const C2String& name, uint32_t id) + : mName{name}, mId{id} {} + +protected: + C2String mName; /* cached component name */ + uint32_t mId; +}; + +/** + * Implementation of the IConfigurable interface that supports caching of + * supported parameters from a supplied ComponentStore. + * + * CachedConfigurable essentially converts a ConfigurableC2Intf into HIDL's + * IConfigurable. A Codec2 object generally implements ConfigurableC2Intf and + * passes the implementation to the constructor of CachedConfigurable. + * + * Note that caching happens + */ +struct CachedConfigurable : public IConfigurable { + CachedConfigurable(std::unique_ptr<ConfigurableC2Intf>&& intf); + + c2_status_t init(ComponentStore* store); + + // Methods from ::android::hardware::media::c2::V1_0::IConfigurable + + virtual Return<uint32_t> getId() override; + + virtual Return<void> getName(getName_cb _hidl_cb) override; + + virtual Return<void> query( + const hidl_vec<uint32_t>& indices, + bool mayBlock, + query_cb _hidl_cb) override; + + virtual Return<void> config( + const hidl_vec<uint8_t>& inParams, + bool mayBlock, + config_cb _hidl_cb) override; + + virtual Return<void> querySupportedParams( + uint32_t start, + uint32_t count, + querySupportedParams_cb _hidl_cb) override; + + virtual Return<void> querySupportedValues( + const hidl_vec<FieldSupportedValuesQuery>& inFields, + bool mayBlock, + querySupportedValues_cb _hidl_cb) override; + +protected: + // Common Codec2.0 interface wrapper + std::unique_ptr<ConfigurableC2Intf> mIntf; + + // Cached supported params + std::vector<std::shared_ptr<C2ParamDescriptor>> mSupportedParams; +}; + +} // namespace utils +} // namespace V1_0 +} // namespace c2 +} // namespace media +} // namespace hardware +} // namespace android + +#endif // CODEC2_HIDL_V1_0_UTILS_CONFIGURABLE_H +
diff --git a/media/codec2/hidl/1.0/utils/include/codec2/hidl/1.0/InputBufferManager.h b/media/codec2/hidl/1.0/utils/include/codec2/hidl/1.0/InputBufferManager.h new file mode 100644 index 0000000..b6857d5 --- /dev/null +++ b/media/codec2/hidl/1.0/utils/include/codec2/hidl/1.0/InputBufferManager.h
@@ -0,0 +1,294 @@ +/* + * Copyright 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 CODEC2_HIDL_V1_0_UTILS_INPUT_BUFFER_MANAGER_H +#define CODEC2_HIDL_V1_0_UTILS_INPUT_BUFFER_MANAGER_H + +#include <android/hardware/media/c2/1.0/IComponentListener.h> +#include <utils/Timers.h> + +#include <C2Buffer.h> +#include <C2Work.h> + +#include <set> +#include <map> +#include <thread> + +namespace android { +namespace hardware { +namespace media { +namespace c2 { +namespace V1_0 { +namespace utils { + +using namespace ::android; + +/** + * InputBufferManager + * ================== + * + * InputBufferManager presents a way to track and untrack input buffers in this + * (codec) process and send a notification to a listener, possibly in a + * different process, when a tracked buffer no longer has any references in this + * process. + * + * InputBufferManager holds a collection of records representing tracked buffers + * and their callback listeners. Conceptually, one record is a triple (listener, + * frameIndex, bufferIndex) where + * + * - (frameIndex, bufferIndex) is a pair of indices used to identify the buffer. + * - listener is of type IComponentListener. Its onInputBuffersReleased() + * function will be called after the associated buffer dies. The argument of + * onInputBuffersReleased() is a list of InputBuffer objects, each of which + * has the following members: + * + * uint64_t frameIndex + * uint32_t arrayIndex + * + * When a tracked buffer associated to the triple (listener, frameIndex, + * bufferIndex) goes out of scope, listener->onInputBuffersReleased() will be + * called with an InputBuffer object whose members are set as follows: + * + * inputBuffer.frameIndex = frameIndex + * inputBuffer.arrayIndex = bufferIndex + * + * IPC Optimization + * ---------------- + * + * Since onInputBuffersReleased() is an IPC call, InputBufferManager tries not + * to call it too often. Any two calls to the same listener are at least + * mNotificationIntervalNs nanoseconds apart, where mNotificationIntervalNs is + * configurable via calling setNotificationInterval(). The default value of + * mNotificationIntervalNs is kDefaultNotificationInternalNs. + * + * Public Member Functions + * ----------------------- + * + * InputBufferManager is a singleton class. Its only instance is accessible via + * the following public functions: + * + * - registerFrameData(const sp<IComponentListener>& listener, + * const C2FrameData& input) + * + * - unregisterFrameData(const sp<IComponentListener>& listener, + * const C2FrameData& input) + * + * - unregisterFrameData(const sp<IComponentListener>& listener) + * + * - setNotificationInterval(nsecs_t notificationIntervalNs) + * + */ + +struct InputBufferManager { + + /** + * The default value for the time interval between 2 subsequent IPCs. + */ + static constexpr nsecs_t kDefaultNotificationIntervalNs = 1000000; /* 1ms */ + + /** + * Track all buffers in a C2FrameData object. + * + * input (C2FrameData) has the following two members that are of interest: + * + * C2WorkOrdinal ordinal + * vector<shared_ptr<C2Buffer>> buffers + * + * Calling registerFrameData(listener, input) will register multiple + * triples (listener, frameIndex, bufferIndex) where frameIndex is equal to + * input.ordinal.frameIndex and bufferIndex runs through the indices of + * input.buffers such that input.buffers[bufferIndex] is not null. + * + * This should be called from queue(). + * + * \param listener Listener of death notifications. + * \param input Input frame data whose input buffers are to be tracked. + */ + static void registerFrameData( + const sp<IComponentListener>& listener, + const C2FrameData& input); + + /** + * Untrack all buffers in a C2FrameData object. + * + * Calling unregisterFrameData(listener, input) will unregister and remove + * pending notifications for all triples (l, fi, bufferIndex) such that + * l = listener and fi = input.ordinal.frameIndex. + * + * This should be called from onWorkDone() and flush(). + * + * \param listener Previously registered listener. + * \param input Previously registered frame data. + */ + static void unregisterFrameData( + const wp<IComponentListener>& listener, + const C2FrameData& input); + + /** + * Untrack all buffers associated to a given listener. + * + * Calling unregisterFrameData(listener) will unregister and remove + * pending notifications for all triples (l, frameIndex, bufferIndex) such + * that l = listener. + * + * This should be called when the component cleans up all input buffers, + * i.e., when reset(), release(), stop() or ~Component() is called. + * + * \param listener Previously registered listener. + */ + static void unregisterFrameData( + const wp<IComponentListener>& listener); + + /** + * Set the notification interval. + * + * \param notificationIntervalNs New notification interval, in nanoseconds. + */ + static void setNotificationInterval(nsecs_t notificationIntervalNs); + +private: + void _registerFrameData( + const sp<IComponentListener>& listener, + const C2FrameData& input); + void _unregisterFrameData( + const wp<IComponentListener>& listener, + const C2FrameData& input); + void _unregisterFrameData( + const wp<IComponentListener>& listener); + void _setNotificationInterval(nsecs_t notificationIntervalNs); + + // The callback function tied to C2Buffer objects. + // + // Note: This function assumes that sInstance is the only instance of this + // class. + static void onBufferDestroyed(const C2Buffer* buf, void* arg); + void _onBufferDestroyed(const C2Buffer* buf, void* arg); + + // Persistent data to be passed as "arg" in onBufferDestroyed(). + // This is essentially the triple (listener, frameIndex, bufferIndex) plus a + // weak pointer to the C2Buffer object. + // + // Note that the "key" is bufferIndex according to operator<(). This is + // designed to work with TrackedBuffersMap defined below. + struct TrackedBuffer { + wp<IComponentListener> listener; + uint64_t frameIndex; + size_t bufferIndex; + std::weak_ptr<C2Buffer> buffer; + TrackedBuffer(const wp<IComponentListener>& listener, + uint64_t frameIndex, + size_t bufferIndex, + const std::shared_ptr<C2Buffer>& buffer) + : listener(listener), + frameIndex(frameIndex), + bufferIndex(bufferIndex), + buffer(buffer) {} + TrackedBuffer(const TrackedBuffer&) = default; + bool operator<(const TrackedBuffer& other) const { + return bufferIndex < other.bufferIndex; + } + }; + + // Map: listener -> frameIndex -> set<TrackedBuffer>. + // Essentially, this is used to store triples (listener, frameIndex, + // bufferIndex) that's searchable by listener and (listener, frameIndex). + // However, the value of the innermost map is TrackedBuffer, which also + // contains an extra copy of listener and frameIndex. This is needed + // because onBufferDestroyed() needs to know listener and frameIndex too. + typedef std::map<wp<IComponentListener>, + std::map<uint64_t, + std::set<TrackedBuffer>>> TrackedBuffersMap; + + // Storage for pending (unsent) death notifications for one listener. + // Each pair in member named "indices" are (frameIndex, bufferIndex) from + // the (listener, frameIndex, bufferIndex) triple. + struct DeathNotifications { + + // The number of pending notifications for this listener. + // count may be 0, in which case the DeathNotifications object will + // remain valid for only a small period (specified + // nanoseconds). + size_t count; + + // The timestamp of the most recent callback on this listener. This is + // used to guarantee that callbacks do not occur too frequently, and + // also to trigger expiration of a DeathNotifications object that has + // count = 0. + nsecs_t lastSentNs; + + // Map: frameIndex -> vector of bufferIndices + // This is essentially a collection of (framdeIndex, bufferIndex). + std::map<uint64_t, std::vector<size_t>> indices; + + DeathNotifications( + nsecs_t notificationIntervalNs = kDefaultNotificationIntervalNs) + : count(0), + lastSentNs(systemTime() - notificationIntervalNs), + indices() {} + }; + + // The minimum time period between IPC calls to notify the client about the + // destruction of input buffers. + std::atomic<nsecs_t> mNotificationIntervalNs{kDefaultNotificationIntervalNs}; + + // Mutex for the management of all input buffers. + std::mutex mMutex; + + // Tracked input buffers. + TrackedBuffersMap mTrackedBuffersMap; + + // Death notifications to be sent. + // + // A DeathNotifications object is associated to each listener. An entry in + // this map will be removed if its associated DeathNotifications has count = + // 0 and lastSentNs < systemTime() - mNotificationIntervalNs. + std::map<wp<IComponentListener>, DeathNotifications> mDeathNotifications; + + // Condition variable signaled when an entry is added to mDeathNotifications. + std::condition_variable mOnBufferDestroyed; + + // Notify the clients about buffer destructions. + // Return false if all destructions have been notified. + // Return true and set timeToRetry to the duration to wait for before + // retrying if some destructions have not been notified. + bool processNotifications(nsecs_t* timeToRetryNs); + + // Main function for the input buffer manager thread. + void main(); + + // The thread that manages notifications. + // + // Note: This variable is declared last so its initialization will happen + // after all other member variables have been initialized. + std::thread mMainThread; + + // Private constructor. + InputBufferManager(); + + // The only instance of this class. + static InputBufferManager& getInstance(); + +}; + +} // namespace utils +} // namespace V1_0 +} // namespace c2 +} // namespace media +} // namespace hardware +} // namespace android + +#endif // CODEC2_HIDL_V1_0_UTILS_INPUT_BUFFER_MANAGER_H +
diff --git a/media/codec2/hidl/1.0/utils/include/codec2/hidl/1.0/InputSurface.h b/media/codec2/hidl/1.0/utils/include/codec2/hidl/1.0/InputSurface.h new file mode 100644 index 0000000..29ed7ff --- /dev/null +++ b/media/codec2/hidl/1.0/utils/include/codec2/hidl/1.0/InputSurface.h
@@ -0,0 +1,91 @@ +/* + * Copyright 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 CODEC2_HIDL_V1_0_UTILS_INPUTSURFACE_H +#define CODEC2_HIDL_V1_0_UTILS_INPUTSURFACE_H + +#include <codec2/hidl/1.0/ComponentStore.h> + +#include <android/hardware/graphics/bufferqueue/2.0/IGraphicBufferProducer.h> +#include <android/hardware/media/c2/1.0/IInputSink.h> +#include <android/hardware/media/c2/1.0/IInputSurface.h> +#include <hidl/Status.h> +#include <media/stagefright/bqhelper/GraphicBufferSource.h> + +#include <util/C2InterfaceHelper.h> + +namespace android { +namespace hardware { +namespace media { +namespace c2 { +namespace V1_0 { +namespace utils { + +using ::android::hardware::hidl_handle; +using ::android::hardware::hidl_string; +using ::android::hardware::hidl_vec; +using ::android::hardware::Return; +using ::android::hardware::Void; +using ::android::sp; + +struct InputSurface : public IInputSurface { + + typedef ::android::hardware::graphics::bufferqueue::V2_0:: + IGraphicBufferProducer HGraphicBufferProducer; + + typedef ::android:: + GraphicBufferSource GraphicBufferSource; + + virtual Return<sp<HGraphicBufferProducer>> getGraphicBufferProducer() override; + + virtual Return<sp<IConfigurable>> getConfigurable() override; + + virtual Return<void> connect( + const sp<IInputSink>& sink, + connect_cb _hidl_cb) override; + +protected: + + class Interface; + class ConfigurableIntf; + + sp<ComponentStore> mStore; + sp<HGraphicBufferProducer> mProducer; + sp<GraphicBufferSource> mSource; + std::shared_ptr<Interface> mIntf; + sp<CachedConfigurable> mConfigurable; + + InputSurface( + const sp<ComponentStore>& store, + const std::shared_ptr<C2ReflectorHelper>& reflector, + const sp<HGraphicBufferProducer>& base, + const sp<GraphicBufferSource>& source); + + virtual ~InputSurface() override = default; + + friend struct ComponentStore; + +}; + + +} // namespace utils +} // namespace V1_0 +} // namespace c2 +} // namespace media +} // namespace hardware +} // namespace android + +#endif // CODEC2_HIDL_V1_0_UTILS_INPUTSURFACE_H
diff --git a/media/codec2/hidl/1.0/utils/include/codec2/hidl/1.0/InputSurfaceConnection.h b/media/codec2/hidl/1.0/utils/include/codec2/hidl/1.0/InputSurfaceConnection.h new file mode 100644 index 0000000..758b6b2 --- /dev/null +++ b/media/codec2/hidl/1.0/utils/include/codec2/hidl/1.0/InputSurfaceConnection.h
@@ -0,0 +1,96 @@ +/* + * Copyright 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 CODEC2_HIDL_V1_0_UTILS_INPUTSURFACECONNECTION_H +#define CODEC2_HIDL_V1_0_UTILS_INPUTSURFACECONNECTION_H + +#include <codec2/hidl/1.0/Component.h> +#include <codec2/hidl/1.0/Configurable.h> + +#include <android/hardware/media/c2/1.0/IComponent.h> +#include <android/hardware/media/c2/1.0/IConfigurable.h> +#include <android/hardware/media/c2/1.0/IInputSurfaceConnection.h> + +#include <media/stagefright/bqhelper/GraphicBufferSource.h> + +#include <hidl/HidlSupport.h> +#include <hidl/Status.h> + +#include <C2Component.h> + +#include <memory> +#include <mutex> + +namespace android { +namespace hardware { +namespace media { +namespace c2 { +namespace V1_0 { +namespace utils { + +using ::android::hardware::Return; +using ::android::hardware::Void; +using ::android::sp; +using ::android::GraphicBufferSource; + +// An InputSurfaceConnection connects an InputSurface to a sink, which may be an +// IInputSink or a local C2Component. This can be specified by choosing the +// corresponding constructor. The reason for distinguishing these two cases is +// that when an InputSurfaceConnection lives in the same process as the +// component that processes the buffers, data parceling is not needed. +struct InputSurfaceConnection : public IInputSurfaceConnection { + + virtual Return<Status> disconnect() override; + + virtual Return<sp<IConfigurable>> getConfigurable() override; + +protected: + + InputSurfaceConnection( + const sp<GraphicBufferSource>& source, + const std::shared_ptr<C2Component>& comp, + const sp<ComponentStore>& store); + + InputSurfaceConnection( + const sp<GraphicBufferSource>& source, + const sp<IInputSink>& sink, + const sp<ComponentStore>& store); + + bool init(); + + friend struct InputSurface; + + InputSurfaceConnection() = delete; + InputSurfaceConnection(const InputSurfaceConnection&) = delete; + void operator=(const InputSurfaceConnection&) = delete; + + struct Impl; + + std::mutex mImplMutex; + sp<Impl> mImpl; + sp<CachedConfigurable> mConfigurable; + + virtual ~InputSurfaceConnection() override; +}; + +} // namespace utils +} // namespace V1_0 +} // namespace c2 +} // namespace media +} // namespace hardware +} // namespace android + +#endif // CODEC2_HIDL_V1_0_UTILS_INPUTSURFACECONNECTION_H
diff --git a/media/codec2/hidl/1.0/utils/include/codec2/hidl/1.0/types.h b/media/codec2/hidl/1.0/utils/include/codec2/hidl/1.0/types.h new file mode 100644 index 0000000..a9928b3 --- /dev/null +++ b/media/codec2/hidl/1.0/utils/include/codec2/hidl/1.0/types.h
@@ -0,0 +1,350 @@ +/* + * Copyright 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 CODEC2_HIDL_V1_0_UTILS_TYPES_H +#define CODEC2_HIDL_V1_0_UTILS_TYPES_H + +#include <bufferpool/ClientManager.h> +#include <android/hardware/media/bufferpool/2.0/IClientManager.h> +#include <android/hardware/media/bufferpool/2.0/types.h> +#include <android/hardware/media/c2/1.0/IComponentStore.h> +#include <android/hardware/media/c2/1.0/types.h> +#include <android/hidl/safe_union/1.0/types.h> + +#include <C2Component.h> +#include <C2Param.h> +#include <C2ParamDef.h> +#include <C2Work.h> +#include <util/C2Debug-base.h> + +#include <chrono> + +using namespace std::chrono_literals; + +namespace android { +namespace hardware { +namespace media { +namespace c2 { +namespace V1_0 { +namespace utils { + +using ::android::hardware::hidl_bitfield; +using ::android::hardware::hidl_handle; +using ::android::hardware::hidl_string; +using ::android::hardware::hidl_vec; +using ::android::status_t; +using ::android::sp; +using ::android::hardware::media::bufferpool::V2_0::implementation:: + ConnectionId; + +// Types of metadata for Blocks. +struct C2Hidl_Range { + uint32_t offset; + uint32_t length; // Do not use "size" because the name collides with C2Info::size(). +}; +typedef C2GlobalParam<C2Info, C2Hidl_Range, 0> C2Hidl_RangeInfo; + +struct C2Hidl_Rect { + uint32_t left; + uint32_t top; + uint32_t width; + uint32_t height; +}; +typedef C2GlobalParam<C2Info, C2Hidl_Rect, 1> C2Hidl_RectInfo; + +// Make asString() and operator<< work with Status as well as c2_status_t. +C2_DECLARE_AS_STRING_AND_DEFINE_STREAM_OUT(Status); + +/** + * All objcpy() functions will return a boolean value indicating whether the + * conversion succeeds or not. + */ + +// C2SettingResult -> SettingResult +bool objcpy( + SettingResult* d, + const C2SettingResult& s); + +// SettingResult -> std::unique_ptr<C2SettingResult> +bool objcpy( + std::unique_ptr<C2SettingResult>* d, + const SettingResult& s); + +// C2ParamDescriptor -> ParamDescriptor +bool objcpy( + ParamDescriptor* d, + const C2ParamDescriptor& s); + +// ParamDescriptor -> std::shared_ptr<C2ParamDescriptor> +bool objcpy( + std::shared_ptr<C2ParamDescriptor>* d, + const ParamDescriptor& s); + +// C2FieldSupportedValuesQuery -> FieldSupportedValuesQuery +bool objcpy( + FieldSupportedValuesQuery* d, + const C2FieldSupportedValuesQuery& s); + +// FieldSupportedValuesQuery -> C2FieldSupportedValuesQuery +bool objcpy( + C2FieldSupportedValuesQuery* d, + const FieldSupportedValuesQuery& s); + +// C2FieldSupportedValuesQuery -> FieldSupportedValuesQueryResult +bool objcpy( + FieldSupportedValuesQueryResult* d, + const C2FieldSupportedValuesQuery& s); + +// FieldSupportedValuesQuery, FieldSupportedValuesQueryResult -> C2FieldSupportedValuesQuery +bool objcpy( + C2FieldSupportedValuesQuery* d, + const FieldSupportedValuesQuery& sq, +