From: chuanji.tang Date: Wed, 14 Jul 2021 01:14:02 +0000 (+0800) Subject: Initialize the code with P4 FET CL9112401 X-Git-Tag: accepted/tizen/unified/20210830.103725~4 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=c5ee59e3b646cd86ec72fd6aaab4b73cfae4ac68;p=platform%2Fcore%2Fmultimedia%2Fesplusplayer.git Initialize the code with P4 FET CL9112401 Change-Id: I22abaca7c3a23d934e1043998c4d047930f83652 --- diff --git a/AUTHORS b/AUTHORS new file mode 100755 index 0000000..d3f5a12 --- /dev/null +++ b/AUTHORS @@ -0,0 +1 @@ + diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100755 index 0000000..135c035 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,61 @@ + +CMAKE_MINIMUM_REQUIRED(VERSION 2.8) + +PROJECT(esplusplayer) +SET(description "new multimedia player, object-oriented model") +SET(PC_NAME "esplusplayer") +SET(PC_LDFLAGS "-lesplusplayer -lmixer") +SET(PC_CFLAGS "-I/usr/include/esplusplayer_capi -I/usr/include/mixer") + +SET(INC_DIR ${PROJECT_SOURCE_DIR}/include/) +INCLUDE_DIRECTORIES(${INC_DIR}) +SET(CMAKE_INSTALL_PREFIX /usr) +SET(PREFIX ${CMAKE_INSTALL_PREFIX}) + +CONFIGURE_FILE(esplusplayer.pc.in + esplusplayer.pc + @ONLY +) + +INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/esplusplayer.pc DESTINATION ${LIB_INSTALL_DIR}/pkgconfig) + +ADD_SUBDIRECTORY(src) + +OPTION(ESPLUSPLAYER_BUILD_UT "Build esplusplayer ut codes" OFF) +IF(ESPLUSPLAYER_BUILD_UT) +ADD_SUBDIRECTORY(ut) +ENDIF(ESPLUSPLAYER_BUILD_UT) + +IF(UNIX) +ADD_CUSTOM_TARGET (distclean @echo cleaning for source distribution) +ADD_CUSTOM_COMMAND( + DEPENDS clean + COMMENT "distribution clean" + COMMAND find + ARGS . + -not -name config.cmake -and \( + -name tester.c -or + -name Testing -or + -name CMakeFiles -or + -name cmake.depends -or + -name cmake.check_depends -or + -name CMakeCache.txt -or + -name cmake.check_cache -or + -name *.cmake -or + -name Makefile -or + -name core -or + -name core.* -or + -name gmon.out -or + -name install_manifest.txt -or + -name *.pc -or + -name *~ \) + | grep -v TC | xargs rm -rf + TARGET distclean + VERBATIM +) +ENDIF(UNIX) + +MESSAGE( STATUS "PROJECT_SOURCE_DIR: " ${PROJECT_SOURCE_DIR} ) +MESSAGE( STATUS "CMAKE_CURRENT_SOURCE_DIR: " ${CMAKE_CURRENT_SOURCE_DIR} ) +MESSAGE( STATUS "LIB_INSTALL_DIR: " ${LIB_INSTALL_DIR} ) +MESSAGE( STATUS "INC_DIR: " ${INC_DIR} ) \ No newline at end of file diff --git a/LICENSE.APLv2 b/LICENSE.APLv2 new file mode 100755 index 0000000..9c13a9b --- /dev/null +++ b/LICENSE.APLv2 @@ -0,0 +1,204 @@ +Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd. All rights reserved. + + 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 + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. + diff --git a/README.md b/README.md new file mode 100755 index 0000000..12c8421 --- /dev/null +++ b/README.md @@ -0,0 +1,74 @@ +# **PlusPlayer** # + PlusPlayer is a new multimedia player object-oriented designed. + +## Notice + * 18_Plusplayer + - SW High Level Design doc. + +## Goals ## + * Improve maintainability / extensibility / reusability + - object oriented design + - separate the modules into high-variation part and low-variation part + - decoupling between each streaming services + * Simplification + - simplified pipeline , creates static pipeline + * Easy to upgrade + - support downloadable features (planned) + +## Architecture Design ## + * PlusPlayer consists of TypeFinder / TrackSource / TrackRenderer / DefaultPlayer objects. + * TypeFinder + - Probing source type by inspecting probing data + - use gstreamer typefinder plugin + * TrackSource + - Fetching data from server , demuxing , controling demuxed packet buffers + - a pipeline consists of `src - typefinder - demuxer - multiqueue - inputselector - fakesink` + - plz refer to `${plusplayer_workspace}/docs/dot/plusplayer_src_start.png` + * TrackRenderer + - maintains a pipeline consists of `appsrc - omx - sink` elements + - plz refer to `${plusplayer_workspace}/docs/dot/plusplayer_renderer_start.png` + * Class diagram + * ${plusplayer_workspace}/docs/class_diagram.plantuml + * or you can directly download it from [class_diagram.plantuml] + +## Development ## + * GitHub Project : + * All the milestones / activities (Codes , CodeReview , Design , Issues , etc) are being uploaded here + * Codereview on Github + - Each Developers should fork master branch and work on their own forked project. + - developer should submit pull-request to apply their changes + * all developers must sync their forked project with the latest master prior to sumit pull-request. so that we can avoid merge conflicts. + - everyone can join the codereviews + * the coding style follows Google C++ coding style guide. refer to [CODING STYLE](### coding-style) + * Test Driven Development (use gtest - plusplayer_ut -for their implementation and verifications) + +#### GTest guide #### + * To write Unit test case for PlusPlayer + * docs/gtest_guide.md + * Check Reference section of above link for more detailed documents. + * example + ``` + sh-3.2# plusplayer_ut --gtest_filter="AvPlusPlayerTest.play" + ``` + +#### Coding Rule Check #### + * check your coding style using **cpplint.py** before uploading your codes + > e.g) $ **cpplint.py** cpp_style_test.cpp + * Or check your coding style and fix your codes automatically using **clang-format** + > e.g) $ **clang-format-3.4** cpp_style_test.cpp -style=google **-i** + +#### Build #### + > 1. Clone or Download plusplayer package. + > 2. $ gbs -c ~/gbsconf/gbs_3_0.conf build -A armv7l --include-all --clean -B ~/GBS_ROOT_PLUSPLAYER + +#### Understanding the codes #### + to understand PlusPlayer concept quickly + plz refer to + - ${plusplayer_workspace}/ut/porting/webapi-plugins-avplay/src/avplusplayer.cc/h + - ${plusplayer_workspace}/src/player/defaultplayer.cpp/h + - ${plusplayer_workspace}/docs/class_diagram.txt + +#### CODING STYLE #### + 'Google C++ Style Guide' based coding. + > Translated(Korean) + > Original(English): [https://google.github.io/styleguide/cppguide.html](https://google.github.io/styleguide/cppguide.html) diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..83d83cc --- /dev/null +++ b/build.sh @@ -0,0 +1,34 @@ +export BMS_SERVICE_DIR=~/bms-service + +#rm -f ./*build.log +tizen clean -- ./src/plusplayer-core +tizen clean -- ./src/tracksource +tizen clean -- ./src/plusplayer + +#Debug mode +#tizen build-native -C Debug -a arm -- ./plusplayer-core -j 16 > plusplayer-core-build.log +tizen build-native -C Debug -a arm -c gcc -- ./src/plusplayer-core +tizen build-native -C Debug -a arm -c gcc -- ./src/tracksource +tizen build-native -C Debug -a arm -c gcc -- ./src/plusplayer + +tizen cli-config "profiles.path=`eval echo $HOME`/tizen-studio-data/profile/profiles.xml" +tizen security-profiles add -n ABS -a packaging/author_test.p12 -p author_test -d packaging/tizen-distributor-partner-manufacturer-signer.p12 -dp tizenpkcs12passfordsigner + +#Release mode +tizen build-native -C Release -a arm -c gcc -- ./src/plusplayer-core +tizen build-native -C Release -a arm -c gcc -- ./src/tracksource +tizen build-native -C Release -a arm -c gcc -- ./src/plusplayer + +tizen cli-config "profiles.path=`eval echo $HOME`/tizen-studio-data/profile/profiles.xml" +tizen security-profiles add -n ABS -a packaging/author_test.p12 -p author_test -d packaging/tizen-distributor-partner-manufacturer-signer.p12 -dp tizenpkcs12passfordsigner + +rm -rf ${BMS_SERVICE_DIR}/plusplayer-api/* +cp -rf ./include/plusplayer ${BMS_SERVICE_DIR}/plusplayer-api +cp -vf ./src/plusplayer-core/Release/libplusplayercore_tvplus.so ${BMS_SERVICE_DIR}/lib/ +cp -vf ./src/tracksource/Release/libtracksource_tvplus.so ${BMS_SERVICE_DIR}/lib/ +cp -vf ./src/plusplayer/Release/libplusplayer_tvplus.so ${BMS_SERVICE_DIR}/lib/ +cp -vf ./config/tvplus/plusplayer.ini ${BMS_SERVICE_DIR}/res/ + +tizen clean -- ./src/plusplayer-core +tizen clean -- ./src/tracksource +tizen clean -- ./src/plusplayer diff --git a/config/esplusplayer.ini b/config/esplusplayer.ini new file mode 100755 index 0000000..56f20e5 --- /dev/null +++ b/config/esplusplayer.ini @@ -0,0 +1,12 @@ +{ + "version" : "0.0.4948390.firmware", + "gstparam1" : "--gst-debug=*:2", + "gstparam2" : "--gst-disable-segtrap", + "gstparam3" : "--gst-plugin-load", + "gstparam4" : "--gst-disable-registry-fork", + "gstparam5" : "--gst-disable-registry-update", + "tz_video_es_dump" : false, + "tz_video_dump_insert_vp9_header" : false, + "tz_audio_es_dump" : false, + "generate_dot" : false +} diff --git a/docs/class_diagram/adapter/bms_avplayer_player_adapter(1).plantuml b/docs/class_diagram/adapter/bms_avplayer_player_adapter(1).plantuml new file mode 100755 index 0000000..b976c76 --- /dev/null +++ b/docs/class_diagram/adapter/bms_avplayer_player_adapter(1).plantuml @@ -0,0 +1,62 @@ +@startuml + +note "AS-IS" as PAGE1 #99FF99 + +class PCTask { + # virtual bool t_OnEvent(); + # virtual bool t_Create(void); + # virtual void t_Destroy(void); +} + +interface IPlayer { + virtual bool Play()=0; + virtual bool Stop()=0; + virtual bool Pause()=0; + virtual bool Resume()=0; +} + +interface IAVPlayHelper { + + virtual bool IsShutdown() = 0; + + virtual bool HandleOnEvent(const TSEvent* pEvent) = 0; + + virtual bool GetPlayTime(unsigned long& time) = 0; + + virtual bool GetCurrentPlayProgram(std::string& outData) = 0; + + virtual void GetDuration(int& pDuration) = 0; +} + +class TCAVPlayer { + - IAVPlayHelper* m_pAVCurrentHelper; + + - IAVPlayHelper* m_pTVPlusAVPlayHelper; + - IAVPlayHelper* m_pATSC30Helper; +} +note top : "BmsAvPlayer.h" + +IPlayer <|-- TCAVPlayer +PCTask <|-- TCAVPlayer + +TCAVPlayer o-- TvplusAVPlayHelper +TCAVPlayer o-- ATSC3AVPlayHelper + +class TvplusAVPlayHelper { +} + +IAVPlayHelper <|-- TvplusAVPlayHelper + +class ATSC3AVPlayHelper { +} + +IAVPlayHelper <|-- ATSC3AVPlayHelper + + +package "capi-media-player" { +} + +package "libmmplayer" { +} + +TvplusAVPlayHelper --> "capi-media-player" +ATSC3AVPlayHelper --> "capi-media-player" + +"capi-media-player" --> "libmmplayer" + +@enduml \ No newline at end of file diff --git a/docs/class_diagram/adapter/bms_avplayer_player_adapter(1).png b/docs/class_diagram/adapter/bms_avplayer_player_adapter(1).png new file mode 100755 index 0000000..e5504b4 Binary files /dev/null and b/docs/class_diagram/adapter/bms_avplayer_player_adapter(1).png differ diff --git a/docs/class_diagram/adapter/bms_avplayer_player_adapter(2).plantuml b/docs/class_diagram/adapter/bms_avplayer_player_adapter(2).plantuml new file mode 100755 index 0000000..106b515 --- /dev/null +++ b/docs/class_diagram/adapter/bms_avplayer_player_adapter(2).plantuml @@ -0,0 +1,59 @@ +@startuml + +note "TO-BE" as PAGE1 #99FF99 + +package "plusplayer" { +} + +package "bms-service" { + +class PCTask { + # virtual bool t_OnEvent(); + # virtual bool t_Create(void); + # virtual void t_Destroy(void); +} + +interface IPlayer { + virtual bool Play()=0; + virtual bool Stop()=0; + virtual bool Pause()=0; + virtual bool Resume()=0; +} + +interface IAVPlayHelper { + + virtual bool IsShutdown() = 0; + + virtual bool HandleOnEvent(const TSEvent* pEvent) = 0; + + virtual bool GetPlayTime(unsigned long& time) = 0; + + virtual bool GetCurrentPlayProgram(std::string& outData) = 0; + + virtual void GetDuration(int& pDuration) = 0; +} + +class TCAVPlayer { + - IAVPlayHelper* m_pAVCurrentHelper; + + - IAVPlayHelper* m_pTVPlusAVPlayHelper; + - IAVPlayHelper* m_pATSC30Helper; +} +note top of TCAVPlayer : "BmsAvPlayer.h" + +IPlayer <|-- TCAVPlayer +PCTask <|-- TCAVPlayer + +TCAVPlayer o-- TvplusPlusplayerHelper +TCAVPlayer o-- ATSC3PlusplayerHelper + +class TvplusPlusplayerHelper { +} + +IAVPlayHelper <|-- TvplusPlusplayerHelper + +class ATSC3PlusplayerHelper { +} + +IAVPlayHelper <|-- ATSC3PlusplayerHelper + +} + +TvplusPlusplayerHelper *--> "plusplayer" +ATSC3PlusplayerHelper *--> "plusplayer" +@enduml \ No newline at end of file diff --git a/docs/class_diagram/adapter/bms_avplayer_player_adapter(2).png b/docs/class_diagram/adapter/bms_avplayer_player_adapter(2).png new file mode 100755 index 0000000..ebc6e0e Binary files /dev/null and b/docs/class_diagram/adapter/bms_avplayer_player_adapter(2).png differ diff --git a/docs/class_diagram/adapter/trackrenderer_adapter.plantuml b/docs/class_diagram/adapter/trackrenderer_adapter.plantuml new file mode 100755 index 0000000..d49a106 --- /dev/null +++ b/docs/class_diagram/adapter/trackrenderer_adapter.plantuml @@ -0,0 +1,11 @@ +@startuml + +title TrackRenderer Adapter + +DefaultPlayer *-- TrackRendererAdapter +class TrackRendererAdapter +note left : RAII container of Trackrenderer instance. +TrackRendererAdapter --> TrackRendererCapi +TrackRendererCapi --> TrackRenderer + +@enduml \ No newline at end of file diff --git a/docs/class_diagram/adapter/webapi_avplay_player_adapter.plantuml b/docs/class_diagram/adapter/webapi_avplay_player_adapter.plantuml new file mode 100755 index 0000000..9251563 --- /dev/null +++ b/docs/class_diagram/adapter/webapi_avplay_player_adapter.plantuml @@ -0,0 +1,80 @@ +@startuml + +title AS-IS + +package "capi-media-player" { +} + +package "webapi-plugins-avplay" { + class AVPlayInstance { + } + + class AVPlayerAdapter { + } +} + +package "webapi-plugins-avplay" { + class AVPlayInstance { + } + + class AVPlayerAdapter { + } + + AVPlayInstance *-- AVPlayerAdapter + AVPlayerAdapter *--> "capi-media-player" +} + +newpage + +title TO-BE + +package "capi-media-player" { +} + +package "webapi-plugins-avplay" { + class AVPlayInstance { + } + + class AVPlayerAdapter { + } + + interface AvPlay { + } + + AVPlayInstance *-- AvPlay + AvPlay <|.. AVPlayerAdapter + AVPlayerAdapter *-- "capi-media-player" + AvPlay <|.. AvPlusPlayer + PlusPlayerEventListener --* AvPlusPlayer +} + +package "plusplayer" { +} + +AvPlusPlayer *--> "plusplayer" : use + +@enduml@startuml + +package "webapi-plugins-avplay" { + class AVPlayInstance { + } + + class AVPlayerAdapter { + } + + interface AvPlay { + } + + AVPlayInstance *-- AvPlay + AvPlay <|.. AVPlayerAdapter + AVPlayerAdapter *-- "capi-media-player" + AvPlay <|.. AvPlusPlayer + PlusPlayerEventListener --* AvPlusPlayer +} + +package "plusplayer" { +} + +AvPlusPlayer *--> "plusplayer" : use + +@enduml \ No newline at end of file diff --git a/docs/class_diagram/adapter/webapi_avplay_player_adapter.png b/docs/class_diagram/adapter/webapi_avplay_player_adapter.png new file mode 100755 index 0000000..e9a25c6 Binary files /dev/null and b/docs/class_diagram/adapter/webapi_avplay_player_adapter.png differ diff --git a/docs/class_diagram/adapter/webmedia_mse_eme.plantuml b/docs/class_diagram/adapter/webmedia_mse_eme.plantuml new file mode 100755 index 0000000..5053958 --- /dev/null +++ b/docs/class_diagram/adapter/webmedia_mse_eme.plantuml @@ -0,0 +1,17 @@ +@startuml + +'HTML5 VideoTag case +package "webmedia" { + class OriginalMediaPort { + } + interface MediaPort { + } + class PlusPlayerMediaPort { + /' Adapter '/ + } + MediaPort <|.. OriginalMediaPort + MediaPort <|. PlusPlayerMediaPort + "capi-media-player" --* OriginalMediaPort +} + +@enduml \ No newline at end of file diff --git a/docs/class_diagram/bmservice_drmmanager_plusplayer.plantuml b/docs/class_diagram/bmservice_drmmanager_plusplayer.plantuml new file mode 100755 index 0000000..581fcad --- /dev/null +++ b/docs/class_diagram/bmservice_drmmanager_plusplayer.plantuml @@ -0,0 +1,31 @@ +@startuml + +class TvplusPlusplayerHelper { + +} + +package "plusplayer" { +class Plusplayer { +} +} + +class CBmsDrmHandler { + +} + +package "DrmManager" { + class DrmManager { + } +} + +package "IEME" { + +} + +TvplusPlusplayerHelper *-- Plusplayer +TvplusPlusplayerHelper *-- CBmsDrmHandler +CBmsDrmHandler *-- DrmManager +DrmManager --> IEME +Plusplayer --> IEME + +@enduml \ No newline at end of file diff --git a/docs/class_diagram/details/defaultplayer.plantuml b/docs/class_diagram/details/defaultplayer.plantuml new file mode 100755 index 0000000..512dd28 --- /dev/null +++ b/docs/class_diagram/details/defaultplayer.plantuml @@ -0,0 +1,23 @@ +@startuml + +title DefaultPlayer +' DefaultPlayer + interface PlusPlayer { + } + interface TrackSource { + } + PlusPlayer <|.. DefaultPlayer + +' DefaultPlayer + DefaultPlayer "1" *-- "1" StateManager + DefaultPlayer "1" *-- "1" MsgHandler + DefaultPlayer "1" *--- "1" Feeder + DefaultPlayer "1" *--- "1" TypeFinder + DefaultPlayer "1" *--- "1" TrackSourceCompositor + TrackSource <|-- TrackSourceCompositor + DefaultPlayer "1" *--- "1" TrackRenderer + DefaultPlayer "1" *- "1" TrackSourceEventListener + DefaultPlayer "1" *- "1" TrackRendererEventListener + DefaultPlayer ..> Track + +@enduml \ No newline at end of file diff --git a/docs/class_diagram/details/trackrenderer.plantuml b/docs/class_diagram/details/trackrenderer.plantuml new file mode 100755 index 0000000..953cdce --- /dev/null +++ b/docs/class_diagram/details/trackrenderer.plantuml @@ -0,0 +1,55 @@ +@startuml + +title TrackRenderer + +class TrackRenderer { + + bool Open() + + bool Close() + + bool Prepare() + + bool Start() + + bool Stop() + + void SetDisplayMode() + + bool SetDisplay() + + bool SetDisplayRoi() + + bool SetVisible() + + bool SetTrack() + + bool SubmitPacket(const DecoderInputBufferPtr& data) + + bool GetPlayingTime() + + - std::vector trackinfo_; + - std::unique_ptr> pipeline_; + - AvocHandle avoc_id_; + +} + +class Display { + + void SetDisplayMode() + + bool SetDisplay() + + bool SetDisplayRoi() + + bool SetVisible() +} + +class DecoderInputBuffer { + - GstBuffer* buffer_ +} + +class ResourceManager { + + bool Alloc() + + bool Dealloc() + - static rm_cb_result ResourceConflictCallback_() +} + + DefaultPlayer "1" *-- "1" TrackRenderer + TrackRenderer "1" o-- "1" Display + TrackRenderer *-- ResourceManager + TrackRenderer *-- Track + TrackRenderer ..> DecoderInputBuffer + TrackRenderer *-- Pipeline + + package Gstreamer { + } + Pipeline --> Gstreamer + package Avoc { + } + TrackRenderer --> Avoc +@enduml \ No newline at end of file diff --git a/docs/class_diagram/details/tracksource_compositor.plantuml b/docs/class_diagram/details/tracksource_compositor.plantuml new file mode 100755 index 0000000..72cd16c --- /dev/null +++ b/docs/class_diagram/details/tracksource_compositor.plantuml @@ -0,0 +1,84 @@ +@startuml + +' TrackSource +title TrackSource & Compositor + + interface TrackSource { + + virtual bool AddSource() + + virtual bool StopSource() + + virtual bool DeleteSource() + + virtual bool Open() + + virtual bool Close() + + virtual bool Prepare() + + virtual bool Start() + + virtual bool Stop() + + virtual bool Seek() + + virtual bool SelectTrack() + + virtual bool Deactivate() + + virtual bool GetTrackInfo() + + virtual bool GetDuration() + + virtual bool SetBufferConfig() + + virtual bool RegisterListener(DecoderInputBuffer*) + + virtual bool RegisterEventListener(EventListener*) + + } + + class TrackSourceCompositor { + + bool AddSource() + + bool StopSource() + + bool DeleteSource() + } + + class HlsTrackSource { + + bool Open() + + bool Close() + + bool Prepare() + + bool Start() + + bool Stop() + + bool Seek() + } + + class DashTrackSource { + + bool Open() + + bool Close() + + bool Prepare() + + bool Start() + + bool Stop() + + bool Seek() + } + + class HttpTrackSource { + + bool Open() + + bool Close() + + bool Prepare() + + bool Start() + + bool Stop() + + bool Seek() + - GstElement* pipeline; + } + + class ExternalSubtitleSource { + + bool Open() + + bool Close() + + bool Prepare() + + bool Start() + + bool Stop() + + bool Seek() + } + + TrackSource <|.. TrackSourceCompositor + TrackSource <|.. HlsTrackSource + TrackSource <|.. DashTrackSource + TrackSource <|.. HttpTrackSource + TrackSource <|.. ExternalSubtitleSource + TrackSource --o TrackSourceCompositor + + package Gstreamer { + } + + HlsTrackSource --> Gstreamer + DashTrackSource --> Gstreamer + HttpTrackSource --> Gstreamer + ExternalSubtitleSource --> Gstreamer + +@enduml \ No newline at end of file diff --git a/docs/class_diagram/esplusplayer.plantuml b/docs/class_diagram/esplusplayer.plantuml new file mode 100755 index 0000000..4442ef5 --- /dev/null +++ b/docs/class_diagram/esplusplayer.plantuml @@ -0,0 +1,52 @@ +@startuml + +package "esplusplayer" { + + interface EsPlusPlayer { + } + + EsPlusPlayer <|.. EsPlayer + + class EsEventListener { + } + + class AudioStream { + } + + class VideoStream { + } + + class EsPacket { + } + + class TrackRenderer { + } + +' EsPlayer + EsPlayer "1" *-- "1" StateManager + EsPlayer "1" *-- "1" TrackRenderer + EsPlayer "1" *-- "1" MsgHandler + EsPlayer "1" *-- "1" TrackRendererEventListener + EsPlayer ..> AudioStream + EsPlayer ..> VideoStream + EsPlayer ..> EsPacket + EsPlayer ..> Track + EsPlayer --- DecoderInputBuffer + + AudioStream ..> Track + VideoStream ..> Track + +' TrackRenderer + TrackRenderer "1" o--- "1" Display + TrackRenderer *-- ResourceManager + TrackRenderer *-- Track + TrackRenderer ...> DecoderInputBuffer + TrackRenderer *- Pipeline + + Pipeline --> GstObjectGuard + + ResourceManager --> Resource + ResourceManager *-- Resource +} + +@enduml \ No newline at end of file diff --git a/docs/class_diagram/plusplayer.plantuml b/docs/class_diagram/plusplayer.plantuml new file mode 100755 index 0000000..2f702b2 --- /dev/null +++ b/docs/class_diagram/plusplayer.plantuml @@ -0,0 +1,64 @@ +@startuml + +package "plusplayer" { + + interface PlusPlayer { + } + + PlusPlayer <|.. DefaultPlayer + + class TrackRenderer { + } + + ResourceManager --> Resource + ResourceManager *-- Resource + + class DecoderInputBufferListener { + } + + class TrackSourceCompositor { + } + + class TypeFinder { + } + + TypeFinder *-- SourceType + DecoderInputBufferListener <|-- Feeder + +' DefaultPlayer + DefaultPlayer "1" *-- "1" Feeder + DefaultPlayer "1" *- "1" StateManager + DefaultPlayer "1" *-- "1" TypeFinder + DefaultPlayer "1" *-- "1" TrackSourceCompositor + DefaultPlayer "1" *--- "1" TrackRenderer + DefaultPlayer "1" *-- "1" MsgHandler + DefaultPlayer "1" *-- "1" TrackSourceEventListener + DefaultPlayer "1" *-- "1" TrackRendererEventListener + DefaultPlayer ..> Track + +' TrackSource + interface TrackSource { + } + + class HlsTrackSource { + } + TrackSource <|.. TrackSourceCompositor + TrackSource <|.. HlsTrackSource + TrackSource <|.. ExternalSubtitleSource + TrackSource --o TrackSourceCompositor + HlsTrackSource *-- Track + HlsTrackSource --- DecoderInputBuffer + ExternalSubtitleSource --- DecoderInputBuffer + + Pipeline --> GstObjectGuard + HlsTrackSource --> GstObjectGuard + ExternalSubtitleSource --> GstObjectGuard + + TrackRenderer "1" o--- "1" Display + TrackRenderer *-- ResourceManager + TrackRenderer *-- Track + TrackRenderer ...> DecoderInputBuffer + TrackRenderer *- Pipeline +} + +@enduml \ No newline at end of file diff --git a/docs/class_diagram/plusplayer.png b/docs/class_diagram/plusplayer.png new file mode 100755 index 0000000..0690d2e Binary files /dev/null and b/docs/class_diagram/plusplayer.png differ diff --git a/docs/class_diagram/plusplayer_simple.plantuml b/docs/class_diagram/plusplayer_simple.plantuml new file mode 100755 index 0000000..c7801f6 --- /dev/null +++ b/docs/class_diagram/plusplayer_simple.plantuml @@ -0,0 +1,30 @@ +@startuml + +package "plusplayer" { + + interface PlusPlayer { + } + + PlusPlayer <|.. DefaultPlayer + + class TrackRenderer { + } + DefaultPlayer "1" *-- "1" TrackSourceCompositor + DefaultPlayer "1" *-- "1" TrackRenderer + + class TrackSourceCompositor { + } + +' TrackSource + interface TrackSource { + } + + class HlsTrackSource { + } + TrackSource <|.. TrackSourceCompositor + TrackSource <|.. HlsTrackSource + TrackSource <|.. ExternalSubtitleSource + TrackSource --o TrackSourceCompositor +} + +@enduml \ No newline at end of file diff --git a/docs/coding_rule.md b/docs/coding_rule.md new file mode 100755 index 0000000..73e4d35 --- /dev/null +++ b/docs/coding_rule.md @@ -0,0 +1,55 @@ +## **CODING STYLE** ## + * All codes follow 'Google C++ Style Guide' + - [English](https://google.github.io/styleguide/cppguide.html) + * some major rules are below + +--- + +### § Naming ### + Give as descriptive a name as possible. + +#### 1. Namespace/File/Variable/Struct Data member #### + * All lower case(mandatory) + between underscore(optional) + * e.g) mediaplayer.h , trackrenderer , track_renderer + +#### 2. Class Data Members + * Private attributes : Variable names + Trailing underscore("_") + * e.g) tablename_ , tracksource_ , track_source_ + * Public attributes - Not Allowed. + +#### 3. Type Names - Class,Struct,Type Aliases,Enums and type template parameters #### + * Names start with a capital letter and have a capital letter for each new word + * No Underscore + * e.g) MMPlayer(X) , MmPlayer(O) + * e.g) class MediaPlayer {} ... + * e.g) struct PlayerContext {} , enum SourceType {...} + +#### 4. Macro Names #### + * All capitals + Underscore + +#### 5. Constant / Enumerator #### + * prefix 'k' + TypeNames + * e.g + const int kDaysInAWeek = 7; + enum UrlTableErrors { + kErrorOutOfMemory + } + +### § Formating ### + +#### 1. Indentation #### + * Default indentation : 2 spaces + * We use spaces for indentation. Do not use tabs in your code. + +#### 2. Class #### + * Ordering + * Sections in public - protected - private order + * method(public/protected/private) -> attribute order + * public, protected and private keywords : **1** space indentation + +### § Header Files ### + +#### 1. Order of Includes #### + * Related header, C library, C++ library, other libraries' .h, your project's .h. + +> plz refer to "Google C++ Style Guide" for the rest of detail guide. diff --git a/docs/dot_graph/plusplayer_renderer_start.png b/docs/dot_graph/plusplayer_renderer_start.png new file mode 100755 index 0000000..ca27b1a Binary files /dev/null and b/docs/dot_graph/plusplayer_renderer_start.png differ diff --git a/docs/dot_graph/plusplayer_renderer_stop.png b/docs/dot_graph/plusplayer_renderer_stop.png new file mode 100755 index 0000000..4d92f40 Binary files /dev/null and b/docs/dot_graph/plusplayer_renderer_stop.png differ diff --git a/docs/dot_graph/plusplayer_src_start.png b/docs/dot_graph/plusplayer_src_start.png new file mode 100755 index 0000000..b73c014 Binary files /dev/null and b/docs/dot_graph/plusplayer_src_start.png differ diff --git a/docs/dot_graph/plusplayer_src_stop.png b/docs/dot_graph/plusplayer_src_stop.png new file mode 100755 index 0000000..cf2f2e2 Binary files /dev/null and b/docs/dot_graph/plusplayer_src_stop.png differ diff --git a/docs/downloadable/class_diagram.plantuml b/docs/downloadable/class_diagram.plantuml new file mode 100755 index 0000000..bc57199 --- /dev/null +++ b/docs/downloadable/class_diagram.plantuml @@ -0,0 +1,2 @@ +@startuml +@enduml \ No newline at end of file diff --git a/docs/downloadable/version_control.plantuml b/docs/downloadable/version_control.plantuml new file mode 100755 index 0000000..c1a06ad --- /dev/null +++ b/docs/downloadable/version_control.plantuml @@ -0,0 +1,6 @@ +@startuml + +' to be updated soon +' this is test + +@enduml \ No newline at end of file diff --git a/docs/downloadable/version_control_sequence.plantuml b/docs/downloadable/version_control_sequence.plantuml new file mode 100755 index 0000000..1f3fce1 --- /dev/null +++ b/docs/downloadable/version_control_sequence.plantuml @@ -0,0 +1,80 @@ +@startuml + +/' +skinparam backgroundColor #EEEBDC +skinparam handwritten true +skinparam sequence { + ArrowColor DeepSkyBlue + ActorBorderColor DeepSkyBlue + LifeLineBorderColor blue + LifeLineBackgroundColor #A9DCDF + ParticipantBorderColor DeepSkyBlue + ParticipantBackgroundColor DodgerBlue + ParticipantFontName Impact + ParticipantFontSize 17 + ParticipantFontColor #A9DCDF + ActorBackgroundColor aqua + ActorFontColor DeepSkyBlue + ActorFontSize 17 + ActorFontName Aapex +} +'/ + +title tvplus case(1) dynamic loading + +participant AppLauncher +participant App +participant BmsService +note over BmsService : daemon +participant Plusplayer +participant PlusplayerLoader +database version_info + +== initialization == +alt successful case + AppLauncher -> App : load + App -> BmsService : play() + BmsService -> Plusplayer : SetVersion("path") + Plusplayer -> PlusplayerLoader : SetVersion("path") + PlusplayerLoader -> version_info : ReadVersion() + ' 한번에 모두 로딩 or 분할하여. + PlusplayerLoader -> PlusplayerLoader : LoadProperLibSet_() + BmsService -> Plusplayer : Create() +else some kind of failure (version mismatch or missing) +end + +newpage tvplus case(2) dynamic linking + +actor PlayerManager +participant ServiceDaemonLauncher +participant App +participant BmsService +note over BmsService : daemon +participant Plusplayer +participant PlusplayerLoader +database version_info + + +== initialization == + +group compile time + PlayerManager -> BmsService : update LD_PRELOAD configuration + note over BmsService : adjust bms.service configuration file +end + +alt successful case + ServiceDaemonLauncher -> BmsService : load + note over BmsService : updated libs are dynamically linked + App -> BmsService : play() + BmsService -> Plusplayer : SetVersion("path") + Plusplayer -> PlusplayerLoader : GetVersion("path") + PlusplayerLoader -> version_info : ReadVersion() + PlusplayerLoader -> PlusplayerLoader : CheckLibValidation_() + Plusplayer <-- PlusplayerLoader : success + BmsService -> Plusplayer : Create() +else some kind of failure (version mismatch or missing) +end + + + +@enduml diff --git a/docs/gtest_guide.md b/docs/gtest_guide.md new file mode 100755 index 0000000..0fa6c9e --- /dev/null +++ b/docs/gtest_guide.md @@ -0,0 +1,127 @@ +**GTest guide** +=============== + For unit test for plusplayer + +--- +### Reference +- + +## Assertion +* ASSERT_* : 실패시 해당 테스트를 바로 종료
+* EXPECT_* : 실패하여도 테스트 계속 진행
+ +#### Basic Assertions ### + +These assertions do basic true/false condition testing. + +| **Fatal assertion** | **Nonfatal assertion** | **Verifies** | +|:--------------------|:-----------------------|:-------------| +| `ASSERT_TRUE(`_condition_`)`; | `EXPECT_TRUE(`_condition_`)`; | _condition_ is true | +| `ASSERT_FALSE(`_condition_`)`; | `EXPECT_FALSE(`_condition_`)`; | _condition_ is false | + +#### Binary Comparison ### + +This section describes assertions that compare two values. + +| **Fatal assertion** | **Nonfatal assertion** | **Verifies** | +|:--------------------|:-----------------------|:-------------| +|`ASSERT_EQ(`_val1_`, `_val2_`);`|`EXPECT_EQ(`_val1_`, `_val2_`);`| _val1_ `==` _val2_ | +|`ASSERT_NE(`_val1_`, `_val2_`);`|`EXPECT_NE(`_val1_`, `_val2_`);`| _val1_ `!=` _val2_ | +|`ASSERT_LT(`_val1_`, `_val2_`);`|`EXPECT_LT(`_val1_`, `_val2_`);`| _val1_ `<` _val2_ | +|`ASSERT_LE(`_val1_`, `_val2_`);`|`EXPECT_LE(`_val1_`, `_val2_`);`| _val1_ `<=` _val2_ | +|`ASSERT_GT(`_val1_`, `_val2_`);`|`EXPECT_GT(`_val1_`, `_val2_`);`| _val1_ `>` _val2_ | +|`ASSERT_GE(`_val1_`, `_val2_`);`|`EXPECT_GE(`_val1_`, `_val2_`);`| _val1_ `>=` _val2_ | + + +#### String Comparison ### + +The assertions in this group compare two **C strings**.
+If you want to compare two `string` objects, use `EXPECT_EQ`, `EXPECT_NE`, and etc instead. + +| **Fatal assertion** | **Nonfatal assertion** | **Verifies** | +|:--------------------|:-----------------------|:-------------| +| `ASSERT_STREQ(`_str1_`, `_str2_`);` | `EXPECT_STREQ(`_str1_`, `_str_2`);` | the two C strings have the same content | +| `ASSERT_STRNE(`_str1_`, `_str2_`);` | `EXPECT_STRNE(`_str1_`, `_str2_`);` | the two C strings have different content | +| `ASSERT_STRCASEEQ(`_str1_`, `_str2_`);`| `EXPECT_STRCASEEQ(`_str1_`, `_str2_`);` | the two C strings have the same content, ignoring case | +| `ASSERT_STRCASENE(`_str1_`, `_str2_`);`| `EXPECT_STRCASENE(`_str1_`, `_str2_`);` | the two C strings have different content, ignoring case | + +## Text Fixtures : Using the Same Data Configuration for Multiple Tests ## + +사용자가 유사한 data를 사용해서 하나 이상의 test를 작성한다면, test fixture를 사용할 수 있다. 이 test fixture를 사용한다는 것은 여러개의 다양한 test를 작성하는 과정에서 같은 object의 configuration을 재사용한다는 것을 의미한다. + +Fixture를 작성할 때에는 아래의 내용대로 수행하면 된다. + +1. ::testing::Test 로부터 class를 derive한다. Sub-class 에서 fixture member에 접근해야 하기 때문에 protected 혹은 public 으로 작성해야 한다. +2. Class 내부에서 사용자가 원하는대로 object들을 선언해 사용한다. +3. 필요하다면, 생성자나 SetUp() function을 작성해둔다. +4. 생성자나 SetUp() function을 정의해서 사용하고 있다면, 해당 function에서 사용했던 resource를 반환하기 위해 소멸자나 TearDown() function을 작성한다. +5. Subroutine 들을 작성한다. + +Fixture를 사용하기 위해서는 TEST() 대신에 TEST_F()를 사용해야만 한다. +TEST()에서는 첫번째 argument가 testcase의 이름이었지만 TEST_F()를 사용할 때는 첫번째 argument로 test fixture class의 이름을 사용해야만 한다. + +**Fixture class 기본 구현 Form** +* 관례에 따라 테스트할 클래스가 Foo라면 이름을 FooTest라고 하는게 좋다. +~~~ +class PlusPlayerTest : public ::testing::Test { +public: + PlusPlayerTest(std::string url) + : plusplayer_(nullptr), url_(url) + { + } + + void SetUp() override + { + plusplayer_ = new PlusPlayer(); + create(url_); + } + + void TearDown() override + { + destory(plusplayer_); + } + +private: + std::string url_; + PlusPlayer* plusplayer_; + +} +~~~ + +**실행 순서** +1. 모든 구글 테스트 플래그 상태를 저장한다. +2. 첫 번째 테스트에 대해 테스트 fixture 객체를 생성한다. +3. 만든 객체를 SetUp()에서 초기화한다. +4. 픽스처 객체에 대해 테스트를 실행한다. +5. TearDown()에서 해당 픽스처를 정리한다. +6. 해당 픽스처를 삭제한다. +7. 모든 구글 테스트 플래그 상태를 복원한다. +8. 모든 테스트를 마칠 때까지 다음 테스트에 대해 위 과정을 반복한다. + +--- + +## Arguments +reference + +1. test selection + * --gtest_list_tests
+ > 테스트할 항목을 보여준다. (테스트 실시 X) + * --gtest_also_run_disabled_tests
+ > DISABLED_ 로 막아둔 test case 를 일시적으로 실행 + * --gtest_filter
+ > 특정 case 들만 실행 가능
+ Ex) --gtest_filter="*.create*" : 모든 TC중에서 create 로 시작하는 모든 TC 실행.
+ +2. test Execution + * --gtest_repeat
+ > test 반복 가능. -1일 경우 무한히 반복
+ --gtest_break_on_failure와 함께 사용하면 실패할 경우 멈춤. + * --gtest_shuffle
+ > 무작위로 실행 가능 (test case 간 dependency 가 없어야 하기 때문이다)
+ gtest는 기본적으로 현재시간을 랜덤함수의 시드값으로 사용하나, --gtest_random_seed로 조절가능하다 + * --gtest_random_seed + > 1 ~ 99999까지의 값을 --gtest_shuffle에서 사용할 랜덤함수의 시드로 사용. + +--- +## Reference + * Gtest Primer(EN)
diff --git a/docs/module_view/libav-common.plantuml b/docs/module_view/libav-common.plantuml new file mode 100755 index 0000000..09ba0ae --- /dev/null +++ b/docs/module_view/libav-common.plantuml @@ -0,0 +1,25 @@ +@startuml + +title reduced downloadable module size by libav-common\n dash/hls/http streaming module 9M to 4.5M 50% decreased + +component "dash gst-plugin" as dash +component "hls gst-plugin" as hls +component "httpdemux gst-plugin" as http + +component "libavformat-dash.so" as dashformat +component "libavformat-hls.so" as hlsformat +component "libavformat-httpdemux.so" as httpformat + +package "libav-common" as libavcommon { + [libavcodec.so] + [libavutils.so] +} + +dash --> dashformat : use +http --> httpformat : use +hls --> hlsformat : use + +dashformat --> libavcommon : use +httpformat --> libavcommon : use +hlsformat --> libavcommon : use +@enduml \ No newline at end of file diff --git a/docs/module_view/uses_view.plantuml b/docs/module_view/uses_view.plantuml new file mode 100755 index 0000000..bbf7204 --- /dev/null +++ b/docs/module_view/uses_view.plantuml @@ -0,0 +1,70 @@ +@startuml + +title : module view : uses view + +component [Downloadable module] #Aquamarine + +package "Security" { + IEME -- [EME] +} +package "System" { + package "Subsystem" { + RM -- [ResourceManager] + } + + package "Subsystem" { + AVOC -- [libavoc] + } +} + +package "EFL" { + ecore_wayland_api -- [Ecore_Wayland] +} + +package "Gstreamer" { + [gstreamer] --> [gst-openmax] + package "gst-plugins-streamingengine" { + [gst-plugin-dash] #Aquamarine + [gst-plugin-hls] #Aquamarine + [gst-plugin-http] #Aquamarine + [gstreamer] --> [gst-plugin-dash] + [gstreamer] --> [gst-plugin-hls] + [gstreamer] --> [gst-plugin-http] + } +} + +package "Plusplayer" { + [plusplayer] #Aquamarine + [tracksource] #Aquamarine + [plusplayer-core] #Aquamarine + [plusplayer] --> [tracksource] : use + [plusplayer] --> [trackrenderer] : use + [plusplayer] --> [plusplayer-core] : use + [tracksource] --> [plusplayer-core] : use + '[tracksource] --> [gstreamer] : use + [trackrenderer] --> [plusplayer-core] : use + [trackrenderer] --> RM : use + '[trackrenderer] --> [gstreamer] : use + [trackrenderer] --> [ecore_wayland_api] : use + [trackrenderer] --> [AVOC] : use + [trackrenderer] --> [IEME] : use + [plusplayer-core] --> [gstreamer] : use +} + + +package "drmmanager" { + drmapi -- [DrmManager] + [DrmManager] --> [IEME] : use +} + +package "webapi-avplay" { + [avplay] --> [plusplayer] + [avplay] --> [drmapi] +} + +package "bms-service" { + [BmsAVPlayer] --> [plusplayer] + [BmsAVPlayer] --> [drmapi] +} + +@enduml \ No newline at end of file diff --git a/docs/module_view/uses_view.png b/docs/module_view/uses_view.png new file mode 100755 index 0000000..1f12768 Binary files /dev/null and b/docs/module_view/uses_view.png differ diff --git a/docs/plusplayer_guide.md b/docs/plusplayer_guide.md new file mode 100755 index 0000000..66c6035 --- /dev/null +++ b/docs/plusplayer_guide.md @@ -0,0 +1,69 @@ +# **PlusPlayer** # + PlusPlayer is a new multimedia player object-oriented designed. + +## Goals ## + * Improve maintainability / extensibility / reusability + - object oriented design + - separate the modules into high-variation part and low-variation part + - decoupling between each streaming services + * Simplification + - simplified pipeline , creates static pipeline + * Easy to upgrade + - support downloadable features (planned) + +## Architecture Design ## + * PlusPlayer consists of TypeFinder / TrackSource / TrackRenderer / DefaultPlayer objects. + * TypeFinder + - Probing source type by inspecting probing data + - use gstreamer typefinder plugin + * TrackSource + - Fetching data from server , demuxing , controling demuxed packet buffers + - a pipeline consists of `src - typefinder - demuxer - multiqueue - inputselector - fakesink` + - plz refer to `${plusplayer_workspace}/docs/dot/plusplayer_src_start.png` + * TrackRenderer + - maintains a pipeline consists of `appsrc - omx - sink` elements + - plz refer to `${plusplayer_workspace}/docs/dot/plusplayer_renderer_start.png` + * Class diagram + * ${plusplayer_workspace}/docs/class_diagram.plantuml + * or you can directly download it from [class_diagram.plantuml] + +## Development ## + * GitHub Project : + * All the milestones / activities (Codes , CodeReview , Design , Issues , etc) are being uploaded here + * Codereview on Github + - Each Developers should fork master branch and work on their own forked project. + - developer should submit pull-request to apply their changes + * all developers must sync their forked project with the latest master prior to sumit pull-request. so that we can avoid merge conflicts. + - everyone can join the codereviews + * the coding style follows Google C++ coding style guide. refer to [CODING STYLE](### coding-style) + * Test Driven Development (use gtest - plusplayer_ut -for their implementation and verifications) + +#### GTest guide #### + * To write Unit test case for PlusPlayer + * docs/gtest_guide.md + * Check Reference section of above link for more detailed documents. + * example + ``` + sh-3.2# plusplayer_ut --gtest_filter="AvPlusPlayerTest.play" + ``` +#### Coding Rule Check #### + * check your coding style using **cpplint.py** before uploading your codes + > e.g) $ **cpplint.py** cpp_style_test.cpp + * Or check your coding style and fix your codes automatically using **clang-format** + > e.g) $ **clang-format-3.4** cpp_style_test.cpp -style=google **-i** + +#### Build #### + > 1. Clone or Download plusplayer package. + > 2. $ gbs -c ~/gbsconf/gbs_3_0.conf build -A armv7l --include-all --clean -B ~/GBS_ROOT_PLUSPLAYER + +#### Understanding the codes #### + to understand PlusPlayer concept quickly + plz refer to + - ${plusplayer_workspace}/ut/porting/webapi-plugins-avplay/src/avplusplayer.cc/h + - ${plusplayer_workspace}/src/player/defaultplayer.cpp/h + - ${plusplayer_workspace}/docs/class_diagram.txt + +#### CODING STYLE #### + 'Google C++ Style Guide' based coding. + > Translated(Korean) + > Original(English): [https://google.github.io/styleguide/cppguide.html](https://google.github.io/styleguide/cppguide.html) diff --git a/docs/reference/PlantUML_Language_Reference_Guide.pdf b/docs/reference/PlantUML_Language_Reference_Guide.pdf new file mode 100755 index 0000000..838b475 Binary files /dev/null and b/docs/reference/PlantUML_Language_Reference_Guide.pdf differ diff --git a/docs/sequence_activity_diagram/avplusplayer_adapter/avplusplayer_adapter_sequence_diagram.plantuml b/docs/sequence_activity_diagram/avplusplayer_adapter/avplusplayer_adapter_sequence_diagram.plantuml new file mode 100755 index 0000000..b739ae4 --- /dev/null +++ b/docs/sequence_activity_diagram/avplusplayer_adapter/avplusplayer_adapter_sequence_diagram.plantuml @@ -0,0 +1,33 @@ +@startuml + +actor App +boundary AvPlayInstance + +App -> AvPlayInstance : AVPlayOpen(url) +note left : request playback +AvPlayInstance -> AvPlusPlayer : Open(url) +AvPlusPlayer -> Plusplayer : Open(url) +AvPlusPlayer -> Plusplayer : RegisterEventListener() + +App -> AvPlayInstance : AVPlaySetBufferingParam() +AvPlayInstance -> AvPlusPlayer : setBufferingParam() +AvPlusPlayer -> Plusplayer : SetBufferConfig() +App -> AvPlayInstance : AVPlaySetDisplayRect() +AvPlayInstance -> AvPlusPlayer : SetOutputArea() +AvPlusPlayer -> Plusplayer : SetDisplayMode() +AvPlusPlayer -> Plusplayer : SetDisplayRoi() +AvPlusPlayer -> Plusplayer : SetDisplay(Evas_Object*) +App -> AvPlayInstance : AVPlaySetStreamingProperty() +AvPlayInstance -> AvPlusPlayer : SetStreamingProperty() +AvPlusPlayer -> Plusplayer : SetStreamingProperty() + +App -> AvPlayInstance : AVPlayPrepare() +AvPlayInstance -> AvPlusPlayer : prepareAsync() +AvPlusPlayer -> Plusplayer : PrepareAsync() +Plusplayer --> AvPlusPlayer : OnPrepareDone(ret) +AvPlusPlayer --> AvPlayInstance : onAsyncEventComplete() +App -> AvPlayInstance : AVPlayPlay() +AvPlayInstance -> AvPlusPlayer : play() +AvPlusPlayer -> Plusplayer : Start() + +@enduml \ No newline at end of file diff --git a/docs/sequence_activity_diagram/dash_drm_playback/dash_drm_playback_base_flow_1.plantuml b/docs/sequence_activity_diagram/dash_drm_playback/dash_drm_playback_base_flow_1.plantuml new file mode 100755 index 0000000..83f267b --- /dev/null +++ b/docs/sequence_activity_diagram/dash_drm_playback/dash_drm_playback_base_flow_1.plantuml @@ -0,0 +1,53 @@ +@startuml + +title dash_drm_playback_base_flow_1 + +BmsAvPlayer -> TvplusPlusplayerAdapter : m_Play() +TvplusPlusplayerAdapter -> CBmsDrmHandler : StoreDrmInfo() +activate CBmsDrmHandler +CBmsDrmHandler -> DrmManager : DMGRCreateDRMSession(DRM_TYPE_EME) +CBmsDrmHandler -> DrmManager : DMGRSetDRMLocalMode() +DrmManager -> IEME : create_session() +IEME --> DrmManager : drm_handle* +DrmManager --> CBmsDrmHandler : return +CBmsDrmHandler --> TvplusPlusplayerAdapter : return +deactivate CBmsDrmHandler +TvplusPlusplayerAdapter -> CBmsDrmHandler : GetDrmHandle() +CBmsDrmHandler --> TvplusPlusplayerAdapter : drm_handle* +TvplusPlusplayerAdapter -> Plusplayer : SetDrm(DrmInfo) +Plusplayer -> TrackRenderer : SetDrmInfo(DrmInfo) +TvplusPlusplayerAdapter -> Plusplayer : PrepareAsync() +Plusplayer -> TrackRenderer : Prepare() +activate TrackRenderer +group CreatePipeline + TrackRenderer -> Gstreamer : factory_make(appsrc) + TrackRenderer -> Gstreamer : factory_make(drm_eme) + TrackRenderer --> DrmEme : g_object_set(DrmInitData_Callback) + TrackRenderer -> Gstreamer : factory_make(omx_decoder) + TrackRenderer -> Gstreamer : factory_make(sink) + TrackRenderer -> Gstreamer : set_state(GST_STATE_PAUSE)(async) + TrackRenderer -> TrackRenderer : wait state_changed + activate TrackRenderer #DarkSalmon + DrmEme --> TrackRenderer : DrmInitData_Callback(init_data) + TrackRenderer --> Plusplayer : OnDrmInitData(init_data, tracktype) + Plusplayer --> TvplusPlusplayerAdapter : OnDrmInitData(init_data, tracktype) + activate TvplusPlusplayerAdapter + TvplusPlusplayerAdapter -> CBmsDrmHandler : SetDrmInitData(init_data) + activate CBmsDrmHandler + CBmsDrmHandler -> DrmManager : DMGRSetDrmInitData(init_data) + DrmManager --> CBmsDrmHandler : return + CBmsDrmHandler --> TvplusPlusplayerAdapter : return + deactivate CBmsDrmHandler + TvplusPlusplayerAdapter -> Plusplayer : DrmLicenseAcquiredDone(tracktype) + deactivate TvplusPlusplayerAdapter + Plusplayer -> TrackRenderer : DrmLicenseAcquiredDone(tractype) + TrackRenderer -> DrmEme : g_object_set(getrights-complete-return) + DrmEme --> Gstreamer : state_changed + Gstreamer --> TrackRenderer : state_changed + deactivate TrackRenderer +end +TrackRenderer -> Plusplayer : return +deactivate TrackRenderer +Plusplayer --> TvplusPlusplayerAdapter : OnPrepareDone(ret) + +@enduml \ No newline at end of file diff --git a/docs/sequence_activity_diagram/source_change/source_change_base_flow_1.plantuml b/docs/sequence_activity_diagram/source_change/source_change_base_flow_1.plantuml new file mode 100755 index 0000000..7cfcce8 --- /dev/null +++ b/docs/sequence_activity_diagram/source_change/source_change_base_flow_1.plantuml @@ -0,0 +1,49 @@ +@startuml + +ref over BmsAVPlayer, TvplusPlusplayerHelper, Plusplayer +playing previous channel +end ref + +BmsAVPlayer -> TvplusPlusplayerHelper : m_PlayPosition() +note left : request channel change +TvplusPlusplayerHelper -> TvplusPlusplayerHelper : compare codec info +alt if codec info is same +TvplusPlusplayerHelper -> Plusplayer : ChangeSource(url,resume_time) +Plusplayer -> StateManager : ProcessEvent(StopSource) +StateManager -> TrackSourceCompositor : StopSource(type) +TrackSourceCompositor -> HlSTrackSource : Stop() +TrackSourceCompositor --> StateManager : ret +StateManager -> MetaStateMachine : request state to SourceStopped +MetaStateMachine -> StateManager : state changed +StateManager -> TrackSourceCompositor : DeleteSource(type) +TrackSourceCompositor -> HlSTrackSource : <> +destroy HlSTrackSource +TrackSourceCompositor --> StateManager : ret +StateManager --> Plusplayer : done +Plusplayer -> ChangeSourceTask : create task +ChangeSourceTask -> StateManager : ProcessEvent(InitializeSource) +StateManager -> TrackSourceCompositor : AddSource(type) +create DashTrackSource +TrackSourceCompositor -> DashTrackSource : <> +StateManager --> ChangeSourceTask : ret +ChangeSourceTask -> StateManager : ProcessEvent(PrepareSource) +StateManager -> TrackSourceCompositor : Prepare() +TrackSourceCompositor -> DashTrackSource : Prepare() +StateManager --> ChangeSourceTask : ret +ChangeSourceTask -> StateManager : ProcessEvent(Start) +StateManager -> TrackSourceCompositor : Start() +TrackSourceCompositor -> DashTrackSource : Start() +StateManager --> ChangeSourceTask : ret +ChangeSourceTask --> Plusplayer : ret +Plusplayer --> TvplusPlusplayerHelper : OnChangeSourceDone(ret) +else if codec is not same +TvplusPlusplayerHelper -> Plusplayer : Stop and Destroy +TvplusPlusplayerHelper -> Plusplayer : PlusPlayer::Create() +TvplusPlusplayerHelper -> Plusplayer : Open(url) +TvplusPlusplayerHelper -> Plusplayer : PrepareAsync(url) +... +Plusplayer --> TvplusPlusplayerHelper : OnPrepareDone(ret) +TvplusPlusplayerHelper -> Plusplayer : Start() +end + +@enduml \ No newline at end of file diff --git a/docs/sequence_activity_diagram/trackrenderer_adapter/trackrenderer_adapter.plantuml b/docs/sequence_activity_diagram/trackrenderer_adapter/trackrenderer_adapter.plantuml new file mode 100755 index 0000000..2432b66 --- /dev/null +++ b/docs/sequence_activity_diagram/trackrenderer_adapter/trackrenderer_adapter.plantuml @@ -0,0 +1,13 @@ +@startuml + +title TrackRendererAdapter Sequence Diagram +participant DefaultPlayer + +create TrackRendererAdapter +DefaultPlayer -> TrackRendererAdapter : TrackRendererAdapter::Create() +TrackRendererAdapter -> TrackRendererCapi : trackrenderer_create() +create TrackRenderer +TrackRendererCapi -> TrackRenderer : std::unique_ptr(new TrackRenderer) +TrackRendererCapi --> TrackRendererAdapter : handle + +@enduml diff --git a/docs/sequence_activity_diagram/tracksource/tracksource_alternative_flow_1.plantuml b/docs/sequence_activity_diagram/tracksource/tracksource_alternative_flow_1.plantuml new file mode 100755 index 0000000..c1d70e4 --- /dev/null +++ b/docs/sequence_activity_diagram/tracksource/tracksource_alternative_flow_1.plantuml @@ -0,0 +1,34 @@ +@startuml + +title tracksource_alternative_flow_1 + +actor App +boundary BmsService + +App -> BmsService : request playback +BmsService -> Plusplayer : Open(url) +Plusplayer -> TrackSource : TrackSource::CreateCompositor() +Plusplayer -> TrackSourceCompositor : RegisterEventListener(this) +BmsService -> Plusplayer : RegisterEventListener() +BmsService -> Plusplayer : SetSourceType(sourcetype) +BmsService -> Plusplayer : PrepareAsync() +Plusplayer -> TrackSourceCompositor : AddSource(sourcetype) +activate TrackSourceCompositor +TrackSourceCompositor -> HttpTrackSource : new +HttpTrackSource --> TrackSourceCompositor : object +Plusplayer -> TrackSourceCompositor : Prepare() +TrackSourceCompositor -> HttpTrackSource : Prepare() +group CreatePipeline +HttpTrackSource -> gstreamer : pipeline_new() +HttpTrackSource -> gstreamer : factory_make(src) +HttpTrackSource -> gstreamer : factory_make(http_demuxer) +HttpTrackSource -> gstreamer : factory_make(...) +HttpTrackSource -> gstreamer : set_state(PAUSE) +gstreamer --> HttpTrackSource : state_changed +end +HttpTrackSource --> TrackSourceCompositor : success +TrackSourceCompositor --> Plusplayer : success +deactivate TrackSourceCompositor +Plusplayer --> BmsService : Listener::PrepareDone(success) + +@enduml \ No newline at end of file diff --git a/docs/sequence_activity_diagram/tracksource/tracksource_base_flow_1.plantuml b/docs/sequence_activity_diagram/tracksource/tracksource_base_flow_1.plantuml new file mode 100755 index 0000000..c210b17 --- /dev/null +++ b/docs/sequence_activity_diagram/tracksource/tracksource_base_flow_1.plantuml @@ -0,0 +1,42 @@ +@startuml + +title tracksource_base_flow_1 + +actor App +boundary BmsService + +App -> BmsService : request playback +BmsService -> Plusplayer : Open(url) +Plusplayer -> TrackSource : TrackSource::CreateCompositor() +Plusplayer -> TrackSourceCompositor : RegisterEventListener(this) +BmsService -> Plusplayer : RegisterEventListener() + +BmsService -> Plusplayer : PrepareAsync() +Plusplayer -> TrackSourceCompositor : AddSource(url) +activate TrackSourceCompositor +TrackSourceCompositor -> Typefinder : probe() +group createpipeline +Typefinder -> gstreamer : pipeline_new() +Typefinder -> gstreamer : factory_make(src) +Typefinder -> gstreamer : factory_make(typefinder) +gstreamer --> Typefinder : OnHaveType_(sourcetype) +end +Typefinder --> TrackSourceCompositor : sourcetype +TrackSourceCompositor -> HttpTrackSource : new + +HttpTrackSource --> TrackSourceCompositor : object + +Plusplayer -> TrackSourceCompositor : Prepare() +TrackSourceCompositor -> HttpTrackSource : Prepare() +group CreatePipeline +HttpTrackSource -> gstreamer : factory_make(http_demuxer) +HttpTrackSource -> gstreamer : factory_make(...) +HttpTrackSource -> gstreamer : set_state(PAUSE) +gstreamer --> HttpTrackSource : state_changed +end +HttpTrackSource --> TrackSourceCompositor : success +TrackSourceCompositor --> Plusplayer : success +deactivate TrackSourceCompositor +Plusplayer --> BmsService : Listener::PrepareDone(success) + +@enduml \ No newline at end of file diff --git a/docs/sequence_activity_diagram/tracksource/tracksource_exception_flow_1.plantuml b/docs/sequence_activity_diagram/tracksource/tracksource_exception_flow_1.plantuml new file mode 100755 index 0000000..b127340 --- /dev/null +++ b/docs/sequence_activity_diagram/tracksource/tracksource_exception_flow_1.plantuml @@ -0,0 +1,29 @@ +@startuml + +title tracksource_exception_flow_1 + +actor App +boundary BmsService + +App -> BmsService : request playback +BmsService -> Plusplayer : Open(url) +Plusplayer -> TrackSource : TrackSource::CreateCompositor() +Plusplayer -> TrackSourceCompositor : RegisterEventListener(this) +BmsService -> Plusplayer : RegisterEventListener() + +BmsService -> Plusplayer : PrepareAsync() +Plusplayer -> TrackSourceCompositor : AddSource(url) +activate TrackSourceCompositor +TrackSourceCompositor -> Typefinder : probe() +group createpipeline +Typefinder -> gstreamer : pipeline_new() +Typefinder -> gstreamer : factory_make(src) +Typefinder -> gstreamer : factory_make(typefinder) +gstreamer --> Typefinder : Error() +end +Typefinder --> TrackSourceCompositor : fail +TrackSourceCompositor --> Plusplayer : fail +deactivate TrackSourceCompositor +Plusplayer --> BmsService : Listener::PrepareDone(fail) + +@enduml \ No newline at end of file diff --git a/docs/sequence_activity_diagram/tracksource/tracksource_exception_flow_2.plantuml b/docs/sequence_activity_diagram/tracksource/tracksource_exception_flow_2.plantuml new file mode 100755 index 0000000..bb7ec0e --- /dev/null +++ b/docs/sequence_activity_diagram/tracksource/tracksource_exception_flow_2.plantuml @@ -0,0 +1,29 @@ +@startuml + +title tracksource_exception_flow_2 + +actor App +boundary BmsService + +App -> BmsService : request playback +BmsService -> Plusplayer : Open(url) +Plusplayer -> TrackSource : TrackSource::CreateCompositor() +Plusplayer -> TrackSourceCompositor : RegisterEventListener(this) +BmsService -> Plusplayer : RegisterEventListener() + +BmsService -> Plusplayer : PrepareAsync() +Plusplayer -> TrackSourceCompositor : AddSource(url) +activate TrackSourceCompositor +TrackSourceCompositor -> Typefinder : probe() +group createpipeline +Typefinder -> gstreamer : pipeline_new() +Typefinder -> gstreamer : factory_make(src) +Typefinder -> gstreamer : factory_make(typefinder) +gstreamer --> Typefinder : Error() +end +Typefinder --> TrackSourceCompositor : fail +TrackSourceCompositor --> Plusplayer : fail +deactivate TrackSourceCompositor +Plusplayer --> BmsService : Listener::PrepareDone(fail) + +@enduml \ No newline at end of file diff --git a/docs/sequence_activity_diagram/tracksource/tracksource_exception_flow_3.plantuml b/docs/sequence_activity_diagram/tracksource/tracksource_exception_flow_3.plantuml new file mode 100755 index 0000000..0a5083c --- /dev/null +++ b/docs/sequence_activity_diagram/tracksource/tracksource_exception_flow_3.plantuml @@ -0,0 +1,42 @@ +@startuml + +title tracksource_exception_flow_3 + +actor App +boundary BmsService + +App -> BmsService : request playback +BmsService -> Plusplayer : Open(url) +Plusplayer -> TrackSource : TrackSource::CreateCompositor() +Plusplayer -> TrackSourceCompositor : RegisterEventListener(this) +BmsService -> Plusplayer : RegisterEventListener() + +BmsService -> Plusplayer : PrepareAsync() +Plusplayer -> TrackSourceCompositor : AddSource(url) +activate TrackSourceCompositor +TrackSourceCompositor -> Typefinder : probe() +group createpipeline +Typefinder -> gstreamer : pipeline_new() +Typefinder -> gstreamer : factory_make(src) +Typefinder -> gstreamer : factory_make(typefinder) +gstreamer --> Typefinder : OnHaveType_(sourcetype) +end +Typefinder --> TrackSourceCompositor : sourcetype +TrackSourceCompositor -> HttpTrackSource : new + +HttpTrackSource --> TrackSourceCompositor : object + +Plusplayer -> TrackSourceCompositor : Prepare() +TrackSourceCompositor -> HttpTrackSource : Prepare() +group CreatePipeline +HttpTrackSource -> gstreamer : factory_make(http_demuxer) +HttpTrackSource -> gstreamer : factory_make(...) +HttpTrackSource -> gstreamer : set_state(PAUSE) +gstreamer --> HttpTrackSource : SyncMessageHandler(Error) +end +HttpTrackSource --> TrackSourceCompositor : fail +TrackSourceCompositor --> Plusplayer : fail +deactivate TrackSourceCompositor +Plusplayer --> BmsService : Listener::OnPrepareDone(fail) + +@enduml \ No newline at end of file diff --git a/docs/sequence_activity_diagram/tvplus_update_activity_diagram/risk_management_lib_api_validation.plantuml b/docs/sequence_activity_diagram/tvplus_update_activity_diagram/risk_management_lib_api_validation.plantuml new file mode 100755 index 0000000..19cd0f5 --- /dev/null +++ b/docs/sequence_activity_diagram/tvplus_update_activity_diagram/risk_management_lib_api_validation.plantuml @@ -0,0 +1,26 @@ +@startuml + +database VersionInfo +participant BmsService +participant APIWrapper +database Plusplayer.so + +ref over VersionInfo : VersionInfo contains libVersion and apiVersion +ref over BmsService , VersionInfo : Compiled in SDK +ref over BmsService , APIWrapper : downloaded and installed +BmsService -> APIWrapper : Init() +APIWrapper -> Plusplayer.so : GetVersion() +Plusplayer.so --> APIWrapper : VersionInfo +ref over APIWrapper : update current-apiVersion +... +BmsService -> APIWrapper : NewFunction() +APIWrapper -> APIWrapper : check apiVersion +alt current-apiVersion supports NewFuntion +ref over Plusplayer.so : do something +else current-apiVersion not support NewFuntion +APIWrapper --> BmsService : return false +end +' 수정사항의 일부만 이전 모델에 반영 하는 경우.... + + +@enduml \ No newline at end of file diff --git a/docs/sequence_activity_diagram/tvplus_update_activity_diagram/update_procedure.plantuml b/docs/sequence_activity_diagram/tvplus_update_activity_diagram/update_procedure.plantuml new file mode 100755 index 0000000..e89ecf3 --- /dev/null +++ b/docs/sequence_activity_diagram/tvplus_update_activity_diagram/update_procedure.plantuml @@ -0,0 +1,27 @@ +@startuml + +title TVPlus Player update process + +(*) --> "Developing/Fixing Issues" +--> "Make TC" +--> "Devoloper Functional Verification" +if "is there no issues ?" then +--> [fail] "Developing/Fixing Issues" +else +--> [pass] "API Review w/ API reviewer board" +--> "Binary Compatibility Test" +if "is there no issues ?" then +--> [fail] "Developing/Fixing Issues" +else +--> [pass] "Library distribution to Bms Team" +--> "bms-daemon tpk packaing w/ player libraries" +--> Installation & Verification +if "is there no issues ?" then +--> [fail] "Developing/Fixing Issues" +else +--> [pass] "Minor/Patch Version Update" +--> "Request Release to Release Team" +--> "App server registration" +--> [Ending Process] (*) + +@enduml \ No newline at end of file diff --git a/docs/sequence_activity_diagram/tvplusplusplayerhelper/tvplusplusplayerhelper_sequence_diagram.plantuml b/docs/sequence_activity_diagram/tvplusplusplayerhelper/tvplusplusplayerhelper_sequence_diagram.plantuml new file mode 100755 index 0000000..e1d7ed8 --- /dev/null +++ b/docs/sequence_activity_diagram/tvplusplusplayerhelper/tvplusplusplayerhelper_sequence_diagram.plantuml @@ -0,0 +1,18 @@ +@startuml + +BmsAVPlayer -> TvplusPlusplayerHelper : m_CreatePlayer() +TvplusPlusplayerHelper -> Plusplayer : PlusPlayer::Create() +Plusplayer --> TvplusPlusplayerHelper : object* +BmsAVPlayer -> TvplusPlusplayerHelper : m_OpenStream() +TvplusPlusplayerHelper -> Plusplayer : Open(url) +TvplusPlusplayerHelper -> Plusplayer : SetBufferConfig() +TvplusPlusplayerHelper -> Plusplayer : SetDisplayMode() +TvplusPlusplayerHelper -> Plusplayer : SetDisplayRoi() +TvplusPlusplayerHelper -> Plusplayer : SetDisplay(Evas_Object*) +TvplusPlusplayerHelper -> Plusplayer : SetStreamingProperty() +BmsAVPlayer -> TvplusPlusplayerHelper : m_Play() +TvplusPlusplayerHelper -> Plusplayer : PrepareAsync() +Plusplayer -> TvplusPlusplayerHelper : OnPrepareDone(ret) +TvplusPlusplayerHelper -> Plusplayer : Start() + +@enduml \ No newline at end of file diff --git a/docs/state_diagram/plusplayer_internal_state_diagram.png b/docs/state_diagram/plusplayer_internal_state_diagram.png new file mode 100755 index 0000000..573061b Binary files /dev/null and b/docs/state_diagram/plusplayer_internal_state_diagram.png differ diff --git a/docs/state_diagram/state_diagram.plantuml b/docs/state_diagram/state_diagram.plantuml new file mode 100755 index 0000000..4c69a8a --- /dev/null +++ b/docs/state_diagram/state_diagram.plantuml @@ -0,0 +1,74 @@ +@startuml +'skinparam backgroundColor LightYellow + +title plusplayer_internal_state_diagram + +state None { +} +state Idle { +} +state IdleInactive { +} +state TypeFinderReady { +} +state TrackSourceReady { +} +state Ready { +} +state ResourceConflicted { +} + + +[*] --> None +None --> Idle : open +None --> None : close + +Idle --> None : close +Idle --> TypeFinderReady : probe +Idle --> TypeFinderReady : pause_or_start_defered +Idle --> IdleInactive : stop + +TypeFinderReady --> TrackSourceReady : prepare_source +TypeFinderReady --> TrackSourceReady : pause_or_start_defered +TypeFinderReady --> IdleInactive : stop + +TrackSourceReady --> Ready : prepare_renderer +TrackSourceReady --> Ready : pause_or_start_defered +TrackSourceReady --> IdleInactive : stop + +Ready --> Playing : start +Ready --> Paused : pause +Ready --> ResourceConflicted : resource_conflict +Ready --> IdleInactive : stop +Ready --> SourceStopped : stop_source + +Playing --> Paused : pause +Playing --> Playing : resume +Playing --> Paused : internal_pause +Playing --> ResourceConflicted : resource_conflict +Playing --> IdleInactive : stop +Playing --> SourceStopped : stop_source + +Paused --> Playing : resume +Paused --> Paused: pause +Paused --> Playing : internal_resume +Paused --> ResourceConflicted : resource_conflict +Paused --> IdleInactive : stop +Paused --> SourceStopped : stop_source + +ResourceConflicted --> Ready : restore +ResourceConflicted --> IdleInactive : stop + +SourceChanged --> Playing : start +SourceChanged --> SourceStopped : stop_source +SourceChanged --> IdleInactive : stop +SourceInitialized --> SourceChanged : prepare_source +SourceInitialized --> SourceStopped : stop_source +SourceInitialized --> IdleInactive : stop + +SourceStopped --> SourceInitialized : initialize_source +SourceStopped --> IdleInactive : stop + +IdleInactive --> None : close + +@enduml \ No newline at end of file diff --git a/docs/state_diagram/state_diagram_v2_substatemachine.plantuml b/docs/state_diagram/state_diagram_v2_substatemachine.plantuml new file mode 100755 index 0000000..694fd69 --- /dev/null +++ b/docs/state_diagram/state_diagram_v2_substatemachine.plantuml @@ -0,0 +1,105 @@ +@startuml + +'skinparam backgroundColor LightYellow + +title plusplayer_internal_state_diagram + +state Statemanager { + +note "Active state" as N1 + +state Idle { + [*] --> IdleZero + state TypeFinderReady { + } + state TrackSourceReady { + } + Idle: defer InternalPause + Idle: defer InternalResume + + IdleZero --> PseudoExit3 : close + IdleZero --> TypeFinderReady : probe + TypeFinderReady --> TrackSourceReady : prepare_source + TrackSourceReady --> PseudoExit : prepare_renderer + + IdleZero : defer SelectTrack() + IdleZero : defer Pause() + IdleZero : defer Start() + TypeFinderReady : defer SelectTrack() + TypeFinderReady : defer Pause() + TypeFinderReady : defer Start() + TrackSourceReady : defer Pause() + TrackSourceReady : defer Start() + TrackSourceReady --> TrackSourceReady : SelectTrack() +} + +state ChangingSource { + state SourceStopped { + } + state SourceInitialized { + } + ChangingSource: defer InternalPause + ChangingSource: defer InternalResume + + [*] --> SourceStopped + + SourceStopped --> SourceInitialized : initialize_source + SourceStopped --> SourceStopped : stop_source + + SourceInitialized --> SourceStopped : stop_source + SourceInitialized --> PseudoExit1 : prepare_source +} + +[*] --> None +None --> Idle : open +None --> None : close +PseudoExit3 --> None : close +PseudoExit --> Ready : prepare_renderer + +Ready --> Playing : start +Ready --> Paused : pause +Ready --> Ready : internal_pause +Ready --> Playing : internal_resume +Paused --> Playing : resume +Paused --> Playing : internal_resume +Paused --> Paused : internal_pause +Paused --> Playing : start +Playing --> Paused : pause +Playing --> Paused : internal_pause + +Paused --> Paused: pause +Playing --> Playing : resume + +ResourceConflicted --> Ready : restore +Ready --> ResourceConflicted : resource_conflict +Playing --> ResourceConflicted : resource_conflict +Paused --> ResourceConflicted : resource_conflict + +Ready --> ChangingSource : stop_source +Paused --> ChangingSource : stop_source +Playing --> ChangingSource : stop_source + +PseudoExit1 --> Ready : prepare_source + +Playing --> Playing : seek +Paused --> Paused : seek + +Ready --> Ready : select_track() +Playing --> Playing : select_track() +Paused --> Paused : select_track() + +-- + +note "Inactive state" as N2 +[*] --> Active +Active --> Inactive : stop +Inactive --> Inactive : stop +note right of Inactive +Inactive is interrupt state only allowing {stop , close} events. +others will be ignored. +end note +Inactive --> Close : close + +} + +@enduml \ No newline at end of file diff --git a/esplusplayer.pc.in b/esplusplayer.pc.in new file mode 100755 index 0000000..be8b623 --- /dev/null +++ b/esplusplayer.pc.in @@ -0,0 +1,10 @@ + +prefix = @PREFIX@ +exec_prefix = /usr +libdir = @LIB_INSTALL_DIR@ + +Name: @PC_NAME@ +Description: @PACKAGE_DESCRYPTION@ +Version: @VERSION@ +Libs: -L${libdir} @PC_LDFLAGS@ +Cflags : @PC_CFLAGS@ diff --git a/include/esplusplayer_capi/buffer.h b/include/esplusplayer_capi/buffer.h new file mode 100755 index 0000000..f499b25 --- /dev/null +++ b/include/esplusplayer_capi/buffer.h @@ -0,0 +1,103 @@ +/** + * @file + * @brief The buffer for playback. + * @interfacetype Platform + * @privlevel None-privilege + * @privilege None + * @product TV, AV, B2B + * @version 2.0 + * @SDK_Support N + * @remark This is a group of C style buffer related enum. + * @see N/A + * + * Copyright (c) 2020 Samsung Electronics Co., Ltd All Rights Reserved + * PROPRIETARY/CONFIDENTIAL + * This software is the confidential and proprietary + * information of SAMSUNG ELECTRONICS ("Confidential Information"). You shall + * not disclose such Confidential Information and shall use it only in + * accordance with the terms of the license agreement you entered into with + * SAMSUNG ELECTRONICS. SAMSUNG make no representations or warranties about the + * suitability of the software, either express or implied, including but not + * limited to the implied warranties of merchantability, fitness for a + * particular purpose, or non-infringement. SAMSUNG shall not be liable for any + * damages suffered by licensee as a result of using, modifying or distributing + * this software or its derivatives. + */ + +#ifndef __PLUSPLAYER_ESPLUSPLAYER_CAPI_BUFFER_H__ +#define __PLUSPLAYER_ESPLUSPLAYER_CAPI_BUFFER_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Enumerations for the buffer status + */ +enum esplusplayer_buffer_status { + ESPLUSPLAYER_BUFFER_STATUS_UNDERRUN, + ESPLUSPLAYER_BUFFER_STATUS_OVERRUN +}; + +/** + * @brief Enumerations for video decoded buffer type + * ESPLUSPLAYER_DECODED_VIDEO_FRAME_BUFFER_TYPE_SCALE: + * decoded video frame will be croped and scaled, then sent to + * user by media_packet_video_decoded_cb, only support hw decoder now +*/ +enum esplusplayer_decoded_video_frame_buffer_type { + ESPLUSPLAYER_DECODED_VIDEO_FRAME_BUFFER_TYPE_NONE, + ESPLUSPLAYER_DECODED_VIDEO_FRAME_BUFFER_TYPE_COPY, + ESPLUSPLAYER_DECODED_VIDEO_FRAME_BUFFER_TYPE_REFERENCE, + ESPLUSPLAYER_DECODED_VIDEO_FRAME_BUFFER_TYPE_SCALE +}; + +/** + * @brief Enumerations for buffer size option + * MAX_TIME_SIZE: The maximum amount of data to esplusplayer internally(in ms) + * MIN_TIME_THRESHOLD : Emit under-run when queued bytes drops below this + * percent of max-time-size(size%) + * MAX_BYTE_SIZE: The maximum number of bytes to esplusplayer internally + * MIN_BYTE_THRESHOLD : Emit under-run when queued bytes drops below this + * percent of max-byte-size(size%) + */ +enum esplusplayer_buffer_option { + ESPLUSPLAYER_BUFFER_AUDIO_MAX_TIME_SIZE, + ESPLUSPLAYER_BUFFER_VIDEO_MAX_TIME_SIZE, + ESPLUSPLAYER_BUFFER_AUDIO_MIN_TIME_THRESHOLD, + ESPLUSPLAYER_BUFFER_VIDEO_MIN_TIME_THRESHOLD, + ESPLUSPLAYER_BUFFER_AUDIO_MAX_BYTE_SIZE, + ESPLUSPLAYER_BUFFER_VIDEO_MAX_BYTE_SIZE, + ESPLUSPLAYER_BUFFER_AUDIO_MIN_BYTE_THRESHOLD, + ESPLUSPLAYER_BUFFER_VIDEO_MIN_BYTE_THRESHOLD +}; + +/** + * @brief video decoded buffer struct + */ +typedef struct { + /** + * @description buffer pts, in millisecond + */ + uint64_t pts; + /** + * @description buffer duration, in millisecond + */ + uint64_t duration; + /** + * @description surface data + */ + void* surface_data; + /** + * @description the scaler index,0 1 ... + */ + void* private_data; +} esplusplayer_decoded_video_packet; + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // __PLUSPLAYER_ESPLUSPLAYER_CAPI_BUFFER_H__ diff --git a/include/esplusplayer_capi/display.h b/include/esplusplayer_capi/display.h new file mode 100755 index 0000000..f3adb95 --- /dev/null +++ b/include/esplusplayer_capi/display.h @@ -0,0 +1,73 @@ +/** + * @file + * @brief Display related enums + * @interfacetype Platform + * @privlevel None-privilege + * @privilege None + * @product TV, AV, B2B + * @version 2.0 + * @SDK_Support N + * @remark This is a group of C style display releted data structures + * and enums. + * @see The display related enum values and data structures will be + * converted by this managed C version types to avoid binary + * compatibility. + * + * Copyright (c) 2020 Samsung Electronics Co., Ltd All Rights Reserved + * PROPRIETARY/CONFIDENTIAL + * This software is the confidential and proprietary + * information of SAMSUNG ELECTRONICS ("Confidential Information"). You shall + * not disclose such Confidential Information and shall use it only in + * accordance with the terms of the license agreement you entered into with + * SAMSUNG ELECTRONICS. SAMSUNG make no representations or warranties about the + * suitability of the software, either express or implied, including but not + * limited to the implied warranties of merchantability, fitness for a + * particular purpose, or non-infringement. SAMSUNG shall not be liable for any + * damages suffered by licensee as a result of using, modifying or distributing + * this software or its derivatives. + */ + +#ifndef __PLUSPLAYER_ESPLUSPLAYER_CAPI_DISPLAY_H__ +#define __PLUSPLAYER_ESPLUSPLAYER_CAPI_DISPLAY_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Enumerations for the display mode + */ +enum esplusplayer_display_mode { + ESPLUSPLAYER_DISPLAY_MODE_LETTER_BOX, + ESPLUSPLAYER_DISPLAY_MODE_ORIGIN_SIZE, + ESPLUSPLAYER_DISPLAY_MODE_FULL_SCREEN, + ESPLUSPLAYER_DISPLAY_MODE_CROPPED_FULL, + ESPLUSPLAYER_DISPLAY_MODE_ORIGIN_OR_LETTER, + ESPLUSPLAYER_DISPLAY_MODE_DST_ROI +}; + +/** + * @brief Enumerations for the display type + */ +enum esplusplayer_display_type { + ESPLUSPLAYER_DISPLAY_TYPE_NONE, + ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY, + ESPLUSPLAYER_DISPLAY_TYPE_EVAS, + ESPLUSPLAYER_DISPLAY_TYPE_MIXER +}; + +/** + * @brief Enumerations for the display rotation type + */ +enum esplusplayer_display_rotation_type { + ESPLUSPLAYER_DISPLAY_ROTATION_TYPE_NONE, + ESPLUSPLAYER_DISPLAY_ROTATION_TYPE_90, + ESPLUSPLAYER_DISPLAY_ROTATION_TYPE_180, + ESPLUSPLAYER_DISPLAY_ROTATION_TYPE_270 +}; + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // __PLUSPLAYER_ESPLUSPLAYER_CAPI_DISPLAY_H__ diff --git a/include/esplusplayer_capi/drm.h b/include/esplusplayer_capi/drm.h new file mode 100755 index 0000000..dd2babc --- /dev/null +++ b/include/esplusplayer_capi/drm.h @@ -0,0 +1,183 @@ +/** + * @file + * @brief Drm related enums + * @interfacetype Platform + * @privlevel None-privilege + * @privilege None + * @product TV, AV, B2B + * @version 2.0 + * @SDK_Support N + * @remark This is a group of C style drm releted data structures and + * enums. + * @see Drm releated event listeners, enum classes, etc.. are + * converted to this. + * + * Copyright (c) 2020 Samsung Electronics Co., Ltd All Rights Reserved + * PROPRIETARY/CONFIDENTIAL + * This software is the confidential and proprietary + * information of SAMSUNG ELECTRONICS ("Confidential Information"). You shall + * not disclose such Confidential Information and shall use it only in + * accordance with the terms of the license agreement you entered into with + * SAMSUNG ELECTRONICS. SAMSUNG make no representations or warranties about the + * suitability of the software, either express or implied, including but not + * limited to the implied warranties of merchantability, fitness for a + * particular purpose, or non-infringement. SAMSUNG shall not be liable for any + * damages suffered by licensee as a result of using, modifying or distributing + * this software or its derivatives. + */ + +#ifndef __PLUSPLAYER_ESPLUSPLAYER_CAPI_DRM_H__ +#define __PLUSPLAYER_ESPLUSPLAYER_CAPI_DRM_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/** + * @brief Enumerations for the drm type + */ +enum esplusplayer_drm_type { + ESPLUSPLAYER_DRM_TYPE_NONE, + ESPLUSPLAYER_DRM_TYPE_PLAYREADY, + ESPLUSPLAYER_DRM_TYPE_MARLIN, + ESPLUSPLAYER_DRM_TYPE_VERIMATRIX, + ESPLUSPLAYER_DRM_TYPE_WV_MODULAR +}; + +/** + * @brief Enumerations for the algorithm encrypted + */ +enum esplusplayer_drmb_es_cipher_algorithm { + ESPLUSPLAYER_DRMB_ES_CIPHER_ALGORITHM_UNKNOWN = -1, + ESPLUSPLAYER_DRMB_ES_CIPHER_ALGORITHM_RC4 = 0, + ESPLUSPLAYER_DRMB_ES_CIPHER_ALGORITHM_AES128_CTR = 1, + ESPLUSPLAYER_DRMB_ES_CIPHER_ALGORITHM_AES128_CBC = 2 +}; + +/** + * @brief Enumerations for the algorithm encrypted + */ +enum esplusplayer_drmb_es_media_format { + ESPLUSPLAYER_DRMB_ES_MEDIA_FORMAT_NONE = 0, + ESPLUSPLAYER_DRMB_ES_MEDIA_FORMAT_FMP4 = 1, + ESPLUSPLAYER_DRMB_ES_MEDIA_FORMAT_TS = 2, + ESPLUSPLAYER_DRMB_ES_MEDIA_FORMAT_ASF = 3, + ESPLUSPLAYER_DRMB_ES_MEDIA_FORMAT_FMP4_AUDIO = 4, + ESPLUSPLAYER_DRMB_ES_MEDIA_FORMAT_FMP4_VIDEO = 5, + ESPLUSPLAYER_DRMB_ES_MEDIA_FORMAT_CLEAN_AUDIO = 6, + ESPLUSPLAYER_DRMB_ES_MEDIA_FORMAT_PES = 7 +}; + +/** + * @brief Enumerations for the phase for cipher + */ +enum esplusplayer_drmb_es_cipher_phase { + ESPLUSPLAYER_DRMB_ES_CIPHER_PHASE_NONE = 0, + ESPLUSPLAYER_DRMB_ES_CIPHER_PHASE_INIT = 1, + ESPLUSPLAYER_DRMB_ES_CIPHER_PHASE_UPDATE = 2, + ESPLUSPLAYER_DRMB_ES_CIPHER_PHASE_FINAL = 3 +}; + +/** + * @brief Subsample information structure for encrypted es + */ +typedef struct { + /** + * @description The bytes of clear data. + */ + uint32_t bytes_of_clear_data; + /** + * @description The bytes of encrypted data. + */ + uint32_t bytes_of_encrypted_data; +} esplusplayer_drmb_es_subsample_info; + +/** + * @brief Fragmented MP4 data structure for encrypted es + */ +typedef struct { + /** + * @description The count of subsample informations + */ + uint32_t subsample_count; + /** + * @description The subsample informations + */ + esplusplayer_drmb_es_subsample_info* subsample_infos; +} esplusplayer_drmb_es_fmp4_data; + +/** + * @brief The information to decrypt es packet + */ +typedef struct { + /** + * @description The handle to decrypt es packet. + */ + int32_t handle; + /** + * @description The algorithm encrypted. + */ + esplusplayer_drmb_es_cipher_algorithm algorithm; + /** + * @description The es media format. + */ + esplusplayer_drmb_es_media_format format; + /** + * @description The phase to decrypt. + */ + esplusplayer_drmb_es_cipher_phase phase; + /** + * @description The KID. + */ + unsigned char* kid; + /** + * @description The length of KID. + */ + uint32_t kid_length; + /** + * @description The vector for initialization. + */ + unsigned char* iv; + /** + * @description The length of IV. + */ + uint32_t iv_length; + /** + * @description The sub data. + * @see esplusplayer_drmb_es_fmp4_data + */ + void* sub_data; + /** + * @description The offset of sample. + * It can be NULL. + * If used, it have to be -1 terminated. + * Max offset is 15. + */ + int* split_offsets; + /** + * @description It should be 0 when it must be protected with trustzone. + */ + bool use_out_buffer; + /** + * @description If use 'cbcs' pattern scheme, It should be 1. otherwise 0. + */ + bool use_pattern; + /** + * @description In case that use_patter is 1, + * count of the encrypted blocks in the protection pattern. + */ + uint32_t crypt_byte_block; + /** + * @description In case that use_patter is 1, + * count of the unencrypted blocks in the protection pattern. + */ + uint32_t skip_byte_block; +} esplusplayer_drm_info; + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // __PLUSPLAYER_ESPLUSPLAYER_CAPI_DRM_H__ diff --git a/include/esplusplayer_capi/error.h b/include/esplusplayer_capi/error.h new file mode 100755 index 0000000..17f24bd --- /dev/null +++ b/include/esplusplayer_capi/error.h @@ -0,0 +1,69 @@ +/** + * @file + * @brief Error related enums + * @interfacetype Platform + * @privlevel None-privilege + * @privilege None + * @product TV, AV, B2B + * @version 2.0 + * @SDK_Support N + * @remark This is a group of C style error releted enum. + * @see All error enum values will be converted to this managed error + * types. + * + * Copyright (c) 2020 Samsung Electronics Co., Ltd All Rights Reserved + * PROPRIETARY/CONFIDENTIAL + * This software is the confidential and proprietary + * information of SAMSUNG ELECTRONICS ("Confidential Information"). You shall + * not disclose such Confidential Information and shall use it only in + * accordance with the terms of the license agreement you entered into with + * SAMSUNG ELECTRONICS. SAMSUNG make no representations or warranties about the + * suitability of the software, either express or implied, including but not + * limited to the implied warranties of merchantability, fitness for a + * particular purpose, or non-infringement. SAMSUNG shall not be liable for any + * damages suffered by licensee as a result of using, modifying or distributing + * this software or its derivatives. + */ + +#ifndef __PLUSPLAYER_ESPLUSPLAYER_CAPI_ERROR_H__ +#define __PLUSPLAYER_ESPLUSPLAYER_CAPI_ERROR_H__ + +#include "tizen.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define ESPLUSPLAYER_ERROR_CLASS TIZEN_ERROR_PLAYER | 0x20 + +/* This is for custom defined esplusplayer error. */ +#define ESPLUSPLAYER_CUSTOM_ERROR_CLASS TIZEN_ERROR_PLAYER | 0x1000 + +/** + * @brief Enumerations for the error type + */ +enum esplusplayer_error_type { + ESPLUSPLAYER_ERROR_TYPE_NONE = TIZEN_ERROR_NONE, /**< Successful */ + ESPLUSPLAYER_ERROR_TYPE_OUT_OF_MEMORY = TIZEN_ERROR_OUT_OF_MEMORY, /**< Out of memory */ + ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER = TIZEN_ERROR_INVALID_PARAMETER, /**< Invalid parameter */ + ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION = TIZEN_ERROR_INVALID_OPERATION, /**< Invalid operation */ + ESPLUSPLAYER_ERROR_TYPE_INVALID_STATE = ESPLUSPLAYER_ERROR_CLASS | 0x02, /**< Invalid state */ + ESPLUSPLAYER_ERROR_TYPE_NOT_SUPPORTED_AUDIO_CODEC = ESPLUSPLAYER_ERROR_CLASS | 0x0e, /**< Not supported audio codec but video can be played (Since 4.0)*/ + ESPLUSPLAYER_ERROR_TYPE_NOT_SUPPORTED_VIDEO_CODEC = ESPLUSPLAYER_ERROR_CLASS | 0x0f, /**< Not supported video codec but audio can be played (Since 4.0)*/ + ESPLUSPLAYER_ERROR_TYPE_NOT_SUPPORTED_FILE = ESPLUSPLAYER_ERROR_CLASS | 0x03, /**< File format not supported */ + ESPLUSPLAYER_ERROR_TYPE_CONNECTION_FAILED = ESPLUSPLAYER_ERROR_CLASS | 0x06, /**< Streaming connection failed */ + ESPLUSPLAYER_ERROR_TYPE_DRM_EXPIRED = ESPLUSPLAYER_ERROR_CLASS | 0x08, /**< Expired license */ + ESPLUSPLAYER_ERROR_TYPE_DRM_NO_LICENSE = ESPLUSPLAYER_ERROR_CLASS | 0x09, /**< No license */ + ESPLUSPLAYER_ERROR_TYPE_DRM_FUTURE_USE = ESPLUSPLAYER_ERROR_CLASS | 0x0a, /**< License for future use */ + ESPLUSPLAYER_ERROR_TYPE_NOT_PERMITTED = ESPLUSPLAYER_ERROR_CLASS | 0x0b, /**< Format not permitted */ + + ESPLUSPLAYER_ERROR_TYPE_DRM_DECRYPTION_FAILED = ESPLUSPLAYER_CUSTOM_ERROR_CLASS | 0x05, /**< drm decryption failed */ + ESPLUSPLAYER_ERROR_TYPE_NOT_SUPPORTED_FORMAT = ESPLUSPLAYER_CUSTOM_ERROR_CLASS | 0x08,/**< format not supported */ + ESPLUSPLAYER_ERROR_TYPE_UNKNOWN +}; + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // __PLUSPLAYER_ESPLUSPLAYER_CAPI_ERROR_H__ diff --git a/include/esplusplayer_capi/espacket.h b/include/esplusplayer_capi/espacket.h new file mode 100755 index 0000000..14edd8f --- /dev/null +++ b/include/esplusplayer_capi/espacket.h @@ -0,0 +1,114 @@ +/** + * @file + * @brief The packet for elementary stream + * @interfacetype Platform + * @privlevel None-privilege + * @privilege None + * @product TV, AV, B2B + * @version 2.0 + * @SDK_Support N + * @see plusplayer::EsPlusPlayer class + * + * Copyright (c) 2020 Samsung Electronics Co., Ltd All Rights Reserved + * PROPRIETARY/CONFIDENTIAL + * This software is the confidential and proprietary + * information of SAMSUNG ELECTRONICS ("Confidential Information"). You shall + * not disclose such Confidential Information and shall use it only in + * accordance with the terms of the license agreement you entered into with + * SAMSUNG ELECTRONICS. SAMSUNG make no representations or warranties about the + * suitability of the software, either express or implied, including but not + * limited to the implied warranties of merchantability, fitness for a + * particular purpose, or non-infringement. SAMSUNG shall not be liable for any + * damages suffered by licensee as a result of using, modifying or distributing + * this software or its derivatives. + */ + +#ifndef __PLUSPLAYER_ESPLUSPLAYER_CAPI_ESPACKET_H__ +#define __PLUSPLAYER_ESPLUSPLAYER_CAPI_ESPACKET_H__ + +#include + +#include "esplusplayer_capi/matroska_color.h" +#include "esplusplayer_capi/stream.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Es packet structure + */ +typedef struct { + /** + * @description The stream type. + */ + esplusplayer_stream_type type; + /** + * @description The buffer data pointer + */ + char* buffer; + /** + * @description The buffer size. + */ + uint32_t buffer_size; + /** + * @description The pts value in milisecond. + */ + uint64_t pts; + /** + * @description The duration value in milisecond. + */ + uint64_t duration; + /** + * @description The matroska color information. this value is only for video + * packet. If you set this value on a packet of other type, you can see an + * error when you submit the packet. + */ + esplusplayer_matroska_color* matroska_color_info; + /** + * @description The hdr10+ metadata size. + */ + uint32_t hdr10p_metadata_size; + /** + * @description The hdr10+ metadata. + */ + char* hdr10p_metadata; +} esplusplayer_es_packet; + +/** + * @brief Trust zone es packet structure + */ +typedef struct { + /** + * @description The Stream type. + */ + esplusplayer_stream_type type; + /** + * @description The tz handle. + */ + uint32_t handle; + /** + * @description The tz handle size. + */ + uint32_t handle_size; + /** + * @description The pts value in milisecond. + */ + uint64_t pts; + /** + * @description The duration value in milisecond. + */ + uint64_t duration; + /** + * @description The matroska color information. this value is only for video + * packet. If you set this value on a packet of other type, you can see an + * error when you submit the packet. + */ + esplusplayer_matroska_color* matroska_color_info; +} esplusplayer_es_tz_packet; + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // __PLUSPLAYER_ESPLUSPLAYER_CAPI_ESPACKET_H__ diff --git a/include/esplusplayer_capi/esplusplayer_capi.h b/include/esplusplayer_capi/esplusplayer_capi.h new file mode 100755 index 0000000..c7daeed --- /dev/null +++ b/include/esplusplayer_capi/esplusplayer_capi.h @@ -0,0 +1,2857 @@ +/** + * @file esplusplayer_capi.h + * @brief EsPlusPlayer api c version + * @interfacetype Platform + * @privlevel None-privilege + * @privilege None + * @product TV, AV, B2B + * @version 2.0 + * @SDK_Support N + * @remark This is esplusplayer api header implemented as C style to + * avoid binary compatibility issues. + * + * Copyright (c) 2020 Samsung Electronics Co., Ltd All Rights Reserved + * PROPRIETARY/CONFIDENTIAL + * This software is the confidential and proprietary + * information of SAMSUNG ELECTRONICS ("Confidential Information"). You shall + * not disclose such Confidential Information and shall use it only in + * accordance with the terms of the license agreement you entered into with + * SAMSUNG ELECTRONICS. SAMSUNG make no representations or warranties about the + * suitability of the software, either express or implied, including but not + * limited to the implied warranties of merchantability, fitness for a + * particular purpose, or non-infringement. SAMSUNG shall not be liable for any + * damages suffered by licensee as a result of using, modifying or distributing + * this software or its derivatives. + */ + +#ifndef __PLUSPLAYER_ESPLUSPLAYER_CAPI_ESPLUSPLAYER_CAPI_H__ +#define __PLUSPLAYER_ESPLUSPLAYER_CAPI_ESPLUSPLAYER_CAPI_H__ + +#include "esplusplayer_capi/buffer.h" +#include "esplusplayer_capi/display.h" +#include "esplusplayer_capi/drm.h" +#include "esplusplayer_capi/error.h" +#include "esplusplayer_capi/espacket.h" +#include "esplusplayer_capi/event.h" +#include "esplusplayer_capi/latency.h" +#include "esplusplayer_capi/state.h" +#include "esplusplayer_capi/stream.h" +#include "esplusplayer_capi/submitdatatype.h" +#include "esplusplayer_capi/submitstatus.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +typedef void (*esplusplayer_error_cb)(const esplusplayer_error_type, void*); +typedef void (*esplusplayer_buffer_status_cb)(const esplusplayer_stream_type, + const esplusplayer_buffer_status, + void*); +typedef void (*esplusplayer_buffer_byte_status_cb)( + const esplusplayer_stream_type, const esplusplayer_buffer_status, uint64_t, + void*); +typedef void (*esplusplayer_buffer_time_status_cb)( + const esplusplayer_stream_type, const esplusplayer_buffer_status, uint64_t, + void*); +typedef void (*esplusplayer_resource_conflicted_cb)(void*); +typedef void (*esplusplayer_eos_cb)(void*); +typedef void (*esplusplayer_ready_to_prepare_cb)(const esplusplayer_stream_type, + void*); +typedef void (*esplusplayer_prepare_async_done_cb)(bool, void*); +typedef void (*esplusplayer_seek_done_cb)(void*); +typedef void (*esplusplayer_ready_to_seek_cb)(const esplusplayer_stream_type, + const uint64_t, void*); +typedef void (*esplusplayer_media_packet_video_decoded_cb)( + const esplusplayer_decoded_video_packet*, void*); +typedef void (*esplusplayer_closed_caption_cb)(const char* data, const int size, + void* userdata); +typedef void (*esplusplayer_flush_done_cb)(void*); +typedef void (*esplusplayer_event_cb)(const esplusplayer_event_type, + const esplusplayer_event_msg, void*); +typedef void (*esplusplayer_video_latency_status_cb)( + const esplusplayer_latency_status latency_status, void*); +typedef void (*esplusplayer_audio_latency_status_cb)( + const esplusplayer_latency_status latency_status, void*); +typedef void (*esplusplayer_video_high_latency_cb)(void*); +typedef void (*esplusplayer_audio_high_latency_cb)(void*); + +typedef void* esplusplayer_handle; + +/** + * @brief Enumerations for the Adaptive info type + */ +enum esplusplayer_adaptive_info_type { + ESPLUSPLAYER_ADAPT_INFO_TYPE_NONE, + ESPLUSPLAYER_ADAPT_INFO_TYPE_DROPPED_FRAMES, + ESPLUSPLAYER_ADAPT_INFO_TYPE_DROPPED_VIDEO_FRAMES_FOR_CATCHUP, + ESPLUSPLAYER_ADAPT_INFO_TYPE_DROPPED_AUDIO_FRAMES_FOR_CATCHUP, +}; + +/** + * @brief Enumerations for low latency mode + * @version 3.2 + */ +enum esplusplayer_low_latency_mode { + ESPLUSPLAYER_LOW_LATENCY_MODE_NONE = 0x0000, + /** + * @description to support audio fast decoding/rendering + */ + ESPLUSPLAYER_LOW_LATENCY_MODE_AUDIO = 0x0001, + /** + * @description to support video fast decoding/rendering + * Video stream should be composed only of P and I frames. + */ + ESPLUSPLAYER_LOW_LATENCY_MODE_VIDEO = 0x0010, + /** + * @description to support video fast decoding/rendering and video + * distortion concealment. + * Video stream should be composed only of P and I frames. + * For applications using the UDP protocol, packet loss can + * occur. when video distortion by video packet loss is + * detected, it is a function to conceal distortion by showing + * previous vido frame. It is supported only in h.264 codec & + * FHD or lower resolution. + */ + ESPLUSPLAYER_LOW_LATENCY_MODE_VIDEO_DISTORTION_CONCEALMENT = + ESPLUSPLAYER_LOW_LATENCY_MODE_VIDEO | 0x0020, + /** + * @description to support video fast decoding/rendering and video + * with seamless resolution change. + */ + ESPLUSPLAYER_LOW_LATENCY_MODE_SEAMLESS_RESOLUTION_CHANGE = 0x0040, + /** + * @description to disable clock sync and a/v sync when rendering. it + * includes #ESPLUSPLAYER_LOW_LATENCY_MODE_DISABLE_PREROLL. + */ + ESPLUSPLAYER_LOW_LATENCY_MODE_DISABLE_SYNC = 0x0100, + /** + * @description to disable preroll which means player doesn't wait for + * first buffer when state is changed to + * #ESPLUSPLAYER_STATE_READY from #ESPLUSPLAYER_STATE_IDLE. + * It changes the state immediately. + * It's usually used for sparse stream. (e.g. video packet + * arrives but audio packet does't yet.) + */ + ESPLUSPLAYER_LOW_LATENCY_MODE_DISABLE_PREROLL = 0x0200, + /** + * @description to set lower video quality + */ + ESPLUSPLAYER_LOW_LATENCY_MODE_DISABLE_VIDEO_QUALITY = 0x1000, + /** + * @description to set game mode + * It must be used exclusively with ESPLUSPLAYER_LOW_LATENCY_MODE_DISABLE_VIDEO_QUALITY. + * It must use this value with ESPLUSPLAYER_LOW_LATENCY_MODE_VIDEO. + * If use this value, It can expect better latency performance than ESPLUSPLAYER_LOW_LATENCY_MODE_DISABLE_VIDEO_QUALITY. + * It can use enhanced game_mode. + */ + ESPLUSPLAYER_LOW_LATENCY_MODE_ENABLE_GAME_MODE = 0x2000 +}; + +/** + * @brief Enumerations for esplusplayer audio codec type + */ +enum esplusplayer_audio_codec_type { + /** + * @description hardware codec can only be selected, default type + */ + ESPLUSPLAYER_AUDIO_CODEC_TYPE_HW, + /** + * @description sorfware codec can only be selected + */ + ESPLUSPLAYER_AUDIO_CODEC_TYPE_SW +}; + +/** + * @brief Enumerations for esplusplayer video codec type + */ +enum esplusplayer_video_codec_type { + /** + * @description hardware codec can only be selected, default type + */ + ESPLUSPLAYER_VIDEO_CODEC_TYPE_HW, + /** + * @description software codec can only be selected + */ + ESPLUSPLAYER_VIDEO_CODEC_TYPE_SW, + /** + * @description hardware codec using n-decoding mode can only be selected. + It must set display type to mixer type by display setting + api. + esplusplayer_set_display() + */ + ESPLUSPLAYER_VIDEO_CODEC_TYPE_HW_N_DECODING + +}; +/** + * @brief Enumerations for esplusplayer audio easing type + * @version 3.0 + */ +enum esplusplayer_audio_easing_type { + /** + * @description audio easing function type is linear + */ + ESPLUSPLAYER_AUDIO_EASING_LINEAR, + /** + * @description audio easing function type is incubic + */ + ESPLUSPLAYER_AUDIO_EASING_INCUBIC, + /** + * @description audio easing function type is outcubic + */ + ESPLUSPLAYER_AUDIO_EASING_OUTCUBIC, + /** + * @description audio easing function type is none + */ + ESPLUSPLAYER_AUDIO_EASING_NONE +}; + +/** + * @brief Enumerations for esplusplayer resource type + * @version 3.0 + */ +enum esplusplayer_rsc_type { + /** + * @description video renderer type + */ + ESPLUSPLAYER_RSC_TYPE_VIDEO_RENDERER +}; + +/** + * @brief Enumerations for advanced video quality type + * @version 3.1 + */ +enum esplusplayer_advanced_picture_quality_type { + /** + * @description advanced picture quality for video call + */ + ESPLUSPLAYER_ADVANCED_PICTURE_QUALITY_VIDEO_CALL, + /** + * @description advanced picture quality for usb camera + */ + ESPLUSPLAYER_ADVANCED_PICTURE_QUALITY_USB_CAMERA +}; + +/** + * @brief ESPlusplayer easing target volume, duration, type information + * @version 3.0 + */ +typedef struct { + /** + * @description audio easing target volume (0 ~ 100) + */ + unsigned int volume; + /** + * @description audio easing duration, in millisecond + */ + unsigned int duration; + /** + * @description audio easing function type + */ + esplusplayer_audio_easing_type type; +} esplusplayer_target_audio_easing_info; + +/** + * @brief ESPlusplayer app id, version, type information + */ +typedef struct { + /** + * @description App id for controlling resource. + */ + char* id; + /** + * @description When there is playback market issue, KPI logger will + * send the version. + */ + char* version; + /** + * @description RunningApps.InformationTicker will use this type to show + * stream information. ex) "MSE", "HTML5", etc.. + */ + char* type; +} esplusplayer_app_info; + +typedef struct { + /** + * @description the minimum frame number in case of mid latency + */ + int mid_latency_threshold; + /** + * @description the minimum frame number in case of high latency + */ + int high_latency_threshold; +} esplusplayer_latency_threshold; + +/** + * @brief rational number numerator/denominator + */ +typedef struct { + /** + * @description the numerator value + */ + int num; + /** + * @description the denominator value + */ + int den; +} esplusplayer_rational; + +/** + * @brief resource allocate policy + */ +enum esplusplayer_rsc_alloc_policy { + /** + * @description exclusive policy, RM will return the requested resources, + * default policy + */ + ESPLUSPLAYER_RSC_ALLOC_EXCLUSIVE = 0, + /** + * @description conditional policy, when trying to allocate resources and + * available resources are not left, RM will return fail. + */ + ESPLUSPLAYER_RSC_ALLOC_EXCLUSIVE_CONDITIONAL +}; + +/** + * @brief Create a esplusplayer handle. + * @param None + * @return return esplusplayer handle pointer. + * @code + * esplusplayer_handle esplayer = esplusplayer_create(); + * // ... your codes ... + * esplusplayer_destroy(esplayer); + * @endcode + * @pre None + * @post The player state will be #ESPLUSPLAYER_STATE_NONE. + * @exception None + */ +esplusplayer_handle esplusplayer_create(); + +/** + * @brief Open esplusplayer handle. + * @param [in] handle : esplusplayer handle + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * esplusplayer_handle esplayer = esplusplayer_create(); + * esplusplayer_open(esplayer); + * // ... your codes ... + * esplusplayer_close(esplayer); + * esplusplayer_destroy(esplayer); + * @endcode + * @pre The player state must be #ESPLUSPLAYER_STATE_NONE. + * @post The player state will be #ESPLUSPLAYER_STATE_IDLE. + * @exception None + * @see esplusplayer_close() + */ +int esplusplayer_open(esplusplayer_handle handle); + +/** + * @brief Release all the player resources and all setting except callback + * functions. + * @param [in] handle : esplusplayer handle. + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @pre The player state must be all of #esplusplayer_state except + * #ESPLUSPLAYER_STATE_NONE. + * @post The player state will be #ESPLUSPLAYER_STATE_NONE. + * @exception None + * @see esplusplayer_open() + */ +int esplusplayer_close(esplusplayer_handle handle); + +/** + * @brief Release player handle. + * @param [in] handle : esplusplayer handle. + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * refer to the sample code of esplusplayer_create() + * @endcode + * @pre The player state must be #ESPLUSPLAYER_STATE_NONE + * @post player handle will be removed. + * @exception None + * @see esplusplayer_create() + */ +int esplusplayer_destroy(esplusplayer_handle handle); + +/** + * @brief Flush the specific buffered stream data and release TV resource + * to change stream. + * @remark To activate, the stream must be set again. + * @param [in] handle : esplusplayer handle. + * @param [in] type : stream type which user want to deactivate. + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + refer to the sample code of esplusplayer_activate() + * @endcode + * @pre The player state must be at least #ESPLUSPLAYER_STATE_READY + * @post The player state is same as before calling + * esplusplayer_deactivate(). The deactivated stream will stop + * rendering and release the decorer, renderer resources. + * @exception None + * @see esplusplayer_activate + */ +int esplusplayer_deactivate(esplusplayer_handle handle, + esplusplayer_stream_type type); + +/** + * @brief Reprepare for the specific stream playback. + * @remark There must be active stream to prepare playback. + * @param [in] handle : esplusplayer handle. + * @param [in] type : stream type which user want to activate. + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * prepare esplayer done + * // ... your codes ... + * esplusplayer_deactivate(esplayer, ESPLUSPLAYER_STREAM_TYPE_VIDEO); + * esplusplayer_set_video_stream_info* stream; + * stream->width = 640; + * stream->height = 352; + * stream->mime_type = ESPLUSPLAYER_VIDEO_MIME_TYPE_H264; + * stream->framerate_num = 30; + * stream->framerate_den = 1; + * esplusplayer_set_video_stream_info(esplayer, stream); + * esplusplayer_activate(esplayer, ESPLUSPLAYER_STREAM_TYPE_VIDEO); + * // ... your codes ... + * esplusplayer_close(esplayer); + * esplusplayer_destroy(esplayer); + * @endcode + * @pre The player state must be at least #ESPLUSPLAYER_STATE_READY + * @post The player state is same as before calling + * esplusplayer_activate(). Rebuild pipeline to render the stream. + * @exception None + * @see esplusplayer_prepare_async() + */ +int esplusplayer_activate(esplusplayer_handle handle, + esplusplayer_stream_type type); + +/** + * @brief Prepare the player for playback, asynchronously. + * @param [in] handle : esplusplayer handle. + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * static void OnPrepareDone(bool ret, void* userdata) { + * //Something you want to do when prepare done, but, we strongly + * //recommend DO NOT CALL PLAYER APIs in this callbck + * printf("OnPrepareDone\n"); + * } + * esplusplayer_handle esplayer = esplusplayer_create(); + * esplusplayer_set_prepare_async_done_cb(esplayer, OnPrepareDone,nullptr); + * esplusplayer_open(esplayer); + * esplusplayer_prepare_async(esplayer); + * // ... your codes ... + * esplusplayer_close(esplayer); + * esplusplayer_destroy(esplayer); + * @endcode + * @pre The player state must be #ESPLUSPLAYER_STATE_IDLE. \n + * Call at least one of esplusplayer_set_video_stream_info() or + * esplusplayer_set_audio_stream_info(). \n + * @post It invokes esplusplayer_prepare_async_done_cb() when prepare is + * finished. \n + * Prepare result can be succeeded or not at this moment. \n + * If the result is succeeded, the player state will be + * #ESPLUSPLAYER_STATE_READY and one frame will be displayed + * unless esplusplayer_set_display_visible() is set to @c false. + * @exception None + * @remark esplusplayer_prepare_async_done_cb() can be invoked only when as + * many es packets as at least one decoded frame is submitted. \n + * The player can receive es packets after + * esplusplayer_ready_to_seek_cb() is called. + * @see esplusplayer_open() \n + * esplusplayer_stop() \n + * esplusplayer_submit_packet() \n + * esplusplayer_ready_to_prepare_cb() \n + * esplusplayer_close() + */ +int esplusplayer_prepare_async(esplusplayer_handle handle); + +/** + * @brief Start playback. + * @param [in] handle : esplusplayer handle. + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * prepare esplayer done + * esplusplayer_start(esplayer); + * // ... your codes ... + * esplusplayer_stop(esplayer); + * @endcode + * @pre The player state should be #ESPLUSPLAYER_STATE_READY. + * @post The player state will be #ESPLUSPLAYER_STATE_PLAYING. + * @exception None + * @see esplusplayer_open() \n + * esplusplayer_prepare_async() \n + * esplusplayer_stop() \n + * esplusplayer_close() + */ +int esplusplayer_start(esplusplayer_handle handle); + +/** + * @brief Stop playing media content. + * @param [in] handle : esplusplayer handle. + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * prepare esplayer done + * // ... your codes ... + * esplusplayer_stop(esplayer); + * // ... your codes ... + * esplusplayer_close(esplayer); + * @endcode + * @pre The player state must be all of #esplusplayer_state except + * #ESPLUSPLAYER_STATE_NONE. + * @post The player state will be #ESPLUSPLAYER_STATE_IDLE. + * @exception None + * @remark esplusplayer_close() must be called once after player is stopped + * @see esplusplayer_open() \n + * esplusplayer_prepare_async() \n + * esplusplayer_start() \n + * esplusplayer_pause() \n + * esplusplayer_resume() \n + * esplusplayer_close() + */ +int esplusplayer_stop(esplusplayer_handle handle); + +/** + * @brief Pause playing media content. + * @param [in] handle : esplusplayer handle. + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * prepare esplayer done + * // ... your codes ... + * esplusplayer_pause(esplayer); + * // ... your codes ... + * esplusplayer_stop(esplayer); + * @endcode + * @pre The player state must be one of #ESPLUSPLAYER_STATE_READY or + * #ESPLUSPLAYER_STATE_PAUSED or #ESPLUSPLAYER_STATE_PLAYING. + * @post The player state will be #ESPLUSPLAYER_STATE_PAUSE. + * @exception None + * @see esplusplayer_start() \n + * esplusplayer_resume() \n + * esplusplayer_prepare_async() + */ +int esplusplayer_pause(esplusplayer_handle handle); + +/** + * @brief Resume playing media content. + * @param [in] handle : esplusplayer handle. + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * prepare esplayer done + * // ... your codes ... + * esplusplayer_pause(esplayer); + * // ... your codes ... + * esplusplayer_resume(esplayer); + * // ... your codes ... + * esplusplayer_stop(esplayer); + * @endcode + * @pre The player state must be one of #ESPLUSPLAYER_STATE_PAUSED or + * #ESPLUSPLAYER_STATE_PLAYING. + * @post The player state will be #ESPLUSPLAYER_STATE_PLAYING. + * @exception None + * @see esplusplayer_start() \n + * esplusplayer_pause() \n + * esplusplayer_prepare_async() + */ +int esplusplayer_resume(esplusplayer_handle handle); + +/** + * @brief Set playback rate. + * @param [in] handle : esplusplayer handle. + * @param [in] playback_rate : the playback rate from 0.0 to 2.0. + * @param [in] audio_mute : the audio is mute on/off, true: mute on, false: + * mute off. + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * prepare esplayer done + * // ... your codes ... + * esplusplayer_set_playback_rate(esplayer,2,true); + * // ... your codes ... + * esplusplayer_stop(esplayer); + * @endcode + * @pre The player state must be one of #ESPLUSPLAYER_STATE_READY or + * #ESPLUSPLAYER_STATE_PAUSED or #ESPLUSPLAYER_STATE_PLAYING. \n + * User has to push the data as fast as playback rate. + * @post None + * @exception None + * @see esplusplayer_prepare_async() + */ +int esplusplayer_set_playback_rate(esplusplayer_handle handle, + const double playback_rate, + const bool audio_mute); + +/** + * @brief Seek for playback, asynchronously. + * @param [in] handle : esplusplayer handle. + * @param [in] time_ms : seek time in milliseconds + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * prepare esplayer done + * // ... your codes ... + * const uint64_t ms_to_seek = 0; + * esplusplayer_seek(esplayer,ms_to_seek); + * // ... your codes ... + * esplusplayer_stop(esplayer); + * @endcode + * @pre The player state must be one of #ESPLUSPLAYER_STATE_READY or + * #ESPLUSPLAYER_STATE_PAUSED or #ESPLUSPLAYER_STATE_PLAYING. + * In ESPLUSPLAYER_STATE_IDLE, this api can be called exceptionally + * between esplusplayer_open() and esplusplayer_prepare_async(). + * the start time of plyabak can be set explicitly when starting + * first playback. In this case, esplusplayer_set_seek_done_cb is not + * called. + * @post None + * @exception None + * @remark esplusplayer_set_seek_done_cb() will be invoked if seek operation + * is finished. \n + * Seek result can be succeeded or not at this moment. \n + * esplusplayer_set_seek_done_cb() can be invoked only when as many + * es packets as at least one decoded frame is submitted. \n + * The player can receive es packets from seek time after + * esplusplayer_ready_to_seek_cb() is invoked. + * @see esplusplayer_ready_to_seek_cb() \n + * esplusplayer_prepare_async() + */ +int esplusplayer_seek(esplusplayer_handle handle, uint64_t time_ms); + +/** + * @brief Set App id to esplayer to control resource confliction. + * @param [in] handle : esplusplayer handle. + * @param [in] app_info : application id, version, type. + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * esplusplayer_app_info appinfo; + * appinfo.id = "youtube"; + * appinfo.version = "3.0"; + * appinfo.type = "MSE"; + * esplusplayer_handle esplayer = esplusplayer_create(); + * esplusplayer_open(esplayer); + * esplusplayer_set_app_info(esplayer,&appinfo); + * // ... your codes ... + * esplusplayer_close(esplayer); + * @endcode + * @pre The player state must be #ESPLUSPLAYER_STATE_IDLE. + * @post None + * @exception None + * @see esplusplayer_open() + */ +int esplusplayer_set_app_info(esplusplayer_handle handle, + const esplusplayer_app_info* app_info); + +/** + * @brief Set the video display. + * @param [in] handle : esplusplayer handle. + * @param [in] type : display type. + * @param [in] window : the handle to display window. + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * esplusplayer_open(esplayer); + * esplusplayer_set_display(esplayer,ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY,window); + * // ... your codes ... + * esplusplayer_close(esplayer); + * @endcode + * @pre The player state must be #ESPLUSPLAYER_STATE_IDLE. + * @post None + * @exception None + * @remark Esplusplayer is not supporting changing display. \n + * This API have to be called before calling + * esplusplayer_prepare_async() to reflect the display type. + * @see esplusplayer_open() \n + * esplusplayer_set_display_mode() \n + * esplusplayer_set_display_roi() \n + * esplusplayer_set_display_visible() + */ +int esplusplayer_set_display(esplusplayer_handle handle, + esplusplayer_display_type type, void* window); +/** + * @brief Set the video display. + * @param [in] handle : esplusplayer handle. + * @param [in] type : display type. + * @param [in] subsurface : the ecore wayland subsurface handle. + * @param [in] x : the x coordinate of subsurface. + * @param [in] y : the y coordinate of subsurface. + * @param [in] width : the width of subsurface. + * @param [in] height : the height of subsurface. + * @return @c one of esplusplayer_error_type values will be returned. + * @pre The player state must be #ESPLUSPLAYER_STATE_IDLE. + * @post None + * @exception None + * @version 3.1 + * @see esplusplayer_set_display_mode() \n + * esplusplayer_set_display_roi() \n + * esplusplayer_set_display_visible() + */ +int esplusplayer_set_display_ecore_subsurface(esplusplayer_handle handle, + esplusplayer_display_type type, + void* subsurface, int x, int y, + int width, int height); +/** + * @brief Set the video display mode. + * @param [in] handle : esplusplayer handle. + * @param [in] mode : display mode. + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * esplusplayer_open(esplayer); + * esplusplayer_set_display_mode(esplayer,ESPLUSPLAYER_DISPLAY_MODE_DST_ROI); + * // ... your codes ... + * esplusplayer_close(esplayer); + * @endcode + * @pre The player state can be all of #esplusplayer_state except + * #ESPLUSPLAYER_STATE_NONE. + * @post None + * @exception None + * @remark If no display is set, no operation is performed. + * @see esplusplayer_open() \n + * esplusplayer_set_display_mode() \n + * esplusplayer_set_display_roi() \n + * esplusplayer_set_display_visible() + */ +int esplusplayer_set_display_mode(esplusplayer_handle handle, + esplusplayer_display_mode mode); + +/** + * @brief Set the ROI(Region Of Interest) area of display. + * @param [in] handle : esplusplayer handle. + * @param [in] x : var startPointX in src video area. + * @param [in] y : var startPointY in src video area. + * @param [in] width : width of display in src video area. + * @param [in] height : height of display in src video area. + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * esplusplayer_open(esplayer); + * esplusplayer_set_display(esplayer,ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY,window); + * esplusplayer_set_display_mode(esplayer,ESPLUSPLAYER_DISPLAY_MODE_DST_ROI); + * esplusplayer_set_display_roi(esplayer,0,0,600,500); + * // ... your codes ... + * esplusplayer_close(esplayer); + * @endcode + * @pre The player state can be all of #esplusplayer_state except + * #ESPLUSPLAYER_STATE_NONE. \n + * Before set display ROI, #ESPLUSPLAYER_DISPLAY_MODE_DST_ROI + * must be set with esplusplayer_set_display_mode(). + * @post None + * @exception None + * @remark The minimum value of width and height are 1. + * @see esplusplayer_open() \n + * esplusplayer_set_display() \n + * esplusplayer_set_display_mode() \n + * esplusplayer_set_display_visible() + */ +int esplusplayer_set_display_roi(esplusplayer_handle handle, int x, int y, + int width, int height); + +/** + * @brief Set the Crop Area(Region Of Src ratio) area of display. + * @param [in] handle : esplusplayer handle. + * @param [in] scale_x: x label ratio in src video area. + * @param [in] scale_y: y label ratio in src video area. + * @param [in] scale_w: width ratio in src video area. + * @param [in] scale_h: height ratio in src video area. + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * esplusplayer_open(esplayer); + * esplusplayer_set_display(esplayer,ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY,window); + * esplusplayer_set_video_roi(esplayer,0,0,0.5,0.5); + * // ... your codes ... + * esplusplayer_close(esplayer); + * @endcode + * @pre The player state can be all of #esplusplayer_state except + * #ESPLUSPLAYER_STATE_NONE. \n + * @post None + * @exception None + * @remark The minimum value of input are 0,maximun value is 1. + * @see esplusplayer_open() \n + * esplusplayer_set_display() \n + * esplusplayer_set_display_visible() + */ +int esplusplayer_set_video_roi(esplusplayer_handle handle, double scale_x, + double scale_y, double scale_w, double scale_h); + +/** + * @brief Resize the render rectangle(the max region that video can be + * displayed). + * @param [in] handle : esplusplayer handle. + * @param [in] x: x coordinate of render rectangle. + * @param [in] y: y coordinate of render rectangle. + * @param [in] width: width of render rectangle. + * @param [in] height: height of render rectangle. + * @return @c one of esplusplayer_error_type values will be returned. + * @pre Should be called after esplusplayer_set_display() \n + * esplusplayer_set_surface_display() \n + * esplusplayer_set_ecore_display() \n + * esplusplayer_set_display_ecore_subsurface + * @post None + * @exception None + * @version 3.2 + * @remark The minimum value of width and height are 1. + * @see esplusplayer_set_display() \n + * esplusplayer_set_surface_display() \n + * esplusplayer_set_ecore_display() \n + * esplusplayer_set_display_ecore_subsurface + */ +int esplusplayer_resize_render_rect(esplusplayer_handle handle, int x, int y, + int width, int height); + +/** + * @brief Set the visibility of the video display. + * @param [in] handle : esplusplayer handle. + * @param [in] visible : the visibility of the display. + * (@c true = visible, @c false = non-visible) + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * esplusplayer_open(esplayer); + * esplusplayer_set_display(esplayer,ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY,window); + * esplusplayer_set_display_visible(esplayer,false); + * // ... your codes ... + * esplusplayer_close(esplayer); + * @endcode + * @pre The player state can be all of #esplusplayer_state except + * #ESPLUSPLAYER_STATE_NONE. + * @post None + * @exception None + * @see esplusplayer_open() \n + * esplusplayer_set_display() + */ +int esplusplayer_set_display_visible(esplusplayer_handle handle, bool visible); + +/** + * @brief Set the rotate angle of the video display. + * @param [in] handle : esplusplayer handle. + * @param [in] rotation : the rotate angle of the display. + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * esplusplayer_open(esplayer); + * esplusplayer_set_display(esplayer,ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY,window); + * esplusplayer_set_display_rotation(esplayer_,ESPLUSPLAYER_DISPLAY_ROTATION_TYPE_90); + * // ... your codes ... + * esplusplayer_close(esplayer); + * @endcode + * @pre The player state can be all of #esplusplayer_state except + * #ESPLUSPLAYER_STATE_NONE. + * @post this API worked only when video sink created. + * @exception None + * @see esplusplayer_open() \n + * esplusplayer_set_display() + */ +int esplusplayer_set_display_rotation( + esplusplayer_handle handle, esplusplayer_display_rotation_type rotation); + +/** + * @brief Get the rotate angle of the video display. + * @param [in] handle : esplusplayer handle. + * @param [out] rotation : the rotate angle of the display which want to + * get. + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * esplusplayer_open(esplayer); + * esplusplayer_set_display_rotation(esplayer,ESPLUSPLAYER_DISPLAY_ROTATION_TYPE_90); + * esplusplayer_display_rotation_type rotation_get = ESPLUSPLAYER_DISPLAY_ROTATION_TYPE_NONE; + * // ... your codes ... + * esplusplayer_get_display_rotation(esplayer,&rotation_get); + * // ... your codes ... + * esplusplayer_close(esplayer); + * @endcode + * @pre The player state can be all of #esplusplayer_state except + * #ESPLUSPLAYER_STATE_NONE. + * @post this API worked only when video sink created. + * @exception None + * @see esplusplayer_open() \n + * esplusplayer_set_display_rotation() + */ +int esplusplayer_get_display_rotation( + esplusplayer_handle handle, esplusplayer_display_rotation_type* rotation); + +/** + * @deprecated Deprecated since API V2.0. Use + * esplusplayer_set_submit_data_type() instead. + * @brief Set whether to send decrypted es packets in the trust zone. + * @param [in] handle : esplusplayer handle. + * @param [in] using_tz : whether to use trust zone memory. + * (@c true = if decrypted packets are sent in trust zone, @c false = + * otherwise @c) + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * esplusplayer_open(esplayer); + * esplusplayer_set_tz_use(esplayer, true); + * // ... your codes ... + * esplusplayer_close(esplayer); + * @endcode + * @pre The player state must be set to #ESPLUSPLAYER_STATE_IDLE. + * @post None + * @exception None + * @remark This API have to be called before calling + * esplusplayer_prepare_async(). \n If using_tz is set to true, use + * esplusplayer_submit_trust_zone_packet() to send decrypted packets. + * @see esplusplayer_open() \n + * esplusplayer_submit_trust_zone_packet() + */ +int esplusplayer_set_tz_use(esplusplayer_handle handle, bool using_tz); + +/** + * @brief Set whether to send decrypted es packets in the trust zone or + * encrypted es packets. + * @param [in] handle : esplusplayer handle. + * @param [in] type : whether to use trust zone memory or encrypted data + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * esplusplayer_open(esplayer); + * esplusplayer_set_submit_data_type(esplayer,ESPLUSPLAYER_SUBMIT_DATA_TYPE_CLEAN_DATA); + * // ... your codes ... + * esplusplayer_close(esplayer); + * @endcode + * @pre The player state must be set to #ESPLUSPLAYER_STATE_IDLE. + * @post None + * @exception None + * @remark This API have to be called before calling + * esplusplayer_prepare_async(). \n + * If type is ESPLUSPLAYER_SUBMIT_DATA_TYPE_CLEAN_DATA use + * esplusplayer_submit_packet() to send clean packets. \n + * If type is ESPLUSPLAYER_SUBMIT_DATA_TYPE_TRUSTZONE_DATA, use + * esplusplayer_submit_trust_zone_packet() to send decrypted packets + * in trust zone \n + * If type is ESPLUSPLAYER_SUBMIT_DATA_TYPE_ENCRYPTED_DATA, use + * esplusplayer_submit_encrypted_packet() to send encrypted packets. + * @see esplusplayer_open() \n + * esplusplayer_submit_trust_zone_packet() \n + * esplusplayer_submit_encrypted_packet() \n + * esplusplayer_submit_data_type + */ +int esplusplayer_set_submit_data_type(esplusplayer_handle handle, + esplusplayer_submit_data_type type); + +/** + * @brief Set on mute of the audio sound. + * @param [in] handle : esplusplayer handle. + * @param [in] mute : on mute of the sound. + * (@c true = mute, @c false = non-mute) + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success, otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * esplusplayer_open(esplayer); + * esplusplayer_set_audio_mute(esplayer, true); + * // ... your codes ... + * esplusplayer_close(esplayer); + * @endcode + * @pre The player state can be all of #esplusplayer_state except + * #ESPLUSPLAYER_STATE_NONE. + * @post None + * @exception None + * @see esplusplayer_open() + */ +int esplusplayer_set_audio_mute(esplusplayer_handle handle, bool mute); + +/** + * @brief Get current state of player. + * @param [in] handle : esplusplayer handle. + * @return current #esplusplayer_state of player. + * @code + * esplusplayer_handle esplayer = esplusplayer_create(); + * // ... your codes ... + * esplusplayer_state ret = esplusplayer_get_state(esplayer); + * // ... your codes ... + * esplusplayer_destroy(esplayer); + * @endcode + * @pre None + * @post None + * @exception None + */ +esplusplayer_state esplusplayer_get_state(esplusplayer_handle handle); + +/** + * @brief Submit es packet to decode audio or video. + * @param [in] handle : esplusplayer handle. + * @param [in] packet : es packet pointer. + * @return @c ESPLUSPLAYER_SUBMIT_STATUS_SUCCESS : succeed to submit es + * packet, + * otherwise @c : fail to submit es packet. + * @code + * static void OnPrepareDone(bool ret, void* userdata) { + * // ... your codes ... + * printf ("OnPrepareDone\n"); + * } + * static void OnReadyToPrepare(const esplusplayer_stream_type type,void* userdata) { + * if (type == ESPLUSPLAYER_STREAM_TYPE_VIDEO) { + * //Something you want to do when feed es video stream is allowed + * } else { + * //Something you want to do when feed es audio stream is allowed + * } + * //Something you want to do when OnReadyToPrepare + * printf ("OnReadyToPrepare\n"); + * } + * static void OnBufferByteStatus(const esplusplayer_stream_type type, + * const esplusplayer_buffer_status status, + * uint64_t byte_size, void* userdata) { + * if (type == ESPLUSPLAYER_STREAM_TYPE_VIDEO) { + * if (status == ESPLUSPLAYER_BUFFER_STATUS_UNDERRUN) { + * //Something you want to do when es video buffer is enough + * } else { + * //Something you want to do when es video buffer is not enough + * } + * } else { + * if (status == ESPLUSPLAYER_BUFFER_STATUS_UNDERRUN) { + * //Something you want to do when es audio buffer is enough + * } else { + * //Something you want to do when es audio buffer is not enough + * } + * } + * //Something you want to do when OnBufferByteStatus + * printf ("OnBufferByteStatus\n"); + * } + * static void OnBufferTimeStatus(const esplusplayer_stream_type type, + * const esplusplayer_buffer_status status, + * uint64_t time_size,void* userdata) { + * if (type == ESPLUSPLAYER_STREAM_TYPE_VIDEO) { + * if (status == ESPLUSPLAYER_BUFFER_STATUS_UNDERRUN) { + * //Something you want to do when es video buffer is enough + * } else { + * //Something you want to do when es video buffer is not enough + * } + * } else { + * if (status == ESPLUSPLAYER_BUFFER_STATUS_UNDERRUN) { + * //Something you want to do when es audio buffer is enough + * } else { + * //Something you want to do when es audio buffer is not enough + * } + * } + * //Something you want to do when OnBufferTimeStatus + * printf ("OnBufferTimeStatus\n"); + * } + * void FeedEsPacket(esplusplayer_handle player,esplusplayer_es_packet pkt) { + * // ... your codes ... + * if(feed is allowed && buffer is enough) { + * esplusplayer_submit_packet(player, &pkt); + * } + * // ... your codes ... + * ) + * esplusplayer_handle esplayer = esplusplayer_create(); + * esplusplayer_set_prepare_async_done_cb(esplayer,OnPrepareDone,&esplayer); + * esplusplayer_set_ready_to_prepare_cb(esplayer,OnReadyToPrepare,&esplayer); + * esplusplayer_set_buffer_byte_status_cb(esplayer,OnBufferByteStatus,&esplayer); + * esplusplayer_set_buffer_time_status_cb(esplayer,OnBufferTimeStatus,&esplayer); + * esplusplayer_open(esplayer); + * esplusplayer_prepare_async(esplayer); + * // ... your codes ... + * //FeedEsPacket()(call a new thread to do this) + * // ... your codes ... + * esplusplayer_close(esplayer); + * esplusplayer_destroy(esplayer); + * @endcode + * @pre User can submit es packets after + * esplusplayer_ready_to_prepare_cb() or + * esplusplayer_ready_to_seek_cb() is called. + * @post None + * @exception None + * @remark Amount of packets for at least one decoded frame must be submitted + * after calling esplusplayer_prepare_async() or esplusplayer_seek() + * for invoking esplusplayer_prepare_async_done_cb() or + * esplusplayer_seek_done_cb() \n + * This api must be called from a different thread than other apis. + * @see esplusplayer_set_submit_data_type() \n + * esplusplayer_es_packet \n + * esplusplayer_buffer_status_cb() \n + * esplusplayer_ready_to_prepare_cb() \n + * esplusplayer_ready_to_seek_cb() + */ +esplusplayer_submit_status esplusplayer_submit_packet( + esplusplayer_handle handle, esplusplayer_es_packet* packet); + +/** + * @brief Submit es packet to decode audio or video. + * @param [in] handle : esplusplayer handle. + * @param [in] packet : es packet pointer. + * @param [in] tz_handle : es decrypted tz handle. + * @return @c ESPLUSPLAYER_SUBMIT_STATUS_SUCCESS : succeed to submit es + * packet, + * otherwise @c : fail to submit es packet. + * @code + * refer to the sample code of esplusplayer_submit_packet(); + * @endcode + * @pre User can submit es packets after + * esplusplayer_ready_to_prepare_cb() or + * esplusplayer_ready_to_seek_cb() is called. + * @post None + * @exception None + * @remark Amount of packets for at least one decoded frame must be submitted + * after calling esplusplayer_prepare_async() or esplusplayer_seek() + * for invoking esplusplayer_prepare_async_done_cb() or + * esplusplayer_seek_done_cb(). \n + * To use this api, Must set + * ESPLUSPLAYER_SUBMIT_DATA_TYPE_TRUSTZONE_DATA using + * esplusplayer_set_submit_data_type() \n This api must be called from a + * different thread than other apis. + * @see esplusplayer_es_packet \n + * esplusplayer_buffer_status_cb() \n + * esplusplayer_ready_to_prepare_cb() \n + * esplusplayer_ready_to_seek_cb() + */ +esplusplayer_submit_status esplusplayer_submit_trust_zone_packet( + esplusplayer_handle handle, esplusplayer_es_packet* packet, + uint32_t tz_handle); + +/** + * @brief Submit encrypted es packet to decode and decrypt audio or video. + * @param [in] handle : esplusplayer handle. + * @param [in] packet : es packet pointer. + * @param [in] drm_info : information to decrypt es packet. + * esplusplayer doesn't take ownership. user should + * free it. if you deliver it as (null), this api + * works as esplusplayer_submit_packet(). + * @return @c ESPLUSPLAYER_SUBMIT_STATUS_SUCCESS : succeed to submit es + * packet, + * otherwise @c : fail to submit es packet. + * @code + * refer to the sample code of esplusplayer_submit_packet(); + * @endcode + * @pre User can submit es packets after + * esplusplayer_ready_to_prepare_cb() or + * esplusplayer_ready_to_seek_cb() is called. + * @post None + * @exception None + * @remark Amount of packets for at least one decoded frame must be submitted + * after calling esplusplayer_prepare_async() or esplusplayer_seek() + * for invoking esplusplayer_prepare_async_done_cb() or + * esplusplayer_seek_done_cb(). \n + * To use this api, Must set + * ESPLUSPLAYER_SUBMIT_DATA_TYPE_ENCRYPTED_DATA using + * esplusplayer_set_submit_data_type() \n This api must be called from a + * different thread than other apis. + * @see esplusplayer_es_packet \n + * esplusplayer_drm_info \n + * esplusplayer_buffer_status_cb() \n + * esplusplayer_ready_to_prepare_cb() \n + * esplusplayer_ready_to_seek_cb() \n + * esplusplayer_submit_packet() + */ +esplusplayer_submit_status esplusplayer_submit_encrypted_packet( + esplusplayer_handle handle, esplusplayer_es_packet* packet, + esplusplayer_drm_info* drm_info); + +/** + * @brief Generate EOS(End Of Stream) packet explicitly and submit it to the + * player. + * @param [in] handle : esplusplayer handle. + * @param [in] type : stream type which reaches eos. + * @return @c ESPLUSPLAYER_SUBMIT_STATUS_SUCCESS : succeed to submit EOS + * packet, + * otherwise @c : fail to submit EOS packet. + * @code + * esplusplayer_handle esplayer = esplusplayer_create(); + * // ... your codes ... + * esplusplayer_submit_eos_packet(esplayer,ESPLUSPLAYER_STREAM_TYPE_VIDEO); + * // ... your codes ... + * @endcode + * @pre None + * @post None + * @exception None + */ +esplusplayer_submit_status esplusplayer_submit_eos_packet( + esplusplayer_handle handle, esplusplayer_stream_type type); + +/** + * @brief Set audio stream to have contents information. + * @param [in] handle : esplusplayer handle. + * @param [in] stream : audio stream pointer. + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * esplusplayer_open(esplayer); + * esplusplayer_audio_stream_info audio_stream; + * audio_stream.codec_data = nullptr; + * audio_stream.codec_data_length = 0; + * esplusplayer_set_audio_stream_info(esplayer, &audio_stream); + * // ... your codes ... + * esplusplayer_close(esplayer); + * @endcode + * @pre The player state must be set to #ESPLUSPLAYER_STATE_IDLE except + * audio stream is deactivated. + * @post None + * @exception None + * @remark This API have to be called before calling the + * esplusplayer_prepare_async(). + * @see esplusplayer_open() \n + * esplusplayer_audio_stream_info + */ +int esplusplayer_set_audio_stream_info(esplusplayer_handle handle, + esplusplayer_audio_stream_info* stream); + +/** + * @brief Set video stream to have contents information. + * @param [in] handle : esplusplayer handle. + * @param [in] stream : video stream pointer. + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @pre The player state must be set to #ESPLUSPLAYER_STATE_IDLE except + * video stream is deactivated. + * @post None + * @exception None + * @remark This API have to be called before calling the + * esplusplayer_prepare_async(). + * @see esplusplayer_audio_stream_info + * esplusplayer_activate + */ +int esplusplayer_set_video_stream_info(esplusplayer_handle handle, + esplusplayer_video_stream_info* stream); + +/** + * @brief Get the current playing time of the associated media. + * @param [in] handle : esplusplayer handle. + * @param [out] ms : current playing time in milliseconds. + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * prepare esplayer done + * esplusplayer_start(esplayer); + * // ... your codes ... + * uint64_t cur_time = 0; + * esplusplayer_get_playing_time(esplayer, &cur_time); + * // ... your codes ... + * esplusplayer_stop(esplayer); + * @endcode + * @pre The player must be one of #ESPLUSPLAYER_STATE_PAUSE or + * #ESPLUSPLAYER_STATE_PLAYING. + * @post None + * @exception None + * @see esplusplayer_prepare_async() + */ +int esplusplayer_get_playing_time(esplusplayer_handle handle, uint64_t* ms); +/** + * @brief Get dropped frame counts in videosink. + * @param [in] handle : esplusplayer handle. + * @param [out] padaptive_info : dropped frame counts. + * @param [in] adaptive_type : type of adaptive info which APP want to get. + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * prepare esplayer done + * // ... your codes ... + * uint64_t count = 0; + * esplusplayer_get_adaptive_info(esplayer, + * static_cast(&count),ESPLUSPLAYER_ADAPT_INFO_TYPE_DROPPED_FRAMES); + * // ... your codes ... + * esplusplayer_stop(esplayer); + * @endcode + * @pre The player must be one of #ESPLUSPLAYER_STATE_READY, + * #ESPLUSPLAYER_STATE_PAUSE or #ESPLUSPLAYER_STATE_PLAYING. + * @post None + * @exception None + * @see esplusplayer_prepare_async() + */ +int esplusplayer_get_adaptive_info( + esplusplayer_handle handle, void* padaptive_info, + esplusplayer_adaptive_info_type adaptive_type); + +/** + * @brief Set volume to player + * @param [in] handle : esplusplayer handle. + * @param [in] volume : volume level(0 ~ 100). + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * esplusplayer_open(esplayer); + * int vol = 80; + * esplusplayer_set_volume(esplayer, vol) + * // ... your codes ... + * esplusplayer_close(esplayer); + * @endcode + * @pre The player state must be not #ESPLUSPLAYER_STATE_NONE. + * @post None + * @exception None + * @see esplusplayer_open() + */ +int esplusplayer_set_volume(esplusplayer_handle handle, const int volume); + +/** + * @brief Get volume from player + * @param [in] handle : esplusplayer handle. + * @param [out] volume : volume ptr. + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * esplusplayer_open(esplayer); + * // ... your codes ... + * int vol = 0; + * esplusplayer_get_volume(esplayer, &vol) + * // ... your codes ... + * esplusplayer_close(esplayer); + * @endcode + * @pre The player state must be not #ESPLUSPLAYER_STATE_NONE. + * @post None + * @exception None + * @see esplusplayer_open() + */ +int esplusplayer_get_volume(esplusplayer_handle handle, int* volume); + +/** + * @brief Set decoded video frame buffer type. + * @param [in] handle : esplusplayer handle. + * @param [in] type : one of the video decoded buffer type to set . + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * esplusplayer_open(esplayer); + * esplusplayer_set_video_frame_buffer_type(esplayer, + * ESPLUSPLAYER_DECODED_VIDEO_FRAME_BUFFER_TYPE_NONE); + * // ... your codes ... + * esplusplayer_close(esplayer); + * @endcode + * @pre The player state must be set to #ESPLUSPLAYER_STATE_IDLE when type + is not equal to be + ESPLUSPLAYER_DECODED_VIDEO_FRAME_BUFFER_TYPE_SCALE + The player state must be not #ESPLUSPLAYER_STATE_NONE when + type is equal to be + ESPLUSPLAYER_DECODED_VIDEO_FRAME_BUFFER_TYPE_SCALE + * @post None + * @exception None + * @remark reference can't support sw codec type. + * esplusplayer_set_media_packet_video_decoded_cb() + * esplusplayer_set_video_codec_type() + * when type is SCALE, the target scale resolution can be set by + * esplusplayer_set_video_frame_buffer_scale_resolution() + * @see esplusplayer_open() + */ +int esplusplayer_set_video_frame_buffer_type( + esplusplayer_handle handle, + esplusplayer_decoded_video_frame_buffer_type type); + +/** + * @brief Set the request frame rate of decoded video + * @param [in] handle : esplusplayer handle. + * @param [in] request_framerate : the request frame rate of returned decoded video frame + * The value of track_framerate(A) and request_framerate(B) should be one of the following sets: + * track_framerate indicate the frame rate of input video stream + * 1.A/(A-B) = X ,X means drop 1 frame every X frame + * 2.Special cases,such as 24000/1000 -> 15000/1000 + * when request_framerate.num = 0, return none decoded video frame + * when request_framerate.num/request_framerate.den = + * track_framerate.num/track_framerate.den, return all decoded + * video frame + * when request_framerate.num/request_framerate.den < + * track_framerate.num/track_framerate.den, drop some decoded + * video frame + * @return @c one of esplusplayer_error_type values will be returned. + * @pre The player state must be not #ESPLUSPLAYER_STATE_NONE. + * @post None + * @exception None + * @version 3.3 + * @remark only works when decoded video frame buffer type is scale + * esplusplayer_set_video_frame_buffer_type() + * esplusplayer_set_media_packet_video_decoded_cb() + */ +int esplusplayer_set_decoded_video_frame_rate( + esplusplayer_handle handle, esplusplayer_rational request_framerate); + +/** + * @brief Set the target scale resolution when decoded video frame buffer + * type is scale + * @param [in] handle : esplusplayer handle. + * @param [in] target_width : scale target width of video frame buffer. + * @param [in] target_width : scale target height of video frame buffer. + * @return @c one of esplusplayer_error_type values will be returned. + * @pre The player state must be set to #ESPLUSPLAYER_STATE_IDLE. + * @post None + * @exception None + * @version 3.1 + * @remark esplusplayer_set_video_frame_buffer_type() + * esplusplayer_set_media_packet_video_decoded_cb() + * If user don't call this api to set target_width and target_height, + * the default + * target scale resolution is 960x540 + */ +int esplusplayer_set_video_frame_buffer_scale_resolution( + esplusplayer_handle handle, uint32_t target_width, uint32_t target_height); + +/** + * @brief Flush buffers for a player. + * @param [in] handle : esplusplayer handle ptr. + * @param [in] type : choose which stream data need to be + * flush,audio/video,if need flush all pipeline can call this API + * twice. + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * prepare esplayer done + * // ... your codes ... + * esplusplayer_flush(esplayer, ESPLUSPLAYER_STREAM_TYPE_VIDEO); + * // ... your codes ... + * esplusplayer_stop(esplayer); + * @endcode + * @pre The player state should greater than #ESPLUSPLAYER_STATE_IDLE + * @post None + * @exception None + * @see esplusplayer_prepare_async() + */ +int esplusplayer_flush(esplusplayer_handle handle, + esplusplayer_stream_type type); + +/** + * @brief Convert the esplusplayer error type to a string. + * @param [in] type : esplusplayer error type + * @return @c not nullptr the converted error string otherwise @c failed to + * convert the error. + * @code + * // ... your codes ... + * const char* error; + * error = esplusplayer_get_error_string(ESPLUSPLAYER_ERROR_TYPE_NOT_SUPPORTED_FILE); + * // ... your codes ... + * @endcode + * @pre None + * @post None + * @exception None + */ +const char* esplusplayer_get_error_string(esplusplayer_error_type type); + +/** + * @brief Sets a callback function to be invoked when an error occurs. + * @param [in] handle : esplusplayer handle. + * @param [in] error_cb : the error callback function to register. + * @param [in] userdata : userdata of esplusplayer_error_cb() + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * static void OnError(const esplusplayer_error_type err_code, void* + * userdata) { + * //Something you want to do when error occur + * printf ("OnError\n"); + * } + * esplusplayer_handle esplayer = esplusplayer_create(); + * esplusplayer_set_error_cb(esplayer, OnError, nullptr); + * // ... your codes ... + * esplusplayer_destroy(esplayer); + * @endcode + * @pre The player state must be set to #ESPLUSPLAYER_STATE_NONE + * or #ESPLUSPLAYER_STATE_IDLE. + * @post esplusplayer_error_cb() will be invoked. + * @exception None + * @remark esplusplayer_error_cb() + * if error_cb is set to null, esplusplayer_error_cb() will not be + * invoked anymore. + */ +int esplusplayer_set_error_cb(esplusplayer_handle handle, + esplusplayer_error_cb error_cb, void* userdata); + +/** + * @brief Set a callback function to be invoked when buffer underrun or + * overflow is occurred. + * @param [in] handle : esplusplayer handle. + * @param [in] buffer_status_cb : the buffer status callback function to + * register. + * @param [in] userdata : userdata of esplusplayer_buffer_status_cb() + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * refer to the sample code of esplusplayer_set_error_cb(); + * @endcode + * @pre The player state must be set to #ESPLUSPLAYER_STATE_NONE + * or #ESPLUSPLAYER_STATE_IDLE. + * @post esplusplayer_buffer_status_cb() will be invoked. + * @exception None + * @remark esplusplayer_buffer_status_cb() + * if buffer_status_cb is set to null, + * esplusplayer_buffer_status_cb() will not be invoked anymore. + */ +int esplusplayer_set_buffer_status_cb( + esplusplayer_handle handle, esplusplayer_buffer_status_cb buffer_status_cb, + void* userdata); + +/** + * @brief Set a callback function to be invoked when buffer underrun or + * overflow is occurred and buffer size in byte will be passed. + * @param [in] handle : esplusplayer handle. + * @param [in] buffer_status_cb : the buffer byte status callback function + * to register. + * @param [in] userdata : userdata of esplusplayer_buffer_byte_status_cb() + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * refer to the sample code of esplusplayer_submit_packet(); + * @endcode + * @pre The player state must be set to #ESPLUSPLAYER_STATE_NONE + * or #ESPLUSPLAYER_STATE_IDLE. + * @post esplusplayer_buffer_byte_status_cb() will be invoked. + * @exception None + * @remark esplusplayer_buffer_byte_status_cb() + */ +int esplusplayer_set_buffer_byte_status_cb( + esplusplayer_handle handle, + esplusplayer_buffer_byte_status_cb buffer_status_cb, void* userdata); + +/** + * @brief Set a callback function to be invoked when buffer underrun or + * overflow is occurred and buffer size in time will be passed. + * @param [in] handle : esplusplayer handle. + * @param [in] buffer_status_cb : the buffer time status callback function + * to register. + * @param [in] userdata : userdata of esplusplayer_buffer_time_status_cb() + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + refer to the sample code of esplusplayer_submit_packet(); + * @endcode + * @pre The player state must be set to #ESPLUSPLAYER_STATE_NONE + * or #ESPLUSPLAYER_STATE_IDLE. + * @post esplusplayer_buffer_time_status_cb() will be invoked. + * @exception None + * @remark esplusplayer_buffer_time_status_cb(), + * esplusplayer_buffer_time_status_cb() will be invoked only + * when the duration value of espacket is set. + * if buffer_status_cb is set to null, + * esplusplayer_buffer_time_status_cb() will not be invoked anymore. + */ +int esplusplayer_set_buffer_time_status_cb( + esplusplayer_handle handle, + esplusplayer_buffer_time_status_cb buffer_status_cb, void* userdata); + +/** + * @brief Set a callback function to be invoked when video latency status + * is changed. + * @param [in] handle : esplusplayer handle. + * @param [in] video_high_latency_status_cb : the video high latency status + * callback function to register. + * @param [in] userdata : userdata of + * esplusplayer_set_video_latency_status_cb() + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * refer to the sample code of esplusplayer_set_error_cb(); + * @endcode + * @pre The player state must be set to #ESPLUSPLAYER_STATE_NONE + * or #ESPLUSPLAYER_STATE_IDLE. + * @post esplusplayer_video_latency_status_cb() will be invoked. + * @exception None + * @version 3.0 + * @remark esplusplayer_video_latency_status_cb() will be invoked only + * when mid / high latency threshold is set. + */ +int esplusplayer_set_video_latency_status_cb( + esplusplayer_handle handle, + esplusplayer_video_latency_status_cb video_high_latency_status_cb, + void* userdata); + +/** + * @brief Set a callback function to be invoked when audio latency status + * is changed. + * @param [in] handle : esplusplayer handle. + * @param [in] audio_high_latency_status_cb : the audio high latency status + * callback function to register. + * @param [in] userdata : userdata of + * esplusplayer_set_audio_latency_status_cb() + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * refer to the sample code of esplusplayer_set_error_cb(); + * @endcode + * @pre The player state must be set to #ESPLUSPLAYER_STATE_NONE + * or #ESPLUSPLAYER_STATE_IDLE. + * @post esplusplayer_audio_latency_status_cb() will be invoked. + * @exception None + * @version 3.0 + * @remark esplusplayer_audio_latency_status_cb() will be invoked only + * when mid / high latency threshold is set. + */ +int esplusplayer_set_audio_latency_status_cb( + esplusplayer_handle handle, + esplusplayer_audio_latency_status_cb audio_high_latency_status_cb, + void* userdata); + +/** + * @brief Set buffer size with different option + * @param [in] handle : esplusplayer handle. + * @param [in] option : the option of buffer size. + * @param [in] size : size of selected buffer option. + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * esplusplayer_open(esplayer); + * esplusplayer_set_buffer_size(esplayer,ESPLUSPLAYER_BUFFER_AUDIO_MAX_BYTE_SIZE,10240) + * // ... your codes ... + * esplusplayer_close(esplayer); + * @endcode + * @pre The player state must be set to #ESPLUSPLAYER_STATE_IDLE. + * @post None + * @exception None + * @remark esplusplayer_buffer_option + * @see esplusplayer_open() + */ +int esplusplayer_set_buffer_size(esplusplayer_handle handle, + esplusplayer_buffer_option option, + uint64_t size); +/** + * @brief Set a callback function to be invoked when resource confliction is + * occurred. + * @param [in] handle : esplusplayer handle. + * @param [in] resource_conflicted_cb : the resource conflicted callback + * function to register. + * @param [in] userdata : userdata of resource_conflicted_cb() + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * refer to the sample code of esplusplayer_set_error_cb(); + * @endcode + * @pre The player state must be set to #ESPLUSPLAYER_STATE_NONE + * or #ESPLUSPLAYER_STATE_IDLE. + * @post esplusplayer_resource_conflicted_cb() will be invoked. + * @exception None + * @remark esplusplayer_resource_conflicted_cb() + * if resource_conflicted_cb is set to null, + * esplusplayer_resource_conflicted_cb() will not be invoked + * anymore. + */ +int esplusplayer_set_resource_conflicted_cb( + esplusplayer_handle handle, + esplusplayer_resource_conflicted_cb resource_conflicted_cb, void* userdata); + +/** + * @brief Set a callback function to be invoked when player has reached the + * end of stream. + * @param [in] handle : esplusplayer handle. + * @param [in] eos_cb : the eos callback function to register. + * @param [in] userdata : userdata of esplusplayer_eos_cb() + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * refer to the sample code of esplusplayer_set_error_cb(); + * @endcode + * @pre The player state must be set to #ESPLUSPLAYER_STATE_NONE + * or #ESPLUSPLAYER_STATE_IDLE. + * @post esplusplayer_eos_cb() will be invoked. + * @exception None + * @remark esplusplayer_eos_cb() + * if eos_cb is set to null, esplusplayer_eos_cb() will not be + * invoked anymore. + */ +int esplusplayer_set_eos_cb(esplusplayer_handle handle, + esplusplayer_eos_cb eos_cb, void* userdata); + +/** + * @brief Set a callback function to be invoked when player is prepared to + * receive es packets after calling esplusplayer_prepare_async(). + * @param [in] handle : esplusplayer handle. + * @param [in] ready_to_prepare_cb : the ready to prepare callback function + * to register. + * @param [in] userdata : userdata of ready_to_prepare_cb() + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * refer to the sample code of esplusplayer_submit_packet(); + * @endcode + * @pre The player state must be set to #ESPLUSPLAYER_STATE_NONE + * or #ESPLUSPLAYER_STATE_IDLE. + * @post None + * @exception None + * @remark esplusplayer_prepare_async() + * if ready_to_prepare_cb is set to null, + * esplusplayer_ready_to_prepare_cb() will not be invoked anymore. + */ +int esplusplayer_set_ready_to_prepare_cb( + esplusplayer_handle handle, + esplusplayer_ready_to_prepare_cb ready_to_prepare_cb, void* userdata); + +/** + * @brief Set a callback function to be invoked when player is prepared to + * be started. + * @param [in] handle : esplusplayer handle. + * @param [in] prepare_async_done_cb : the repare async done callback + * function to register. + * @param [in] userdata : userdata of prepare_async_done_cb() + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * refer to the sample code of plusplayer_prepare_async(); + * @endcode + * @pre The player state must be set to #ESPLUSPLAYER_STATE_NONE + * or #ESPLUSPLAYER_STATE_IDLE. + * @post esplusplayer_prepare_async_done_cb() will be invoked. + * @exception It is prohibited to call any player APIs at + * esplusplayer_prepare_async_done_cb callback. + * @remark esplusplayer_prepare_async_done_cb() + * if prepare_async_done_cb is set to null, + * esplusplayer_prepare_async_done_cb() will not be + * invoked anymore. + * @see plusplayer_prepare_async + */ +int esplusplayer_set_prepare_async_done_cb( + esplusplayer_handle handle, + esplusplayer_prepare_async_done_cb prepare_async_done_cb, void* userdata); + +/** + * @brief Set a callback function to be invoked when player is prepared to + * be started. + * @param [in] handle : esplusplayer handle. + * @param [in] seek_done_cb : the seek done callback function to register. + * @param [in] userdata : userdata of esplusplayer_seek_done_cb() + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * refer to the sample code of esplusplayer_set_error_cb(); + * @endcode + * @pre The player state must be set to #ESPLUSPLAYER_STATE_NONE + * or #ESPLUSPLAYER_STATE_IDLE. + * @post esplusplayer_seek_done_cb() will be invoked. + * if seek_done_cb is set to null, esplusplayer_seek_done_cb() will + * not be invoked anymore. + * @exception None + */ +int esplusplayer_set_seek_done_cb(esplusplayer_handle handle, + esplusplayer_seek_done_cb seek_done_cb, + void* userdata); + +/** + * @brief Set a callback function to be invoked when player is prepared to + * receive es packets after flushing all submitted es packets for + * seek. + * @param [in] handle : esplusplayer handle. + * @param [in] ready_to_seek_cb : the ready to seek callback function to + * register. + * @param [in] userdata : userdata of esplusplayer_ready_to_seek_cb() + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * refer to the sample code of esplusplayer_set_error_cb(); + * @endcode + * @pre The player state must be set to #ESPLUSPLAYER_STATE_NONE + * or #ESPLUSPLAYER_STATE_IDLE. + * @post None + * @exception None + * @remark esplusplayer_seek() + * if ready_to_seek_cb is set to null, esplusplayer_ready_to_seek_cb() + * will not be invoked anymore. + */ +int esplusplayer_set_ready_to_seek_cb( + esplusplayer_handle handle, esplusplayer_ready_to_seek_cb ready_to_seek_cb, + void* userdata); + +/** + * @brief Set a callback function to be invoked when player decoded video + * frame. A video frame can be retrieved using a registered callback. + * @param [in] handle : esplusplayer handle. + * @param [in] media_packet_video_decoded_cb : the media packet video + * decoded callback function to register. + * @param [in] userdata : userdata of + * esplusplayer_set_media_packet_video_decoded_cb() + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * refer to the sample code of esplusplayer_set_error_cb(); + * @endcode + * @pre The player state must be set to #ESPLUSPLAYER_STATE_NONE + * or #ESPLUSPLAYER_STATE_IDLE. + * @post None + * @exception None + * @remark esplusplayer_set_video_frame_buffer_type() + * if media_packet_video_decoded_cb is set to null, + * esplusplayer_error_cb() will not be invoked anymore. + * media packets have to be released by calling + * esplusplayer_decoded_buffer_destroy(). + * @see esplusplayer_set_video_frame_buffer_scale_resolution + */ +int esplusplayer_set_media_packet_video_decoded_cb( + esplusplayer_handle handle, + esplusplayer_media_packet_video_decoded_cb media_packet_video_decoded_cb, + void* userdata); + +/** + * @brief Set closed caption callback function. + * @description In this function set closed caption callback to handle the + * closed caption. If there is closed caption to display, + * esplusplayer_closed_caption_cb will be called to notify there + * is closed caption to display. + * @param [in] handle : esplusplayer handle ptr. + * @param [in] closed_caption_cb : the closed caption callback function to + * register. + * @param [in] userdata : userdata of esplusplayer_closed_caption_cb + * callback function. + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * refer to the sample code of esplusplayer_set_error_cb(); + * @endcode + * @pre The player state must be set to #ESPLUSPLAYER_STATE_NONE + * or #ESPLUSPLAYER_STATE_IDLE. + * @post When there is closed caption data, call + * esplusplayer_closed_caption_cb to nofity that there is closed + * caption needed to be displayed. + * @exception None + * @remark esplusplayer_closed_caption_cb \n + * [in] data : closed caption data \n + * [in] size : length of closed caption data \n + * [in] userdata : userdata of esplusplayer_closed_caption_cb + * callback function. + * if closed_caption_cb is set to null, esplusplayer_closed_caption_cb() + * will not be invoked anymore. + */ +int esplusplayer_set_closed_caption_cb( + esplusplayer_handle handle, + esplusplayer_closed_caption_cb closed_caption_cb, void* userdata); + +/** + * @brief Set a callback function to be invoked when player is flush + * successed. + * @param [in] handle : esplusplayer handle. + * @param [in] flush_done_cb : the flush done callback function to register. + * @param [in] userdata : userdata of esplusplayer_flush_done_cb() + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * refer to the sample code of esplusplayer_set_error_cb(); + * @endcode + * @pre This api should be called before esplusplayer_flush() is called + * @post esplusplayer_flush_done_cb() will be invoked. + * @exception None + * @remark called before esplusplayer_flush(). + * if flush_done_cb is set to null, esplusplayer_error_cb() will + * not be invoked anymore. + */ +int esplusplayer_set_flush_done_cb(esplusplayer_handle handle, + esplusplayer_flush_done_cb flush_done_cb, + void* userdata); +/** + * @brief Set a callback function to be invoked when a specific event + * occurs. + * @param [in] handle : esplusplayer handle. + * @param [in] event_cb : the callback function to register. + * @param [in] userdata : userdata of esplusplayer_event_cb() + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * refer to the sample code of esplusplayer_set_error_cb(); + * @endcode + * @pre The player state must be set to #ESPLUSPLAYER_STATE_NONE + * or #ESPLUSPLAYER_STATE_IDLE. + * @post esplusplayer_event_cb() will be invoked. + * @exception None + * @remark esplusplayer_set_event_cb() + * if event_cb is set to null, esplusplayer_event_cb() will not be + * invoked anymore. + */ +int esplusplayer_set_event_cb(esplusplayer_handle handle, + esplusplayer_event_cb event_cb, void* userdata); +/** + * @brief Provided api for destroying decoded buffer. + * @param [in] handle : esplusplayer handle. + * @param [in] packet : the decoded buffer. + * @return @c one of esplusplayer_error_type values will be returned. + * @pre The player state can be greater than #ESPLUSPLAYER_STATE_IDLE. + * @post esplusplayer_decoded_buffer_destroy will be invoked for video + * texturing + * @exception None + * @remark esplusplayer_decoded_buffer_destroy(). + */ +int esplusplayer_decoded_buffer_destroy( + esplusplayer_handle handle, esplusplayer_decoded_video_packet* packet); + +/** + * @brief Provided api for setting low latency mode, multiple modes can be + * set to duplicate. + * @param [in] handle : esplusplayer handle. + * @param [in] mode : one of the low latency mode to set. + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * esplusplayer_open(esplayer); + * esplusplayer_set_low_latency_mode(esplayer,ESPLUSPLAYER_LOW_LATENCY_MODE_NONE); + * // ... your codes ... + * esplusplayer_close(esplayer); + * @endcode + * @pre The player state must be set to #ESPLUSPLAYER_STATE_IDLE. + * @post None + * @exception None + * @remark esplusplayer_set_low_latency_mode(). + * if set ESPLUSPLAYER_LOW_LATENCY_MODE_DISABLE_SYNC: + * 1, esplusplayer_buffer_status_cb/ + * esplusplayer_buffer_byte_status_cb/ + * esplusplayer_buffer_time_status_cb/ + * esplusplayer_ready_to_prepare_cb/ + * esplusplayer_ready_to_seek_cb/esplusplayer_seek_done_cb + * callbacks are not invoked + * 2, If es packets are sent after esplusplayer_start() is called, + * it will be played immediately. + * @see esplusplayer_open() + */ +int esplusplayer_set_low_latency_mode(esplusplayer_handle handle, + esplusplayer_low_latency_mode mode); + +/** + * @brief Provided api for enabling video frame peek mode + * @param [in] handle : esplusplayer handle. + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * esplusplayer_open(esplayer); + * esplusplayer_set_video_frame_peek_mode(esplayer); + * // ... your codes ... + * esplusplayer_close(esplayer); + * @endcode + * @pre The player state must be set to #ESPLUSPLAYER_STATE_IDLE. + * @post None + * @exception None + * @see esplusplayer_open() \n + * esplusplayer_render_video_frame(). + */ +int esplusplayer_set_video_frame_peek_mode(esplusplayer_handle handle); + +/** + * @brief Provided api for rendering a video frame which is holded by video + * frame peek mode. + * @param [in] handle : esplusplayer handle. + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * prepare esplayer done + * // ... your codes ... + * esplusplayer_render_video_frame(esplayer); + * // ... your codes ... + * esplusplayer_stop(esplayer); + * @endcode + * @pre In order to use this api, + * The player state must be one of #ESPLUSPLAYER_STATE_READY or + * #ESPLUSPLAYER_STATE_PAUSED after esplusplayer_seek_done_cb or + * esplusplayer_prepare_async_done_cb is called \n + * @post None + * @exception None + * @see esplusplayer_set_video_frame_peek_mode() \n + * esplusplayer_prepare_async() + */ +int esplusplayer_render_video_frame(esplusplayer_handle handle); + +/** + * @brief Provided api for setting unlimited max buffer mode, the player + * does not limit es packet transmission although in buffer overrun + * status + * @param [in] handle : esplusplayer handle. + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * esplusplayer_open(esplayer); + * esplusplayer_set_unlimited_max_buffer_mode(esplayer); + * // ... your codes ... + * esplusplayer_close(esplayer); + * @endcode + * @pre The player state must be set to #ESPLUSPLAYER_STATE_IDLE. + * @post None + * @exception None + * @remark esplusplayer_set_unlimited_max_buffer_mode(). + * esplusplayer_buffer_status_cb() will be invoked in + * overrun/underrun buffer status. but + * esplusplayer_submit_packet()/esplusplayer_submit_trust_zone_packet() + * /esplusplayer_submit_encrypted_packet() + * does not return ESPLUSPLAYER_SUBMIT_STATUS_FULL + * @see esplusplayer_open() \n + * esplusplayer_submit_packet() \n + * esplusplayer_submit_trust_zone_packet() \n + * esplusplayer_submit_encrypted_packet() + */ +int esplusplayer_set_unlimited_max_buffer_mode(esplusplayer_handle handle); +/** + * @brief Provided api for enabling film maker mode. + * @param [in] handle : esplusplayer handle. + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * esplusplayer_open(esplayer); + * esplusplayer_set_fmm_mode(esplayer); + * // ... your codes ... + * esplusplayer_close(esplayer); + * @endcode + * @pre The player state must be set to #ESPLUSPLAYER_STATE_IDLE. + * @post None + * @exception None + * @see esplusplayer_open() + */ +int esplusplayer_set_fmm_mode(esplusplayer_handle handle); +/** + * @brief Provided api for setting audio codec type for playback. + * @param [in] handle : esplusplayer handle. + * @param [in] type : codec type(hardware/software). + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * esplusplayer_open(esplayer); + * esplusplayer_set_audio_codec_type(esplayer,ESPLUSPLAYER_AUDIO_CODEC_TYPE_SW); + * // ... your codes ... + * esplusplayer_close(esplayer); + * @endcode + * @pre The player state must be set to #ESPLUSPLAYER_STATE_IDLE. + When the audio stream is not set or deactivated, it can be set + in #ESPLUSPLAYER_STATE_READY, #ESPLUSPLAYER_STATE_PAUSED and + #ESPLUSPLAYER_STATE_PLAYING. The set codec type will be + applied when esplusplayer_activate() is called. + * @post None + * @exception None + * @see esplusplayer_open() \n + * esplusplayer_deactivate() \n + esplusplayer_activate() \n + esplusplayer_set_audio_stream_info() + */ +int esplusplayer_set_audio_codec_type(esplusplayer_handle handle, + esplusplayer_audio_codec_type type); +/** + * @brief Provided api for setting video codec type for playback. + * @param [in] handle : esplusplayer handle. + * @param [in] type : codec type(hardware/software). + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * esplusplayer_open(esplayer); + * esplusplayer_set_video_codec_type(esplayer,ESPLUSPLAYER_VIDEO_CODEC_TYPE_SW); + * // ... your codes ... + * esplusplayer_close(esplayer); + * @endcode + * @pre The player state must be set to #ESPLUSPLAYER_STATE_IDLE. + When the video stream is not set or deactivated, it can be set + in #ESPLUSPLAYER_STATE_READY, #ESPLUSPLAYER_STATE_PAUSED and + #ESPLUSPLAYER_STATE_PLAYING. The set codec type will be + applied when esplusplayer_activate() is called. + * @post None + * @exception None + * @see esplusplayer_open() \n + * esplusplayer_deactivate() \n + esplusplayer_activate() \n + esplusplayer_set_video_stream_info() + */ +int esplusplayer_set_video_codec_type(esplusplayer_handle handle, + esplusplayer_video_codec_type type); +/** + * @brief Provided api for setting alternative video resource(sub decoder + * and sub scaler) + * @param [in] handle : esplusplayer handle ptr. + * @param [in] rsc_type : set alternative video resource + * (@c 0 [defualt] = set all video resources(decoder/scaler) to main + * resources, + * @c 1 = set all video resources(decoder/scaler) to sub resources, + * @c 2 = set only decoder to sub resource, + * @c 3 = set only scaler to sub resource) + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * esplusplayer_open(esplayer); + * esplusplayer_set_alternative_video_resource(esplayer,1); + * // ... your codes ... + * esplusplayer_close(esplayer); + * @endcode + * @pre The player state can be all of #State except + * #ESPLUSPLAYER_STATE_NONE. + * @post None + * @exception None + * @version 3.0 + * @see esplusplayer_open() + */ +int esplusplayer_set_alternative_video_resource(esplusplayer_handle handle, + unsigned int rsc_type); + +/** + * @brief Provided api for switching audio stream between the different + * audio codec types on the fly + * @param [in] handle : esplusplayer handle ptr. + * @param [in] stream : audio stream pointer + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * prepare esplayer done + * // ... your codes ... + * esplusplayer_audio_stream_info audio_stream; + * audio_stream.mime_type = ESPLUSPLAYER_AUDIO_MIME_TYPE_AC3; + * audio_stream.sample_rate = 48000; + * audio_stream.channels = 2; + * esplusplayer_switch_audio_stream_onthefly(esplayer, + * &audio_stream); + * // ... your codes ... + * esplusplayer_close(esplayer); + * @endcode + * @pre The player state must be one of #ESPLUSPLAYER_STATE_READY, + * #ESPLUSPLAYER_STATE_PAUSED or #ESPLUSPLAYER_STATE_PLAYING + * @post None + * @exception None + * @remark Audio codec can be switched between only + * #ESPLUSPLAYER_AUDIO_MIME_TYPE_AAC, + * #ESPLUSPLAYER_AUDIO_MIME_TYPE_EAC3 + * and #ESPLUSPLAYER_AUDIO_MIME_TYPE_AC3. + * if other codec is set, this api will return false. + * @version 3.0 + * @see esplusplayer_prepare_async() + */ +int esplusplayer_switch_audio_stream_onthefly( + esplusplayer_handle handle, esplusplayer_audio_stream_info* stream); +/** + * @brief Provided api for setting aifilter + * @param [in] handle : esplusplayer handle ptr. + * @param [in] aifilter : aifilter plugin. + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * esplusplayer_open(esplayer); + * GstElement* aifilter_ = + * gst_element_factory_make("aifilter_autozoom","auto_zoom"); + * g_object_set(G_OBJECT(aifilter_), "az_cb_out_fps", 30, NULL); + * g_object_set(G_OBJECT(aifilter_), "az_inference_fps", 2, NULL); + * g_object_set(G_OBJECT(aifilter_), "az_disp_width", 1920, NULL); + * g_object_set(G_OBJECT(aifilter_), "az_disp_height", 1080, NULL); + * g_object_set(G_OBJECT(aifilter_), "az_detection_type", 2, NULL); + * g_object_set(G_OBJECT(aifilter_), "az_scaler_type", 1, NULL); + * g_object_set(G_OBJECT(aifilter_), "az_target_num", 2, NULL); + * esplusplayer_set_aifilter(esplayer,aifilter_); + * // ... your codes ... + * esplusplayer_close(esplayer); + * @endcode + * @pre The player state must be set to #ESPLUSPLAYER_STATE_IDLE. + * @post None + * @exception None + * @version 3.0 + * @see esplusplayer_open() + */ +int esplusplayer_set_aifilter(esplusplayer_handle handle, void* aifilter); + +/** + * @brief Provided api for setting render time offset + * @param [in] handle : esplusplayer handle ptr. + * @param [in] type : stream type + * @param [in] offset : offset (milisecond). + * G_MININT64 <= offset * 1000000 <= G_MAXINT64 + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * esplusplayer_open(esplayer); + * esplusplayer_set_low_latency_mode(esplayer,ESPLUSPLAYER_LOW_LATENCY_MODE_NONE); + * prepare esplayer + * // ... your codes ... + * int64_t set_offset = 10; + * esplusplayer_set_render_time_offset(esplayer,ESPLUSPLAYER_STREAM_TYPE_VIDEO, + * set_offset); + * int64_t get_offset = 0; + * esplusplayer_get_render_time_offset(esplayer_,ESPLUSPLAYER_STREAM_TYPE_VIDEO, + * &get_offset); + * // ... your codes ... + * esplusplayer_close(esplayer); + * @endcode + * @pre The player state must be set to #ESPLUSPLAYER_STATE_READY, + * #ESPLUSPLAYER_STATE_PAUSED or #ESPLUSPLAYER_STATE_PLAYING. + * It have to be set to low latency mode. (all mode except + * # ESPLUSPLAYER_LOW_LATENCY_MODE_DISABLE_SYNC) + * @remark esplusplayer_set_low_latency_mode(). + * @post None + * @exception None + * @version 3.0 + * @see esplusplayer_open() + */ +int esplusplayer_set_render_time_offset(esplusplayer_handle handle, + esplusplayer_stream_type type, + int64_t offset); +/** + * @brief Provided api for getting render time offset + * @param [in] handle : esplusplayer handle ptr. + * @param [in] type : stream type + * @param [in] offset : offset ptr (milisecond). + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @pre The player state must be set to #ESPLUSPLAYER_STATE_READY, + * #ESPLUSPLAYER_STATE_PAUSED or #ESPLUSPLAYER_STATE_PLAYING. + * It have to be set to low latency mode. + * @remark esplusplayer_set_low_latency_mode(). + * @post None + * @exception None + * @version 3.0 + * see esplusplayer_set_render_time_offset + */ +int esplusplayer_get_render_time_offset(esplusplayer_handle handle, + esplusplayer_stream_type type, + int64_t* offset); +/** + * @brief Provided api for setting catch up speed level in low latency mode + * @param [in] handle : esplusplayer handle. + * @param [in] level : speed level to catch up + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * esplusplayer_open(esplayer); + * esplusplayer_set_low_latency_mode(esplayer,ESPLUSPLAYER_LOW_LATENCY_MODE_VIDEO); + * esplusplayer_set_catch_up_speed(esplayer,ESPLUSPLAYER_CATCH_UP_SPEED_MID); + * // ... your codes ... + * esplusplayer_close(esplayer); + * @endcode + * @pre The player state must be one of #ESPLUSPLAYER_STATE_IDLE, + * #ESPLUSPLAYER_STATE_READY, #ESPLUSPLAYER_STATE_PLAYING + * or #ESPLUSPLAYER_STATE_PAUSED + * esplusplayer_set_low_latency_mode() should be called as below + * before this api is called. + * esplusplayer_set_low_latency_mode(handle, + * ESPLUSPLAYER_LOW_LATENCY_MODE_AUDIO), + * esplusplayer_set_low_latency_mode(handle, + * ESPLUSPLAYER_LOW_LATENCY_MODE_VIDEO), + * esplusplayer_set_low_latency_mode(handle, + * ESPLUSPLAYER_LOW_LATENCY_MODE_DISABLE_SYNC), + * esplusplayer_set_low_latency_mode(handle, + * ESPLUSPLAYER_LOW_LATENCY_MODE_DISABLE_VIDEO_QUALITY) + * @post None + * @exception None + * @version 3.0 + * @see esplusplayer_open() + */ +int esplusplayer_set_catch_up_speed(esplusplayer_handle handle, + esplusplayer_catch_up_speed level); + +/** + * @brief Provided api for getting current video latency status + * @param [in] handle : esplusplayer handle. + * @param [out] status : current latency status + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * prepare esplayer done + * // ... your codes ... + * esplusplayer_latency_status current_status = + * ESPLUSPLAYER_LATENCY_LOW; + * esplusplayer_get_video_latency_status(esplayer, ¤t_status); + * // ... your codes ... + * esplusplayer_stop(esplayer); + * @endcode + * @pre The player state must be one of #ESPLUSPLAYER_STATE_READY, + * #ESPLUSPLAYER_STATE_PLAYING or #ESPLUSPLAYER_STATE_PAUSED + * @post None + * @exception None + * @version 3.0 + * @see esplusplayer_prepare_async() + */ +int esplusplayer_get_video_latency_status(esplusplayer_handle handle, + esplusplayer_latency_status* status); + +/** + * @brief Provided api for getting current audio latency status + * @param [in] handle : esplusplayer handle. + * @param [out] status : current latency status + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * prepare esplayer done + * // ... your codes ... + * esplusplayer_latency_status current_status = + * ESPLUSPLAYER_LATENCY_LOW; + * esplusplayer_get_audio_latency_status(esplayer, ¤t_status); + * // ... your codes ... + * esplusplayer_stop(esplayer); + * @endcode + * @pre The player state must be one of #ESPLUSPLAYER_STATE_READY, + * #ESPLUSPLAYER_STATE_PLAYING or #ESPLUSPLAYER_STATE_PAUSED + * @post None + * @exception None + * @version 3.0 + * @see esplusplayer_prepare_async() + */ +int esplusplayer_get_audio_latency_status(esplusplayer_handle handle, + esplusplayer_latency_status* status); + +/** + * @brief Provided api for setting video mid latency threshold for low + * latency + * playback + * @param [in] handle : esplusplayer handle. + * @param [in] threshold: the threshold(number) of the video frames for mid + * latency. + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * esplusplayer_open(esplayer); + * esplusplayer_set_low_latency_mode(esplayer,ESPLUSPLAYER_LOW_LATENCY_MODE_VIDEO); + * esplusplayer_set_video_mid_latency_threshold(esplayer,2); + * // ... your codes ... + * esplusplayer_close(esplayer); + * @endcode + * @pre The player state must be one of #ESPLUSPLAYER_STATE_IDLE, + * #ESPLUSPLAYER_STATE_READY, #ESPLUSPLAYER_STATE_PLAYING + * or #ESPLUSPLAYER_STATE_PAUSED + * esplusplayer_set_low_latency_mode() should be called as below + * before this api is called. + * esplusplayer_set_low_latency_mode(handle, + * ESPLUSPLAYER_LOW_LATENCY_MODE_AUDIO), + * esplusplayer_set_low_latency_mode(handle, + * ESPLUSPLAYER_LOW_LATENCY_MODE_VIDEO), + * esplusplayer_set_low_latency_mode(handle, + * ESPLUSPLAYER_LOW_LATENCY_MODE_DISABLE_SYNC), + * esplusplayer_set_low_latency_mode(handle, + * ESPLUSPLAYER_LOW_LATENCY_MODE_ENABLE_GAME_MODE) + * @post None + * @exception None + * @version 3.0 + * @see esplusplayer_open() + */ +/// TODO:: set the min/max value of the threshold +int esplusplayer_set_video_mid_latency_threshold(esplusplayer_handle handle, + const unsigned int threshold); + +/** + * @brief Provided api for setting audio mid latency threshold for low + * latency + * playback + * @param [in] handle : esplusplayer handle. + * @param [in] threshold: the threshold(number) of the audio frames for mid + * latency. + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * esplusplayer_open(esplayer); + * esplusplayer_set_low_latency_mode(esplayer,ESPLUSPLAYER_LOW_LATENCY_MODE_AUDIO); + * esplusplayer_set_audio_mid_latency_threshold(esplayer,2); + * // ... your codes ... + * esplusplayer_close(esplayer); + * @endcode + * @pre The player state must be one of #ESPLUSPLAYER_STATE_IDLE, + * #ESPLUSPLAYER_STATE_READY, #ESPLUSPLAYER_STATE_PLAYING + * or #ESPLUSPLAYER_STATE_PAUSED + * esplusplayer_set_low_latency_mode() should be called as below + * before this api is called. + * esplusplayer_set_low_latency_mode(handle, + * ESPLUSPLAYER_LOW_LATENCY_MODE_AUDIO), + * esplusplayer_set_low_latency_mode(handle, + * ESPLUSPLAYER_LOW_LATENCY_MODE_VIDEO), + * esplusplayer_set_low_latency_mode(handle, + * ESPLUSPLAYER_LOW_LATENCY_MODE_DISABLE_SYNC), + * esplusplayer_set_low_latency_mode(handle, + * ESPLUSPLAYER_LOW_LATENCY_MODE_ENABLE_GAME_MODE) + * @post None + * @exception None + * @version 3.0 + * @see esplusplayer_open() + */ +/// TODO:: set the min/max value of the threshold +int esplusplayer_set_audio_mid_latency_threshold(esplusplayer_handle handle, + const unsigned int threshold); + +/** + * @brief Provided api for setting video high latency threshold for low + * latency + * playback + * @param [in] handle : esplusplayer handle. + * @param [in] threshold: the threshold(number) of the video frames for high + * latency. + * @param [in] video_high_latency_cb : high latency callback function to + * register + * @param [in] userdata : userdata of esplusplayer_high_latency_cb() + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * static void OnVideoHighLatency(void* userdata) { + * printf ("OnVideoHighLatency\n"); + * } + * esplusplayer_open(esplayer); + * esplusplayer_set_low_latency_mode(esplayer,ESPLUSPLAYER_LOW_LATENCY_MODE_VIDEO); + * esplusplayer_set_video_high_latency_threshold(esplayer, 2, + * OnVideoHighLatency, nullptr); + * // ... your codes ... + * esplusplayer_close(esplayer); + * @endcode + * @pre The player state must be one of #ESPLUSPLAYER_STATE_IDLE, + * #ESPLUSPLAYER_STATE_READY, #ESPLUSPLAYER_STATE_PLAYING + * or #ESPLUSPLAYER_STATE_PAUSED + * esplusplayer_set_low_latency_mode() should be called as below + * before this api is called. + * esplusplayer_set_low_latency_mode(handle, + * ESPLUSPLAYER_LOW_LATENCY_MODE_AUDIO), + * esplusplayer_set_low_latency_mode(handle, + * ESPLUSPLAYER_LOW_LATENCY_MODE_VIDEO), + * esplusplayer_set_low_latency_mode(handle, + * ESPLUSPLAYER_LOW_LATENCY_MODE_DISABLE_SYNC), + * esplusplayer_set_low_latency_mode(handle, + * ESPLUSPLAYER_LOW_LATENCY_MODE_ENABLE_GAME_MODE) + * @post None + * @exception None + * @version 3.0 + * @see esplusplayer_open() + */ +/// TODO:: set the min/max value of the threshold +int esplusplayer_set_video_high_latency_threshold( + esplusplayer_handle handle, const unsigned int threshold, + esplusplayer_video_high_latency_cb video_high_latency_cb, void* userdata); + +/** + * @brief Provided api for setting audio high latency threshold for low + * latency + * playback + * @param [in] handle : esplusplayer handle. + * @param [in] threshold: the threshold(number) of the audio frames for high + * latency. + * @param [in] audio_high_latency_cb : high latency callback function to + * register + * @param [in] userdata : userdata of esplusplayer_high_latency_cb() + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * static void OnAudioHighLatency(void* userdata) { + * printf ("OnAudioHighLatency\n"); + * } + * esplusplayer_open(esplayer); + * esplusplayer_set_low_latency_mode(esplayer,ESPLUSPLAYER_LOW_LATENCY_MODE_AUDIO); + * esplusplayer_set_audio_high_latency_threshold(esplayer, 2, + * OnAudioHighLatency, nullptr); + * // ... your codes ... + * esplusplayer_close(esplayer); + * @endcode + * @pre The player state must be one of #ESPLUSPLAYER_STATE_IDLE, + * #ESPLUSPLAYER_STATE_READY, #ESPLUSPLAYER_STATE_PLAYING + * or #ESPLUSPLAYER_STATE_PAUSED + * esplusplayer_set_low_latency_mode() should be called as below + * before this api is called. + * esplusplayer_set_low_latency_mode(handle, + * ESPLUSPLAYER_LOW_LATENCY_MODE_AUDIO), + * esplusplayer_set_low_latency_mode(handle, + * ESPLUSPLAYER_LOW_LATENCY_MODE_VIDEO), + * esplusplayer_set_low_latency_mode(handle, + * ESPLUSPLAYER_LOW_LATENCY_MODE_DISABLE_SYNC), + * esplusplayer_set_low_latency_mode(handle, + * ESPLUSPLAYER_LOW_LATENCY_MODE_ENABLE_GAME_MODE) + * @post None + * @exception None + * @version 3.0 + * @see esplusplayer_open() + */ +/// TODO:: set the min/max value of the threshold +int esplusplayer_set_audio_high_latency_threshold( + esplusplayer_handle handle, const unsigned int threshold, + esplusplayer_audio_high_latency_cb audio_high_latency_cb, void* userdata); + +/** + * @brief Initialize easing info to esplayer. + * @param [in] handle : esplusplayer handle. + * @param [in] init_volume : initial easing volume (0 ~ 100). + * @param [in] elapsed_time : initial elapsed time (millisecond). + * @param [in] easing_info : target volume, duration, type. + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * esplusplayer_open(esplayer); + * uint32_t volume = 50, elapsed_time = 10000; + * esplusplayer_target_audio_easing_info easing_info; + * easing_info.volume = 30; + * easing_info.duration = 100; + * easing_info.type = ESPLUSPLAYER_AUDIO_EASING_INCUBIC; + * esplusplayer_init_audio_easing_info(esplayer,volume,elapsed_time,&easing_info); + * // ... your codes ... + * esplusplayer_close(esplayer); + * @endcode + * @pre The player state can be all of #esplusplayer_state except + * #ESPLUSPLAYER_STATE_NONE. + * @post None + * @exception None + * @version 3.0 + * @see esplusplayer_open() + */ +int esplusplayer_init_audio_easing_info( + esplusplayer_handle handle, uint32_t init_volume, uint32_t elapsed_time, + const esplusplayer_target_audio_easing_info* easing_info); + +/** + * @brief Update easing info to esplayer to update target info. + * @param [in] handle : esplusplayer handle. + * @param [in] easing_info : target volume, duration, type. + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * esplusplayer_open(esplayer); + * // ... your codes ... + * esplusplayer_target_audio_easing_info easing_info; + * easing_info.volume = 30; + * easing_info.duration = 100; + * easing_info.type = ESPLUSPLAYER_AUDIO_EASING_INCUBIC; + * esplusplayer_update_audio_easing_info(esplayer,&easing_info); + * // ... your codes ... + * esplusplayer_close(esplayer); + * @endcode + * @pre The player state can be all of #esplusplayer_state except + * #ESPLUSPLAYER_STATE_NONE. + * This api should be called after + * esplusplayer_init_audio_easing_info() is called + * @post None + * @exception None + * @version 3.0 + * @see esplusplayer_open() + */ +int esplusplayer_update_audio_easing_info( + esplusplayer_handle handle, + const esplusplayer_target_audio_easing_info* easing_info); + +/** + * @brief Get easing info currently in easing operation from esplayer + * @param [in] handle : esplusplayer handle. + * @param [out] current_volume : current volume (0 ~ 100). + * @param [out] elapsed_time : elapsed time (millisecond). + * @param [out] easing_info : target volume, duration, type. + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * esplusplayer_open(esplayer); + * // ... your codes ... + * uint32_t cur_volume = 50, elapsed_time = 0; + * esplusplayer_target_audio_easing_info easing_info; + * esplusplayer_get_audio_easing_info(esplayer,&cur_volume,&elapsed_time,&easing_info); + * // ... your codes ... + * esplusplayer_close(esplayer); + * @endcode + * @pre The player state can be all of #esplusplayer_state except + * #ESPLUSPLAYER_STATE_NONE. + * This api should be called after + * esplusplayer_init_audio_easing_info() is called + * @post None + * @exception None + * @version 3.0 + * @see esplusplayer_open() + + */ +int esplusplayer_get_audio_easing_info( + esplusplayer_handle handle, uint32_t* current_volume, + uint32_t* elapsed_time, esplusplayer_target_audio_easing_info* easing_info); + +/** + * @brief Start audio easing using a registered audio easing info + * @param [in] handle : esplusplayer handle. + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * prepare esplayer done + * uint32_t volume = 50, elapsed_time = 10000; + * esplusplayer_target_audio_easing_info easing_info; + * easing_info.volume = 30; + * easing_info.duration = 100; + * easing_info.type = ESPLUSPLAYER_AUDIO_EASING_INCUBIC; + * esplusplayer_init_audio_easing_info(esplayer,volume,elapsed_time,&easing_info); + * esplusplayer_start_audio_easing(esplayer); + * // ... your codes ... + * esplusplayer_close(esplayer); + * @endcode + * @pre The player state should be at least #ESPLUSPLAYER_STATE_READY. + * This api should be called after + * esplusplayer_init_audio_easing_info() is called + * @post None + * @exception None + * @version 3.0 + * @see esplusplayer_open() \n + * esplusplayer_init_audio_easing_info() \n + * esplusplayer_update_audio_easing_info() \n + * esplusplayer_stop_audio_easing() \n + * esplusplayer_prepare_async() + */ +int esplusplayer_start_audio_easing(esplusplayer_handle handle); + +/** + * @brief Stop audio easing + * @param [in] handle : esplusplayer handle. + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * esplusplayer_open(esplayer); + * // ... your codes ... + * esplusplayer_stop_audio_easing(esplayer); + * // ... your codes ... + * esplusplayer_close(esplayer); + * @endcode + * @pre The player state can be all of #esplusplayer_state except + * #ESPLUSPLAYER_STATE_NONE. + * This api should be called after + * esplusplayer_init_audio_easing_info() is called + * @post None + * @exception None + * @version 3.0 + * @see esplusplayer_open() \n + * esplusplayer_start_audio_easing() + */ +int esplusplayer_stop_audio_easing(esplusplayer_handle handle); + +/** + * @brief Get virtual resource id + * @param [in] handle : esplusplayer handle. + * @param [in] type : The resource type of virtual id. + * @param [out] virtual_id : Stored virtual resource id value. + * @return @c ESPLUSPLAYER_ERROR_TYPE_NONE on success,otherwise @c one of esplusplayer_error_type + * values will be returned. + * @retval #ESPLUSPLAYER_ERROR_TYPE_NONE Successful + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER Invalid parameter + * @retval #ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION Internal operation failed + * @code + * prepare esplayer done + * // ... your codes ... + * int virtual_id; + * esplusplayer_get_virtual_rsc_id(esplayer,ESPLUSPLAYER_RSC_TYPE_VIDEO_RENDERER,&virtual_id); + * // ... your codes ... + * esplusplayer_close(esplayer); + * esplusplayer_destroy(esplayer); + * @endcode + * @pre The player state should be #State::kReady, #State::kPlaying or + * #State::kPaused + * @post None + * @return @c True on success, otherwise @c False ("virtual_id" will be -1) + * @exception None + * @version 3.0 + * @remark This function returns virtual resource id which player is + * allocated from resource manager. For example, virtual scaler id is + * required for an application to use capture API directly. + * @see esplusplayer_prepare_async() + + */ +int esplusplayer_get_virtual_rsc_id(esplusplayer_handle handle, + const esplusplayer_rsc_type type, + int* virtual_id); + +/** + * @brief Set advanced picture quality type. + * @param [in] handle : esplusplayer handle. + * @param [in] type : The picture quality type. + * @pre The player state must be set to #ESPLUSPLAYER_STATE_IDLE. + * @post None + * @return @c one of esplusplayer_error_type values will be returned. + * @exception None + * @version 3.1 + */ +int esplusplayer_set_advanced_picture_quality_type( + esplusplayer_handle handle, + esplusplayer_advanced_picture_quality_type type); + +/** + * @brief Set resource allocate policy. + * @param [in] handle : esplusplayer handle. + * @param [in] policy : The resource allocate policy. + * @pre The player state must be set to #ESPLUSPLAYER_STATE_IDLE. + * @post None + * @return @c one of esplusplayer_error_type values will be returned. + * @exception None + * @version 3.2 + */ +int esplusplayer_set_resource_allocate_policy( + esplusplayer_handle handle, esplusplayer_rsc_alloc_policy policy); + +#ifdef __cplusplus +} +#endif + +#endif // __PLUSPLAYER_ESPLUSPLAYER_CAPI_ESPLUSPLAYER_CAPI_H__ diff --git a/include/esplusplayer_capi/esplusplayer_internal.h b/include/esplusplayer_capi/esplusplayer_internal.h new file mode 100755 index 0000000..a50d4ae --- /dev/null +++ b/include/esplusplayer_capi/esplusplayer_internal.h @@ -0,0 +1,174 @@ +/** + * @file esplusplayer_internal.h + * @brief EsPlusPlayer internally used api c version + * @interfacetype module + * @privlevel None-privilege + * @privilege None + * @product TV, AV, B2B + * @version 2.0 + * @SDK_Support N + * @remark This is esplusplayer api header implemented as C style to + * avoid binary compatibility issues. + * + * Copyright (c) 2020 Samsung Electronics Co., Ltd All Rights Reserved + * PROPRIETARY/CONFIDENTIAL + * This software is the confidential and proprietary + * information of SAMSUNG ELECTRONICS ("Confidential Information"). You shall + * not disclose such Confidential Information and shall use it only in + * accordance with the terms of the license agreement you entered into with + * SAMSUNG ELECTRONICS. SAMSUNG make no representations or warranties about the + * suitability of the software, either express or implied, including but not + * limited to the implied warranties of merchantability, fitness for a + * particular purpose, or non-infringement. SAMSUNG shall not be liable for any + * damages suffered by licensee as a result of using, modifying or distributing + * this software or its derivatives. + */ + +#ifndef __PLUSPLAYER_ESPLUSPLAYER_CAPI_ESPLUSPLAYER_INTERNAL_H__ +#define __PLUSPLAYER_ESPLUSPLAYER_CAPI_ESPLUSPLAYER_INTERNAL_H__ + +#include "esplusplayer_capi/display.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +typedef void (*esplusplayer_decoder_underrun_cb)(void* userdata); +typedef void* esplusplayer_handle; +typedef void (*esplusplayer_first_video_decoding_done_cb)(void*); + +/** + * @brief Set the video display. + * @param [in] handle : esplusplayer handle. + * @param [in] type : display type. + * @param [in] window : the ecore wayland window handle. + * @param [in] x : the x coordinate of window. + * @param [in] y : the ycoordinate of window. + * @param [in] width : the width of window. + * @param [in] height : the height of window. + * @return @c one of esplusplayer_error_type values will be returned. + * @pre The player state can be all of #esplusplayer_state except + * #ESPLUSPLAYER_STATE_NONE. + * @post None + * @exception None + * @see esplusplayer_set_display_mode() \n + * esplusplayer_set_display_roi() \n + * esplusplayer_set_display_visible() + */ +int esplusplayer_set_ecore_display(esplusplayer_handle handle, + esplusplayer_display_type type, void* window, + int x, int y, int width, int height); +/** + * @brief Set the video display. + * @param [in] handle : esplusplayer handle. + * @param [in] type : display type. + * @param [in] surface_id : resource id of window. + * @param [in] x : the x coordinate of window. + * @param [in] y : the ycoordinate of window. + * @param [in] width : the width of window. + * @param [in] height : the height of window. + * @return @c one of esplusplayer_error_type values will be returned. + * @pre The player state can be all of #esplusplayer_state except + * #ESPLUSPLAYER_STATE_NONE. + * @post None + * @exception None + * @see esplusplayer_set_display_mode() \n + * esplusplayer_set_display_roi() \n + * esplusplayer_set_display_visible() + */ +int esplusplayer_set_surface_display(esplusplayer_handle handle, + esplusplayer_display_type type, + unsigned int surface_id, int x, int y, + int width, int height); + +int esplusplayer_set_first_video_decoding_done_cb( + esplusplayer_handle handle, + esplusplayer_first_video_decoding_done_cb first_video_decoding_done_cb, + void* userdata); + +/** + * @brief Set a callback function to be invoked when buffer underrun is + * occurred from a video decoder. + * @param [in] handle : esplusplayer handle. + * @param [in] callback : the callback function to register. + * @param [in] userdata : userdata of esplusplayer_decoder_underrun_cb() + * @return @c one of esplusplayer_error_type values will be returned. + * @pre The player state must be set to #ESPLUSPLAYER_STATE_NONE or + * #ESPLUSPLAYER_STATE_IDLE. + * @post esplusplayer_decoder_underrun_cb() will be invoked. + * @exception None + * @remark esplusplayer_decoder_underrun_cb(). + * if video_decoder_underrun_cb is set to null, + * esplusplayer_decoder_underrun_cb() will not be invoked anymore. + */ +int esplusplayer_set_video_decoder_underrun_cb( + esplusplayer_handle handle, + esplusplayer_decoder_underrun_cb video_decoder_underrun_cb, void* userdata); + +/** + * @brief Get the size of struct esplusplayer_app_info + * @param None + * @return Total size of struct esplusplayer_app_info + * @pre None + * @post None + * @exception None + */ +int get_size_of_esplusplayer_app_info(void); + +/** + * @brief Get the size of struct esplusplayer_es_packet + * @param None + * @return Total size of struct esplusplayer_es_packet + * @pre None + * @post None + * @exception None + */ +int get_size_of_esplusplayer_es_packet(void); + +/** + * @brief Get the size of struct esplusplayer_es_tz_packet + * @param None + * @return Total size of struct esplusplayer_es_tz_packet + * @pre None + * @post None + * @exception None + */ +int get_size_of_esplusplayer_es_tz_packet(void); + +/** + * @brief Get the size of struct esplusplayer_audio_stream_info + * @param None + * @return Total size of struct esplusplayer_audio_stream_info + * @pre None + * @post None + * @exception None + */ +int get_size_of_esplusplayer_audio_stream_info(void); + +/** + * @brief Get the size of struct esplusplayer_video_stream_info + * @param None + * @return Total size of struct esplusplayer_video_stream_info + * @pre None + * @post None + * @exception None + */ +int get_size_of_esplusplayer_video_stream_info(void); + +/** + * @brief Get the size of struct esplusplayer_drm_info + * @param None + * @return Total size of struct esplusplayer_drm_info + * @pre None + * @post None + * @exception None + */ +int get_size_of_esplusplayer_drm_info(void); + +#ifdef __cplusplus +} +#endif + +#endif // __PLUSPLAYER_ESPLUSPLAYER_CAPI_ESPLUSPLAYER_INTERNAL_H__ diff --git a/include/esplusplayer_capi/event.h b/include/esplusplayer_capi/event.h new file mode 100755 index 0000000..3278991 --- /dev/null +++ b/include/esplusplayer_capi/event.h @@ -0,0 +1,63 @@ +/** + * @file + * @brief The event for playback. + * @interfacetype Platform + * @privlevel None-privilege + * @privilege None + * @product TV, AV, B2B + * @version 2.0 + * @SDK_Support N + * @remark This is a group of C style event related enum and structure. + * @see N/A + * + * Copyright (c) 2020 Samsung Electronics Co., Ltd All Rights Reserved + * PROPRIETARY/CONFIDENTIAL + * This software is the confidential and proprietary + * information of SAMSUNG ELECTRONICS ("Confidential Information"). You shall + * not disclose such Confidential Information and shall use it only in + * accordance with the terms of the license agreement you entered into with + * SAMSUNG ELECTRONICS. SAMSUNG make no representations or warranties about the + * suitability of the software, either express or implied, including but not + * limited to the implied warranties of merchantability, fitness for a + * particular purpose, or non-infringement. SAMSUNG shall not be liable for any + * damages suffered by licensee as a result of using, modifying or distributing + * this software or its derivatives. + */ + +#ifndef __PLUSPLAYER_ESPLUSPLAYER_CAPI_EVENT_H__ +#define __PLUSPLAYER_ESPLUSPLAYER_CAPI_EVENT_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Event message + */ +typedef struct { + /** + * @description event message data + * eg) ESPLUSPLAYER_EVENT_RESOLUTION_CHANGED : "1920x1080" + */ + char* data; + /** + * @description the length of event message data + */ + uint64_t len; +} esplusplayer_event_msg; + +/** + * @brief Enumerations for event message types + */ +enum esplusplayer_event_type { + ESPLUSPLAYER_EVENT_NONE, + ESPLUSPLAYER_EVENT_RESOLUTION_CHANGED, +}; + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // __PLUSPLAYER_ESPLUSPLAYER_CAPI_EVENT_H__ diff --git a/include/esplusplayer_capi/latency.h b/include/esplusplayer_capi/latency.h new file mode 100755 index 0000000..7548a6f --- /dev/null +++ b/include/esplusplayer_capi/latency.h @@ -0,0 +1,56 @@ +/** + * @file + * @brief Latency enum. + * @interfacetype Platform + * @privlevel None-privilege + * @privilege None + * @product TV, AV, B2B + * @version 3.0 + * @SDK_Support N + * @remark This is a group of C style state related enum. + * @see State enum convertion. + * + * Copyright (c) 2020 Samsung Electronics Co., Ltd All Rights Reserved + * PROPRIETARY/CONFIDENTIAL + * This software is the confidential and proprietary + * information of SAMSUNG ELECTRONICS ("Confidential Information"). You shall + * not disclose such Confidential Information and shall use it only in + * accordance with the terms of the license agreement you entered into with + * SAMSUNG ELECTRONICS. SAMSUNG make no representations or warranties about the + * suitability of the software, either express or implied, including but not + * limited to the implied warranties of merchantability, fitness for a + * particular purpose, or non-infringement. SAMSUNG shall not be liable for any + * damages suffered by licensee as a result of using, modifying or distributing + * this software or its derivatives. + */ + +#ifndef __PLUSPLAYER_ESPLUSPLAYER_CAPI_LATENCY_H__ +#define __PLUSPLAYER_ESPLUSPLAYER_CAPI_LATENCY_H__ + +#ifdef __cplusplus +extern "C" { +#endif +/** + * @brief Enumerations for esplusplayer catch up speed + */ +enum esplusplayer_catch_up_speed { + ESPLUSPLAYER_CATCH_UP_SPEED_NONE, /**< do not use catch up mode */ + ESPLUSPLAYER_CATCH_UP_SPEED_SLOW, /**< catch up speed is slow */ + ESPLUSPLAYER_CATCH_UP_SPEED_MID, /**< catch up speed is normal */ + ESPLUSPLAYER_CATCH_UP_SPEED_FAST /**< catch up speed is fast */ +}; + +/** + * @brief Enumerations for esplusplayer latency status + */ +enum esplusplayer_latency_status { + ESPLUSPLAYER_LATENCY_LOW, /**< latency status is low */ + ESPLUSPLAYER_LATENCY_MID, /**< latency status is middle */ + ESPLUSPLAYER_LATENCY_HIGH /**< latency status is high */ +}; + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // __PLUSPLAYER_ESPLUSPLAYER_CAPI_LATENCY_H__ diff --git a/include/esplusplayer_capi/matroska_color.h b/include/esplusplayer_capi/matroska_color.h new file mode 100755 index 0000000..4b9b828 --- /dev/null +++ b/include/esplusplayer_capi/matroska_color.h @@ -0,0 +1,170 @@ +/** + * @file + * @brief The matroska color info + * @interfacetype Platform + * @privlevel None-privilege + * @privilege None + * @product TV, AV, B2B + * @version 2.0 + * @SDK_Support N + * @see plusplayer::EsPlusPlayer class + * + * Copyright (c) 2020 Samsung Electronics Co., Ltd All Rights Reserved + * PROPRIETARY/CONFIDENTIAL + * This software is the confidential and proprietary + * information of SAMSUNG ELECTRONICS ("Confidential Information"). You shall + * not disclose such Confidential Information and shall use it only in + * accordance with the terms of the license agreement you entered into with + * SAMSUNG ELECTRONICS. SAMSUNG make no representations or warranties about the + * suitability of the software, either express or implied, including but not + * limited to the implied warranties of merchantability, fitness for a + * particular purpose, or non-infringement. SAMSUNG shall not be liable for any + * damages suffered by licensee as a result of using, modifying or distributing + * this software or its derivatives. + */ + +#ifndef __PLUSPLAYER_ESPLUSPLAYER_CAPI_MATROSKA_COLOR_H__ +#define __PLUSPLAYER_ESPLUSPLAYER_CAPI_MATROSKA_COLOR_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Structure of matroska matering metadata + */ +typedef struct { + /** + * @description Red X chromaticity coordinate as defined by CIE 1931. + */ + double primary_r_chromaticity_x; + /** + * @description Red Y chromaticity coordinate as defined by CIE 1931. + */ + double primary_r_chromaticity_y; + /** + * @description Green X chromaticity coordinate as defined by CIE 1931. + */ + double primary_g_chromaticity_x; + /** + * @description Green Y chromaticity coordinate as defined by CIE 1931. + */ + double primary_g_chromaticity_y; + /** + * @description Blue X chromaticity coordinate as defined by CIE 1931. + */ + double primary_b_chromaticity_x; + /** + * @description Blue Y chromaticity coordinate as defined by CIE 1931. + */ + double primary_b_chromaticity_y; + /** + * @description White X chromaticity coordinate as defined by CIE 1931. + */ + double white_point_chromaticity_x; + /** + * @description White Y chromaticity coordinate as defined by CIE 1931. + */ + double white_point_chromaticity_y; + /** + * @description Maximum luminance. Represented in candelas per square meter + * (cd/m²). + */ + double luminance_max; + /** + * @description Mininum luminance. Represented in candelas per square meter + * (cd/m²). + */ + double luminance_min; +} esplusplayer_matroska_mastering_metadata; + +/** + * @brief Structure of matroska color information + */ +typedef struct { + /** + * @description The Matrix Coefficients of the video used to derive luma and + * chroma values from red, green, and blue color primaries. For clarity, the + * value and meanings for MatrixCoefficients are adopted from Table 4 of + * ISO/IEC 23001-8:2013/DCOR1. + */ + uint32_t matrix_coefficients; + /** + * @description Number of decoded bits per channel. A value of 0 indicates + * that the BitsPerChannel is unspecified. + */ + uint32_t bits_per_channel; + /** + * @description The amount of pixels to remove in the Cr and Cb channels for + * every pixel not removed horizontally. + */ + uint32_t chroma_subsampling_horizontal; + /** + * @description The amount of pixels to remove in the Cr and Cb channels for + * every pixel not removed vertically. + */ + uint32_t chroma_subsampling_vertical; + /** + * @description The amount of pixels to remove in the Cb channel for every + * pixel not removed horizontally. This is additive with + * chroma_subsampling_horizontal. + */ + uint32_t cb_subsampling_horizontal; + /** + * @description The amount of pixels to remove in the Cb channel for every + * pixel not removed vertically. This is additive with + * chroma_subsampling_vertical. + */ + uint32_t cb_subsampling_vertical; + /** + * @description How chroma is subsampled horizontally. + */ + uint32_t chroma_siting_horizontal; + /** + * @description How chroma is subsampled vertically. + */ + uint32_t chroma_siting_vertical; + /** + * @description Clipping of the color ranges. + */ + uint32_t range; + /** + * @description The transfer characteristics of the video. For clarity, the + * value and meanings for transfer_characteristics 1-15 are adopted from Table + * 3 of ISO/IEC 23001-8:2013/DCOR1. transfer_characteristics 16-18 are + * proposed values. + */ + uint32_t transfer_characteristics; + /** + * @description The colour primaries of the video. For clarity, the value + * and meanings for primaries are adopted from Table 2 of ISO/IEC + * 23001-8:2013/DCOR1. + */ + uint32_t primaries; + /** + * @description Maximum brightness of a single pixel (Maximum Content Light + * Level) in candelas per square meter (cd/m²). + */ + uint32_t max_cll; + /** + * @description Maximum brightness of a single full frame (Maximum + * Frame-Average Light Level) in candelas per square meter (cd/m²). + */ + uint32_t max_fall; + /** + * @description SMPTE 2086 mastering data. + */ + esplusplayer_matroska_mastering_metadata metadata; + /** + * @description flag to check if this file is hdr10+ (cd/m²). + */ + uint32_t isHDR10p; +} esplusplayer_matroska_color; + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // __PLUSPLAYER_ESPLUSPLAYER_CAPI_MATROSKA_COLOR_H__ diff --git a/include/esplusplayer_capi/state.h b/include/esplusplayer_capi/state.h new file mode 100755 index 0000000..95048d7 --- /dev/null +++ b/include/esplusplayer_capi/state.h @@ -0,0 +1,51 @@ +/** + * @file + * @brief State enum. + * @interfacetype Platform + * @privlevel None-privilege + * @privilege None + * @product TV, AV, B2B + * @version 2.0 + * @SDK_Support N + * @remark This is a group of C style state related enum. + * @see State enum convertion. + * + * Copyright (c) 2020 Samsung Electronics Co., Ltd All Rights Reserved + * PROPRIETARY/CONFIDENTIAL + * This software is the confidential and proprietary + * information of SAMSUNG ELECTRONICS ("Confidential Information"). You shall + * not disclose such Confidential Information and shall use it only in + * accordance with the terms of the license agreement you entered into with + * SAMSUNG ELECTRONICS. SAMSUNG make no representations or warranties about the + * suitability of the software, either express or implied, including but not + * limited to the implied warranties of merchantability, fitness for a + * particular purpose, or non-infringement. SAMSUNG shall not be liable for any + * damages suffered by licensee as a result of using, modifying or distributing + * this software or its derivatives. + */ + +#ifndef __PLUSPLAYER_ESPLUSPLAYER_CAPI_STATE_H__ +#define __PLUSPLAYER_ESPLUSPLAYER_CAPI_STATE_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Enumerations for es player state. + */ +enum esplusplayer_state { + ESPLUSPLAYER_STATE_NONE, /** + +#include + +namespace plusplayer { + +struct DecodedRawPlaneInfo { + std::uint32_t phyaddr; + std::uint32_t viraddr; + std::uint32_t linesize; +}; + +struct DecodedRawInfo { + std::uint32_t width; + std::uint32_t height; + DecodedRawPlaneInfo y_info; + DecodedRawPlaneInfo uv_info; +}; + +struct DecodedVideoKeyTypeInfo { + std::uint32_t width; + std::uint32_t height; + tbm_key key; +}; + +} // namespace plusplayer + +#endif // __PLUSPLAYER_MIXER_DECODED_VIDEO_INFO_H__ diff --git a/include/mixer/mixer.h b/include/mixer/mixer.h new file mode 100755 index 0000000..892c78a --- /dev/null +++ b/include/mixer/mixer.h @@ -0,0 +1,271 @@ +/** + * @file mixer.h + * @brief Mixes raw video frame and rendering mixed frame + * @interfacetype Module + * @privlevel None-privilege + * @privilege None + * @product TV, AV, B2B + * @version 0.0.1 + * @SDK_Support N + * + * Copyright (c) 2020 Samsung Electronics Co., Ltd All Rights Reserved + * PROPRIETARY/CONFIDENTIAL + * This software is the confidential and proprietary + * information of SAMSUNG ELECTRONICS ("Confidential Information"). You shall + * not disclose such Confidential Information and shall use it only in + * accordance with the terms of the license agreement you entered into with + * SAMSUNG ELECTRONICS. SAMSUNG make no representations or warranties about the + * suitability of the software, either express or implied, including but not + * limited to the implied warranties of merchantability, fitness for a + * particular purpose, or non-infringement. SAMSUNG shall not be liable for any + * damages suffered by licensee as a result of using, modifying or distributing + * this software or its derivatives. + */ + +#ifndef __PLUSPLAYER_MIXER_MIXER__H__ +#define __PLUSPLAYER_MIXER_MIXER__H__ + +#include +#include + +#include "mixer/mixer_eventlistener.h" +#include "mixer/mixerticket.h" +#include "plusplayer/types/display.h" + +namespace plusplayer { +/** + * @brief Enumerations for Resource allocation policy + */ +enum class RscAllocMode { + kDefault, /**< Main -> Sub -> S/W */ + kNdecoder, /**< Only N decoder */ + kDisable /**< Mixer is NOT involved in resource allocation */ +}; +/** + * @brief Class Mixer + */ +/** + * @brief Provides methods to control mixer pipeline and modify + * mixed frame. + * @remark It internally manages MixerTicket objects which are + * combined with player objects. + * @see Mixerticket + */ +class Mixer : private boost::noncopyable { + public: + using Ptr = std::unique_ptr; + /** + * @brief Create a mixer object + * @remarks You must use this to get mixer object + * @pre None + * @post None + * @exception None + * @return mixer object (unique_ptr) + */ + static Ptr Create(); + /** + * @brief Struct for Resolution info of mixed frame. + * @brief Default info is 1920x1080, 30fps + */ + struct ResolutionInfo { + int width = 1920; /**< Width of mixed frame */ + int height = 1080; /**< Height of mixed frame */ + int framerate_num = 30; /**< Framerate numerator of mixed frame */ + int framerate_den = 1; /**< Framerate denominator of mixed frame */ + }; + + public: + /** + * @brief Destructor of Mixer + * @pre None + * @post None + * @exception None + * @return None + * @see Mixer::Create() + */ + virtual ~Mixer(){}; + /** + * @brief Starts Mixer + * @pre None + * @post Black frame or mixed frame will be displayed on the screen + * @exception None + * @return @c True on success, otherwise @c False + * @see Mixer::Stop() + */ + virtual bool Start() { return false; } + /** + * @brief Stops Mixer + * @pre Mixer::Start() was called + * @post None + * @exception None + * @return @c True on success, otherwise @c False + * @see Mixer::Start() + */ + virtual bool Stop() { return false; } + /** + * @brief Gets maximal number of players which can be connected to Mixer + * @pre None + * @post None + * @exception None + * @return Non-zero value on success, otherwise zero + */ + virtual int GetMaximumAllowedNumberOfPlayer() { return 0; } + /** + * @brief Sets the video display + * @remarks We are not supporting changing display. + * @remarks This API have to be called before calling the + * Mixer::Start() to reflect the display type. + * @param [in] type : display type + * @param [in] obj : The handle to display window + * @pre None + * @post None + * @exception None + * @return @c True on success, otherwise @c False + * @see DisplayType + * Mixer::SetDisplayRoi() + */ + virtual bool SetDisplay(const DisplayType type, void* obj) { return false; } + /** + * @brief Sets the video display + * @remarks We are not supporting changing display. + * @remarks This API have to be called before calling the + * Mixer::Start() to reflect the display type. + * @param [in] type : display type + * @param [in] serface_id : resource id of window + * @param [in] x : x param of display window + * @param [in] y : y param of display window + * @param [in] w : width of display window + * @param [in] h : height of display window + * @pre None + * @post None + * @exception None + * @return @c True on success, otherwise @c False + * @see DisplayType + * Mixer::SetDisplayRoi() + */ + virtual bool SetDisplay(const DisplayType type, const uint32_t surface_id, const int x, const int y, const int w, const int h) { return false; } + /** + * @brief Set the video display mode + * @param [in] mode : display mode + * @pre None + * @post None + * @exception None + * @return @c True on success, otherwise @c False + * @see DisplayMode + * @see Mixer::SetDisplay() + * @see Mixer::SetDisplayRoi() + */ + virtual bool SetDisplayMode(const DisplayMode& mode) { return false; } + /** + * @brief Sets the ROI(Region Of Interest) area of display + * @remarks The minimum value of width and height are 1. + * @param [in] geometry : Roi Geometry + * @pre Before set display ROI, #DisplayMode::kDstRoi must be set + * with Mixer::SetDisplayMode(). + * @post None + * @exception None + * @return @c True on success, otherwise @c False + * @see DisplayMode \n + * Mixer::SetDisplay() \n + * Mixer::SetDisplayMode() + */ + virtual bool SetDisplayRoi(const Geometry& geometry) { return false; } + /** + * @brief Set Resource allocation policy. + * @param [in] mode : Resource allocation policy + * @pre This api should be called before setting player's display type to + * mixer type. + * @post None + * @exception None + * @return @c True on success, otherwise @c False + */ + virtual bool SetRscAllocMode(const RscAllocMode& mode) { return false; } + /** + * @brief Disable audio focus setting. + * The mixer has no authority to set audio focus and player can + * control audio pipeline's activation/deactivation. + * @pre This api should be called before setting player's display type to + * mixer type. + * @post SetAudioFocus() will return false. + * @exception None + * @return @c True on success, otherwise @c False + */ + virtual bool DisableAudioFocusSetting() { return false; } + /** + * @brief Set the alternative video scaler. + * The mixer change to use the sub video scaler instead of main video + * scaler(default). + * @pre This api should be called before setting player's display type to + * mixer type. + * @post None + * @exception None + * @return @c True on success, otherwise @c False + */ + virtual bool SetAlternativeVideoScaler() { return false; } + /** + * @brief Sets audio focus on the specific player object + * @param [in] player_instance : The handle to player instance + * @pre None + * @post The player which gets audio focus will activate its audio + * pipeline. + * By default, players deactivate audio until setting audio focus. + * @exception None + * @return @c True on success, otherwise @c False + */ + virtual bool SetAudioFocus(const void* player_instance) { return false; } + /** + * @brief Applies geometry changes on mixed frame + * @pre SetDisplayRoi() was called for players which are connected to + * Mixer. + * @post All the geometry changes will be applied to mixed frame. + * @exception None + * @return @c True on success, otherwise @c False + */ + virtual bool Commit() { return false; } + /** + * @brief Sets resolution of mixed frame + * @remarks By default, the resolution of mixed frame is 1920x1080. + * @param [in] info : The resolution info of mixed frame + * @pre Mixer has no connected players yet. + * @post The resolution of mixed frame will be changed. + * @exception None + * @return @c True on success, otherwise @c False + */ + virtual bool SetResolution(const ResolutionInfo& info) { return false; } + /** + * @brief Register eventlistener to Mixer + * @param [in] listener : listener object + * @pre None + * @post None + * @exception None + * @return @c True on success, otherwise @c False + * @see MixerEventListener + */ + virtual bool RegisterListener(MixerEventListener* listener) { return false; } + /** + * @brief Creates MixerTicket object for the specific player + * @param [in] player_instance : The handle to player instance + * @pre None + * @post None + * @exception None + * @return A valid @c MixerTicket object on success, otherwise @c nullptr + */ + virtual MixerTicket* CreateTicket(const void* player_instance) { + return nullptr; + } + + protected: + /** + * @brief Constructor of Mixer + * @pre None + * @post None + * @exception None + * @return None + * @see Mixer::Create() + */ + Mixer() noexcept {}; +}; + +} // namespace plusplayer + +#endif // __PLUSPLAYER_MIXER_MIXER__H__ diff --git a/include/mixer/mixer_eventlistener.h b/include/mixer/mixer_eventlistener.h new file mode 100755 index 0000000..1459b05 --- /dev/null +++ b/include/mixer/mixer_eventlistener.h @@ -0,0 +1,80 @@ +/** + * @file mixer_eventlistener.h + * @brief Handles various events which comes from Mixer + * @interfacetype Module + * @privlevel None-privilege + * @privilege None + * @product TV, AV, B2B + * @version 0.0.1 + * @SDK_Support N + * + * Copyright (c) 2020 Samsung Electronics Co., Ltd All Rights Reserved + * PROPRIETARY/CONFIDENTIAL + * This software is the confidential and proprietary + * information of SAMSUNG ELECTRONICS ("Confidential Information"). You shall + * not disclose such Confidential Information and shall use it only in + * accordance with the terms of the license agreement you entered into with + * SAMSUNG ELECTRONICS. SAMSUNG make no representations or warranties about the + * suitability of the software, either express or implied, including but not + * limited to the implied warranties of merchantability, fitness for a + * particular purpose, or non-infringement. SAMSUNG shall not be liable for any + * damages suffered by licensee as a result of using, modifying or distributing + * this software or its derivatives. + */ + +#ifndef __PLUSPLAYER_SRC_MIXER_MIXER_EVENTLISTENER__H__ +#define __PLUSPLAYER_SRC_MIXER_MIXER_EVENTLISTENER__H__ + +namespace plusplayer { +/** + * @brief Class MixerEventListener + */ +/** + * @brief Notifies event that an application needs to handle. + * @remark An application should implement concrete class. + * @see Mixer::RegisterListener + */ +class MixerEventListener { + public: + /** + * @brief Constuctor of MixerEventListener + * @pre None + * @post None + * @exception None + * @return None + */ + MixerEventListener(){}; + /** + * @brief Destructor of MixerEventListener + * @pre None + * @post None + * @exception None + * @return None + */ + virtual ~MixerEventListener(){}; + /** + * @brief It will be invoked when an error is occured in Mixer object + * @remark OnError means that Mixer can't continue its operation. + * An application may need to terminate Mixer usage. + * @pre MixerEventListener object is registered + * @post None + * @exception None + * @return None + */ + virtual void OnError() {} + /** + * @brief It will be invoked when Mixer's own pipeline is notified + * resource confliction + * @remark OnResourceConflicted means that Mixer can't play anymore. + * An application must terminate Mixer usage. + * @pre MixerEventListener object is registered + * @post None + * @exception None + * @return None + */ + virtual void OnResourceConflicted() {} +}; + +} // namespace plusplayer + +#endif // __PLUSPLAYER_SRC_MIXER_MIXER_EVENTLISTENER__H__ diff --git a/include/mixer/mixerticket.h b/include/mixer/mixerticket.h new file mode 100755 index 0000000..f490c3b --- /dev/null +++ b/include/mixer/mixerticket.h @@ -0,0 +1,178 @@ +/** + * @file mixerticket.h + * @brief Connects player to Mixer and allows it to send raw video + * frame + * @interfacetype Module + * @privlevel None-privilege + * @privilege None + * @product TV, AV, B2B + * @version 0.0.1 + * @SDK_Support N + * + * Copyright (c) 2020 Samsung Electronics Co., Ltd All Rights Reserved + * PROPRIETARY/CONFIDENTIAL + * This software is the confidential and proprietary + * information of SAMSUNG ELECTRONICS ("Confidential Information"). You shall + * not disclose such Confidential Information and shall use it only in + * accordance with the terms of the license agreement you entered into with + * SAMSUNG ELECTRONICS. SAMSUNG make no representations or warranties about the + * suitability of the software, either express or implied, including but not + * limited to the implied warranties of merchantability, fitness for a + * particular purpose, or non-infringement. SAMSUNG shall not be liable for any + * damages suffered by licensee as a result of using, modifying or distributing + * this software or its derivatives. + */ + +#ifndef __PLUSPLAYER_SRC_MIXER_MIXERTICKET__H__ +#define __PLUSPLAYER_SRC_MIXER_MIXERTICKET__H__ + +#include + +#include "mixer/decodedvideoinfo.h" +#include "mixer/mixerticket_eventlistener.h" + +namespace plusplayer { +/** + * @brief Enumerations for Resource category + * @brief Each player informs mixer with this category so that + * Mixer can understand which player uses which resources. + */ +enum class ResourceCategory { + kVideoDecoder, /**< Video decoder category */ + kAudioDecoder, /**< Audio decoder category */ + kScaler /**< Scaler category */ +}; +/** + * @brief Enumerations for Resource type + * @brief Each player informs mixer with this type so that + * Mixer can understand which player uses main or sub type. + */ +enum class ResourceType { + kHwMain, /**< H/W Main resource type */ + kHwSub, /**< H/W Sub resource type */ + kSw, /**< S/W resource type. Only valid for decoder category */ + kNdecoder, /**< N decoder resource type */ + kMax /**< Size of this enumeration (not used) */ +}; +/** + * @brief Class MixerTicket + */ +/** + * @brief Provides methods for player to send their raw video + * frame to Mixer. + * @remark It also helps player to request H/W resources without + * causing confliction. + * @see Mixer + */ +class MixerTicket { + public: + /** + * @brief Constuctor of MixerTicket + * @pre None + * @post None + * @exception None + * @return None + */ + MixerTicket(){}; + /** + * @brief Destructor of MixerTicket + * @pre None + * @post None + * @exception None + * @return None + */ + virtual ~MixerTicket(){}; + /** + * @brief Gets currently available resource type for the player + * @param [in] category : The resource category that player wants to use + * @param [out] type : The resource type that player can allocate + * @pre None + * @post None + * @exception None + * @return @c True on success, otherwise @c False + */ + virtual bool GetAvailableResourceType(const ResourceCategory& category, + ResourceType* type) { + return false; + } + /** + * @brief Informs mixer about resource allocation status + * @param [in] category : The resource category that player is using + * @param [in] type : The resource type that player is using + * @pre None + * @post None + * @exception None + * @return @c True on success, otherwise @c False + */ + virtual bool Alloc(const ResourceCategory& category, + const ResourceType& type) { + return false; + } + /** + * @brief Renders decoded video frame into mixed frame in Mixer object + * @param [in] info : The decoded physical address by H/W deocder + * and the resolution of video frame. + * @pre MixerTicket::Prepare() must be performed without error + * @post None + * @exception None + * @return @c True on success, otherwise @c False + */ + virtual bool Render(const DecodedRawInfo& info) { return false; } + /** + * @brief Renders decoded video frame into mixed frame in Mixer object + * @param [in] info : The information for tbm_key exported and the + * resolution of video frame. + * @pre MixerTicket::Prepare() must be performed without error + * @post None + * @exception None + * @return @c True on success, otherwise @c False + */ + virtual bool Render(const DecodedVideoKeyTypeInfo& info) { return false; } + /** + * @brief Registers eventlistener to MixerTicket + * @param [in] listener : listener object + * @pre None + * @post None + * @exception None + * @return @c True on success, otherwise @c False + * @see MixerTicketEventListener + */ + virtual bool RegisterListener(MixerTicketEventListener* listener) { + return false; + } + /** + * @brief Prepares MixerTicket objects to be ready for use + * @pre None + * @post None + * @exception None + * @return @c True on success, otherwise @c False + */ + virtual bool Prepare() { return false; } + /** + * @brief Get the status whether the mixer can handle player's audio focus. + * @pre None + * @post None + * @exception None + * @return @c True : The mixer can handle player's audio focus, + * @c False : The mixer can't handle player's audio focus and + * the attached players will determin whether to activate + * audio track directly. + */ + virtual bool IsAudioFocusHandler() { return false; } + /** + * @brief Get the status whether Mixer can handle player's resource + * allocation. + * @pre None + * @post None + * @exception None + * @return @c True : The mixer will allocate appropriate video decoder + * resources to the attached players, + * @c False : The attached player sets the video decoder resource to + * be allocated directly instead of the mixer. + */ + virtual bool IsRscAllocHandler() { return false; } +}; + +} // namespace plusplayer + +#endif // __PLUSPLAYER_SRC_MIXER_MIXERTICKET__H__ \ No newline at end of file diff --git a/include/mixer/mixerticket_eventlistener.h b/include/mixer/mixerticket_eventlistener.h new file mode 100755 index 0000000..3ec62c8 --- /dev/null +++ b/include/mixer/mixerticket_eventlistener.h @@ -0,0 +1,87 @@ +/** + * @file mixerticket_eventlistener.h + * @brief Handles various events which comes from MixerTicket + * @interfacetype Module + * @privlevel None-privilege + * @privilege None + * @product TV, AV, B2B + * @version 0.0.1 + * @SDK_Support N + * + * Copyright (c) 2020 Samsung Electronics Co., Ltd All Rights Reserved + * PROPRIETARY/CONFIDENTIAL + * This software is the confidential and proprietary + * information of SAMSUNG ELECTRONICS ("Confidential Information"). You shall + * not disclose such Confidential Information and shall use it only in + * accordance with the terms of the license agreement you entered into with + * SAMSUNG ELECTRONICS. SAMSUNG make no representations or warranties about the + * suitability of the software, either express or implied, including but not + * limited to the implied warranties of merchantability, fitness for a + * particular purpose, or non-infringement. SAMSUNG shall not be liable for any + * damages suffered by licensee as a result of using, modifying or distributing + * this software or its derivatives. + */ + +#ifndef __PLUSPLAYER_SRC_MIXER_MIXERTICKET_EVENTLISTENER__H__ +#define __PLUSPLAYER_SRC_MIXER_MIXERTICKET_EVENTLISTENER__H__ + +#include "plusplayer/types/display.h" + +namespace plusplayer { +/** + * @brief Class MixerTicketEventListener + */ +/** + * @brief Notifies event that player needs to handle. + * @remark Player should implement concrete class. + * @see MixerTicket::RegisterListener + */ +class MixerTicketEventListener { + public: + /** + * @brief Constuctor of MixerTicketEventListener + * @pre None + * @post None + * @exception None + * @return None + */ + MixerTicketEventListener(){}; + /** + * @brief Destructor of MixerTicketEventListener + * @pre None + * @post None + * @exception None + * @return None + */ + virtual ~MixerTicketEventListener(){}; + /** + * @brief It will be invoked when audio focus is being changed + * @param [in] active : @c True if the player gets focused, otherwise @c + * False. + * @pre MixerTicketEventListener object is registered + * @post None + * @exception None + * @return @c True on success, otherwise @c False + */ + virtual bool OnAudioFocusChanged(bool active) { return false; } + /** + * @brief It will be invoked when the mixed frame's geometry is being + * changed + * @param [in] cur_info : current display info that Mixer stores for this + * player + * @param [out] new_info : update display info that player needs to + * provide for Mixer + * @pre MixerTicketEventListener object is registered + * @post None + * @exception None + * @return @c True on success, otherwise @c False + */ + virtual bool OnUpdateDisplayInfo(const DisplayInfo& cur_info, + DisplayInfo* new_info) { + return false; + } +}; + +} // namespace plusplayer + +#endif // __PLUSPLAYER_SRC_MIXER_MIXERTICKET_EVENTLISTENER__H__ diff --git a/include/mixer_capi/display.h b/include/mixer_capi/display.h new file mode 100755 index 0000000..ba103ca --- /dev/null +++ b/include/mixer_capi/display.h @@ -0,0 +1,61 @@ +/** + * @file + * @brief Display related enums + * @interfacetype Platform + * @privlevel None-privilege + * @privilege None + * @product TV, AV, B2B + * @version 3.0 + * @SDK_Support N + * @remark This is a group of C style display releted data structures + * and enums. + * @see The display related enum values and data structures will be + * converted by this managed C version types to avoid binary + * compatibility. + * + * Copyright (c) 2020 Samsung Electronics Co., Ltd All Rights Reserved + * PROPRIETARY/CONFIDENTIAL + * This software is the confidential and proprietary + * information of SAMSUNG ELECTRONICS ("Confidential Information"). You shall + * not disclose such Confidential Information and shall use it only in + * accordance with the terms of the license agreement you entered into with + * SAMSUNG ELECTRONICS. SAMSUNG make no representations or warranties about the + * suitability of the software, either express or implied, including but not + * limited to the implied warranties of merchantability, fitness for a + * particular purpose, or non-infringement. SAMSUNG shall not be liable for any + * damages suffered by licensee as a result of using, modifying or distributing + * this software or its derivatives. + */ + +#ifndef __PLUSPLAYER_MIXER_CAPI_DISPLAY_H__ +#define __PLUSPLAYER_MIXER_CAPI_DISPLAY_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Enumerations for the display mode + */ +enum mixer_display_mode { + MIXER_DISPLAY_MODE_LETTER_BOX, + MIXER_DISPLAY_MODE_ORIGIN_SIZE, + MIXER_DISPLAY_MODE_FULL_SCREEN, + MIXER_DISPLAY_MODE_CROPPED_FULL, + MIXER_DISPLAY_MODE_ORIGIN_OR_LETTER, + MIXER_DISPLAY_MODE_DST_ROI +}; + +/** + * @brief Enumerations for the display type + */ +enum mixer_display_type { + MIXER_DISPLAY_TYPE_NONE, + MIXER_DISPLAY_TYPE_OVERLAY, +}; + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // __PLUSPLAYER_MIXER_CAPI_DISPLAY_H__ \ No newline at end of file diff --git a/include/mixer_capi/error.h b/include/mixer_capi/error.h new file mode 100755 index 0000000..903d825 --- /dev/null +++ b/include/mixer_capi/error.h @@ -0,0 +1,52 @@ +/** + * @file + * @brief Error related enums + * @interfacetype Platform + * @privlevel None-privilege + * @privilege None + * @product TV, AV, B2B + * @version 3.0 + * @SDK_Support N + * @remark This is a group of C style error releted enum. + * @see All error enum values will be converted to this managed error + * types. + * + * Copyright (c) 2020 Samsung Electronics Co., Ltd All Rights Reserved + * PROPRIETARY/CONFIDENTIAL + * This software is the confidential and proprietary + * information of SAMSUNG ELECTRONICS ("Confidential Information"). You shall + * not disclose such Confidential Information and shall use it only in + * accordance with the terms of the license agreement you entered into with + * SAMSUNG ELECTRONICS. SAMSUNG make no representations or warranties about the + * suitability of the software, either express or implied, including but not + * limited to the implied warranties of merchantability, fitness for a + * particular purpose, or non-infringement. SAMSUNG shall not be liable for any + * damages suffered by licensee as a result of using, modifying or distributing + * this software or its derivatives. + */ + +#ifndef __PLUSPLAYER_MIXER_CAPI_ERROR_H__ +#define __PLUSPLAYER_MIXER_CAPI_ERROR_H__ + +#include "tizen.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define MIXER_ERROR_CLASS TIZEN_ERROR_PLAYER | 0x20 + +/** + * @brief Enumerations for the error type + */ +enum mixer_error_type { + MIXER_ERROR_TYPE_NONE = TIZEN_ERROR_NONE, /**< Successful */ + MIXER_ERROR_TYPE_INVALID_PARAMETER = TIZEN_ERROR_INVALID_PARAMETER, /**< Invalid parameter */ + MIXER_ERROR_TYPE_INVALID_OPERATION = TIZEN_ERROR_INVALID_OPERATION, /**< Invalid operation */ +}; + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // __PLUSPLAYER_MIXER_CAPI_ERROR_H__ \ No newline at end of file diff --git a/include/mixer_capi/mixer_capi.h b/include/mixer_capi/mixer_capi.h new file mode 100755 index 0000000..b2f39d2 --- /dev/null +++ b/include/mixer_capi/mixer_capi.h @@ -0,0 +1,304 @@ +/** + * @file mixer_capi.h + * @brief Mixer api c version + * @interfacetype Platform + * @privlevel None-privilege + * @privilege None + * @product TV, AV, B2B + * @version 3.0 + * @SDK_Support N + * @remark This is mixer api header implemented as C style to + * avoid binary compatibility issues. + * + * Copyright (c) 2020 Samsung Electronics Co., Ltd All Rights Reserved + * PROPRIETARY/CONFIDENTIAL + * This software is the confidential and proprietary + * information of SAMSUNG ELECTRONICS ("Confidential Information"). You shall + * not disclose such Confidential Information and shall use it only in + * accordance with the terms of the license agreement you entered into with + * SAMSUNG ELECTRONICS. SAMSUNG make no representations or warranties about the + * suitability of the software, either express or implied, including but not + * limited to the implied warranties of merchantability, fitness for a + * particular purpose, or non-infringement. SAMSUNG shall not be liable for any + * damages suffered by licensee as a result of using, modifying or distributing + * this software or its derivatives. + */ + +#ifndef __PLUSPLAYER_MIXER_CAPI_MIXER_CAPI_H__ +#define __PLUSPLAYER_MIXER_CAPI_MIXER_CAPI_H__ + +#include "mixer_capi/error.h" +#include "mixer_capi/display.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +typedef void (*mixer_resource_conflicted_cb)(void*); + +typedef void* mixer_handle; +typedef void* mixer_ticket_h; + +/** + * @brief Enumerations for the resource allocation mode + */ +enum mixer_rsc_alloc_mode { + MIXER_RSC_ALLOC_MODE_DEFAULT, /**< Main -> Sub -> S/W */ + MIXER_RSC_ALLOC_MODE_NDECODER, /**< Only N decoder */ + MIXER_RSC_ALLOC_MODE_DISABLE /**< Mixer is NOT involved in resource allocation */ +}; + +/** + * @brief Create a mixer handle + * @param None + * @return return mixer handle pointer + * @code + // basic api call sequence + mixer_handle mixer = mixer_create(); + mixer_set_display(mixer, MIXER_DISPLAY_TYPE_OVERLAY, window); + + esplusplayer_handle player1 = esplusplayer_create(); + esplusplayer_open(player1); + + esplusplayer_set_display(player1, ESPLUSPLAYER_DISPLAY_TYPE_MIXER, mixer); + eplusplayer_set_display_roi(player1, x, y, w, h); + + mixer_set_audio_focus(mixer, player1); + + esplusplayer_set_video_stream_info(player1, video_info); + esplusplayer_set_audio_stream_info(player1, audio_info); + esplusplayer_prepare_async(player1); + + mixer_start(mixer); + esplusplayer_start(player1); + + mixer_stop(mixer); + esplusplayer_close(player1); + mixer_destroy(mixer); + esplusplayer_destroy(player1); + * @endcode + * @pre None + * @post None + * @exception None + */ +mixer_handle mixer_create(); + +/** + * @brief Release mixer handle + * @param [in] handle : mixer handle + * @return @c one of mixer_error_type values will be returned + * @pre None + * @post mixer handle will be removed + * @exception None + * @see mixer_create() + */ +int mixer_destroy(mixer_handle handle); + +/** + * @brief Starts Mixer. + * @param [in] handle : mixer handle + * @return @c one of mixer_error_type values will be returned + * @pre None + * @post Black frame or mixed frame will be displayed on the screen + * @exception None + * @see mixer_stop() + */ +int mixer_start(mixer_handle handle); + +/** + * @brief Stops Mixer + * @param [in] handle : Mixer handle + * @return @c one of mixer_error_type values will be returned + * @pre mixer_start() should be called + * @post None + * @exception None + * @see mixer_start() + */ +int mixer_stop(mixer_handle handle); + +/** + * @brief Gets maximal number of players which can be connected to Mixer + * @param [in] handle : Mixer handle + * @return Non-zero value on success, otherwise zero + * @pre None + * @post None + * @exception None + */ +int mixer_get_max_allowed_number_of_player(mixer_handle handle); + +/** + * @brief Sets the video display + * @param [in] handle : Mixer handle + * @param [in] type : display type + * @param [in] window : The handle to display window + * @return @c one of mixer_error_type values will be returned + * @pre This API have to be called before calling the + * mixer_start() to reflect the display type + * @post None + * @exception None + * @remarks We are not supporting changing display + * @see mixer_display_type \n + * mixer_set_display_roi() + */ +int mixer_set_display(mixer_handle handle, mixer_display_type type, + void* window); + +/** + * @brief Set the video display mode + * @param [in] handle : Mixer handle + * @param [in] mode : display mode + * @return @c one of mixer_error_type values will be returned + * @pre None + * @post None + * @exception None + * @see mixer_display_mode + * @see mixer_set_display() \n + * mixer_set_display_roi() + */ +int mixer_set_display_mode(mixer_handle handle, mixer_display_mode mode); + +/** + * @brief Sets the ROI(Region Of Interest) area of display + * @param [in] handle : Mixer handle + * @param [in] geometry : Roi Geometry + * @return @c one of mixer_error_type values will be returned + * @code + // The ROI of mixer means display area in tv screen. + // The ROI of each player means display area in mixer's ROI. + + mixer_set_display_mode(mixer, MIXER_DISPLAY_MODE_DST_ROI); + mixer_set_display_roi(mixer, 0, 0, 1920, 1080); + + esplusplayer_set_display_roi(player1, 20, 20, 700, 400); + esplusplayer_set_display_roi(player2, 20, 20, 700, 400); + + mixer_commit(mixer); + * @endcode + * @pre Before set display ROI, #MIXER_DISPLAY_MODE_DST_ROI must be set + * with mixer_set_display_mode() + * @post None + * @exception None + * @remarks The minimum value of width and height are 1. + * @see mixer_display_mode \n + * mixer_set_display() \n + * mixer_set_display_mode() + */ +int mixer_set_display_roi(mixer_handle handle, const int x, const int y, + const int width, const int height); + + /** + * @brief Applies geometry changes on mixed frame + * @param [in] handle : Mixer handle + * @return @c one of mixer_error_type values will be returned + * @pre set display roi api was called for players which are connected to + * Mixer. + * @post All the geometry changes will be applied to mixed frame. + * @exception None + * @see mixer_set_display_roi() + */ + int mixer_commit(mixer_handle handle); + + /** + * @brief Set Resource allocation policy + * @param [in] handle : Mixer handle + * @param [in] mode : Resource allocation policy + * @return @c one of mixer_error_type values will be returned + * @pre This api should be called before setting player's display type to + * mixer type. + * @post None + * @exception None + */ + int mixer_set_rsc_alloc_mode(mixer_handle handle, + const mixer_rsc_alloc_mode mode); + +/** + * @brief Disable audio focus setting. + * The mixer has no authority to set audio focus and player can + * control audio pipeline's activation/deactivation. + * @param [in] handle : Mixer handle + * @return @c one of mixer_error_type values will be returned + * @pre This api should be called before setting player's display type to + * mixer type. + * @post mixer_set_audio_focus() will return error. + * @exception None + */ +int mixer_disable_audio_focus_setting(mixer_handle handle); + +/** + * @brief Set the alternative video scaler. + * The mixer change to use the sub video scaler instead of main video + * scaler(default). + * @param [in] handle : Mixer handle + * @return @c one of mixer_error_type values will be returned + * @pre This api should be called before mixer_start(). + * @post None + * @exception None + */ +int mixer_set_alternative_video_scaler(mixer_handle handle); + +/** + * @brief Sets audio focus on the specific player object + * @param [in] handle : Mixer handle + * @param [in] player_instance : The handle to player instance + * @return @c one of mixer_error_type values will be returned + * @pre None + * @post The player which gets audio focus will activate its audio + * pipeline. By default, players deactivate audio until setting + * audio focus. + * @exception None + */ +int mixer_set_audio_focus(mixer_handle handle, const void* player_instance); + +/** + * @brief Sets resolution of mixed frame + * @param [in] handle : Mixer handle + * @param [in] info : The resolution info of mixed frame + * @return @c one of mixer_error_type values will be returned + * @pre Mixer has no connected players yet. + * @post The resolution of mixed frame will be changed. + * @exception None + * @remarks By default, the resolution of mixed frame is 1920x1080. + */ +int mixer_set_resolution(mixer_handle handle, const int width, const int height, + const int framerate_num, const int framerate_den); + +/** + * @brief Creates MixerTicket object for the specific player + * @param [in] handle : Mixer handle + * @param [in] player_instance : The handle to player instance + * @return A valid @c MixerTicket object on success, otherwise @c nullptr + * @pre None + * @post None + * @exception None + */ +mixer_ticket_h mixer_create_ticket(mixer_handle handle, + const void* player_instance); + +// callback function + +/** + * @brief It will be invoked when Mixer's own pipeline is notified + * resource confliction + * @param [in] handle : Mixer handle + * @param [in] callback : the callback function to register + * @param [in] userdata : userdata of resource_conflicted_cb() + * @return @c one of mixer_error_type values will be returned + * @pre None + * @post resource_conflicted_cb will be invoked when resources are + * conflicted + * @exception None + * @remark resource_conflicted_cb means that Mixer can't play anymore. + * An application must terminate Mixer usage. + */ +int mixer_set_resource_conflicted_cb( + mixer_handle handle, mixer_resource_conflicted_cb resource_conflicted_cb, + void* userdata); + +#ifdef __cplusplus +} +#endif + +#endif // __PLUSPLAYER_MIXER_CAPI_MIXER_CAPI_H__ + diff --git a/include/plusplayer/appinfo.h b/include/plusplayer/appinfo.h new file mode 100755 index 0000000..2b0106b --- /dev/null +++ b/include/plusplayer/appinfo.h @@ -0,0 +1,41 @@ +/** + * @file attribute.h + * @interfacetype module + * @privlevel None-privilege + * @privilege None + * @product TV, AV, B2B + * @version 1.0 + * @SDK_Support N + * + * Copyright (c) 2018 Samsung Electronics Co., Ltd All Rights Reserved + * PROPRIETARY/CONFIDENTIAL + * This software is the confidential and proprietary + * information of SAMSUNG ELECTRONICS ("Confidential Information"). You shall + * not disclose such Confidential Information and shall use it only in + * accordance with the terms of the license agreement you entered into with + * SAMSUNG ELECTRONICS. SAMSUNG make no representations or warranties about the + * suitability of the software, either express or implied, including but not + * limited to the implied warranties of merchantability, fitness for a + * particular purpose, or non-infringement. SAMSUNG shall not be liable for any + * damages suffered by licensee as a result of using, modifying or distributing + * this software or its derivatives. + */ +#ifndef __PLUSPLAYER_APPINFO_H__ +#define __PLUSPLAYER_APPINFO_H__ + +#include + +namespace plusplayer { + +/** +* @brief Player app information. +*/ +struct PlayerAppInfo { + std::string id; /**< App id */ + std::string version; /**< App version */ + std::string type; /**< App type. ex)"MSE", "HTML5", etc.. */ +}; + +} // namespace plusplayer + +#endif // __PLUSPLAYER_PLAYER_APPINFO_H__ \ No newline at end of file diff --git a/include/plusplayer/attribute.h b/include/plusplayer/attribute.h new file mode 100755 index 0000000..d6113d6 --- /dev/null +++ b/include/plusplayer/attribute.h @@ -0,0 +1,50 @@ +/** + * @file attribute.h + * @interfacetype module + * @privlevel None-privilege + * @privilege None + * @product TV, AV, B2B + * @version 1.0 + * @SDK_Support N + * + * Copyright (c) 2018 Samsung Electronics Co., Ltd All Rights Reserved + * PROPRIETARY/CONFIDENTIAL + * This software is the confidential and proprietary + * information of SAMSUNG ELECTRONICS ("Confidential Information"). You shall + * not disclose such Confidential Information and shall use it only in + * accordance with the terms of the license agreement you entered into with + * SAMSUNG ELECTRONICS. SAMSUNG make no representations or warranties about the + * suitability of the software, either express or implied, including but not + * limited to the implied warranties of merchantability, fitness for a + * particular purpose, or non-infringement. SAMSUNG shall not be liable for any + * damages suffered by licensee as a result of using, modifying or distributing + * this software or its derivatives. + */ +#ifndef __PLUSPLAYER_ATTRIBUTE_H__ +#define __PLUSPLAYER_ATTRIBUTE_H__ + +namespace plusplayer { + +/** + * @brief Enumeration for plusplayer attribute + * If there is new attribute, please write details in below documents. + */ +enum class Attribute { + kVideoQueueMaxByte, // std::uint64_t + kAudioQueueMaxByte, // std::uint64_t + kVideoQueueCurrentLevelByte, // std::uint64_t + kAudioQueueCurrentLevelByte, // std::uint64_t + kVideoMinByteThreshold, // std::uint32_t + kAudioMinByteThreshold, // std::uint32_t + kVideoQueueMaxTime, // std::uint64_t + kAudioQueueMaxTime, // std::uint64_t + kVideoQueueCurrentLevelTime, // std::uint64_t + kAudioQueueCurrentLevelTime, // std::uint64_t + kVideoMinTimeThreshold, // std::uint32_t + kAudioMinTimeThreshold, // std::uint32_t + kMax, +}; + +} // namespace plusplayer + +#endif // __PLUSPLAYER_ATTRIBUTE_H__ \ No newline at end of file diff --git a/include/plusplayer/audioeasinginfo.h b/include/plusplayer/audioeasinginfo.h new file mode 100755 index 0000000..f6671c8 --- /dev/null +++ b/include/plusplayer/audioeasinginfo.h @@ -0,0 +1,45 @@ +/** + * @file audioeasinginfo.h + * @interfacetype module + * @privlevel None-privilege + * @privilege None + * @product TV, AV, B2B + * @version 3.0 + * @SDK_Support N + * + * Copyright (c) 2020 Samsung Electronics Co., Ltd All Rights Reserved + * PROPRIETARY/CONFIDENTIAL + * This software is the confidential and proprietary + * information of SAMSUNG ELECTRONICS ("Confidential Information"). You shall + * not disclose such Confidential Information and shall use it only in + * accordance with the terms of the license agreement you entered into with + * SAMSUNG ELECTRONICS. SAMSUNG make no representations or warranties about the + * suitability of the software, either express or implied, including but not + * limited to the implied warranties of merchantability, fitness for a + * particular purpose, or non-infringement. SAMSUNG shall not be liable for any + * damages suffered by licensee as a result of using, modifying or distributing + * this software or its derivatives. + */ +#ifndef __PLUSPLAYER_AUDIOEASINGINFO_H__ +#define __PLUSPLAYER_AUDIOEASINGINFO_H__ + +namespace plusplayer { + +enum class AudioEasingType { + kAudioEasingLinear = 0, + kAudioEasingIncubic, + kAudioEasingOutcubic, + kAudioEasingNone +}; + +/** + * @brief audio easing information struct + */ +struct AudioEasingInfo { + uint32_t target_volume; /**< Audio easing target volume (0 ~ 100)*/ + uint32_t duration; /**< Audio easing duration, in millisecond */ + AudioEasingType type; /**< Audio easing type */ +}; +} // namespace plusplayer + +#endif // __PLUSPLAYER_AUDIOEASINGINFO_H__ \ No newline at end of file diff --git a/include/plusplayer/decodedvideopacketex.h b/include/plusplayer/decodedvideopacketex.h new file mode 100755 index 0000000..3e36bc9 --- /dev/null +++ b/include/plusplayer/decodedvideopacketex.h @@ -0,0 +1,73 @@ +/** + * @file + * @brief the decoded video packet for playback + * @interfacetype Module + * @privlevel None-privilege + * @privilege None + * @product TV, AV, B2B + * @version 0.0.1 + * @SDK_Support N + * + * Copyright (c) 2020 Samsung Electronics Co., Ltd All Rights Reserved + * PROPRIETARY/CONFIDENTIAL + * This software is the confidential and proprietary + * information of SAMSUNG ELECTRONICS ("Confidential Information"). You shall + * not disclose such Confidential Information and shall use it only in + * accordance with the terms of the license agreement you entered into with + * SAMSUNG ELECTRONICS. SAMSUNG make no representations or warranties about the + * suitability of the software, either express or implied, including but not + * limited to the implied warranties of merchantability, fitness for a + * particular purpose, or non-infringement. SAMSUNG shall not be liable for any + * damages suffered by licensee as a result of using, modifying or distributing + * this software or its derivatives. + */ +#ifndef __PLUSPLAYER_DECODED_VIDEO_PACKET_EX_H__ +#define __PLUSPLAYER_DECODED_VIDEO_PACKET_EX_H__ + +#include +#include +#include + +#include "tbm_surface.h" +#include "tbm_type.h" + +namespace plusplayer { + +class DecodedVideoPacketEx : private boost::noncopyable { + public: + using Ptr = std::unique_ptr; + + static Ptr Create(const uint64_t pts = 0, const uint64_t duration = 0, + tbm_surface_h surface_data = nullptr, + const void* scaler_index = nullptr); + + DecodedVideoPacketEx() = delete; + + virtual ~DecodedVideoPacketEx(); + + uint64_t GetPts() const { return pts_; } + uint64_t GetDuration() const { return duration_; } + const tbm_surface_h GetTbmSurface() const { return surface_data_; } + const void* GetScalerIndex() const { return scaler_index_; } + + protected: + explicit DecodedVideoPacketEx(const uint64_t pts, const uint64_t duration, + tbm_surface_h surface_data, + const void* scaler_index) + : pts_(pts), + duration_(duration), + surface_data_(surface_data), + scaler_index_(scaler_index) {} + + private: + const uint64_t pts_ = 0; + const uint64_t duration_ = 0; + tbm_surface_h surface_data_ = nullptr; // tbm_surface + const void* scaler_index_ = nullptr; +}; + +using DecodedVideoPacketExPtr = DecodedVideoPacketEx::Ptr; + +} // namespace plusplayer + +#endif // __PLUSPLAYER_DECODED_VIDEO_PACKET_EX_H__ diff --git a/include/plusplayer/drm.h b/include/plusplayer/drm.h new file mode 100755 index 0000000..f299eb0 --- /dev/null +++ b/include/plusplayer/drm.h @@ -0,0 +1,61 @@ +/** +* @file +* @interfacetype module +* @privlevel None-privilege +* @privilege None +* @product TV, AV, B2B +* @version 1.0 +* @SDK_Support N +* +* Copyright (c) 2018 Samsung Electronics Co., Ltd All Rights Reserved +* PROPRIETARY/CONFIDENTIAL +* This software is the confidential and proprietary +* information of SAMSUNG ELECTRONICS ("Confidential Information"). You shall +* not disclose such Confidential Information and shall use it only in +* accordance with the terms of the license agreement you entered into with +* SAMSUNG ELECTRONICS. SAMSUNG make no representations or warranties about the +* suitability of the software, either express or implied, including but not +* limited to the implied warranties of merchantability, fitness for a +* particular purpose, or non-infringement. SAMSUNG shall not be liable for any +* damages suffered by licensee as a result of using, modifying or distributing +* this software or its derivatives. +*/ + +#ifndef __PLUSPLAYER_DRM_H__ +#define __PLUSPLAYER_DRM_H__ + +namespace plusplayer { + +namespace drm { + +using LicenseAcquiredCb = void*; +using UserData = void*; +using DrmHandle = int; + +enum class Type { + kNone = 0, + kPlayready, + kMarlin, + kVerimatrix, + kWidevineClassic, + kSecuremedia, + kSdrm, + kWidevineCdm = 8, + kMax +}; + +// post from hlsdemux for getright + +struct Property { + Type type = Type::kNone; // Drm type + DrmHandle handle = 0; // Drm handle + bool external_decryption = false; // External Decryption Mode + LicenseAcquiredCb license_acquired_cb = nullptr; // The cb will be invoked when license was acquired. + UserData license_acquired_userdata = nullptr; // The userdata will be sent by license_acquired_cb +}; + +} // namespace drm + +} // namespace plusplayer + +#endif // __PLUSPLAYER_DRM_H__ diff --git a/include/plusplayer/elementary_stream.h b/include/plusplayer/elementary_stream.h new file mode 100755 index 0000000..88b9101 --- /dev/null +++ b/include/plusplayer/elementary_stream.h @@ -0,0 +1,341 @@ +/** + * @file elementary_stream.h + * @brief the contents information for elementary stream + * @interfacetype Module + * @privlevel None-privilege + * @privilege None + * @product TV, AV, B2B + * @version 0.0.1 + * @SDK_Support N + * @remark You must add contents information of elementary streams for + * plusplayer::EsPlusPlayer + * @see plusplayer::EsPlusPlayer class + * + * Copyright (c) 2018 Samsung Electronics Co., Ltd All Rights Reserved + * PROPRIETARY/CONFIDENTIAL + * This software is the confidential and proprietary + * information of SAMSUNG ELECTRONICS ("Confidential Information"). You shall + * not disclose such Confidential Information and shall use it only in + * accordance with the terms of the license agreement you entered into with + * SAMSUNG ELECTRONICS. SAMSUNG make no representations or warranties about the + * suitability of the software, either express or implied, including but not + * limited to the implied warranties of merchantability, fitness for a + * particular purpose, or non-infringement. SAMSUNG shall not be liable for any + * damages suffered by licensee as a result of using, modifying or distributing + * this software or its derivatives. + */ +#ifndef PLUSPLAYER_ELEMENTARY_STREAM_H_ +#define PLUSPLAYER_ELEMENTARY_STREAM_H_ + +#include +#include + +#include "plusplayer/track.h" +#include "plusplayer/types/stream.h" + +namespace plusplayer { + +/** + * @brief Enumerations for audio mime type + */ +enum class AudioMimeType { + kUnKnown, + kAAC, + kMP2, + kMP3, + kAC3, + kEAC3, + kVORBIS, + kOPUS, + kPCM_S16LE, + kPCM_S16BE, + kPCM_U16LE, + kPCM_U16BE, + kPCM_S24LE, + kPCM_S24BE, + kPCM_U24LE, + kPCM_U24BE, + kPCM_S32LE, + kPCM_S32BE, + kPCM_U32LE, + kPCM_U32BE, + kG711_MULAW +}; +/** + * @brief Enumerations for video mime type + */ +enum class VideoMimeType { + kUnKnown, + kH263, + kH264, + kHEVC, + kMPEG1, + kMPEG2, + kMPEG4, + kVP8, + kVP9, + kWMV3, + kAV1, + kMJPEG +}; + +/** + * @brief the interface of the contents information class for audio stream + * @remark You must add contents information of at least one audio or video + * stream + */ +class AudioStream : private boost::noncopyable { + public: + using Ptr = std::unique_ptr; + /** + * @brief Create a audio stream object + * @remarks You must use this to get audio stream object + * @return audio stream object (unique_ptr) + */ + static Ptr Create() { return Ptr(new AudioStream); } + + AudioStream() noexcept; + + ~AudioStream() {} + /** + * @brief Set mime type for the associated audio stream + * @param [in] mimetype : the mime type of stream + * @return void + * @see AudioStream::GetMimeType() + */ + void SetMimeType(AudioMimeType mimetype); + /** + * @brief Get mime type for the associated audio stream + * @return the mime type of stream (AudioMimeType) + * @see AudioStream::SetMimeType() + */ + AudioMimeType GetMimeType() const { return mimetype_; } + /** + * @brief Set samplerate for the associated audio stream + * @param [in] sample_rate : the samplerate of stream + * @return void + * @see AudioStream::GetSamplerate() + */ + void SetSamplerate(uint32_t sample_rate) { track_.sample_rate = sample_rate; } + /** + * @brief Get samplerate for the associated audio stream + * @return samplerate : the samplerate of stream + * @see AudioStream::SetSamplerate() + */ + uint32_t GetSamplerate() const { return track_.sample_rate; } + /** + * @brief Set channels for the associated audio stream + * @param [in] channels : the channels of stream + * @return void + * @see AudioStream::GetChannels() + */ + void SetChannels(uint32_t channels) { track_.channels = channels; } + /** + * @brief Get channels for the associated audio stream + * @return the channels of stream + * @see AudioStream::SetChannels() + */ + uint32_t GetChannels() const { return track_.channels; } + /** + * @brief Set bitrate for the associated audio stream + * @param [in] bitrate : the bitrate of stream + * @return void + * @see AudioStream::GetBitrate() + */ + void SetBitrate(uint32_t bitrate) { track_.bitrate = bitrate; } + /** + * @brief Get bitrate for the associated audio stream + * @return the bitrate of stream + * @see AudioStream::SetBitrate() + */ + uint32_t GetBitrate() const { return track_.bitrate; } + /** + * @brief Set codec data for the associated audio stream + * @param [in] data : the codec data of stream + * @param [in] data_size : the size of codec data + * @return void + * @see AudioStream::GetCodecData(), AudioStream::GetCodecDataSize() + */ + void SetCodecData(std::shared_ptr data, uint32_t data_size) { + track_.codec_data = data; + track_.codec_data_len = data_size; + } + /** + * @brief Get codec data for the associated audio stream + * @return std::shared_ptr of codec data + * @see AudioStream::SetCodecData(), AudioStream::GetCodecDataSize() + */ + std::shared_ptr GetCodecData() const { return track_.codec_data; } + /** + * @brief Get codec data for the associated audio stream + * @return the size of codec data + * @see AudioStream::SetCodecData(), AudioStream::GetCodecData() + */ + uint32_t GetCodecDataSize() const { return track_.codec_data_len; } + /** + * @brief Get whether to use the sw decoder forcibly + * @return @c True if the sw decoder is use, otherwise @c False. + */ + bool GetForceSwDecoderUse() { return force_swdecoder_use_; } + + private: + void SetMimeType_(AudioMimeType mimetype); + + Track GetTrack_() const { return track_; } + + friend class EsPlayer; + + private: + Track track_; + AudioMimeType mimetype_ = AudioMimeType::kUnKnown; + bool force_swdecoder_use_ = false; +}; + +using AudioStreamPtr = AudioStream::Ptr; + +/** + * @brief the interface of the contents information class for video stream + * @remark You must add contents information of at least one audio or video + * stream + */ +class VideoStream : private boost::noncopyable { + public: + using Ptr = std::unique_ptr; + /** + * @brief Create a video stream object + * @remarks You must use this to get video stream object + * @return video stream object (unique_ptr) + */ + static Ptr Create() { return Ptr(new VideoStream); } + + VideoStream() noexcept; + + ~VideoStream() {} + + /** + * @brief Set mime type for the associated video stream + * @param [in] mimetype : the mime type of stream + * @return void + * @see VideoStream::GetMimeType() + */ + void SetMimeType(VideoMimeType mimetype); + /** + * @brief Get mime type for the associated video stream + * @return the mime type (VideoMimeType) + * @see VideoStream::SetMimeType() + */ + VideoMimeType GetMimeType() const { return mimetype_; } + /** + * @brief Set width for the associated video stream + * @param [in] width : the width of stream + * @return void + * @see VideoStream::GetWidth() + */ + void SetWidth(uint32_t width) { track_.width = width; } + /** + * @brief Get width for the associated video stream + * @return the width of stream + * @see VideoStream::SetWidth() + */ + uint32_t GetWidth() const { return track_.width; } + /** + * @brief Set height for the associated video stream + * @param [in] height : the height of stream + * @return void + * @see VideoStream::GetHeight() + */ + void SetHeight(uint32_t height) { track_.height = height; } + /** + * @brief Get height for the associated video stream + * @return the height of stream + * @see VideoStream::SetHeight() + */ + uint32_t GetHeight() const { return track_.height; } + /** + * @brief Set maximum width for the associated video stream + * @param [in] maxwidth : the maximum width of stream + * @return void + * @see VideoStream::GetMaxWidth() + */ + void SetMaxWidth(uint32_t maxwidth) { track_.maxwidth = maxwidth; } + /** + * @brief Get maximum width for the associated video stream + * @return the maximum width of stream + * @see VideoStream::SetMaxWidth() + */ + uint32_t GetMaxWidth() const { return track_.maxwidth; } + /** + * @brief Set maximum height for the associated video stream + * @param [in] maxheight : the maximum height of stream + * @return void + * @see VideoStream::GetMaxHeight() + */ + void SetMaxHeight(uint32_t maxheight) { track_.maxheight = maxheight; } + /** + * @brief Get maximum height for the associated video stream + * @return the maximum height of stream + * @see VideoStream::SetMaxHeight() + */ + uint32_t GetMaxHeight() const { return track_.maxheight; } + /** + * @brief Set framerate for the associated video stream + * @param [in] num : the numerator of framerate + * @param [in] den : the denominator of framerate + * @return void + * @see VideoStream::GetFramerate() + */ + void SetFramerate(uint32_t num, uint32_t den) { + track_.framerate_num = num; + track_.framerate_den = den; + } + /** + * @brief Get framerate for the associated video stream + * @param [out] num : the numerator of framerate + * @param [out] den : the denominator of framerate + * @return void + * @see VideoStream::SetFramerate() + */ + void GetFramerate(uint32_t* num, uint32_t* den) { + *num = track_.framerate_num; + *den = track_.framerate_den; + } + /** + * @brief Set codec data for the associated video stream + * @param [in] data : the codec data of stream + * @param [in] data_size : the size of codec data + * @return void + * @see VideoStream::GetCodecData(), VideoStream::GetCodecDataSize() + */ + void SetCodecData(std::shared_ptr data, uint32_t data_size) { + track_.codec_data = data; + track_.codec_data_len = data_size; + } + /** + * @brief Get codec data for the associated video stream + * @return std::shared_ptr of codec data + * @see VideoStream::SetCodecData(), VideoStream::GetCodecDataSize() + */ + std::shared_ptr GetCodecData() const { return track_.codec_data; } + /** + * @brief Get codec data for the associated video stream + * @return the size of codec data + * @see VideoStream::SetCodecData(), VideoStream::GetCodecData() + */ + uint32_t GetCodecDataSize() const { return track_.codec_data_len; } + + private: + void SetMimeType_(VideoMimeType mimetype); + Track GetTrack_() const { return track_; } + + friend class EsPlayer; + + private: + Track track_; + VideoMimeType mimetype_ = VideoMimeType::kUnKnown; +}; + +using VideoStreamPtr = VideoStream::Ptr; + +} // namespace plusplayer + +#endif // PLUSPLAYER_ELEMENTARY_STREAM_H_ diff --git a/include/plusplayer/es_eventlistener.h b/include/plusplayer/es_eventlistener.h new file mode 100755 index 0000000..33e9535 --- /dev/null +++ b/include/plusplayer/es_eventlistener.h @@ -0,0 +1,220 @@ +/** + * @file es_eventlistener.h + * @brief the event listener for plusplayer::EsPlusPlayer + * @interfacetype Module + * @privlevel None-privilege + * @privilege None + * @product TV, AV, B2B + * @version 0.0.1 + * @SDK_Support N + * @see plusplayer::EsPlusPlayer class + * + * Copyright (c) 2018 Samsung Electronics Co., Ltd All Rights Reserved + * PROPRIETARY/CONFIDENTIAL + * This software is the confidential and proprietary + * information of SAMSUNG ELECTRONICS ("Confidential Information"). You shall + * not disclose such Confidential Information and shall use it only in + * accordance with the terms of the license agreement you entered into with + * SAMSUNG ELECTRONICS. SAMSUNG make no representations or warranties about the + * suitability of the software, either express or implied, including but not + * limited to the implied warranties of merchantability, fitness for a + * particular purpose, or non-infringement. SAMSUNG shall not be liable for any + * damages suffered by licensee as a result of using, modifying or distributing + * this software or its derivatives. + */ +#ifndef __PLUSPLAYER_ES_EVENTLISTENER__H__ +#define __PLUSPLAYER_ES_EVENTLISTENER__H__ + +#include "plusplayer/drm.h" +#include "plusplayer/types/buffer.h" +#include "plusplayer/types/error.h" +#include "plusplayer/types/event.h" +#include "plusplayer/types/latency.h" +#include "plusplayer/types/stream.h" + +namespace plusplayer { + +/** + * @brief the interface of es player EventListener class + * @remark You must implement concrete class and register it to get es player + * events. + * @see EsPlusPlayer::RegisterListener() + */ +class EsEventListener { + public: + EsEventListener() noexcept {} + using UserData = void*; + virtual ~EsEventListener() {} + /** + * @brief It will be invoked when error has happened + * @param [in] error_code : #ErrorType + * @param [in] userdata : user data + * @see #ErrorType + * @see OnErrorMsg() if a detailed error message is required + */ + virtual void OnError(const ErrorType& error_code, UserData userdata) {} + /** + * @brief It will be invoked when error has happened + * @param [in] error_code : #ErrorType + * @param [in] error_msg : detailed error message including info related to + * codec,demuxer,network status, etc. + * @see #ErrorType + * @see OnError() if only simple error code is required + */ + virtual void OnErrorMsg(const ErrorType& error_code, const char* error_msg, + UserData userdata) {} + /** + * @brief It will be invoked when buffer overrun or underrun is detected + * @param [in] type : the type of target stream + * @param [in] status : current buffer status + * @param [in] userdata : the user data passed from the event listener + * registration function + */ + virtual void OnBufferStatus(const StreamType& type, + const BufferStatus& status, + const uint64_t byte_size, + const uint64_t time_size, UserData userdata) {} + /** + * @brief It will be invoked when H/W resource is conflicted + * @param [in] userdata : the user data passed from the event listener + * registration function + * @remarks Player state will be changed to #EsState::kPaused + */ + virtual void OnResourceConflicted(UserData userdata) {} + /** + * @brief It will be invoked when player has reached the end of the stream + * @param [in] userdata : the user data passed from the event listener + * registration function + */ + virtual void OnEos(UserData userdata) {} + /** + * @brief It will be invoked when player is prepared to be started + * @details This will be invoked when user calls + * EsPlusPlayer::PrepareAsync() + * @param [in] ret : statue of prepare (@c true = success, @c false = + * fail) + * @param [in] userdata : the user data passed from the event listener + * registration function + * @see EsPlusPlayer::PrepareAsync() + */ + virtual void OnPrepareDone(bool ret, UserData userdata) {} + /** + * @brief It will be invoked when ready to receive es packets after + * prepare + * @details This will be invoked when user calls + * EsPlusPlayer::PrepareAsync() + * @param [in] type : the stream type + * @param [in] userdata : the user data passed from the event listener + * registration function + * @see EsPlusPlayer::PrepareAsync() \n + * @see EsPlusPlayer::SubmitPacket() + */ + virtual void OnReadyToPrepare(const StreamType& type, UserData userdata) {} + /** + * @brief It will be invoked when the seek operation is completed + * @param [in] userdata : the user data passed from the event listener + * registration function + * @remarks OnSeekDone() will be called once seek operation is finished + * @see EsPlusPlayer::Seek() + */ + virtual void OnSeekDone(UserData userdata) {} + /** + * @brief It will be invoked when ready to receive es packets after seek + * @details This will be invoked when user calls EsPlusPlayer::Seek() + * @param [in] type : the stream type + * @param [in] offset : the new position to seek in milliseconds + * @param [in] userdata : the user data passed from the event listener + * registration function + * @see EsPlusPlayer::Seek() \n + * @see EsPlusPlayer::SubmitPacket() + */ + virtual void OnReadyToSeek(const StreamType& type, const uint64_t offset, + UserData userdata) {} + + /** + * @brief Set a callback function to be invoked when trackrender side need + * to get an useful tbm surface. + * @param [in] ptr : pointer which set to get tbm address. + * @param [in] is_scale_change : parameter which indicate whether the + * scale resolution changed. + * @remark SetVideoFrameBufferType() + */ + virtual void OnMediaPacketGetTbmBufPtr(void** ptr, bool is_scale_change) {} + + /** + * @brief Set a callback function to be invoked when player decoded video + * frame. A video frame can be retrieved + * @param [in] packet : decoded video packet + * @param [in] userdata : the user data passed from the event listener + * @remark SetVideoFrameBufferType() + */ + virtual void OnMediaPacketVideoDecoded(const DecodedVideoPacket& packet) {} + + /** + * @brief It will be invoked when player gets closed caption data from + * decoder + * @param [in] data : closed caption data + * @param [in] size : size of closed caption data + */ + virtual void OnClosedCaptionData(std::unique_ptr data, const int size, + UserData userdata) {} + + /** + * @brief It will be invoked when the flush operation is completed + * @param [in] userdata : the user data passed from the event listener + * registration function + * @remarks OnFlushDone() will be called once flush operation is finished + * @see EsPlusPlayer::Flush() + */ + virtual void OnFlushDone(UserData userdata) {} + + virtual void OnEvent(const EventType& event_type, const EventMsg& msg_data, + UserData userdata) {} + + virtual void OnFirstDecodingDone(UserData userdata) {} + + /** + * @brief It will be invoked when buffer underrun is detected from a video + * decoder. + * @param [in] userdata : the user data passed from the event listener + * registration function + */ + virtual void OnVideoDecoderUnderrun(UserData userdata) {} + + /** + * @brief It will be invoked when the latency status of the video stream + * changes. + * @param [in] latency_status : the latency status + * [in] userdata : the user data passed from the event listener + * registration function + */ + virtual void OnVideoLatencyStatus(const LatencyStatus& latency_status, + UserData userdata) {} + + /** + * @brief It will be invoked when the latency status of the audio stream + * changes. + * @param [in] latency_status : the latency status + * [in] userdata : the user data passed from the event listener + * registration function + */ + virtual void OnAudioLatencyStatus(const LatencyStatus& latency_status, + UserData userdata) {} + /** + * @brief It will be invoked when video high latency occurs. + * @param [in] userdata : the user data passed from the event listener + * registration function + */ + virtual void OnVideoHighLatency(UserData userdata) {} + + /** + * @brief It will be invoked when audio high latency occurs. + * @param [in] userdata : the user data passed from the event listener + * registration function + */ + virtual void OnAudioHighLatency(UserData userdata) {} +}; + +} // namespace plusplayer + +#endif // __PLUSPLAYER_ES_EVENTLISTENER__H__ \ No newline at end of file diff --git a/include/plusplayer/espacket.h b/include/plusplayer/espacket.h new file mode 100755 index 0000000..afea7e2 --- /dev/null +++ b/include/plusplayer/espacket.h @@ -0,0 +1,188 @@ +/** + * @file espacket.h + * @brief the packet for elementary stream + * @interfacetype Module + * @privlevel None-privilege + * @privilege None + * @product TV, AV, B2B + * @version 0.0.1 + * @SDK_Support N + * @see plusplayer::EsPlusPlayer class + * + * Copyright (c) 2018 Samsung Electronics Co., Ltd All Rights Reserved + * PROPRIETARY/CONFIDENTIAL + * This software is the confidential and proprietary + * information of SAMSUNG ELECTRONICS ("Confidential Information"). You shall + * not disclose such Confidential Information and shall use it only in + * accordance with the terms of the license agreement you entered into with + * SAMSUNG ELECTRONICS. SAMSUNG make no representations or warranties about the + * suitability of the software, either express or implied, including but not + * limited to the implied warranties of merchantability, fitness for a + * particular purpose, or non-infringement. SAMSUNG shall not be liable for any + * damages suffered by licensee as a result of using, modifying or distributing + * this software or its derivatives. + */ +#ifndef __PLUSPLAYER_ESPACKET_H__ +#define __PLUSPLAYER_ESPACKET_H__ + +#include +#include + +#include "plusplayer/types/stream.h" + +namespace plusplayer { + +/** + * @brief Structure of matroska meta data + */ +struct MatroskaMetaData { + double primary_r_chromaticity_x; + double primary_r_chromaticity_y; + double primary_g_chromaticity_x; + double primary_g_chromaticity_y; + double primary_b_chromaticity_x; + double primary_b_chromaticity_y; + double white_point_chromaticity_x; + double white_point_chromaticity_y; + double luminance_max; + double luminance_min; +}; +/** + * @brief Structure of matroska color information + */ +struct MatroskaColor { + uint32_t matrix_coefficients; + uint32_t bits_per_channel; + uint32_t chroma_subsampling_horizontal; + uint32_t chroma_subsampling_vertical; + uint32_t cb_subsampling_horizontal; + uint32_t cb_subsampling_vertical; + uint32_t chroma_siting_horizontal; + uint32_t chroma_siting_vertical; + uint32_t range; + uint32_t transfer_characteristics; + uint32_t primaries; + uint32_t max_cll; + uint32_t max_fall; + MatroskaMetaData metadata; + uint32_t is_hdr_10p; +}; + +/** + * @brief the interface of the packet class for elementary stream + * @see plusplayer::EsPlusPlayer class + */ +class EsPacket : private boost::noncopyable { + public: + using Ptr = std::unique_ptr; + /** + * @brief Create a espacket object + * @remarks You must use this to get espacket object + * @return espacket object (unique_ptr) + */ + static Ptr Create(const StreamType type = StreamType::kMax, + std::shared_ptr buffer = nullptr, + const uint32_t buffer_size = 0, const uint64_t pts = 0, + const uint64_t duration = 0, const uint32_t hdr10p_size = 0, + std::shared_ptr hdr10p_data = nullptr); + /** + * @brief Create an eos espacket object + * @remarks You must use this to get espacket object + * @return espacket object (unique_ptr) + */ + static Ptr CreateEos(const StreamType type = StreamType::kMax); + EsPacket() = delete; + + ~EsPacket() {} + /** + * @brief Get the stream type for the associated packet + * @return the stream type to set (StreamType) + */ + StreamType GetType() const { return type_; } + /** + * @brief Get the size of the buffer for the associated packet + * @return the size of the buffer to set (StreamType) + */ + uint32_t GetSize() const { return buffer_size_; } + /** + * @brief Get the size of the hdr10+ metadata size for the associated + * packet + * @return the size of the hdr10+ metadata to set (StreamType) + */ + uint32_t GetHdr10pSize() const { return hdr10p_metadata_size_; } + /** + * @brief Get the pts of buffer for the associated packet + * @return the pts to set + */ + uint64_t GetPts() const { return pts_; } + /** + * @brief Get the pts of buffer for the associated packet + * @return the duration to set + */ + uint64_t GetDuration() const { return duration_; } + /** + * @brief Get the buffer for the associated packet + * @return the buffer to set + */ + std::shared_ptr GetBuffer() const { return buffer_; } + /** + * @brief Get the Hdr10p Metadata for the associated packet + * @return the Hdr10p Metadata to set + */ + std::shared_ptr GetHdr10pData() const { return hdr10p_metadata_; } + /** + * @brief Set the matroska color information for the associated packet + * @return @c False if the streamtype of this EsPacket isn't + * StreamType::kVideo, otherwise @c True + * @see MatroskaColor + */ + bool SetMatroskaColorInfo(const MatroskaColor& color_info) { + if (type_ != StreamType::kVideo) return false; + matroska_color_info_.reset(new MatroskaColor(color_info)); + return true; + } + /** + * @brief Get the matroska color information for the associated packet + * @return the matroska color information to set + * @see MatroskaColor + */ + const MatroskaColor& GetMatroskaColorInfo() const { + return *matroska_color_info_.get(); + } + /** + * @brief Inform whether the matroska color information for the associated + * packet is set + * @return @c True if matroska color information is set, otherwise @c False + * @see SetMatroskaColorInfo(), GetMatroskaColorInfo() + */ + bool HasMatroskaColorInfo() const { return matroska_color_info_ != nullptr; } + /** + * @brief Inform whether this EsPacket is for EndOfStream(EOS) + * @return @c True if this EsPacket is EndOfStream(EOS) Packet, otherwise + * @c False + * @see CreateEos() + */ + bool IsEosPacket() const { return buffer_size_ == 0 && buffer_ == nullptr; } + + private: + explicit EsPacket(const StreamType type, std::shared_ptr buffer, + const uint32_t buffer_size, const uint64_t pts, + const uint64_t duration, const uint32_t hdr10p_size, + std::shared_ptr hdr10p_data); + + private: + const StreamType type_ = StreamType::kMax; + std::shared_ptr buffer_ = nullptr; + const uint32_t buffer_size_ = 0; + const uint64_t pts_ = 0; + const uint64_t duration_ = 0; + const uint32_t hdr10p_metadata_size_ = 0; + std::shared_ptr hdr10p_metadata_ = nullptr; + std::unique_ptr matroska_color_info_ = nullptr; +}; + +using EsPacketPtr = EsPacket::Ptr; + +} // namespace plusplayer + +#endif // __PLUSPLAYER_ESPACKET_H__ \ No newline at end of file diff --git a/include/plusplayer/esplusplayer.h b/include/plusplayer/esplusplayer.h new file mode 100755 index 0000000..890dbda --- /dev/null +++ b/include/plusplayer/esplusplayer.h @@ -0,0 +1,1066 @@ +/** + * @file esplusplayer.h + * @brief the playback for elementary stream + * @interfacetype Module + * @privlevel None-privilege + * @privilege None + * @product TV, AV, B2B + * @version 0.0.1 + * @SDK_Support N + * + * Copyright (c) 2018 Samsung Electronics Co., Ltd All Rights Reserved + * PROPRIETARY/CONFIDENTIAL + * This software is the confidential and proprietary + * information of SAMSUNG ELECTRONICS ("Confidential Information"). You shall + * not disclose such Confidential Information and shall use it only in + * accordance with the terms of the license agreement you entered into with + * SAMSUNG ELECTRONICS. SAMSUNG make no representations or warranties about the + * suitability of the software, either express or implied, including but not + * limited to the implied warranties of merchantability, fitness for a + * particular purpose, or non-infringement. SAMSUNG shall not be liable for any + * damages suffered by licensee as a result of using, modifying or distributing + * this software or its derivatives. + */ +#ifndef __PLUSPLAYER_ESPLUSPLAYER__H__ +#define __PLUSPLAYER_ESPLUSPLAYER__H__ + +#include +#include +#include +#include +#include +#include +#include + +#ifndef IS_AUDIO_PRODUCT +#include "mixer/mixer.h" +#endif + +#include "plusplayer/appinfo.h" +#include "plusplayer/audioeasinginfo.h" +#include "plusplayer/drm.h" +#include "plusplayer/elementary_stream.h" +#include "plusplayer/es_eventlistener.h" +#include "plusplayer/espacket.h" +#include "plusplayer/external_drm.h" +#include "plusplayer/types/buffer.h" +#include "plusplayer/types/display.h" +#include "plusplayer/types/error.h" +#include "plusplayer/types/latency.h" +#include "plusplayer/types/picturequality.h" +#include "plusplayer/types/resource.h" +#include "plusplayer/types/submitdata.h" + +namespace plusplayer { + +/** + * @brief Enumerations for es player state. + */ +enum class EsState { + kNone, // Player is created, but not opened + kIdle, // Player is opened, but not prepared or player is stopped + kReady, // Player is ready to play(start) + kPlaying, // Player is playing media + kPaused, // Player is paused while playing media +}; +/** + * @brief Enumerations for espacket submit status. + */ +enum class PacketSubmitStatus { + kNotPrepared, // not prepared to get packet + kInvalidPacket, // invalid packet + kOutOfMemory, // out of memory on device + kFull, // buffer already full + kSuccess // submit succeeded +}; +/** + * @brief Enumerations for adaptive info type. + */ +enum class PlayerAdaptiveInfo { + kMinType, + kVideoDroppedFrames, + kDroppedVideoFramesForCatchup, + kDroppedAudioFramesForCatchup, + kMaxType, +}; +/** + * @brief Enumerations for low latency mode + */ +enum PlayerLowLatencyMode { + kLowLatencyModeNone = 0x0000, + /** + * @description to support audio fast decoding/rendering + */ + kLowLatencyModeAudio = 0x0001, + /** + * @description to support video fast decoding/rendering + * only this value is set, it does not support seamless resolution change. + */ + kLowLatencyModeVideo = 0x0010, + /** + * @description to support video fast decoding/rendering and video + distortion concealment. + Video stream should be composed only of P and I frames. + For applications using the UDP protocol, packet loss can + occur. when video distortion by video packet loss is + detected, it is a function to conceal distortion by showing + previous vido frame. It is supported only in h.264 codec & + FHD or lower resolution. + */ + kLowLatencyModeVideoDistortionConcealment = kLowLatencyModeVideo | 0x0020, + /** + * @description to support video fast decoding/rendering and video + * with seamless resolution change. + */ + kLowLatencyModeVideoWithSeamlessResolutionChange = 0x0040, + /** + * @description to disable clock sync and a/v sync when rendering. it + * includes #kLowLatencyModeDisablePreroll. + */ + kLowLatencyModeDisableAVSync = 0x0100, + /** + * @description to disable preroll which means player doesn't wait for + * first buffer when state is changed to + * #EsState::kReady from #EsState::kIdle. + * It changes the state immediately. + * It's usually used for sparse stream. (e.g. video packet + * arrives but audio packet does't yet.) + */ + kLowLatencyModeDisablePreroll = 0x0200, + /** + * @description to set lower video quality + */ + kLowLatencyModeDisableVideoQuality = 0x1000, + /** + * @description to set game mode + */ + kLowLatencyModeEnableGameMode = 0x2000 +}; + +/** + * @brief Enumerations for player audio codec type + */ +enum PlayerAudioCodecType { + /** + * @description hardware codec can only be selected, default type + */ + kPlayerAudioCodecTypeHW, + /** + * @description software codec can only be selected. + */ + kPlayerAudioCodecTypeSW, +}; + +/** + * @brief Enumerations for player video codec type + */ +enum PlayerVideoCodecType { + /** + * @description hardware codec can only be selected, default type + */ + kPlayerVideoCodecTypeHW, + /** + * @description software codec can only be selected. + */ + kPlayerVideoCodecTypeSW, + /** + * @description hardware codec using n-decoding mode can only be selected. + It must set display type to mixer type by display setting api. + */ + kPlayerVideoCodecTypeHWNdecoding, +}; + +/** + * @brief the interface of the playback class for elementary stream + */ +class EsPlusPlayer : private boost::noncopyable { + public: + using Ptr = std::unique_ptr; + /** + * @brief Create a esplusplayer object + * @remarks You must use this to get esplusplayer object + * @return Player object (unique_ptr) + */ + static Ptr Create(); + + public: + virtual ~EsPlusPlayer() {} + /** + * @brief Make player get ready to set playback mode + * @remarks General call sequence to start playback: + * Open() -> SetStream() -> PrepareAsync() -> Start() -> Stop() -> + * Close() + * @pre Player state must be kNone + * @post The player state will be EsState::kIdle + * @return @c True on success, otherwise @c False + * @see Close() + */ + virtual bool Open() { return false; } + /** + * @brief Release all the player resources + * @pre The player state must be one of + * EsState::kIdle or EsState::kNone + * @post The player state will be kNone + * @return @c True on success, otherwise @c False + * @see EsPlusPlayer::Open() + */ + virtual bool Close() { return false; } + + /** + * @brief Flush the specific buffered stream data and release TV resource + * to change stream. + * @remark To activate, the stream must be set again. + * @pre The player must be set to at least #EsState::kReady + * @post The player state is same as before calling Deactivate(). + * @return @c True on success, otherwise @c False. + * @see EsPlusPlayer::Deactivate(). + */ + virtual bool Deactivate(const StreamType type) { return false; } + + /** + * @brief Reprepare for the specific stream playback. + * @remark There must be active stream to prepare playback. + * @pre The player must be set to at least #EsState::kReady + * The StreamType must be deactivated in advance. + * Stream should be set in advance. + * @return @c True on success, otherwise @c False + * @code + PrepareAsync(); + Deactivate(StreamType::kVideo); + VideoStreamPtr video_stream = VideoStream::Create(); + video_stream->SetMimeType(VideoMimeType::kH264); + video_stream->SetWidth(640); + video_stream->SetHeight(352); + video_stream->SetFramerate(30, 1); + SetStream(video_stream); + Activate(StreamType::kVideo); + * @endcode + * @see EsPlusPlayer::Activate() + */ + virtual bool Activate(const StreamType type) { return false; } + + /** + * @brief Prepare the player for playback, asynchronously. + * @remarks EsEventListener::OnPrepareDone() will be called when prepare is + * finished + * + * @pre The player state must be set to #EsState::kIdle + * @post The player state will be #EsState::kReady + * @return @c true if async task is correctly started + * @see EsPlusPlayer::Open() \n + * EsPlusPlayer::Stop() \n + * EsPlusPlayer::SubmitPacket() + */ + virtual bool PrepareAsync() { return false; } + /** + * @brief Start playback + * @pre The player state should be #EsState::kReady + * @post The player state will be #EsState::kPlaying + * @return @c True on success, otherwise @c False + * @see EsPlusPlayer::Open() \n + * EsPlusPlayer::PrepareAsync() \n + * EsPlusPlayer::Stop() \n + * EsPlusPlayer::Close() + */ + virtual bool Start() { return false; } + /** + * @brief Stop playing media content + * @remarks EsPlusPlayer::Close() must be called once after player is + * stopped + * @pre The player state must be all of #EsState except #EsState::kNone + * @post The player state will be #EsState::kIdle + * @return @c True on success, otherwise @c False + * @see EsPlusPlayer::Open() \n + * EsPlusPlayer::PrepareAsync() \n + * EsPlusPlayer::Start() \n + * EsPlusPlayer::Pause() \n + * EsPlusPlayer::Resume() \n + * EsPlusPlayer::Close() + */ + virtual bool Stop() { return false; } + /** + * @brief Pause playing media content + * @remarks You can resume playback by using EsPlusPlayer::Resume() + * @pre The player state must be one of #EsState::kReady or + * #EsState::kPlaying or #EsState::kPaused + * @post The player state will be #EsState::kPaused + * @return @c True on success, otherwise @c False + * @see EsPlusPlayer::Start() \n + * EsPlusPlayer::Resume() + */ + virtual bool Pause() { return false; } + /** + * @brief Resume a media content + * @pre The player state must be one of #EsState::kPlaying or + * #EsState::kPaused + * @post The player state will be #EsState::kPlaying + * @return @c True on success, otherwise @c False + * @see EsPlusPlayer::Start() \n + * EsPlusPlayer::Pause() + */ + virtual bool Resume() { return false; } + /** + * @brief SetAppId + * @remarks Set app_id to resource manager. Resource manager check the + * priority to control resource. + * @param [in] app_info : application id, version, type + * @pre The player state must be set to #EsState::kIdle + * @return None + */ + virtual void SetAppInfo(const PlayerAppInfo& app_info) { return; } + /** + * @brief SetPlaybackRate. + * @remarks Set playback rate from 0.0 to 2.0. + * @param [in] rate : The playback rate from 0.0 to 2.0. EsPlayer isn't + * support trick play. + * @param [in] audio_mute : The audio is mute on/off,true: mute on, false: + * mute off. + * @pre The source and feeder have to push the data as fast as playback + * rate. + * @return @c True if set playback rate is finished without any problem + * otherwise @c False + */ + virtual bool SetPlaybackRate(const double rate, bool audio_mute) { + return false; + } + /** + * @brief Seek for playback, asynchronously. + * @remarks EsEventListener::OnSeekDone() will be called if seek operation + * is finished \n Seek result can be succeeded or not at this moment. \n + * @param [in] time_millisecond : the absolute position(playingtime) of + * the stream in milliseconds + * @pre The player state must be one of #EsState::kReady, + * #EsState::kPlaying or #EsState::kPaused + * @return @c True if seek operation is started without any problem + * otherwise @c False + * @see EsEventListener::OnSeekDone() \n + * EsPlusPlayer::SubmitPacket() + */ + virtual bool Seek(const uint64_t time_millisecond) { return false; } + /** + * @brief Set the video display + * @remarks We are not supporting changing display. + * @remarks This API have to be called before calling the + * EsPlusPlayer::PrepareAsync() to reflect the display type. + * @param [in] type : display type + * @param [in] obj : The handle to display window + * @pre The player state must be set to #EsState::kIdle + * @return @c True on success, otherwise @c False + * @see DisplayType \n + * EsPlusPlayer::SetDisplayMode() \n + * EsPlusPlayer::SetDisplayRoi() \n + * EsPlusPlayer::SetDisplayVisible() + */ + virtual bool SetDisplay(const DisplayType& type, void* obj) { return false; } +#ifndef IS_AUDIO_PRODUCT + /** + * @brief Set the video display + * @remarks We are not supporting changing display. + * @remarks This API have to be called before calling the + * PlusPlayer::Prepare() + * or PlusPlayer::PrepareAsync() to reflect the display type. + * @param [in] type : display type + * @param [in] handle : The handle of mixer ticket + * @pre The player state must be set to #EsState::kIdle + * @post None + * @return @c True on success, otherwise @c False + * @see DisplayType + * PlusPlayer::SetDisplayRoi() + * PlusPlayer::SetDisplayVisible() + * @exception None + */ + virtual bool SetDisplay(const DisplayType& type, MixerTicket* handle) { + return false; + } +#endif + /** + * @brief Set the video display + * @remarks We are not supporting changing display. + * @remarks This API have to be called before calling the + * EsPlusPlayer::PrepareAsync() to reflect the display type. + * @param [in] type : display type + * @param [in] ecore_wl2_window : The ecore wayland window handle + * @param [in] x : x param of display window + * @param [in] y : y param of display window + * @param [in] w : width of display window + * @param [in] h : height of display window + * @pre The player state must be set to #EsState::kIdle + * @return @c True on success, otherwise @c False + * @see DisplayType \n + * EsPlusPlayer::SetDisplayMode() \n + * EsPlusPlayer::SetDisplayRoi() \n + * EsPlusPlayer::SetDisplayVisible() + */ + virtual bool SetDisplay(const DisplayType& type, void* ecore_wl2_window, + const int x, const int y, const int w, const int h) { + return false; + } + /** + * @brief Set the video display + * @remarks We are not supporting changing display. + * @remarks This API have to be called before calling the + * EsPlusPlayer::PrepareAsync() to reflect the display type. + * @param [in] type : display type + * @param [in] ecore_wl2_subsurface : The ecore wayland subsurface handle + * @param [in] x : x param of display window + * @param [in] y : y param of display window + * @param [in] w : width of display window + * @param [in] h : height of display window + * @pre The player state must be set to #EsState::kIdle + * @return @c True on success, otherwise @c False + * @see DisplayType \n + * EsPlusPlayer::SetDisplayMode() \n + * EsPlusPlayer::SetDisplayRoi() \n + * EsPlusPlayer::SetDisplayVisible() + */ + virtual bool SetDisplaySubsurface(const DisplayType& type, + void* ecore_wl2_subsurface, const int x, + const int y, const int w, const int h) { + return false; + } + /** + * @brief Set the video display + * @remarks We are not supporting changing display. + * @remarks This API have to be called before calling the + * EsPlusPlayer::PrepareAsync() to reflect the display type. + * @param [in] type : display type + * @param [in] surface_id : resource id of window. + * @param [in] x : x param of display window + * @param [in] y : y param of display window + * @param [in] w : width of display window + * @param [in] h : height of display window + * @pre The player state must be set to #EsState::kIdle + * @return @c True on success, otherwise @c False + * @see DisplayType \n + * EsPlusPlayer::SetDisplayMode() \n + * EsPlusPlayer::SetDisplayRoi() \n + * EsPlusPlayer::SetDisplayVisible() + */ + virtual bool SetDisplay(const DisplayType& type, unsigned int surface_id, + const int x, const int y, const int w, const int h) { + return false; + } + /** + * @brief Set the video display mode + * @param [in] mode : display mode + * @pre The player state can be all of #EsState except #EsState::kNone + * @return @c True on success, otherwise @c False + * @see #DisplayMode + * @see EsPlusPlayer::SetDisplay() + * @see EsPlusPlayer::SetDisplayRoi() + * @see EsPlusPlayer::SetDisplayVisible() + */ + virtual bool SetDisplayMode(const DisplayMode& mode) { return false; } + /** + * @brief Set the ROI(Region Of Interest) area of display + * @remarks The minimum value of width and height are 1. + * @param [in] roi : Roi Geometry + * @pre The player state can be all of #EsState except #EsState::kNone + * \n Before set display ROI, #DisplayMode::kDstRoi must be set with + * EsPlusPlayer::SetDisplayMode(). + * @return @c True on success, otherwise @c False + * @see DisplayMode \n + * EsPlusPlayer::SetDisplay() \n + * EsPlusPlayer::SetDisplayMode() \n + * EsPlusPlayer::SetDisplayVisible() + */ + virtual bool SetDisplayRoi(const Geometry& roi) { return false; } + + /** + * @brief Set scaled area ratio of display + * @param [in] area : Crop ratio of src area + * @pre The player state can be all of #EsState except #EsState::kNone + * @return @c True on success, otherwise @c False + * @remark The minimum value of input are 0,maximun is 1. + */ + virtual bool SetVideoRoi(const CropArea& area) { return false; } + + /** + * @brief Resize the render rectangle(the max region that video can be + * displayed). + * @param [in] rect : render rectangle. + * @pre Should be called after SetDisplay() + * @return @c True on success, otherwise @c False + * @remark The minimum value of width and height are 1. + */ + virtual bool ResizeRenderRect(const RenderRect& rect) { return false; } + + /** + * @brief Set the rotate angle of display + * @remarks The default value is 0. + * @param [in] rotate : Rotate angle. + * @pre The player state can be all of #EsState except #EsState::kNone + * @return @c True on success, otherwise @c False + * @see EsPlusPlayer::SetDisplay() + */ + virtual bool SetDisplayRotate(const DisplayRotation& rotate) { return false; } + + /** + * @brief Get the rotate angle of display + * @remarks The default value is 0. + * @param [out] rotate : Stored rotate angle value. + * @pre The player state can be all of #EsState except #EsState::kNone + * @return @c True on success, otherwise @c False + * @see EsPlusPlayer::SetDisplayRotate() + */ + virtual bool GetDisplayRotate(DisplayRotation* rotate) { return false; } + + /** + * @brief Set the visibility of the video display + * @param [in] is_visible : The visibility of the display + * (@c true = visible, @c false = non-visible) + * @pre The player state can be all of #EsState except #EsState::kNone + * @return @c True on success, otherwise @c False + * @see EsPlusPlayer::SetDisplay() + */ + virtual bool SetDisplayVisible(bool is_visible) { return false; } + + /** + * @deprecated Use SetSubmitDataType instead. + * @brief Set whether to send decrypted es packets in the trust zone + * @remarks This API have to be called before calling the + * EsPlusPlayer::PrepareAsync() \n If is_using_tz is set to true, use + * EsPlusPlayer::SubmitTrustZonePacket() to send decrypted packets \n + * @param [in] is_using_tz : whether to use trust zone memory + * (@c true = if decrypted packets are sent in trust zone, @c false + * = otherwise) + * @pre The player state must be set to #EsState::kIdle + * @return @c True on success, otherwise @c False + * @see EsPlusPlayer::SubmitTrustZonePacket() + */ + virtual bool SetTrustZoneUse(bool is_using_tz) { return false; } + /** + * @brief Set whether to send decrypted es packets in the trust zone or + * encrypted es packets + * @remarks This API have to be called before calling the + * EsPlusPlayer::PrepareAsync() \n + * If type is kCleanData, Use SubmitPacket() \n + * If type is kTrustZoneData, Use SubmitTrustZonePacket() \n + * If type is kEncryptedData, Use SubmitEncryptedPacket() + * @param [in] type : whether to use trust zone memory or encrypted data + * @pre The player state must be set to #EsState::kIdle + * @return @c True on success, otherwise @c False + * @see EsPlusPlayer::SubmitPacket() \n + * EsPlusPlayer::SubmitTrustZonePacket() \n + * EsPlusPlayer::SubmitEncryptedPacket() \\n + * SubmitDataType + */ + virtual bool SetSubmitDataType(SubmitDataType type) { return false; } + /** + * @brief Set audio stream to have contents information + * @remarks This API have to be called before calling the + * EsPlusPlayer::PrepareAsync() + * @param [in] stream : audio stream object + * @pre The player state must be set to #EsState::kIdle + * @return @c True on success, otherwise @c False + * @see AudioStream + */ + virtual bool SetStream(const AudioStreamPtr& stream) { return false; } + /** + * @brief Set video stream to have contents information + * @remarks This API have to be called before calling the + * EsPlusPlayer::PrepareAsync() + * @param [in] stream : video stream object + * @pre The player state must be set to #EsState::kIdle + * @return @c True on success, otherwise @c False + * @see VideoStream + */ + virtual bool SetStream(const VideoStreamPtr& stream) { return false; } + /** + * @brief Submit es packet to decode audio or video + * @remarks Amount of packets for at least one decoded frame must be + * submitted after EsPlusPlayer::PrepareAsync() or EsPlusPlayer::Seek() for + * calling the EsPlusPlayer::OnPrepareDone() or EsPlusPlayer::OnSeekDone() \n + * User can submit es packets when EsPlusPlayer::OnReadyToPrepare() + * or EsPlusPlayer::OnReadyToSeek() is called \n + * To use this api, Must set SubmitDataType::kCleanData using + * SetSubmitDataType() or Don't set any SubmitDataType \n + * This api must be called from a different thread than other apis + * @param [in] packet : es packet object + * @pre The player state can be all of #EsState except #EsState::kNone + * @return @c PacketSubmitStatus::kSuccess : succeed to submit es packet + * @c otherwise : fail to submit es packet + * @see SetSubmitDataType() \n + * EsPacket \n + * OnBufferStatus \n + * OnReadyToPrepare \n + * OnReadyToSeek \n + * PacketSubmitStatus + */ + virtual PacketSubmitStatus SubmitPacket(const EsPacketPtr& packet) { + return PacketSubmitStatus::kNotPrepared; + } + /** + * @brief Submit es packet to decode audio or video in the trust zone + * @remarks Amount of decrypted packets for at least one decoded frame must + * be submitted after EsPlusPlayer::PrepareAsync() or EsPlusPlayer::Seek() for + * calling the EsPlusPlayer::OnPrepareDone() or EsPlusPlayer::OnSeekDone() \n + * User can submit es packets when EsPlusPlayer::OnReadyToPrepare() + * or EsPlusPlayer::OnReadyToSeek() is called \n + * EsPlusPlayer::SetTrustZoneUse() must be set to true \n + * To use this api, Must set SubmitDataType::kTrustZoneData using + * SetSubmitDataType() \n + * This api must be called from a different thread than other apis. + * @param [in] packet : es packet object + * [in] tz_handle : a handle for es packet in the trust zone + * @pre The player state can be all of #EsState except #EsState::kNone + * @return @c PacketSubmitStatus::kSuccess : succeed to submit es packet + * @c otherwise : fail to submit es packet + * @see SetSubmitDataType() \n + * EsPacket \n + * OnBufferStatus \n + * OnReadyToPrepare \n + * OnReadyToSeek \n + * PacketSubmitStatus + */ + virtual PacketSubmitStatus SubmitTrustZonePacket(const EsPacketPtr& packet, + uint32_t tz_handle = 0) { + return PacketSubmitStatus::kNotPrepared; + } + /** + * @brief Submit encrypted es packet to decode audio or video + * @remarks Amount of encrypted packets for at least one decoded frame must + * be submitted after EsPlusPlayer::PrepareAsync() or EsPlusPlayer::Seek() for + * calling the EsPlusPlayer::OnPrepareDone() or EsPlusPlayer::OnSeekDone() + * User can submit es packets when EsPlusPlayer::OnReadyToPrepare() + * or EsPlusPlayer::OnReadyToSeek() is called \n + * EsPlusPlayer::SetDrm() must be set to an appropriate drm type \n + * To use this api, Must set SubmitDataType::kEncryptedData using + * SetSubmitDataType() \n + * This api must be called from a different thread than other apis. + * @param [in] packet : encrypted es packet object + * [in] drm_info : information for decryption + * @pre The player state can be all of #EsState except #EsState::kNone + * @return @c PacketSubmitStatus::kSuccess : succeed to submit es packet + * @c PacketSubmitStatus::kFull, PacketSubmitStatus::kNotPrepared : + * fail to submit es packet + * @see SetSubmitDataType() \n + * EsPacket \n + * OnBufferStatus \n + * OnReadyToPrepare \n + * OnReadyToSeek \n + * PacketSubmitStatus + */ + virtual PacketSubmitStatus SubmitEncryptedPacket( + const EsPacketPtr& packet, const drm::EsPlayerEncryptedInfo& drm_info) { + return PacketSubmitStatus::kNotPrepared; + } + /** + * @brief Get current state of player + * @return current #EsState of player + */ + virtual EsState GetState() { return EsState::kNone; } + /** + * @brief Get the current playing time of the associated media. + * @param [out] time_in_milliseconds : current playing time in + * milliseconds + * @pre The player must be one of #EsState::kPlaying or + * #EsState::kPaused + * @return @c True on success, otherwise @c False + * ("time_in_milliseconds" will be 0) + */ + virtual bool GetPlayingTime(uint64_t* time_in_milliseconds) { return false; } + /** + * @brief Get the adaptive info from the plugins + * @param [in] adaptive_type : App wanted get info type + * @param [out] padaptive_info : return value of requested(such as dropped + * frames) + * @pre The player must be one of #EsState::kPlaying or + * #EsState::kPaused or #EsState::kReady + * @return @c True on success, otherwise @c False + */ + virtual bool GetAdaptiveInfo(void* padaptive_info, + const PlayerAdaptiveInfo& adaptive_type) { + return false; + } + /** + * @brief Set on mute of the audio sound + * @param [in] is_mute : On mute of the sound + * (@c true = mute, @c false = non-mute) + * @pre The player state can be all of #EsState except #EsState::kNone + * @return @c True on success, otherwise @c False + */ + virtual bool SetAudioMute(bool is_mute) { return false; } + + /** + * @brief Set decoded video frame buffer type. + * @param [in] type : A type of decoded video frame buffer. + * @pre The player state must be set to #EsState::kIdle + */ + virtual bool SetVideoFrameBufferType(DecodedVideoFrameBufferType type) { + return false; + } + + /** + * @brief Set the request frame rate of decoded video + * @remarks Only works when decoded video frame buffer type is scale + * @param [in] request_framerate : the request frame rate of returned decoded video frame + * The value of track_framerate and request_framerate should be one of the following sets: + * track_framerate indicate the frame rate of input video stream + * 1.A/(A-B) = X ,X means drop 1 frame every X frame + * 2.Special cases,such as 24000/1000 -> 15000/1000 + * when request_framerate.num = 0, return none decoded video frame. + * when request_framerate.num/request_framerate.den = + * track_framerate.num/track_framerate.den, return all decoded + * video frame. + * when request_framerate.num/request_framerate.den < + * track_framerate.num/track_framerate.den, drop some decoded + * video frame. + * @pre The player state must be not #EsState::kNone + * @return @c True on success, otherwise @c False + */ + virtual bool SetDecodedVideoFrameRate(const Rational& request_framerate) { + return false; + } + + /** + * @brief Set the target scale resolution when decoded video frame buffer + * type is scale + * @param [in] target_width : target width of scale. + * @param [in] target_height : target height of scale. + * @pre The player state must be set to #EsState::kIdle + * @return @c True on success, otherwise @c False + */ + virtual bool SetVideoFrameBufferScaleResolution( + const uint32_t& target_width, const uint32_t& target_height) { + return false; + } + + /** + * @brief Register eventlistener to player + * @param [in] listener : listener object + * @param [in] userdata : listener object's userdata to be returned via + * notification without any modification + * @pre The player state can be all of #EsState except #EsState::kNone + * @see EsEventListener + */ + virtual void RegisterListener(EsEventListener* listener, + EsEventListener::UserData userdata) { + return; + } + /** + * @brief Set volume to player + * @param [in] volume : volume level + * @pre The player state can be all of #EsState except #EsState::kNone + * @return @c True on success, otherwise @c False + */ + virtual bool SetVolume(const int& volume) { return false; } + /** + * @brief Get volume to player + * @param [out] volume : volume ptr + * @pre The player state can be all of #EsState except #EsState::kNone + * @return @c True on success, otherwise @c False + */ + virtual bool GetVolume(int* volume) { return false; } + /** + * @brief Flush the data in pipeline + * @param [in] type : can be kAudio/kVideo + * @pre The player state can greater than #EsState::kIdle + * @return @c True on success, otherwise @c False + */ + virtual bool Flush(const StreamType& type) { return false; } + /** + * @brief Set the buffer size + * @param [in] option : A type of Buffer Option + * @pre The player state must be set to #EsState::kIdle + * @return @c True on success, otherwise @c False + */ + virtual void SetBufferSize(const BufferOption& option, uint64_t size) { + return; + } + /** + * @brief Provided api for setting low latency mode + * @remarks This API have to be called before calling the + * EsPlusPlayer::PrepareAsync() + * @param [in] mode : one of the low latency mode to set. + * @pre The player state must be set to #EsState::kIdle + * @return @c True on success, otherwise @c False + */ + virtual bool SetLowLatencyMode(const PlayerLowLatencyMode& mode) { + return false; + } + + /** + * @brief Provided api for enabling video frame peek mode + * @pre The player state must be set to #EsState::kIdle. + * @return @c True on success, otherwise @c False + * @see RenderVideoFrame(). + */ + virtual bool SetVideoFramePeekMode() { return false; } + + /** + * @brief Provided api for rendering a video frame which is holded by + * video frame peek mode. + * @pre In order to use this api, The player state must be one of + * #EsState::kReady or #EsState::kPaused after EsEventListener::OnSeekDone() + * or EsEventListener::OnPrepareDone() is called \n + * @see SetVideoFramePeekMode(). + */ + virtual bool RenderVideoFrame() { return false; } + /** + * @brief Provided api for setting unlimited max buffer mode + * @remarks The player does not limit es packet transmission although in + * buffer overrun status. + * @pre The player state must be set to #EsState::kIdle + * @return @c True on success, otherwise @c False + */ + virtual bool SetUnlimitedMaxBufferMode() { return false; } + /** + * @brief Provided api for enabling fmm mode + * @pre The player state must be set to #EsState::kIdle. + * @return @c True on success, otherwise @c False + */ + virtual bool SetFmmMode() { return false; } + /** + * @brief Provided api for setting audio codec type + * @pre The player state must be set to #EsState::kIdle. + * @return @c True on success, otherwise @c False + */ + virtual bool SetAudioCodecType(const PlayerAudioCodecType& type) { + return false; + } + /** + * @brief Provided api for setting video codec type + * @pre The player state must be set to #EsState::kIdle. + * @return @c True on success, otherwise @c False + */ + virtual bool SetVideoCodecType(const PlayerVideoCodecType& type) { + return false; + } + /** + * @brief Provided api for setting alternative video resource(sub decoder + * and sub scaler) + * @param [in] is_set : set alternative video resource + * (@c 0 [defualt] = set all video resources(decoder/scaler) to + * main resources, + * @c 1 = set all video resources(decoder/scaler) to sub + * resources, + * @c 2 = set only decoder to sub resource, + * @c 3 = set only scaler to sub resource) + * @pre The player state can be all of #EsState except #EsState::kNone + * @return @c True on success, otherwise @c False + */ + virtual bool SetAlternativeVideoResource(unsigned int rsc_type) { + return false; + } + /** + * @brief Provided api for switching audio stream between the different + * audio codec types on the fly + * @param [in] stream : audio stream object + * @remark Audio stream can be switched between only #AudioMimeType::kAAC, + * #AudioMimeType::kEac3 and #AudioMimeType::kAC3. + * if other codec type is set, this api will return false. + * @pre The player state must be one of #EsState::kReady, + * #EsState::kPlaying, #EsState::kPaused. + * @return @c True on success, otherwise @c False + */ + virtual bool SwitchAudioStreamOnTheFly(const AudioStreamPtr& stream) { + return false; + } + + /** + * @brief Provided api for setting aifilter to video pipeline + * @pre The player state must be set to #EsState::kIdle. + * @return @c True on success, otherwise @c False + */ + virtual bool SetAiFilter(void* aifilter) { return false; } + /** + * @brief Provided api for setting render time offset + * @param [in] type : stream type + * @param [in] offset : offset (milisecond). + * @pre The player state must be set to #EsState::kReady, + * #EsState::kPaused or #EsState::kPlaying. + * It have to be set to low latency mode. + * @remark esplusplayer_set_low_latency_mode(). + * @return @c True on success, otherwise @c False + */ + virtual bool SetRenderTimeOffset(const StreamType type, int64_t offset) { + return false; + } + /** + * @brief Provided api for getting render time offset + * @param [in] type : stream type + * @param [in] offset : offset ptr (milisecond). + * @pre The player state must be set to #EsState::kReady, + * #EsState::kPaused or #EsState::kPlaying. + * It have to be set to low latency mode. + * @remark esplusplayer_set_low_latency_mode(). + * @return @c True on success, otherwise @c False + */ + virtual bool GetRenderTimeOffset(const StreamType type, int64_t* offset) { + return false; + } + + /** + * @brief Provided api for setting catch up speed level + * @pre The player state must be set to #EsState::kIdle, + * #EsState::kReady, #EsState::kPlaying or #EsState::kPaused + * @return @c True on success, otherwise @c False + */ + virtual bool SetCatchUpSpeed(const CatchUpSpeed& level) { return false; } + /** + * @brief Provided api for getting current video latency status + * @pre The player state must be one of #EsState::kReady, + * #EsState::kPlaying or #EsState::kPaused + * @return @c True on success, otherwise @c False + */ + virtual bool GetVideoLatencyStatus(LatencyStatus* status) { return false; } + /** + * @brief Provided api for getting current audio latency status + * @pre The player state must be one of #EsState::kReady, + * #EsState::kPlaying or #EsState::kPaused + * @return @c True on success, otherwise @c False + */ + virtual bool GetAudioLatencyStatus(LatencyStatus* status) { return false; } + /** + * @brief Provided api for setting video mid latency threshold for low + * latency playback + * @pre The player state must be set to #EsState::kIdle, + * #EsState::kReady, #EsState::kPlaying or #EsState::kPaused + * @return @c True on success, otherwise @c False + */ + virtual bool SetVideoMidLatencyThreshold(const unsigned int threshold) { + return false; + } + + /** + * @brief Provided api for setting audio mid latency threshold for low + * latency playback + * @pre The player state must be set to #EsState::kIdle, + * #EsState::kReady, #EsState::kPlaying or #EsState::kPaused + * @return @c True on success, otherwise @c False + */ + virtual bool SetAudioMidLatencyThreshold(const unsigned int threshold) { + return false; + } + + /** + * @brief Provided api for setting video high latency threshold for low + * latency playback + * @pre The player state must be set to #EsState::kIdle, + * #EsState::kReady, #EsState::kPlaying or #EsState::kPaused + * @return @c True on success, otherwise @c False + */ + virtual bool SetVideoHighLatencyThreshold(const unsigned int threshold) { + return false; + } + + /** + * @brief Provided api for setting audio high latency threshold for low + * latency playback + * @pre The player state must be set to #EsState::kIdle, + * #EsState::kReady, #EsState::kPlaying or #EsState::kPaused + * @return @c True on success, otherwise @c False + */ + virtual bool SetAudioHighLatencyThreshold(const unsigned int threshold) { + return false; + } + + /** + * @brief Provided api for initializing audio easing information + * @param [in] init_volume : initial volume + * @param [in] init_elapsed_time : initail elapsed time (milisecond). + * @param [in] easing_info : target_volume, duration(milisecond), easing + * type + * @pre The player state can be all of #EsState except #EsState::kNone + * @return @c True on success, otherwise @c False + */ + virtual bool InitAudioEasingInfo(const uint32_t init_volume, + const uint32_t init_elapsed_time, + const AudioEasingInfo& easing_info) { + return false; + } + + /** + * @brief Provided api for updating audio easing information + * @param [in] easing_info : target_volume, duration(milisecond), easing + * type + * @pre The player state can be all of #EsState except #EsState::kNone + * @remarks This API have to be called after calling the + * EsPlusPlayer::InitAudioEasingInfo() + * @return @c True on success, otherwise @c False + */ + virtual bool UpdateAudioEasingInfo(const AudioEasingInfo& easing_info) { + return false; + } + + /** + * @brief Provided api for getting audio easing information + * @param [out] current_volume : current volume + * @param [out] elapsed_time : elapsed time (milisecond). + * @param [out] easing_info : target_volume, duration(milisecond), easing + * type + * @pre The player state can be all of #EsState except #EsState::kNone + * @remarks This API have to be called after calling the + * EsPlusPlayer::InitAudioEasingInfo() + * @return @c True on success, otherwise @c False + */ + virtual bool GetAudioEasingInfo(uint32_t* current_volume, + uint32_t* elapsed_time, + AudioEasingInfo* easing_info) { + return false; + } + + /** + * @brief Provided api for starting audio easing + * @pre The player state should be at least #EsState::kReady + * @remarks This API have to be called after calling the + * EsPlusPlayer::InitAudioEasingInfo() + * @return @c True on success, otherwise @c False + */ + virtual bool StartAudioEasing() { return false; } + + /** + * @brief Provided api for stopping audio easing + * @pre The player state can be all of #EsState except #EsState::kNone + * @remarks This API have to be called after calling the + * EsPlusPlayer::InitAudioEasingInfo() + * @return @c True on success, otherwise @c False + */ + virtual bool StopAudioEasing() { return false; } + + /** + * @brief Get virtual resource id + * @param [in] type : The resource type of virtual id. + * @param [out] virtual_id : Stored virtual resource id value. + * @pre The player state should be #EsState::kReady, #EsState::kPlaying + * or #EsState::kPaused + * @post None + * @return @c True on success, otherwise @c False ("virtual_id" will be -1) + * @exception None + */ + virtual bool GetVirtualRscId(const RscType type, int* virtual_id) { + return false; + } + + /** + * @brief Set advanced picture quality type. + * @param [in] type : The picture quality type. + * @pre The player state must be set to #EsState::kIdle. + * @post None + * @return @c True on success, otherwise @c False + * @exception None + */ + virtual bool SetAdvancedPictureQualityType(const AdvPictureQualityType type) { + return false; + } + + /** + * @brief Set resource allocate policy. + * @param [in] policy : The resource allocate policy. + * @pre The player state must be set to #EsState::kIdle. + * @post None + * @return @c True on success, otherwise @c False + * @exception None + */ + virtual bool SetResourceAllocatePolicy(const RscAllocPolicy policy) { + return false; + } + + protected: + EsPlusPlayer() noexcept {}; +}; // class EsPlusPlayer + +} // namespace plusplayer + +#endif // __PLUSPLAYER_ESPLUSPLAYER__H__ diff --git a/include/plusplayer/external_drm.h b/include/plusplayer/external_drm.h new file mode 100755 index 0000000..226c038 --- /dev/null +++ b/include/plusplayer/external_drm.h @@ -0,0 +1,111 @@ +/** + * @file external_drm.h + * @brief the extrnal drm information for elementary stream + * @interfacetype Module + * @privlevel None-privilege + * @privilege None + * @product TV, AV, B2B + * @version 0.0.1 + * @SDK_Support N + * + * Copyright (c) 2018 Samsung Electronics Co., Ltd All Rights Reserved + * PROPRIETARY/CONFIDENTIAL + * This software is the confidential and proprietary + * information of SAMSUNG ELECTRONICS ("Confidential Information"). You shall + * not disclose such Confidential Information and shall use it only in + * accordance with the terms of the license agreement you entered into with + * SAMSUNG ELECTRONICS. SAMSUNG make no representations or warranties about the + * suitability of the software, either express or implied, including but not + * limited to the implied warranties of merchantability, fitness for a + * particular purpose, or non-infringement. SAMSUNG shall not be liable for any + * damages suffered by licensee as a result of using, modifying or distributing + * this software or its derivatives. + */ +#ifndef __PLUSPLAYER_EXTERNAL_DRM_H__ +#define __PLUSPLAYER_EXTERNAL_DRM_H__ + +#include +#include +#include + +namespace plusplayer { + +namespace drm { + +using MetaData = void*; +/** + * @brief Enumerations for cipher algorithm for drm + */ +enum class DrmbEsCipherAlgorithm : int { + kUnknown = -1, + kRc4 = 0, + kAes128Ctr = 1, + kAes128Cbc = 2 +}; +/** + * @brief Enumerations for media format for drm + */ +enum class DrmbEsMediaFormat : int { + kNone = 0, + kFragmentedMp4 = 1, + kTs = 2, + kAsf = 3, + kFragmentedMp4Audio = 4, + kFragmentedMp4Video = 5, + kCleanAudio = 6, // Clean Audio Data + kPes = 7, // Packetized ES +}; +/** + * @brief Enumerations for cipher phase for drm + */ +enum class DrmbEsCipherPhase : int { + kNone = 0, + kInit = 1, + kUpdate = 2, + kFinal = 3 +}; +/** + * @brief Structure of subsample information for drm + */ +struct DrmbEsSubSampleInfo { + explicit DrmbEsSubSampleInfo(const uint32_t _bytes_of_clear_data, + const uint32_t _bytes_of_encrypted_data) + : bytes_of_clear_data(_bytes_of_clear_data), + bytes_of_encrypted_data(_bytes_of_encrypted_data) {} + uint32_t bytes_of_clear_data; + uint32_t bytes_of_encrypted_data; +}; +/** + * @brief Structure of fragmented mp4 data for drm + */ +struct DrmbEsFragmentedMp4Data { + std::vector sub_sample_info_vector; +}; +/** + * @brief Structure of encrypted information for es playback + */ +struct EsPlayerEncryptedInfo { + int32_t handle = 0; + + DrmbEsCipherAlgorithm algorithm = DrmbEsCipherAlgorithm::kUnknown; + DrmbEsMediaFormat format = DrmbEsMediaFormat::kNone; + DrmbEsCipherPhase phase = DrmbEsCipherPhase::kNone; + + std::vector kid; + std::vector initialization_vector; + + MetaData sub_data = nullptr; + std::array split_offsets; + + bool use_out_buffer = false; + bool use_pattern = false; + + uint32_t crypt_byte_block = 0; + uint32_t skip_byte_block = 0; +}; + +} // namespace drm + +} // namespace plusplayer + +#endif // __PLUSPLAYER_EXTERNAL_DRM_H__ diff --git a/include/plusplayer/track.h b/include/plusplayer/track.h new file mode 100755 index 0000000..5a13cd9 --- /dev/null +++ b/include/plusplayer/track.h @@ -0,0 +1,161 @@ +/** +* @file +* @interfacetype module +* @privlevel None-privilege +* @privilege None +* @product TV, AV, B2B +* @version 1.0 +* @SDK_Support N +* +* Copyright (c) 2018 Samsung Electronics Co., Ltd All Rights Reserved +* PROPRIETARY/CONFIDENTIAL +* This software is the confidential and proprietary +* information of SAMSUNG ELECTRONICS ("Confidential Information"). You shall +* not disclose such Confidential Information and shall use it only in +* accordance with the terms of the license agreement you entered into with +* SAMSUNG ELECTRONICS. SAMSUNG make no representations or warranties about the +* suitability of the software, either express or implied, including but not +* limited to the implied warranties of merchantability, fitness for a +* particular purpose, or non-infringement. SAMSUNG shall not be liable for any +* damages suffered by licensee as a result of using, modifying or distributing +* this software or its derivatives. +*/ + +#ifndef __PLUSPLAYER_TRACK_H__ +#define __PLUSPLAYER_TRACK_H__ + +#include +#include +#include +#include +#include +#include +#include + +namespace plusplayer { + +const int kInvalidTrackIndex = -1; + +enum TrackType { + kTrackTypeAudio = 0, + kTrackTypeVideo, + kTrackTypeSubtitle, + kTrackTypeMax +}; + +struct Track { + int index = kInvalidTrackIndex; + int id = 0; + std::string mimetype; + std::string streamtype; + std::string container_type; + TrackType type = kTrackTypeMax; + std::shared_ptr codec_data; + unsigned int codec_tag = 0; + int codec_data_len = 0; + int width = 0; + int height = 0; + int maxwidth = 0; + int maxheight = 0; + int framerate_num = 0; + int framerate_den = 0; + int sample_rate = 0; + int sample_format = 0; + int channels = 0; + int version = 0; + int layer = 0; + int bits_per_sample = 0; + int block_align = 0; + int bitrate = 0; + int endianness = 1234; // little endian : 1234 others big endian + bool is_signed = false; + bool active = false; + bool use_swdecoder = false; + std::string language_code; + std::string subtitle_format; + Track() {}; + Track(int _index, int _id, std::string _mimetype, std::string _streamtype, std::string _container_type, + TrackType _type, std::shared_ptr _codec_data, unsigned int _codec_tag, int _codec_data_len, + int _width, int _height, int _maxwidth, int _maxheight, int _framerate_num, int _framerate_den, + int _sample_rate, int _sample_format, int _channels, int _version, int _layer, int _bits_per_sample, + int _block_align, int _bitrate, int _endianness, bool _is_signed, bool _active, bool _use_swdecoder, + std::string _language_code, std::string _subtitle_format) + : index(_index), id(_id), mimetype(_mimetype), streamtype(_streamtype), container_type(_container_type), + type(_type), codec_data(_codec_data), codec_tag(_codec_tag), codec_data_len(_codec_data_len), + width(_width), height(_height), maxwidth(_maxwidth), maxheight(_maxheight), framerate_num(_framerate_num), framerate_den(_framerate_den), + sample_rate(_sample_rate), sample_format(_sample_format), channels(_channels), version(_version), layer(_layer), bits_per_sample(_bits_per_sample), + block_align(_block_align), bitrate(_bitrate), endianness(_endianness), is_signed(_is_signed), active(_active), use_swdecoder(_use_swdecoder), + language_code(_language_code), subtitle_format(_subtitle_format) {}; +}; + +enum SubtitleAttrType { + kSubAttrRegionXPos = 0, // float type + kSubAttrRegionYPos, // float type + kSubAttrRegionWidth, // float type + kSubAttrRegionHeight, // float type + kSubAttrWindowXPadding, // float type + kSubAttrWindowYPadding, // float type + kSubAttrWindowLeftMargin, // int type + kSubAttrWindowRightMargin, // int type + kSubAttrWindowTopMargin, // int type + kSubAttrWindowBottomMargin, // int type + kSubAttrWindowBgColor, // int type + kSubAttrWindowOpacity, // float type + kSubAttrWindowShowBg, // how to show window background, uint type + kSubAttrFontFamily, // char* type + kSubAttrFontSize, // float type + kSubAttrFontWeight, // int type + kSubAttrFontStyle, // int type + kSubAttrFontColor, // int type + kSubAttrFontBgColor, // int type + kSubAttrFontOpacity, // float type + kSubAttrFontBgOpacity, // float type + kSubAttrFontTextOutlineColor, // int type + kSubAttrFontTextOutlineThickness, // int type + kSubAttrFontTextOutlineBlurRadius, // int type + kSubAttrFontVerticalAlign, // int type + kSubAttrFontHorizontalAlign, // int type + kSubAttrRawSubtitle, // char* type + kSubAttrWebvttCueLine, // float type + kSubAttrWebvttCueLineNum, // int type + kSubAttrWebvttCueLineAlign, // int type + kSubAttrWebvttCueAlign, // int type + kSubAttrWebvttCueSize, // float type + kSubAttrWebvttCuePosition, // float type + kSubAttrWebvttCuePositionAlign, // int type + kSubAttrWebvttCueVertical, // int type + kSubAttrTimestamp, + kSubAttrExtsubIndex, // File index of external subtitle + kSubAttrTypeNone +}; + +enum class SubtitleType { + kText, + kPicture, + kInvalid +}; + +struct SubtitleAttr { + explicit SubtitleAttr(const SubtitleAttrType _type, + const uint32_t _start_time, const uint32_t _stop_time, + const boost::any _value, const int _extsub_index) + : type(_type), + start_time(_start_time), + stop_time(_stop_time), + value(_value), + extsub_index(_extsub_index) {} + const SubtitleAttrType type = kSubAttrTypeNone; + const uint32_t start_time = std::numeric_limits::max(); + const uint32_t stop_time = std::numeric_limits::max(); + const boost::any value; + const int extsub_index = -1; +}; +using SubtitleAttrList = std::list; +using SubtitleAttrListPtr = std::unique_ptr; +struct Rational { + int num = 0; // the numerator value + int den = 0; // the denominator value +}; +} // namespace plusplayer + +#endif // __PLUSPLAYER_TRACK_H__ \ No newline at end of file diff --git a/include/plusplayer/types/buffer.h b/include/plusplayer/types/buffer.h new file mode 100755 index 0000000..2dc5737 --- /dev/null +++ b/include/plusplayer/types/buffer.h @@ -0,0 +1,61 @@ +/** + * @file + * @brief the buffer for playback + * @interfacetype Module + * @privlevel None-privilege + * @privilege None + * @product TV, AV, B2B + * @version 0.0.1 + * @SDK_Support N + * + * Copyright (c) 2018 Samsung Electronics Co., Ltd All Rights Reserved + * PROPRIETARY/CONFIDENTIAL + * This software is the confidential and proprietary + * information of SAMSUNG ELECTRONICS ("Confidential Information"). You shall + * not disclose such Confidential Information and shall use it only in + * accordance with the terms of the license agreement you entered into with + * SAMSUNG ELECTRONICS. SAMSUNG make no representations or warranties about the + * suitability of the software, either express or implied, including but not + * limited to the implied warranties of merchantability, fitness for a + * particular purpose, or non-infringement. SAMSUNG shall not be liable for any + * damages suffered by licensee as a result of using, modifying or distributing + * this software or its derivatives. + */ +#ifndef __PLUSPLAYER_TYPES_BUFFER_H__ +#define __PLUSPLAYER_TYPES_BUFFER_H__ + +#include + +#include "tbm_type.h" + +namespace plusplayer { + +/** + * @brief Enumerations for the buffer status + */ +enum class BufferStatus { kUnderrun, kOverrun }; + +enum class DecodedVideoFrameBufferType { kNone, kCopy, kReference, kScale }; + +enum class BufferOption { + kBufferAudioMaxTimeSize, + kBufferVideoMaxTimeSize, + kBufferAudioMinTimeThreshold, + kBufferVideoMinTimeThreshold, + kBufferAudioMaxByteSize, + kBufferVideoMaxByteSize, + kBufferAudioMinByteThreshold, + kBufferVideoMinByteThreshold, + kBufferOptionMax +}; + +struct DecodedVideoPacket { + uint64_t pts = 0; + uint64_t duration = 0; + tbm_surface_h surface_data = nullptr; // tbm_surface + void* scaler_index = nullptr; +}; + +} // namespace plusplayer + +#endif // __PLUSPLAYER_TYPES_BUFFER_H__ diff --git a/include/plusplayer/types/display.h b/include/plusplayer/types/display.h new file mode 100755 index 0000000..c05c738 --- /dev/null +++ b/include/plusplayer/types/display.h @@ -0,0 +1,82 @@ +/** + * @file + * @interfacetype module + * @privlevel None-privilege + * @privilege None + * @product TV, AV, B2B + * @version 1.0 + * @SDK_Support N + * + * Copyright (c) 2018 Samsung Electronics Co., Ltd All Rights Reserved + * PROPRIETARY/CONFIDENTIAL + * This software is the confidential and proprietary + * information of SAMSUNG ELECTRONICS ("Confidential Information"). You shall + * not disclose such Confidential Information and shall use it only in + * accordance with the terms of the license agreement you entered into with + * SAMSUNG ELECTRONICS. SAMSUNG make no representations or warranties about the + * suitability of the software, either express or implied, including but not + * limited to the implied warranties of merchantability, fitness for a + * particular purpose, or non-infringement. SAMSUNG shall not be liable for any + * damages suffered by licensee as a result of using, modifying or distributing + * this software or its derivatives. + */ + +#ifndef __PLUSPLAYER_TYPES_DISPLAY_H__ +#define __PLUSPLAYER_TYPES_DISPLAY_H__ + +namespace plusplayer { + +enum class DisplayType { kNone, kOverlay, kEvas, kMixer }; + +enum class DisplayMode { + kLetterBox, + kOriginSize, + kFullScreen, + kCroppedFull, + kOriginOrLetter, + kDstRoi, + kAutoAspectRatio, + kMax +}; + +enum class DisplayRotation { kNone, kRotate90, kRotate180, kRotate270 }; + +struct Geometry { + int x = 0, y = 0; + int w = 1920, h = 1080; +}; + +struct CropArea { + double scale_x = 0.0; + double scale_y = 0.0; + double scale_w = 1.0; + double scale_h = 1.0; +}; + +struct RenderRect { + int x = 0, y = 0; + int w = 1920, h = 1080; +}; + +enum class VisibleStatus { kHide, kVisible }; + +struct DisplayInfo { + Geometry geometry; + CropArea croparea; + VisibleStatus visible_status = VisibleStatus::kVisible; +}; + +enum class StillMode { kNone, kOff, kOn }; + +struct DisplayObject { + DisplayType type_; + int surface_id_; + DisplayMode mode_; + Geometry geometry_; + void* obj_; + bool is_obj_ = false; +}; + +} // namespace plusplayer + +#endif // __PLUSPLAYER_TYPES_DISPLAY_H__ diff --git a/include/plusplayer/types/error.h b/include/plusplayer/types/error.h new file mode 100755 index 0000000..d73cc05 --- /dev/null +++ b/include/plusplayer/types/error.h @@ -0,0 +1,77 @@ +/** +* @file +* @interfacetype module +* @privlevel None-privilege +* @privilege None +* @product TV, AV, B2B +* @version 1.0 +* @SDK_Support N +* +* Copyright (c) 2018 Samsung Electronics Co., Ltd All Rights Reserved +* PROPRIETARY/CONFIDENTIAL +* This software is the confidential and proprietary +* information of SAMSUNG ELECTRONICS ("Confidential Information"). You shall +* not disclose such Confidential Information and shall use it only in +* accordance with the terms of the license agreement you entered into with +* SAMSUNG ELECTRONICS. SAMSUNG make no representations or warranties about the +* suitability of the software, either express or implied, including but not +* limited to the implied warranties of merchantability, fitness for a +* particular purpose, or non-infringement. SAMSUNG shall not be liable for any +* damages suffered by licensee as a result of using, modifying or distributing +* this software or its derivatives. +*/ + +#ifndef __PLUSPLAYER_TYPES_ERROR_H__ +#define __PLUSPLAYER_TYPES_ERROR_H__ + +#include "tizen.h" + +namespace plusplayer { + +#define PLUSPLAYER_ERROR_CLASS TIZEN_ERROR_PLAYER | 0x20 + +/* This is for custom defined player error. */ +#define PLUSPLAYER_CUSTOM_ERROR_CLASS TIZEN_ERROR_PLAYER | 0x1000 + + +enum class ErrorType { + kNone = TIZEN_ERROR_NONE, /**< Successful */ + kOutOfMemory = TIZEN_ERROR_OUT_OF_MEMORY, /**< Out of memory */ + kInvalidParameter = TIZEN_ERROR_INVALID_PARAMETER, /**< Invalid parameter */ + kNoSuchFile = TIZEN_ERROR_NO_SUCH_FILE, /**< No such file or directory */ + kInvalidOperation = TIZEN_ERROR_INVALID_OPERATION, /**< Invalid operation */ + kFileNoSpaceOnDevice = TIZEN_ERROR_FILE_NO_SPACE_ON_DEVICE, /**< No space left on the device */ + kFeatureNotSupportedOnDevice = TIZEN_ERROR_NOT_SUPPORTED, /**< Not supported */ + kSeekFailed = PLUSPLAYER_ERROR_CLASS | 0x01, /**< Seek operation failure */ + kInvalidState = PLUSPLAYER_ERROR_CLASS | 0x02, /**< Invalid state */ + kNotSupportedFile = PLUSPLAYER_ERROR_CLASS | 0x03, /**< File format not supported */ + kInvalidUri = PLUSPLAYER_ERROR_CLASS | 0x04, /**< Invalid URI */ + kSoundPolicy = PLUSPLAYER_ERROR_CLASS | 0x05, /**< Sound policy error */ + kConnectionFailed = PLUSPLAYER_ERROR_CLASS | 0x06, /**< Streaming connection failed */ + kVideoCaptureFailed = PLUSPLAYER_ERROR_CLASS | 0x07, /**< Video capture failed */ + kDrmExpired = PLUSPLAYER_ERROR_CLASS | 0x08, /**< Expired license */ + kDrmNoLicense = PLUSPLAYER_ERROR_CLASS | 0x09, /**< No license */ + kDrmFutureUse = PLUSPLAYER_ERROR_CLASS | 0x0a, /**< License for future use */ + kDrmNotPermitted = PLUSPLAYER_ERROR_CLASS | 0x0b, /**< Format not permitted */ + kResourceLimit = PLUSPLAYER_ERROR_CLASS | 0x0c, /**< Resource limit */ + kPermissionDenied = TIZEN_ERROR_PERMISSION_DENIED, /**< Permission denied */ + kServiceDisconnected = PLUSPLAYER_ERROR_CLASS | 0x0d, /**< Socket connection lost (Since 3.0) */ + kBufferSpace = TIZEN_ERROR_BUFFER_SPACE, /**< No buffer space available (Since 3.0)*/ + kNotSupportedAudioCodec = PLUSPLAYER_ERROR_CLASS | 0x0e, /**< Not supported audio codec but video can be played (Since 4.0) */ + kNotSupportedVideoCodec = PLUSPLAYER_ERROR_CLASS | 0x0f, /**< Not supported video codec but audio can be played (Since 4.0) */ + kNotSupportedSubtitle = PLUSPLAYER_ERROR_CLASS | 0x10, /**< Not supported subtitle format (Since 4.0) */ + + kDrmInfo = PLUSPLAYER_CUSTOM_ERROR_CLASS | 0x05, /**< playready drm error info */ + kNotSupportedFormat = PLUSPLAYER_CUSTOM_ERROR_CLASS | 0x08, + kStreamingPlayer = PLUSPLAYER_CUSTOM_ERROR_CLASS | 0x09, + kDtcpFsk = PLUSPLAYER_CUSTOM_ERROR_CLASS | 0x0a, + kPreLoadingTimeOut =PLUSPLAYER_CUSTOM_ERROR_CLASS | 0x0b, /**< can't finish preloading in time*/ + kNetworkError = PLUSPLAYER_CUSTOM_ERROR_CLASS | 0x0c, /**< for network error */ + kChannelSurfingFailed = PLUSPLAYER_CUSTOM_ERROR_CLASS | 0x0d, /**< for channel surfing error */ + + kUnknown +}; + +} // namespace plusplayer + +#endif // __PLUSPLAYER_TYPES_ERROR_H__ diff --git a/include/plusplayer/types/event.h b/include/plusplayer/types/event.h new file mode 100755 index 0000000..fb6305d --- /dev/null +++ b/include/plusplayer/types/event.h @@ -0,0 +1,55 @@ +/** + * @file + * @brief The event for playback. + * @interfacetype Module + * @privlevel None-privilege + * @privilege None + * @product TV, AV, B2B + * @version 1.0 + * @SDK_Support N + * @remark This is a group of C style event related enum and structure. + * @see N/A + * + * Copyright (c) 2018 Samsung Electronics Co., Ltd All Rights Reserved + * PROPRIETARY/CONFIDENTIAL + * This software is the confidential and proprietary + * information of SAMSUNG ELECTRONICS ("Confidential Information"). You shall + * not disclose such Confidential Information and shall use it only in + * accordance with the terms of the license agreement you entered into with + * SAMSUNG ELECTRONICS. SAMSUNG make no representations or warranties about the + * suitability of the software, either express or implied, including but not + * limited to the implied warranties of merchantability, fitness for a + * particular purpose, or non-infringement. SAMSUNG shall not be liable for any + * damages suffered by licensee as a result of using, modifying or distributing + * this software or its derivatives. + */ + +#ifndef __PLUSPLAYER_TYPES_EVENT_H__ +#define __PLUSPLAYER_TYPES_EVENT_H__ + +namespace plusplayer { +/** + * @brief + */ +typedef struct { + /** + * @description + */ + std::string data; + /** + * @description + */ + uint64_t len; +} EventMsg; + +/** + * @brief + */ +enum class EventType { + kNone, + kResolutionChanged, +}; + +} // namespace plusplayer + +#endif // __PLUSPLAYER_TYPES_EVENT_H__ diff --git a/include/plusplayer/types/latency.h b/include/plusplayer/types/latency.h new file mode 100755 index 0000000..87a8e65 --- /dev/null +++ b/include/plusplayer/types/latency.h @@ -0,0 +1,44 @@ +/** +* @file +* @interfacetype module +* @privlevel None-privilege +* @privilege None +* @product TV, AV, B2B +* @version 3.0 +* @SDK_Support N +* +* Copyright (c) 2020 Samsung Electronics Co., Ltd All Rights Reserved +* PROPRIETARY/CONFIDENTIAL +* This software is the confidential and proprietary +* information of SAMSUNG ELECTRONICS ("Confidential Information"). You shall +* not disclose such Confidential Information and shall use it only in +* accordance with the terms of the license agreement you entered into with +* SAMSUNG ELECTRONICS. SAMSUNG make no representations or warranties about the +* suitability of the software, either express or implied, including but not +* limited to the implied warranties of merchantability, fitness for a +* particular purpose, or non-infringement. SAMSUNG shall not be liable for any +* damages suffered by licensee as a result of using, modifying or distributing +* this software or its derivatives. +*/ + +#ifndef __PLUSPLAYER_TYPES_LATENCY_H__ +#define __PLUSPLAYER_TYPES_LATENCY_H__ + +namespace plusplayer { + +enum class CatchUpSpeed { + kNone, + kSlow, + kNormal, + kFast +}; + +enum class LatencyStatus { + kLow, + kMid, + kHigh +}; + +} // namespace plusplayer + +#endif // __PLUSPLAYER_TYPES_LATENCY_H__ \ No newline at end of file diff --git a/include/plusplayer/types/picturequality.h b/include/plusplayer/types/picturequality.h new file mode 100755 index 0000000..c4106e8 --- /dev/null +++ b/include/plusplayer/types/picturequality.h @@ -0,0 +1,38 @@ +/** +* @file +* @interfacetype module +* @privlevel None-privilege +* @privilege None +* @product TV, AV, B2B +* @version 1.0 +* @SDK_Support N +* +* Copyright (c) 2018 Samsung Electronics Co., Ltd All Rights Reserved +* PROPRIETARY/CONFIDENTIAL +* This software is the confidential and proprietary +* information of SAMSUNG ELECTRONICS ("Confidential Information"). You shall +* not disclose such Confidential Information and shall use it only in +* accordance with the terms of the license agreement you entered into with +* SAMSUNG ELECTRONICS. SAMSUNG make no representations or warranties about the +* suitability of the software, either express or implied, including but not +* limited to the implied warranties of merchantability, fitness for a +* particular purpose, or non-infringement. SAMSUNG shall not be liable for any +* damages suffered by licensee as a result of using, modifying or distributing +* this software or its derivatives. +*/ + +#ifndef __PLUSPLAYER_PICTUREQUALITY_H__ +#define __PLUSPLAYER_PICTUREQUALITY_H__ + +namespace plusplayer { + +/** +* @brief Advanced Picture Quality Type. +*/ +enum class AdvPictureQualityType { + kVideoCall, + kUsbCamera +}; + +} // namespace plusplayer +#endif // __PLUSPLAYER_PICTUREQUALITY_H__ \ No newline at end of file diff --git a/include/plusplayer/types/resource.h b/include/plusplayer/types/resource.h new file mode 100755 index 0000000..ea7d09b --- /dev/null +++ b/include/plusplayer/types/resource.h @@ -0,0 +1,50 @@ +/** + * @file + * @brief the stream information for playback + * @interfacetype Module + * @privlevel None-privilege + * @privilege None + * @product TV, AV, B2B + * @version 3.0 + * @SDK_Support N + * + * Copyright (c) 2020 Samsung Electronics Co., Ltd All Rights Reserved + * PROPRIETARY/CONFIDENTIAL + * This software is the confidential and proprietary + * information of SAMSUNG ELECTRONICS ("Confidential Information"). You shall + * not disclose such Confidential Information and shall use it only in + * accordance with the terms of the license agreement you entered into with + * SAMSUNG ELECTRONICS. SAMSUNG make no representations or warranties about the + * suitability of the software, either express or implied, including but not + * limited to the implied warranties of merchantability, fitness for a + * particular purpose, or non-infringement. SAMSUNG shall not be liable for any + * damages suffered by licensee as a result of using, modifying or distributing + * this software or its derivatives. + */ +#ifndef __PLUSPLAYER_TYPES_RESOURCE_H__ +#define __PLUSPLAYER_TYPES_RESOURCE_H__ + +namespace plusplayer { + +/** + * @brief Enumerations for the resource type + */ +enum class RscType { kVideoRenderer }; + +/** + * @brief Enumerations for resource allocate policy + */ +enum class RscAllocPolicy { + /** + * @description exclusive policy, default policy + */ + kRscAllocExclusive, + /** + * @description conditional policy + */ + kRscAllocConditional, +}; + +} // namespace plusplayer + +#endif // __PLUSPLAYER_TYPES_RESOURCE_H__ \ No newline at end of file diff --git a/include/plusplayer/types/source.h b/include/plusplayer/types/source.h new file mode 100755 index 0000000..3f2502a --- /dev/null +++ b/include/plusplayer/types/source.h @@ -0,0 +1,70 @@ +/** +* @file source.h +* @brief Types related to TrackSource +* @interfacetype module +* @privlevel None-privilege +* @privilege None +* @product TV, AV, B2B +* @version 1.0 +* @SDK_Support N +* +* Copyright (c) 2018 Samsung Electronics Co., Ltd All Rights Reserved +* PROPRIETARY/CONFIDENTIAL +* This software is the confidential and proprietary +* information of SAMSUNG ELECTRONICS ("Confidential Information"). You shall +* not disclose such Confidential Information and shall use it only in +* accordance with the terms of the license agreement you entered into with +* SAMSUNG ELECTRONICS. SAMSUNG make no representations or warranties about the +* suitability of the software, either express or implied, including but not +* limited to the implied warranties of merchantability, fitness for a +* particular purpose, or non-infringement. SAMSUNG shall not be liable for any +* damages suffered by licensee as a result of using, modifying or distributing +* this software or its derivatives. +*/ + +#ifndef __PLUSPLAYER_SRC_TRACKSOURCE_TYPES_H__ +#define __PLUSPLAYER_SRC_TRACKSOURCE_TYPES_H__ + +namespace plusplayer { + +enum class SourceType { + kNone, + kBase, + kHttp, + kHls, + kDash, + kSmooth, + kFile, + kExternalSubtitle, + kNotFound, + kMax +}; + +enum class ContentFormat { + kNone, + kMP4Mov, + kMpegts, + k3GpMov, + kAudioMpeg, + kAudioMpegAac, + kMkv, + kAvi, + kVideoAsf, + kAppXid3, + kUnknown +}; + +enum class TrickPlayMode { + kNone, + kDefault, + kBySeek +}; + +enum class PlayingTimeSupport { + kNone, + kNeeded +}; + +} // namespace plusplayer + +#endif // __PLUSPLAYER_SRC_TRACKSOURCE_TYPES_H__ diff --git a/include/plusplayer/types/stream.h b/include/plusplayer/types/stream.h new file mode 100755 index 0000000..f107592 --- /dev/null +++ b/include/plusplayer/types/stream.h @@ -0,0 +1,36 @@ +/** + * @file + * @brief the stream information for playback + * @interfacetype Module + * @privlevel None-privilege + * @privilege None + * @product TV, AV, B2B + * @version 0.0.1 + * @SDK_Support N + * + * Copyright (c) 2018 Samsung Electronics Co., Ltd All Rights Reserved + * PROPRIETARY/CONFIDENTIAL + * This software is the confidential and proprietary + * information of SAMSUNG ELECTRONICS ("Confidential Information"). You shall + * not disclose such Confidential Information and shall use it only in + * accordance with the terms of the license agreement you entered into with + * SAMSUNG ELECTRONICS. SAMSUNG make no representations or warranties about the + * suitability of the software, either express or implied, including but not + * limited to the implied warranties of merchantability, fitness for a + * particular purpose, or non-infringement. SAMSUNG shall not be liable for any + * damages suffered by licensee as a result of using, modifying or distributing + * this software or its derivatives. + */ +#ifndef __PLUSPLAYER_TYPES_STREAM_H__ +#define __PLUSPLAYER_TYPES_STREAM_H__ + +namespace plusplayer { + +/** + * @brief Enumerations for the stream type + */ +enum class StreamType { kAudio = 0, kVideo, kMax }; + +} // namespace plusplayer + +#endif // __PLUSPLAYER_TYPES_STREAM_H__ \ No newline at end of file diff --git a/include/plusplayer/types/streaming_message.h b/include/plusplayer/types/streaming_message.h new file mode 100755 index 0000000..6c5de10 --- /dev/null +++ b/include/plusplayer/types/streaming_message.h @@ -0,0 +1,65 @@ +/** +* @file streaming_message.h +* @interfacetype module +* @privlevel None-privilege +* @privilege None +* @product TV, AV, B2B +* @version 1.0 +* @SDK_Support N +* +* Copyright (c) 2018 Samsung Electronics Co., Ltd All Rights Reserved +* PROPRIETARY/CONFIDENTIAL +* This software is the confidential and proprietary +* information of SAMSUNG ELECTRONICS ("Confidential Information"). You shall +* not disclose such Confidential Information and shall use it only in +* accordance with the terms of the license agreement you entered into with +* SAMSUNG ELECTRONICS. SAMSUNG make no representations or warranties about the +* suitability of the software, either express or implied, including but not +* limited to the implied warranties of merchantability, fitness for a +* particular purpose, or non-infringement. SAMSUNG shall not be liable for any +* damages suffered by licensee as a result of using, modifying or distributing +* this software or its derivatives. +*/ + +#ifndef __PLUSPLAYER_TYPES_STREAMING_MESSAGE_H__ +#define __PLUSPLAYER_TYPES_STREAMING_MESSAGE_H__ + +namespace plusplayer { + +enum class StreamingMessageType { + kNone = 0, + // kResolutionChanged, + // kAdEnd, + // kAdStart, + // kRenderDone, + kBitrateChange, + // kFragmentInfo, + kSparseTrackDetect, + // kStreamingEvent, + // kDrmChallengeData, + kDrmInitData, + // kHttpErrorCode, + // kDrmRenewSessionData, + kStreamEventType, + kStreamEventData, + kStreamSyncFlush, + kStreamMrsUrlChanged, + kDrmKeyRotation, + kFragmentDownloadInfo, + kDvrLiveLag, + kSparseTrackData, + kConnectionRetry, + kConfigLowLatency, + kCurlErrorDebugInfo, + kParDarChange +}; + +struct MessageParam { + std::string data; + int size = 0; + int code = 0; // Error or warning code +}; + +} // namespace plusplayer + +#endif // __PLUSPLAYER_TYPES_STREAMING_MESSAGE_H__ \ No newline at end of file diff --git a/include/plusplayer/types/submitdata.h b/include/plusplayer/types/submitdata.h new file mode 100755 index 0000000..8ade19f --- /dev/null +++ b/include/plusplayer/types/submitdata.h @@ -0,0 +1,40 @@ +/** +* @file submitdata.h +* @brief the data type to submit +* @interfacetype Module +* @privlevel None-privilege +* @privilege None +* @product TV, AV, B2B +* @version 0.0.1 +* @SDK_Support N +* +* Copyright (c) 2019 Samsung Electronics Co., Ltd All Rights Reserved +* PROPRIETARY/CONFIDENTIAL +* This software is the confidential and proprietary +* information of SAMSUNG ELECTRONICS ("Confidential Information"). You shall +* not disclose such Confidential Information and shall use it only in +* accordance with the terms of the license agreement you entered into with +* SAMSUNG ELECTRONICS. SAMSUNG make no representations or warranties about the +* suitability of the software, either express or implied, including but not +* limited to the implied warranties of merchantability, fitness for a +* particular purpose, or non-infringement. SAMSUNG shall not be liable for any +* damages suffered by licensee as a result of using, modifying or distributing +* this software or its derivatives. +*/ +#ifndef __PLUSPLAYER_TYPES_SUBMITDATA_H__ +#define __PLUSPLAYER_TYPES_SUBMITDATA_H__ + +namespace plusplayer { + +/** + * @brief Enumerations for the type of espacket submitted + */ +enum class SubmitDataType { + kCleanData, // For clean data + kEncryptedData, // For encrypted data + kTrustZoneData // For trustzone data +}; + +} // namespace plusplayer + +#endif // __PLUSPLAYER_TYPES_SUBMITDATA_H__ \ No newline at end of file diff --git a/out/docs/class_diagram/adapter/bms_avplayer_player_adapter(1)/bms_avplayer_player_adapter(1).png b/out/docs/class_diagram/adapter/bms_avplayer_player_adapter(1)/bms_avplayer_player_adapter(1).png new file mode 100755 index 0000000..e5504b4 Binary files /dev/null and b/out/docs/class_diagram/adapter/bms_avplayer_player_adapter(1)/bms_avplayer_player_adapter(1).png differ diff --git a/out/docs/class_diagram/adapter/bms_avplayer_player_adapter(2)/bms_avplayer_player_adapter(2).png b/out/docs/class_diagram/adapter/bms_avplayer_player_adapter(2)/bms_avplayer_player_adapter(2).png new file mode 100755 index 0000000..8a0d3bf Binary files /dev/null and b/out/docs/class_diagram/adapter/bms_avplayer_player_adapter(2)/bms_avplayer_player_adapter(2).png differ diff --git a/out/docs/class_diagram/adapter/webapi_avplay_player_adapter/webapi_avplay_player_adapter-TO-BE page 1.png b/out/docs/class_diagram/adapter/webapi_avplay_player_adapter/webapi_avplay_player_adapter-TO-BE page 1.png new file mode 100755 index 0000000..207bdf9 Binary files /dev/null and b/out/docs/class_diagram/adapter/webapi_avplay_player_adapter/webapi_avplay_player_adapter-TO-BE page 1.png differ diff --git a/out/docs/class_diagram/adapter/webapi_avplay_player_adapter/webapi_avplay_player_adapter-TO-BE page 2.png b/out/docs/class_diagram/adapter/webapi_avplay_player_adapter/webapi_avplay_player_adapter-TO-BE page 2.png new file mode 100755 index 0000000..a0c1341 Binary files /dev/null and b/out/docs/class_diagram/adapter/webapi_avplay_player_adapter/webapi_avplay_player_adapter-TO-BE page 2.png differ diff --git a/out/docs/class_diagram/adapter/webapi_avplay_player_adapter/webapi_avplay_player_adapter.png b/out/docs/class_diagram/adapter/webapi_avplay_player_adapter/webapi_avplay_player_adapter.png new file mode 100755 index 0000000..0022fc6 Binary files /dev/null and b/out/docs/class_diagram/adapter/webapi_avplay_player_adapter/webapi_avplay_player_adapter.png differ diff --git a/out/docs/class_diagram/bmservice_drmmanager_plusplayer/bmservice_drmmanager_plusplayer.png b/out/docs/class_diagram/bmservice_drmmanager_plusplayer/bmservice_drmmanager_plusplayer.png new file mode 100755 index 0000000..64fc589 Binary files /dev/null and b/out/docs/class_diagram/bmservice_drmmanager_plusplayer/bmservice_drmmanager_plusplayer.png differ diff --git a/out/docs/class_diagram/details/defaultplayer/DefaultPlayer.png b/out/docs/class_diagram/details/defaultplayer/DefaultPlayer.png new file mode 100755 index 0000000..ca9fd33 Binary files /dev/null and b/out/docs/class_diagram/details/defaultplayer/DefaultPlayer.png differ diff --git a/out/docs/class_diagram/details/trackrenderer/TrackRenderer.png b/out/docs/class_diagram/details/trackrenderer/TrackRenderer.png new file mode 100755 index 0000000..0eb2615 Binary files /dev/null and b/out/docs/class_diagram/details/trackrenderer/TrackRenderer.png differ diff --git a/out/docs/class_diagram/details/tracksource_compositor/TrackSource & Compositor.png b/out/docs/class_diagram/details/tracksource_compositor/TrackSource & Compositor.png new file mode 100755 index 0000000..281653e Binary files /dev/null and b/out/docs/class_diagram/details/tracksource_compositor/TrackSource & Compositor.png differ diff --git a/out/docs/class_diagram/esplusplayer/esplusplayer.png b/out/docs/class_diagram/esplusplayer/esplusplayer.png new file mode 100755 index 0000000..3e5a9e1 Binary files /dev/null and b/out/docs/class_diagram/esplusplayer/esplusplayer.png differ diff --git a/out/docs/class_diagram/plusplayer/plusplayer.png b/out/docs/class_diagram/plusplayer/plusplayer.png new file mode 100755 index 0000000..1d13d0e Binary files /dev/null and b/out/docs/class_diagram/plusplayer/plusplayer.png differ diff --git a/out/docs/class_diagram/plusplayer_simple/plusplayer_simple.png b/out/docs/class_diagram/plusplayer_simple/plusplayer_simple.png new file mode 100755 index 0000000..0a787ea Binary files /dev/null and b/out/docs/class_diagram/plusplayer_simple/plusplayer_simple.png differ diff --git a/out/docs/downloadable/version_control_sequence/tvplus case(1) dynamic loading page 1.png b/out/docs/downloadable/version_control_sequence/tvplus case(1) dynamic loading page 1.png new file mode 100755 index 0000000..f11efbb Binary files /dev/null and b/out/docs/downloadable/version_control_sequence/tvplus case(1) dynamic loading page 1.png differ diff --git a/out/docs/downloadable/version_control_sequence/tvplus case(1) dynamic loading page 2.png b/out/docs/downloadable/version_control_sequence/tvplus case(1) dynamic loading page 2.png new file mode 100755 index 0000000..0230148 Binary files /dev/null and b/out/docs/downloadable/version_control_sequence/tvplus case(1) dynamic loading page 2.png differ diff --git a/out/docs/downloadable/version_control_sequence/tvplus case1) dynamic loading page 1.png b/out/docs/downloadable/version_control_sequence/tvplus case1) dynamic loading page 1.png new file mode 100755 index 0000000..9ae0fc9 Binary files /dev/null and b/out/docs/downloadable/version_control_sequence/tvplus case1) dynamic loading page 1.png differ diff --git a/out/docs/downloadable/version_control_sequence/tvplus case1) dynamic loading page 2.png b/out/docs/downloadable/version_control_sequence/tvplus case1) dynamic loading page 2.png new file mode 100755 index 0000000..84c65fe Binary files /dev/null and b/out/docs/downloadable/version_control_sequence/tvplus case1) dynamic loading page 2.png differ diff --git a/out/docs/module_view/libav-common/reduced downloadable module size by libav-common dash hls http streaming module 9M to 4.5M.png b/out/docs/module_view/libav-common/reduced downloadable module size by libav-common dash hls http streaming module 9M to 4.5M.png new file mode 100755 index 0000000..0c8c978 Binary files /dev/null and b/out/docs/module_view/libav-common/reduced downloadable module size by libav-common dash hls http streaming module 9M to 4.5M.png differ diff --git a/out/docs/module_view/uses_view/module view uses view.png b/out/docs/module_view/uses_view/module view uses view.png new file mode 100755 index 0000000..da737ff Binary files /dev/null and b/out/docs/module_view/uses_view/module view uses view.png differ diff --git a/out/docs/sequence_activity_diagram/source_change/source_change_base_flow_1/source_change_base_flow_1.png b/out/docs/sequence_activity_diagram/source_change/source_change_base_flow_1/source_change_base_flow_1.png new file mode 100755 index 0000000..0378d7b Binary files /dev/null and b/out/docs/sequence_activity_diagram/source_change/source_change_base_flow_1/source_change_base_flow_1.png differ diff --git a/out/docs/sequence_activity_diagram/tracksource/tracksource_alternative_flow_1/tracksource_alternative_flow_1.png b/out/docs/sequence_activity_diagram/tracksource/tracksource_alternative_flow_1/tracksource_alternative_flow_1.png new file mode 100755 index 0000000..ffe67ee Binary files /dev/null and b/out/docs/sequence_activity_diagram/tracksource/tracksource_alternative_flow_1/tracksource_alternative_flow_1.png differ diff --git a/out/docs/sequence_activity_diagram/tracksource/tracksource_base_flow_1/tracksource_base_flow_1.png b/out/docs/sequence_activity_diagram/tracksource/tracksource_base_flow_1/tracksource_base_flow_1.png new file mode 100755 index 0000000..6e6208f Binary files /dev/null and b/out/docs/sequence_activity_diagram/tracksource/tracksource_base_flow_1/tracksource_base_flow_1.png differ diff --git a/out/docs/sequence_activity_diagram/tracksource/tracksource_exception_flow_1/tracksource_exception_flow_1.png b/out/docs/sequence_activity_diagram/tracksource/tracksource_exception_flow_1/tracksource_exception_flow_1.png new file mode 100755 index 0000000..fde5983 Binary files /dev/null and b/out/docs/sequence_activity_diagram/tracksource/tracksource_exception_flow_1/tracksource_exception_flow_1.png differ diff --git a/out/docs/sequence_activity_diagram/tracksource/tracksource_exception_flow_2/tracksource_exception_flow_2.png b/out/docs/sequence_activity_diagram/tracksource/tracksource_exception_flow_2/tracksource_exception_flow_2.png new file mode 100755 index 0000000..dd0b012 Binary files /dev/null and b/out/docs/sequence_activity_diagram/tracksource/tracksource_exception_flow_2/tracksource_exception_flow_2.png differ diff --git a/out/docs/sequence_activity_diagram/tracksource/tracksource_exception_flow_3/tracksource_exception_flow_3.png b/out/docs/sequence_activity_diagram/tracksource/tracksource_exception_flow_3/tracksource_exception_flow_3.png new file mode 100755 index 0000000..d7b36d9 Binary files /dev/null and b/out/docs/sequence_activity_diagram/tracksource/tracksource_exception_flow_3/tracksource_exception_flow_3.png differ diff --git a/out/docs/sequence_activity_diagram/tvplus_update_activity_diagram/risk_management_lib_api_validation/risk_management_lib_api_validation.png b/out/docs/sequence_activity_diagram/tvplus_update_activity_diagram/risk_management_lib_api_validation/risk_management_lib_api_validation.png new file mode 100755 index 0000000..81bd0e0 Binary files /dev/null and b/out/docs/sequence_activity_diagram/tvplus_update_activity_diagram/risk_management_lib_api_validation/risk_management_lib_api_validation.png differ diff --git a/out/docs/sequence_activity_diagram/tvplus_update_activity_diagram/update_procedure/TVPlus Player update process.png b/out/docs/sequence_activity_diagram/tvplus_update_activity_diagram/update_procedure/TVPlus Player update process.png new file mode 100755 index 0000000..9e7da9e Binary files /dev/null and b/out/docs/sequence_activity_diagram/tvplus_update_activity_diagram/update_procedure/TVPlus Player update process.png differ diff --git a/out/docs/state_diagram/state_diagram/plusplayer_internal_state_diagram.png b/out/docs/state_diagram/state_diagram/plusplayer_internal_state_diagram.png new file mode 100755 index 0000000..76736f5 Binary files /dev/null and b/out/docs/state_diagram/state_diagram/plusplayer_internal_state_diagram.png differ diff --git a/out/docs/state_diagram/state_diagram_v2_substatemachine/plusplayer_internal_state_diagram.png b/out/docs/state_diagram/state_diagram_v2_substatemachine/plusplayer_internal_state_diagram.png new file mode 100755 index 0000000..938863a Binary files /dev/null and b/out/docs/state_diagram/state_diagram_v2_substatemachine/plusplayer_internal_state_diagram.png differ diff --git a/packaging/esplusplayer.manifest b/packaging/esplusplayer.manifest new file mode 100755 index 0000000..f3090b0 --- /dev/null +++ b/packaging/esplusplayer.manifest @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/packaging/esplusplayer.spec b/packaging/esplusplayer.spec new file mode 100755 index 0000000..6616451 --- /dev/null +++ b/packaging/esplusplayer.spec @@ -0,0 +1,239 @@ +# %bcond_with : disable ESPLUSPLAYER_UT by default, %bcond_without : enable ESPLUSPLAYER_UT +%if ("%{_vd_cfg_product_type}" != "AUDIO") +%bcond_without ESPLUSPLAYER_UT +%else +%bcond_with ESPLUSPLAYER_UT +%endif +#echo "Product Type: %{_vd_cfg_product_type}" +Name: esplusplayer +Summary: new multimedia streaming player +Version: 0.0.1 +Release: 0 +Group: Multimedia/Libraries +License: Apache-2.0 +Source0: %{name}-%{version}.tar.gz +Source1001: esplusplayer.manifest +BuildRequires: cmake +BuildRequires: curl +BuildRequires: pkgconfig(glib-2.0) +BuildRequires: pkgconfig(capi-base-common) +BuildRequires: pkgconfig(gstreamer-1.0) +BuildRequires: pkgconfig(gstreamer-plugins-base-1.0) +BuildRequires: pkgconfig(dlog) +BuildRequires: pkgconfig(boost) +BuildRequires: pkgconfig(elementary) +BuildRequires: pkgconfig(ecore) +BuildRequires: pkgconfig(evas) +BuildRequires: pkgconfig(ecore-wl2) +BuildRequires: pkgconfig(wayland-client) +BuildRequires: pkgconfig(tizen-extension-client) +BuildRequires: pkgconfig(tv-resource-manager) +BuildRequires: pkgconfig(tv-resource-information) +BuildRequires: pkgconfig(libtzplatform-config) +BuildRequires: pkgconfig(jsoncpp) +BuildRequires: pkgconfig(gstreamer-ffsubtitle-1.0) +BuildRequires: pkgconfig(icu-i18n) +BuildRequires: pkgconfig(drmdecrypt) +BuildRequires: pkgconfig(vconf) +BuildRequires: pkgconfig(logger) +BuildRequires: pkgconfig(gio-2.0) +BuildRequires: pkgconfig(libtbm) +BuildRequires: pkgconfig(lwipc) +BuildRequires: pkgconfig(capi-screensaver) +BuildRequires: pkgconfig(context-aware-api) +BuildRequires: pkgconfig(capi-trackrenderer-tv) +BuildRequires: pkgconfig(libtbm) +BuildRequires: pkgconfig(smart-deadlock) + +%if ("%{_vd_cfg_product_type}" != "AUDIO") +BuildRequires: pkgconfig(graphics-control) +%endif + +%if ("%{_vd_cfg_licensing}" == "n") +# for ut +BuildRequires: pkgconfig(capi-media-player) +BuildRequires: pkgconfig(gtest_gmock) +BuildRequires: pkgconfig(appcore-efl) +BuildRequires: pkgconfig(libresourced) +%endif + +%define _packagedir /usr +%define _bindir %{_packagedir}/bin +%define _libdir %{_packagedir}/lib +%define _includedir %{_packagedir}/include +%define _pkgconfigdir %{_libdir}/pkgconfig +%define _unpackaged_files_terminate_build 0 +%define _missing_doc_files_terminate_build 0 + +%description +new multimedia player, object-oriented model + +%package devel +Summary: Developement for multimedia player +Group: Development/Libraries +Requires: %{name} = %{version}-%{release} + +%package config +Summary: Configuration for multimedia player +Group: Development/Libraries +Requires: %{name} = %{version}-%{release} + +%description devel +%devel_desc + +%description config + +################################################# +# gcov +################################################# +%if 0%{?vd_gcov:1} +%package gcov +Summary: gcov enabled package +Group: gcov package + +%description gcov +This package is gcov package for coverage measurement. +%endif + +%prep +%setup -q +cp %{SOURCE1001} . + +%if ("%{_vd_cfg_licensing}" == "n") +%if ("%{_vd_cfg_product_type}" != "AUDIO") +%{?!TOMATO: %define TOMATO n} + +%define _tomatoname esplusplayer +%define _tomatodir /opt/usr/apps/tomato/testcase/%{name} +%define _tomatobin /opt/usr/apps/tomato/testcase/%{name}/bin + +%package ut-component-tomato +Summary: Test package with TOMATO +BuildRequires: pkgconfig(tomato) +BuildRequires: pkgconfig(gtest_gmock) +BuildRequires: pkgconfig(video-capture) +BuildRequires: pkgconfig(audio-control) +BuildRequires: libjpeg-turbo-devel +BuildRequires: elementary-devel +BuildRequires: opencv-devel +BuildRequires: pkgconfig(opencv) +BuildRequires: pkgconfig(capi-appfw-application) +Requires: %{name} = %{version}-%{release} + +%description ut-component-tomato +This package is for test + +%files ut-component-tomato +%defattr(-,root,root,-) +%{_bindir}/esplusplayer_ut +%{_tomatodir}/* + +%endif +%endif + +%build +export CFLAGS+=" -Wno-deprecated-declarations" +export CXXFLAGS+=" -Wno-deprecated-declarations" + +%if ("%{_vd_cfg_product_type}" == "AUDIO") +export CFLAGS+=" -DIS_AUDIO_PRODUCT" +export CXXFLAGS+=" -DIS_AUDIO_PRODUCT" +%define PRODUCT_TYPE_AUDIO yes +%else +%define PRODUCT_TYPE_AUDIO no +%endif + +%if ("%{_vd_cfg_product_type}" == "AV") +export CFLAGS+=" -DIS_AV_PRODUCT" +export CXXFLAGS+=" -DIS_AV_PRODUCT" +%endif + +%if 0%{?vd_gcov:1} +export CFLAGS+=" -fprofile-arcs -ftest-coverage" +export CXXFLAGS+=" -fprofile-arcs -ftest-coverage" +export FFLAGS+=" -fprofile-arcs -ftest-coverage" +export LDFLAGS+=" -lgcov" +export CFLAGS+=" -DIS_TOMATO" +export CXXFLAGS+=" -DIS_TOMATO" +%endif + +export CXXFLAGS+=" -Wno-pessimizing-move" + +%if ("%{_vd_cfg_licensing}" == "n") +%if %{with ESPLUSPLAYER_UT} +%cmake . -DESPLUSPLAYER_BUILD_UT=ON -DPRODUCT_TYPE_AUDIO=%PRODUCT_TYPE_AUDIO +%else +%cmake . -DPRODUCT_TYPE_AUDIO=%PRODUCT_TYPE_AUDIO +%endif +%else +%cmake . -DPRODUCT_TYPE_AUDIO=%PRODUCT_TYPE_AUDIO +%endif + +make %{?jobs:-j%jobs} + +%install +rm -rf %{buildroot} + +%if ("%{_vd_cfg_licensing}" == "n") +mkdir -p %{buildroot}%{_tomatodir} +mkdir -p %{buildroot}%{_tomatodir}/log +mkdir -p %{buildroot}%{_tomatodir}/result +mkdir -p %{buildroot}%{_tomatodir}/tc +cp -rf tomato/tc/* %{buildroot}%{_tomatodir}/tc +%endif + +%make_install +mkdir -p %{buildroot}%TZ_SYS_RO_ETC/multimedia +#mkdir -p %{buildroot}%TZ_SYS_RW_APP/multimedia +#mkdir -p %{buildroot}/opt +#mkdir -p %{buildroot}/opt/usr +#mkdir -p %{buildroot}/opt/usr/home +#mkdir -p %{buildroot}/opt/usr/home/owner +#mkdir -p %{buildroot}/opt/usr/home/owner/models +cp -rf config/esplusplayer.ini %{buildroot}%TZ_SYS_RO_ETC/multimedia/esplusplayer.ini + +%if 0%{?vd_gcov:1} +mkdir -p %{buildroot}%{_datadir}/gcov/obj +find . \( -name '*.gcno' -o -name '*.cpp' -o -name '*.c' -o -name '*.hpp' -o -name '*.h' \) ! -path "./ut/*" ! -path "./test/*" ! -path "*/CompilerIdCXX/*" -exec cp --parents -r '{}' %{buildroot}%{_datadir}/gcov/obj ';' +%endif + +%files +%defattr(-,root,root,-) +%manifest esplusplayer.manifest +%license LICENSE.APLv2 +%{_libdir}/libespplayer-core.so +%{_libdir}/libesplusplayer.so +%if ("%{_vd_cfg_product_type}" != "AUDIO") +%{_libdir}/libmixer.so +%endif + +%TZ_SYS_RO_ETC/multimedia/esplusplayer.ini +#%attr(0775, owner,users) %TZ_SYS_RW_APP/multimedia + +#remove esplusplayer_ut in esplusplayer package by flash memory issue +#%if ("%{_vd_cfg_licensing}" == "n") +#%if %{with ESPLUSPLAYER_UT} +#%{_bindir}/esplusplayer_ut +#%defattr(-,root,root,-) +#%{_tomatodir}/* +#%endif +#%endif + +%files devel +%defattr(-,root,root,-) +%{_includedir}/esplusplayer_capi/*.h +%{_includedir}/mixer/*.h +%{_includedir}/mixer_capi/*.h +%{_pkgconfigdir}/esplusplayer.pc + +%files config +%defattr(-,root,root,-) +%manifest esplusplayer.manifest +%license LICENSE.APLv2 +%TZ_SYS_RO_ETC/multimedia/esplusplayer.ini +%attr(0775, owner,users) %TZ_SYS_RO_ETC/multimedia + +%if 0%{?vd_gcov:1} +%files gcov +%{_datadir}/gcov/* +%endif diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100755 index 0000000..82d4cda --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,5 @@ +ADD_SUBDIRECTORY(plusplayer-core) +ADD_SUBDIRECTORY(esplusplayer) +IF(${PRODUCT_TYPE_AUDIO} STREQUAL "no") +ADD_SUBDIRECTORY(mixer) +ENDIF(${PRODUCT_TYPE_AUDIO} STREQUAL) diff --git a/src/cpplint.py b/src/cpplint.py new file mode 100755 index 0000000..4b9d830 --- /dev/null +++ b/src/cpplint.py @@ -0,0 +1,6123 @@ +#!/usr/bin/env python +# +# Copyright (c) 2009 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""Does google-lint on c++ files. + +The goal of this script is to identify places in the code that *may* +be in non-compliance with google style. It does not attempt to fix +up these problems -- the point is to educate. It does also not +attempt to find all problems, or to ensure that everything it does +find is legitimately a problem. + +In particular, we can get very confused by /* and // inside strings! +We do a small hack, which is to ignore //'s with "'s after them on the +same line, but it is far from perfect (in either direction). +""" + +import codecs +import copy +import getopt +import math # for log +import os +import re +import sre_compile +import string +import sys +import unicodedata + + +_USAGE = """ +Syntax: cpplint.py [--verbose=#] [--output=vs7] [--filter=-x,+y,...] + [--counting=total|toplevel|detailed] [--root=subdir] + [--linelength=digits] [--headers=x,y,...] + [file] ... + + The style guidelines this tries to follow are those in + https://google-styleguide.googlecode.com/svn/trunk/cppguide.xml + + Every problem is given a confidence score from 1-5, with 5 meaning we are + certain of the problem, and 1 meaning it could be a legitimate construct. + This will miss some errors, and is not a substitute for a code review. + + To suppress false-positive errors of a certain category, add a + 'NOLINT(category)' comment to the line. NOLINT or NOLINT(*) + suppresses errors of all categories on that line. + + The files passed in will be linted; at least one file must be provided. + Default linted extensions are .cc, .cpp, .cu, .cuh and .h. Change the + extensions with the --extensions flag. + + Flags: + + output=vs7 + By default, the output is formatted to ease emacs parsing. Visual Studio + compatible output (vs7) may also be used. Other formats are unsupported. + + verbose=# + Specify a number 0-5 to restrict errors to certain verbosity levels. + + filter=-x,+y,... + Specify a comma-separated list of category-filters to apply: only + error messages whose category names pass the filters will be printed. + (Category names are printed with the message and look like + "[whitespace/indent]".) Filters are evaluated left to right. + "-FOO" and "FOO" means "do not print categories that start with FOO". + "+FOO" means "do print categories that start with FOO". + + Examples: --filter=-whitespace,+whitespace/braces + --filter=whitespace,runtime/printf,+runtime/printf_format + --filter=-,+build/include_what_you_use + + To see a list of all the categories used in cpplint, pass no arg: + --filter= + + counting=total|toplevel|detailed + The total number of errors found is always printed. If + 'toplevel' is provided, then the count of errors in each of + the top-level categories like 'build' and 'whitespace' will + also be printed. If 'detailed' is provided, then a count + is provided for each category like 'build/class'. + + root=subdir + The root directory used for deriving header guard CPP variable. + By default, the header guard CPP variable is calculated as the relative + path to the directory that contains .git, .hg, or .svn. When this flag + is specified, the relative path is calculated from the specified + directory. If the specified directory does not exist, this flag is + ignored. + + Examples: + Assuming that src/.git exists, the header guard CPP variables for + src/chrome/browser/ui/browser.h are: + + No flag => CHROME_BROWSER_UI_BROWSER_H_ + --root=chrome => BROWSER_UI_BROWSER_H_ + --root=chrome/browser => UI_BROWSER_H_ + + linelength=digits + This is the allowed line length for the project. The default value is + 80 characters. + + Examples: + --linelength=120 + + extensions=extension,extension,... + The allowed file extensions that cpplint will check + + Examples: + --extensions=hpp,cpp + + headers=x,y,... + The header extensions that cpplint will treat as .h in checks. Values are + automatically added to --extensions list. + + Examples: + --headers=hpp,hxx + --headers=hpp + + cpplint.py supports per-directory configurations specified in CPPLINT.cfg + files. CPPLINT.cfg file can contain a number of key=value pairs. + Currently the following options are supported: + + set noparent + filter=+filter1,-filter2,... + exclude_files=regex + linelength=80 + root=subdir + headers=x,y,... + + "set noparent" option prevents cpplint from traversing directory tree + upwards looking for more .cfg files in parent directories. This option + is usually placed in the top-level project directory. + + The "filter" option is similar in function to --filter flag. It specifies + message filters in addition to the |_DEFAULT_FILTERS| and those specified + through --filter command-line flag. + + "exclude_files" allows to specify a regular expression to be matched against + a file name. If the expression matches, the file is skipped and not run + through liner. + + "linelength" allows to specify the allowed line length for the project. + + The "root" option is similar in function to the --root flag (see example + above). + + The "headers" option is similar in function to the --headers flag + (see example above). + + CPPLINT.cfg has an effect on files in the same directory and all + sub-directories, unless overridden by a nested configuration file. + + Example file: + filter=-build/include_order,+build/include_alpha + exclude_files=.*\.cc + + The above example disables build/include_order warning and enables + build/include_alpha as well as excludes all .cc from being + processed by linter, in the current directory (where the .cfg + file is located) and all sub-directories. +""" + +# We categorize each error message we print. Here are the categories. +# We want an explicit list so we can list them all in cpplint --filter=. +# If you add a new error message with a new category, add it to the list +# here! cpplint_unittest.py should tell you if you forget to do this. +_ERROR_CATEGORIES = [ + 'build/class', + 'build/c++11', + 'build/c++14', + 'build/c++tr1', + 'build/deprecated', + 'build/endif_comment', + 'build/explicit_make_pair', + 'build/forward_decl', + 'build/header_guard', + 'build/include', + 'build/include_alpha', + 'build/include_order', + 'build/include_what_you_use', + 'build/namespaces', + 'build/printf_format', + 'build/storage_class', + 'legal/copyright', + 'readability/alt_tokens', + 'readability/braces', + 'readability/casting', + 'readability/check', + 'readability/constructors', + 'readability/fn_size', + 'readability/inheritance', + 'readability/multiline_comment', + 'readability/multiline_string', + 'readability/namespace', + 'readability/nolint', + 'readability/nul', + 'readability/strings', + 'readability/todo', + 'readability/utf8', + 'runtime/arrays', + 'runtime/casting', + 'runtime/explicit', + 'runtime/int', + 'runtime/init', + 'runtime/invalid_increment', + 'runtime/member_string_references', + 'runtime/memset', + 'runtime/indentation_namespace', + 'runtime/operator', + 'runtime/printf', + 'runtime/printf_format', + 'runtime/references', + 'runtime/string', + 'runtime/threadsafe_fn', + 'runtime/vlog', + 'whitespace/blank_line', + 'whitespace/braces', + 'whitespace/comma', + 'whitespace/comments', + 'whitespace/empty_conditional_body', + 'whitespace/empty_if_body', + 'whitespace/empty_loop_body', + 'whitespace/end_of_line', + 'whitespace/ending_newline', + 'whitespace/forcolon', + 'whitespace/indent', + 'whitespace/line_length', + 'whitespace/newline', + 'whitespace/operators', + 'whitespace/parens', + 'whitespace/semicolon', + 'whitespace/tab', + 'whitespace/todo', + ] + +# These error categories are no longer enforced by cpplint, but for backwards- +# compatibility they may still appear in NOLINT comments. +_LEGACY_ERROR_CATEGORIES = [ + 'readability/streams', + 'readability/function', + ] + +# The default state of the category filter. This is overridden by the --filter= +# flag. By default all errors are on, so only add here categories that should be +# off by default (i.e., categories that must be enabled by the --filter= flags). +# All entries here should start with a '-' or '+', as in the --filter= flag. +_DEFAULT_FILTERS = ['-build/include_alpha'] + +# The default list of categories suppressed for C (not C++) files. +_DEFAULT_C_SUPPRESSED_CATEGORIES = [ + 'readability/casting', + ] + +# The default list of categories suppressed for Linux Kernel files. +_DEFAULT_KERNEL_SUPPRESSED_CATEGORIES = [ + 'whitespace/tab', + ] + +# We used to check for high-bit characters, but after much discussion we +# decided those were OK, as long as they were in UTF-8 and didn't represent +# hard-coded international strings, which belong in a separate i18n file. + +# C++ headers +_CPP_HEADERS = frozenset([ + # Legacy + 'algobase.h', + 'algo.h', + 'alloc.h', + 'builtinbuf.h', + 'bvector.h', + 'complex.h', + 'defalloc.h', + 'deque.h', + 'editbuf.h', + 'fstream.h', + 'function.h', + 'hash_map', + 'hash_map.h', + 'hash_set', + 'hash_set.h', + 'hashtable.h', + 'heap.h', + 'indstream.h', + 'iomanip.h', + 'iostream.h', + 'istream.h', + 'iterator.h', + 'list.h', + 'map.h', + 'multimap.h', + 'multiset.h', + 'ostream.h', + 'pair.h', + 'parsestream.h', + 'pfstream.h', + 'procbuf.h', + 'pthread_alloc', + 'pthread_alloc.h', + 'rope', + 'rope.h', + 'ropeimpl.h', + 'set.h', + 'slist', + 'slist.h', + 'stack.h', + 'stdiostream.h', + 'stl_alloc.h', + 'stl_relops.h', + 'streambuf.h', + 'stream.h', + 'strfile.h', + 'strstream.h', + 'tempbuf.h', + 'tree.h', + 'type_traits.h', + 'vector.h', + # 17.6.1.2 C++ library headers + 'algorithm', + 'array', + 'atomic', + 'bitset', + 'chrono', + 'codecvt', + 'complex', + 'condition_variable', + 'deque', + 'exception', + 'forward_list', + 'fstream', + 'functional', + 'future', + 'initializer_list', + 'iomanip', + 'ios', + 'iosfwd', + 'iostream', + 'istream', + 'iterator', + 'limits', + 'list', + 'locale', + 'map', + 'memory', + 'mutex', + 'new', + 'numeric', + 'ostream', + 'queue', + 'random', + 'ratio', + 'regex', + 'scoped_allocator', + 'set', + 'sstream', + 'stack', + 'stdexcept', + 'streambuf', + 'string', + 'strstream', + 'system_error', + 'thread', + 'tuple', + 'typeindex', + 'typeinfo', + 'type_traits', + 'unordered_map', + 'unordered_set', + 'utility', + 'valarray', + 'vector', + # 17.6.1.2 C++ headers for C library facilities + 'cassert', + 'ccomplex', + 'cctype', + 'cerrno', + 'cfenv', + 'cfloat', + 'cinttypes', + 'ciso646', + 'climits', + 'clocale', + 'cmath', + 'csetjmp', + 'csignal', + 'cstdalign', + 'cstdarg', + 'cstdbool', + 'cstddef', + 'cstdint', + 'cstdio', + 'cstdlib', + 'cstring', + 'ctgmath', + 'ctime', + 'cuchar', + 'cwchar', + 'cwctype', + ]) + +# Type names +_TYPES = re.compile( + r'^(?:' + # [dcl.type.simple] + r'(char(16_t|32_t)?)|wchar_t|' + r'bool|short|int|long|signed|unsigned|float|double|' + # [support.types] + r'(ptrdiff_t|size_t|max_align_t|nullptr_t)|' + # [cstdint.syn] + r'(u?int(_fast|_least)?(8|16|32|64)_t)|' + r'(u?int(max|ptr)_t)|' + r')$') + + +# These headers are excluded from [build/include] and [build/include_order] +# checks: +# - Anything not following google file name conventions (containing an +# uppercase character, such as Python.h or nsStringAPI.h, for example). +# - Lua headers. +_THIRD_PARTY_HEADERS_PATTERN = re.compile( + r'^(?:[^/]*[A-Z][^/]*\.h|lua\.h|lauxlib\.h|lualib\.h)$') + +# Pattern for matching FileInfo.BaseName() against test file name +_TEST_FILE_SUFFIX = r'(_test|_unittest|_regtest)$' + +# Pattern that matches only complete whitespace, possibly across multiple lines. +_EMPTY_CONDITIONAL_BODY_PATTERN = re.compile(r'^\s*$', re.DOTALL) + +# Assertion macros. These are defined in base/logging.h and +# testing/base/public/gunit.h. +_CHECK_MACROS = [ + 'DCHECK', 'CHECK', + 'EXPECT_TRUE', 'ASSERT_TRUE', + 'EXPECT_FALSE', 'ASSERT_FALSE', + ] + +# Replacement macros for CHECK/DCHECK/EXPECT_TRUE/EXPECT_FALSE +_CHECK_REPLACEMENT = dict([(m, {}) for m in _CHECK_MACROS]) + +for op, replacement in [('==', 'EQ'), ('!=', 'NE'), + ('>=', 'GE'), ('>', 'GT'), + ('<=', 'LE'), ('<', 'LT')]: + _CHECK_REPLACEMENT['DCHECK'][op] = 'DCHECK_%s' % replacement + _CHECK_REPLACEMENT['CHECK'][op] = 'CHECK_%s' % replacement + _CHECK_REPLACEMENT['EXPECT_TRUE'][op] = 'EXPECT_%s' % replacement + _CHECK_REPLACEMENT['ASSERT_TRUE'][op] = 'ASSERT_%s' % replacement + +for op, inv_replacement in [('==', 'NE'), ('!=', 'EQ'), + ('>=', 'LT'), ('>', 'LE'), + ('<=', 'GT'), ('<', 'GE')]: + _CHECK_REPLACEMENT['EXPECT_FALSE'][op] = 'EXPECT_%s' % inv_replacement + _CHECK_REPLACEMENT['ASSERT_FALSE'][op] = 'ASSERT_%s' % inv_replacement + +# Alternative tokens and their replacements. For full list, see section 2.5 +# Alternative tokens [lex.digraph] in the C++ standard. +# +# Digraphs (such as '%:') are not included here since it's a mess to +# match those on a word boundary. +_ALT_TOKEN_REPLACEMENT = { + 'and': '&&', + 'bitor': '|', + 'or': '||', + 'xor': '^', + 'compl': '~', + 'bitand': '&', + 'and_eq': '&=', + 'or_eq': '|=', + 'xor_eq': '^=', + 'not': '!', + 'not_eq': '!=' + } + +# Compile regular expression that matches all the above keywords. The "[ =()]" +# bit is meant to avoid matching these keywords outside of boolean expressions. +# +# False positives include C-style multi-line comments and multi-line strings +# but those have always been troublesome for cpplint. +_ALT_TOKEN_REPLACEMENT_PATTERN = re.compile( + r'[ =()](' + ('|'.join(_ALT_TOKEN_REPLACEMENT.keys())) + r')(?=[ (]|$)') + + +# These constants define types of headers for use with +# _IncludeState.CheckNextIncludeOrder(). +_C_SYS_HEADER = 1 +_CPP_SYS_HEADER = 2 +_LIKELY_MY_HEADER = 3 +_POSSIBLE_MY_HEADER = 4 +_OTHER_HEADER = 5 + +# These constants define the current inline assembly state +_NO_ASM = 0 # Outside of inline assembly block +_INSIDE_ASM = 1 # Inside inline assembly block +_END_ASM = 2 # Last line of inline assembly block +_BLOCK_ASM = 3 # The whole block is an inline assembly block + +# Match start of assembly blocks +_MATCH_ASM = re.compile(r'^\s*(?:asm|_asm|__asm|__asm__)' + r'(?:\s+(volatile|__volatile__))?' + r'\s*[{(]') + +# Match strings that indicate we're working on a C (not C++) file. +_SEARCH_C_FILE = re.compile(r'\b(?:LINT_C_FILE|' + r'vim?:\s*.*(\s*|:)filetype=c(\s*|:|$))') + +# Match string that indicates we're working on a Linux Kernel file. +_SEARCH_KERNEL_FILE = re.compile(r'\b(?:LINT_KERNEL_FILE)') + +_regexp_compile_cache = {} + +# {str, set(int)}: a map from error categories to sets of linenumbers +# on which those errors are expected and should be suppressed. +_error_suppressions = {} + +# The root directory used for deriving header guard CPP variable. +# This is set by --root flag. +_root = None + +# The allowed line length of files. +# This is set by --linelength flag. +_line_length = 80 + +# The allowed extensions for file names +# This is set by --extensions flag. +_valid_extensions = set(['cc', 'h', 'cpp', 'cu', 'cuh']) + +# Treat all headers starting with 'h' equally: .h, .hpp, .hxx etc. +# This is set by --headers flag. +_hpp_headers = set(['h']) + +# {str, bool}: a map from error categories to booleans which indicate if the +# category should be suppressed for every line. +_global_error_suppressions = {} + +def ProcessHppHeadersOption(val): + global _hpp_headers + try: + _hpp_headers = set(val.split(',')) + # Automatically append to extensions list so it does not have to be set 2 times + _valid_extensions.update(_hpp_headers) + except ValueError: + PrintUsage('Header extensions must be comma seperated list.') + +def IsHeaderExtension(file_extension): + return file_extension in _hpp_headers + +def ParseNolintSuppressions(filename, raw_line, linenum, error): + """Updates the global list of line error-suppressions. + + Parses any NOLINT comments on the current line, updating the global + error_suppressions store. Reports an error if the NOLINT comment + was malformed. + + Args: + filename: str, the name of the input file. + raw_line: str, the line of input text, with comments. + linenum: int, the number of the current line. + error: function, an error handler. + """ + matched = Search(r'\bNOLINT(NEXTLINE)?\b(\([^)]+\))?', raw_line) + if matched: + if matched.group(1): + suppressed_line = linenum + 1 + else: + suppressed_line = linenum + category = matched.group(2) + if category in (None, '(*)'): # => "suppress all" + _error_suppressions.setdefault(None, set()).add(suppressed_line) + else: + if category.startswith('(') and category.endswith(')'): + category = category[1:-1] + if category in _ERROR_CATEGORIES: + _error_suppressions.setdefault(category, set()).add(suppressed_line) + elif category not in _LEGACY_ERROR_CATEGORIES: + error(filename, linenum, 'readability/nolint', 5, + 'Unknown NOLINT error category: %s' % category) + + +def ProcessGlobalSuppresions(lines): + """Updates the list of global error suppressions. + + Parses any lint directives in the file that have global effect. + + Args: + lines: An array of strings, each representing a line of the file, with the + last element being empty if the file is terminated with a newline. + """ + for line in lines: + if _SEARCH_C_FILE.search(line): + for category in _DEFAULT_C_SUPPRESSED_CATEGORIES: + _global_error_suppressions[category] = True + if _SEARCH_KERNEL_FILE.search(line): + for category in _DEFAULT_KERNEL_SUPPRESSED_CATEGORIES: + _global_error_suppressions[category] = True + + +def ResetNolintSuppressions(): + """Resets the set of NOLINT suppressions to empty.""" + _error_suppressions.clear() + _global_error_suppressions.clear() + + +def IsErrorSuppressedByNolint(category, linenum): + """Returns true if the specified error category is suppressed on this line. + + Consults the global error_suppressions map populated by + ParseNolintSuppressions/ProcessGlobalSuppresions/ResetNolintSuppressions. + + Args: + category: str, the category of the error. + linenum: int, the current line number. + Returns: + bool, True iff the error should be suppressed due to a NOLINT comment or + global suppression. + """ + return (_global_error_suppressions.get(category, False) or + linenum in _error_suppressions.get(category, set()) or + linenum in _error_suppressions.get(None, set())) + + +def Match(pattern, s): + """Matches the string with the pattern, caching the compiled regexp.""" + # The regexp compilation caching is inlined in both Match and Search for + # performance reasons; factoring it out into a separate function turns out + # to be noticeably expensive. + if pattern not in _regexp_compile_cache: + _regexp_compile_cache[pattern] = sre_compile.compile(pattern) + return _regexp_compile_cache[pattern].match(s) + + +def ReplaceAll(pattern, rep, s): + """Replaces instances of pattern in a string with a replacement. + + The compiled regex is kept in a cache shared by Match and Search. + + Args: + pattern: regex pattern + rep: replacement text + s: search string + + Returns: + string with replacements made (or original string if no replacements) + """ + if pattern not in _regexp_compile_cache: + _regexp_compile_cache[pattern] = sre_compile.compile(pattern) + return _regexp_compile_cache[pattern].sub(rep, s) + + +def Search(pattern, s): + """Searches the string for the pattern, caching the compiled regexp.""" + if pattern not in _regexp_compile_cache: + _regexp_compile_cache[pattern] = sre_compile.compile(pattern) + return _regexp_compile_cache[pattern].search(s) + + +def _IsSourceExtension(s): + """File extension (excluding dot) matches a source file extension.""" + return s in ('c', 'cc', 'cpp', 'cxx') + + +class _IncludeState(object): + """Tracks line numbers for includes, and the order in which includes appear. + + include_list contains list of lists of (header, line number) pairs. + It's a lists of lists rather than just one flat list to make it + easier to update across preprocessor boundaries. + + Call CheckNextIncludeOrder() once for each header in the file, passing + in the type constants defined above. Calls in an illegal order will + raise an _IncludeError with an appropriate error message. + + """ + # self._section will move monotonically through this set. If it ever + # needs to move backwards, CheckNextIncludeOrder will raise an error. + _INITIAL_SECTION = 0 + _MY_H_SECTION = 1 + _C_SECTION = 2 + _CPP_SECTION = 3 + _OTHER_H_SECTION = 4 + + _TYPE_NAMES = { + _C_SYS_HEADER: 'C system header', + _CPP_SYS_HEADER: 'C++ system header', + _LIKELY_MY_HEADER: 'header this file implements', + _POSSIBLE_MY_HEADER: 'header this file may implement', + _OTHER_HEADER: 'other header', + } + _SECTION_NAMES = { + _INITIAL_SECTION: "... nothing. (This can't be an error.)", + _MY_H_SECTION: 'a header this file implements', + _C_SECTION: 'C system header', + _CPP_SECTION: 'C++ system header', + _OTHER_H_SECTION: 'other header', + } + + def __init__(self): + self.include_list = [[]] + self.ResetSection('') + + def FindHeader(self, header): + """Check if a header has already been included. + + Args: + header: header to check. + Returns: + Line number of previous occurrence, or -1 if the header has not + been seen before. + """ + for section_list in self.include_list: + for f in section_list: + if f[0] == header: + return f[1] + return -1 + + def ResetSection(self, directive): + """Reset section checking for preprocessor directive. + + Args: + directive: preprocessor directive (e.g. "if", "else"). + """ + # The name of the current section. + self._section = self._INITIAL_SECTION + # The path of last found header. + self._last_header = '' + + # Update list of includes. Note that we never pop from the + # include list. + if directive in ('if', 'ifdef', 'ifndef'): + self.include_list.append([]) + elif directive in ('else', 'elif'): + self.include_list[-1] = [] + + def SetLastHeader(self, header_path): + self._last_header = header_path + + def CanonicalizeAlphabeticalOrder(self, header_path): + """Returns a path canonicalized for alphabetical comparison. + + - replaces "-" with "_" so they both cmp the same. + - removes '-inl' since we don't require them to be after the main header. + - lowercase everything, just in case. + + Args: + header_path: Path to be canonicalized. + + Returns: + Canonicalized path. + """ + return header_path.replace('-inl.h', '.h').replace('-', '_').lower() + + def IsInAlphabeticalOrder(self, clean_lines, linenum, header_path): + """Check if a header is in alphabetical order with the previous header. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + header_path: Canonicalized header to be checked. + + Returns: + Returns true if the header is in alphabetical order. + """ + # If previous section is different from current section, _last_header will + # be reset to empty string, so it's always less than current header. + # + # If previous line was a blank line, assume that the headers are + # intentionally sorted the way they are. + if (self._last_header > header_path and + Match(r'^\s*#\s*include\b', clean_lines.elided[linenum - 1])): + return False + return True + + def CheckNextIncludeOrder(self, header_type): + """Returns a non-empty error message if the next header is out of order. + + This function also updates the internal state to be ready to check + the next include. + + Args: + header_type: One of the _XXX_HEADER constants defined above. + + Returns: + The empty string if the header is in the right order, or an + error message describing what's wrong. + + """ + error_message = ('Found %s after %s' % + (self._TYPE_NAMES[header_type], + self._SECTION_NAMES[self._section])) + + last_section = self._section + + if header_type == _C_SYS_HEADER: + if self._section <= self._C_SECTION: + self._section = self._C_SECTION + else: + self._last_header = '' + return error_message + elif header_type == _CPP_SYS_HEADER: + if self._section <= self._CPP_SECTION: + self._section = self._CPP_SECTION + else: + self._last_header = '' + return error_message + elif header_type == _LIKELY_MY_HEADER: + if self._section <= self._MY_H_SECTION: + self._section = self._MY_H_SECTION + else: + self._section = self._OTHER_H_SECTION + elif header_type == _POSSIBLE_MY_HEADER: + if self._section <= self._MY_H_SECTION: + self._section = self._MY_H_SECTION + else: + # This will always be the fallback because we're not sure + # enough that the header is associated with this file. + self._section = self._OTHER_H_SECTION + else: + assert header_type == _OTHER_HEADER + self._section = self._OTHER_H_SECTION + + if last_section != self._section: + self._last_header = '' + + return '' + + +class _CppLintState(object): + """Maintains module-wide state..""" + + def __init__(self): + self.verbose_level = 1 # global setting. + self.error_count = 0 # global count of reported errors + # filters to apply when emitting error messages + self.filters = _DEFAULT_FILTERS[:] + # backup of filter list. Used to restore the state after each file. + self._filters_backup = self.filters[:] + self.counting = 'total' # In what way are we counting errors? + self.errors_by_category = {} # string to int dict storing error counts + + # output format: + # "emacs" - format that emacs can parse (default) + # "vs7" - format that Microsoft Visual Studio 7 can parse + self.output_format = 'emacs' + + def SetOutputFormat(self, output_format): + """Sets the output format for errors.""" + self.output_format = output_format + + def SetVerboseLevel(self, level): + """Sets the module's verbosity, and returns the previous setting.""" + last_verbose_level = self.verbose_level + self.verbose_level = level + return last_verbose_level + + def SetCountingStyle(self, counting_style): + """Sets the module's counting options.""" + self.counting = counting_style + + def SetFilters(self, filters): + """Sets the error-message filters. + + These filters are applied when deciding whether to emit a given + error message. + + Args: + filters: A string of comma-separated filters (eg "+whitespace/indent"). + Each filter should start with + or -; else we die. + + Raises: + ValueError: The comma-separated filters did not all start with '+' or '-'. + E.g. "-,+whitespace,-whitespace/indent,whitespace/badfilter" + """ + # Default filters always have less priority than the flag ones. + self.filters = _DEFAULT_FILTERS[:] + self.AddFilters(filters) + + def AddFilters(self, filters): + """ Adds more filters to the existing list of error-message filters. """ + for filt in filters.split(','): + clean_filt = filt.strip() + if clean_filt: + self.filters.append(clean_filt) + for filt in self.filters: + if not (filt.startswith('+') or filt.startswith('-')): + raise ValueError('Every filter in --filters must start with + or -' + ' (%s does not)' % filt) + + def BackupFilters(self): + """ Saves the current filter list to backup storage.""" + self._filters_backup = self.filters[:] + + def RestoreFilters(self): + """ Restores filters previously backed up.""" + self.filters = self._filters_backup[:] + + def ResetErrorCounts(self): + """Sets the module's error statistic back to zero.""" + self.error_count = 0 + self.errors_by_category = {} + + def IncrementErrorCount(self, category): + """Bumps the module's error statistic.""" + self.error_count += 1 + if self.counting in ('toplevel', 'detailed'): + if self.counting != 'detailed': + category = category.split('/')[0] + if category not in self.errors_by_category: + self.errors_by_category[category] = 0 + self.errors_by_category[category] += 1 + + def PrintErrorCounts(self): + """Print a summary of errors by category, and the total.""" + for category, count in self.errors_by_category.iteritems(): + sys.stderr.write('Category \'%s\' errors found: %d\n' % + (category, count)) + sys.stdout.write('Total errors found: %d\n' % self.error_count) + +_cpplint_state = _CppLintState() + + +def _OutputFormat(): + """Gets the module's output format.""" + return _cpplint_state.output_format + + +def _SetOutputFormat(output_format): + """Sets the module's output format.""" + _cpplint_state.SetOutputFormat(output_format) + + +def _VerboseLevel(): + """Returns the module's verbosity setting.""" + return _cpplint_state.verbose_level + + +def _SetVerboseLevel(level): + """Sets the module's verbosity, and returns the previous setting.""" + return _cpplint_state.SetVerboseLevel(level) + + +def _SetCountingStyle(level): + """Sets the module's counting options.""" + _cpplint_state.SetCountingStyle(level) + + +def _Filters(): + """Returns the module's list of output filters, as a list.""" + return _cpplint_state.filters + + +def _SetFilters(filters): + """Sets the module's error-message filters. + + These filters are applied when deciding whether to emit a given + error message. + + Args: + filters: A string of comma-separated filters (eg "whitespace/indent"). + Each filter should start with + or -; else we die. + """ + _cpplint_state.SetFilters(filters) + +def _AddFilters(filters): + """Adds more filter overrides. + + Unlike _SetFilters, this function does not reset the current list of filters + available. + + Args: + filters: A string of comma-separated filters (eg "whitespace/indent"). + Each filter should start with + or -; else we die. + """ + _cpplint_state.AddFilters(filters) + +def _BackupFilters(): + """ Saves the current filter list to backup storage.""" + _cpplint_state.BackupFilters() + +def _RestoreFilters(): + """ Restores filters previously backed up.""" + _cpplint_state.RestoreFilters() + +class _FunctionState(object): + """Tracks current function name and the number of lines in its body.""" + + _NORMAL_TRIGGER = 250 # for --v=0, 500 for --v=1, etc. + _TEST_TRIGGER = 400 # about 50% more than _NORMAL_TRIGGER. + + def __init__(self): + self.in_a_function = False + self.lines_in_function = 0 + self.current_function = '' + + def Begin(self, function_name): + """Start analyzing function body. + + Args: + function_name: The name of the function being tracked. + """ + self.in_a_function = True + self.lines_in_function = 0 + self.current_function = function_name + + def Count(self): + """Count line in current function body.""" + if self.in_a_function: + self.lines_in_function += 1 + + def Check(self, error, filename, linenum): + """Report if too many lines in function body. + + Args: + error: The function to call with any errors found. + filename: The name of the current file. + linenum: The number of the line to check. + """ + if not self.in_a_function: + return + + if Match(r'T(EST|est)', self.current_function): + base_trigger = self._TEST_TRIGGER + else: + base_trigger = self._NORMAL_TRIGGER + trigger = base_trigger * 2**_VerboseLevel() + + if self.lines_in_function > trigger: + error_level = int(math.log(self.lines_in_function / base_trigger, 2)) + # 50 => 0, 100 => 1, 200 => 2, 400 => 3, 800 => 4, 1600 => 5, ... + if error_level > 5: + error_level = 5 + error(filename, linenum, 'readability/fn_size', error_level, + 'Small and focused functions are preferred:' + ' %s has %d non-comment lines' + ' (error triggered by exceeding %d lines).' % ( + self.current_function, self.lines_in_function, trigger)) + + def End(self): + """Stop analyzing function body.""" + self.in_a_function = False + + +class _IncludeError(Exception): + """Indicates a problem with the include order in a file.""" + pass + + +class FileInfo(object): + """Provides utility functions for filenames. + + FileInfo provides easy access to the components of a file's path + relative to the project root. + """ + + def __init__(self, filename): + self._filename = filename + + def FullName(self): + """Make Windows paths like Unix.""" + return os.path.abspath(self._filename).replace('\\', '/') + + def RepositoryName(self): + """FullName after removing the local path to the repository. + + If we have a real absolute path name here we can try to do something smart: + detecting the root of the checkout and truncating /path/to/checkout from + the name so that we get header guards that don't include things like + "C:\Documents and Settings\..." or "/home/username/..." in them and thus + people on different computers who have checked the source out to different + locations won't see bogus errors. + """ + fullname = self.FullName() + + if os.path.exists(fullname): + project_dir = os.path.dirname(fullname) + + if os.path.exists(os.path.join(project_dir, ".svn")): + # If there's a .svn file in the current directory, we recursively look + # up the directory tree for the top of the SVN checkout + root_dir = project_dir + one_up_dir = os.path.dirname(root_dir) + while os.path.exists(os.path.join(one_up_dir, ".svn")): + root_dir = os.path.dirname(root_dir) + one_up_dir = os.path.dirname(one_up_dir) + + prefix = os.path.commonprefix([root_dir, project_dir]) + return fullname[len(prefix) + 1:] + + # Not SVN <= 1.6? Try to find a git, hg, or svn top level directory by + # searching up from the current path. + root_dir = current_dir = os.path.dirname(fullname) + while current_dir != os.path.dirname(current_dir): + if (os.path.exists(os.path.join(current_dir, ".git")) or + os.path.exists(os.path.join(current_dir, ".hg")) or + os.path.exists(os.path.join(current_dir, ".svn"))): + root_dir = current_dir + current_dir = os.path.dirname(current_dir) + + if (os.path.exists(os.path.join(root_dir, ".git")) or + os.path.exists(os.path.join(root_dir, ".hg")) or + os.path.exists(os.path.join(root_dir, ".svn"))): + prefix = os.path.commonprefix([root_dir, project_dir]) + return fullname[len(prefix) + 1:] + + # Don't know what to do; header guard warnings may be wrong... + return fullname + + def Split(self): + """Splits the file into the directory, basename, and extension. + + For 'chrome/browser/browser.cc', Split() would + return ('chrome/browser', 'browser', '.cc') + + Returns: + A tuple of (directory, basename, extension). + """ + + googlename = self.RepositoryName() + project, rest = os.path.split(googlename) + return (project,) + os.path.splitext(rest) + + def BaseName(self): + """File base name - text after the final slash, before the final period.""" + return self.Split()[1] + + def Extension(self): + """File extension - text following the final period.""" + return self.Split()[2] + + def NoExtension(self): + """File has no source file extension.""" + return '/'.join(self.Split()[0:2]) + + def IsSource(self): + """File has a source file extension.""" + return _IsSourceExtension(self.Extension()[1:]) + + +def _ShouldPrintError(category, confidence, linenum): + """If confidence >= verbose, category passes filter and is not suppressed.""" + + # There are three ways we might decide not to print an error message: + # a "NOLINT(category)" comment appears in the source, + # the verbosity level isn't high enough, or the filters filter it out. + if IsErrorSuppressedByNolint(category, linenum): + return False + + if confidence < _cpplint_state.verbose_level: + return False + + is_filtered = False + for one_filter in _Filters(): + if one_filter.startswith('-'): + if category.startswith(one_filter[1:]): + is_filtered = True + elif one_filter.startswith('+'): + if category.startswith(one_filter[1:]): + is_filtered = False + else: + assert False # should have been checked for in SetFilter. + if is_filtered: + return False + + return True + + +def Error(filename, linenum, category, confidence, message): + """Logs the fact we've found a lint error. + + We log where the error was found, and also our confidence in the error, + that is, how certain we are this is a legitimate style regression, and + not a misidentification or a use that's sometimes justified. + + False positives can be suppressed by the use of + "cpplint(category)" comments on the offending line. These are + parsed into _error_suppressions. + + Args: + filename: The name of the file containing the error. + linenum: The number of the line containing the error. + category: A string used to describe the "category" this bug + falls under: "whitespace", say, or "runtime". Categories + may have a hierarchy separated by slashes: "whitespace/indent". + confidence: A number from 1-5 representing a confidence score for + the error, with 5 meaning that we are certain of the problem, + and 1 meaning that it could be a legitimate construct. + message: The error message. + """ + if _ShouldPrintError(category, confidence, linenum): + _cpplint_state.IncrementErrorCount(category) + if _cpplint_state.output_format == 'vs7': + sys.stderr.write('%s(%s): error cpplint: [%s] %s [%d]\n' % ( + filename, linenum, category, message, confidence)) + elif _cpplint_state.output_format == 'eclipse': + sys.stderr.write('%s:%s: warning: %s [%s] [%d]\n' % ( + filename, linenum, message, category, confidence)) + else: + sys.stderr.write('%s:%s: %s [%s] [%d]\n' % ( + filename, linenum, message, category, confidence)) + + +# Matches standard C++ escape sequences per 2.13.2.3 of the C++ standard. +_RE_PATTERN_CLEANSE_LINE_ESCAPES = re.compile( + r'\\([abfnrtv?"\\\']|\d+|x[0-9a-fA-F]+)') +# Match a single C style comment on the same line. +_RE_PATTERN_C_COMMENTS = r'/\*(?:[^*]|\*(?!/))*\*/' +# Matches multi-line C style comments. +# This RE is a little bit more complicated than one might expect, because we +# have to take care of space removals tools so we can handle comments inside +# statements better. +# The current rule is: We only clear spaces from both sides when we're at the +# end of the line. Otherwise, we try to remove spaces from the right side, +# if this doesn't work we try on left side but only if there's a non-character +# on the right. +_RE_PATTERN_CLEANSE_LINE_C_COMMENTS = re.compile( + r'(\s*' + _RE_PATTERN_C_COMMENTS + r'\s*$|' + + _RE_PATTERN_C_COMMENTS + r'\s+|' + + r'\s+' + _RE_PATTERN_C_COMMENTS + r'(?=\W)|' + + _RE_PATTERN_C_COMMENTS + r')') + + +def IsCppString(line): + """Does line terminate so, that the next symbol is in string constant. + + This function does not consider single-line nor multi-line comments. + + Args: + line: is a partial line of code starting from the 0..n. + + Returns: + True, if next character appended to 'line' is inside a + string constant. + """ + + line = line.replace(r'\\', 'XX') # after this, \\" does not match to \" + return ((line.count('"') - line.count(r'\"') - line.count("'\"'")) & 1) == 1 + + +def CleanseRawStrings(raw_lines): + """Removes C++11 raw strings from lines. + + Before: + static const char kData[] = R"( + multi-line string + )"; + + After: + static const char kData[] = "" + (replaced by blank line) + ""; + + Args: + raw_lines: list of raw lines. + + Returns: + list of lines with C++11 raw strings replaced by empty strings. + """ + + delimiter = None + lines_without_raw_strings = [] + for line in raw_lines: + if delimiter: + # Inside a raw string, look for the end + end = line.find(delimiter) + if end >= 0: + # Found the end of the string, match leading space for this + # line and resume copying the original lines, and also insert + # a "" on the last line. + leading_space = Match(r'^(\s*)\S', line) + line = leading_space.group(1) + '""' + line[end + len(delimiter):] + delimiter = None + else: + # Haven't found the end yet, append a blank line. + line = '""' + + # Look for beginning of a raw string, and replace them with + # empty strings. This is done in a loop to handle multiple raw + # strings on the same line. + while delimiter is None: + # Look for beginning of a raw string. + # See 2.14.15 [lex.string] for syntax. + # + # Once we have matched a raw string, we check the prefix of the + # line to make sure that the line is not part of a single line + # comment. It's done this way because we remove raw strings + # before removing comments as opposed to removing comments + # before removing raw strings. This is because there are some + # cpplint checks that requires the comments to be preserved, but + # we don't want to check comments that are inside raw strings. + matched = Match(r'^(.*?)\b(?:R|u8R|uR|UR|LR)"([^\s\\()]*)\((.*)$', line) + if (matched and + not Match(r'^([^\'"]|\'(\\.|[^\'])*\'|"(\\.|[^"])*")*//', + matched.group(1))): + delimiter = ')' + matched.group(2) + '"' + + end = matched.group(3).find(delimiter) + if end >= 0: + # Raw string ended on same line + line = (matched.group(1) + '""' + + matched.group(3)[end + len(delimiter):]) + delimiter = None + else: + # Start of a multi-line raw string + line = matched.group(1) + '""' + else: + break + + lines_without_raw_strings.append(line) + + # TODO(unknown): if delimiter is not None here, we might want to + # emit a warning for unterminated string. + return lines_without_raw_strings + + +def FindNextMultiLineCommentStart(lines, lineix): + """Find the beginning marker for a multiline comment.""" + while lineix < len(lines): + if lines[lineix].strip().startswith('/*'): + # Only return this marker if the comment goes beyond this line + if lines[lineix].strip().find('*/', 2) < 0: + return lineix + lineix += 1 + return len(lines) + + +def FindNextMultiLineCommentEnd(lines, lineix): + """We are inside a comment, find the end marker.""" + while lineix < len(lines): + if lines[lineix].strip().endswith('*/'): + return lineix + lineix += 1 + return len(lines) + + +def RemoveMultiLineCommentsFromRange(lines, begin, end): + """Clears a range of lines for multi-line comments.""" + # Having // dummy comments makes the lines non-empty, so we will not get + # unnecessary blank line warnings later in the code. + for i in range(begin, end): + lines[i] = '/**/' + + +def RemoveMultiLineComments(filename, lines, error): + """Removes multiline (c-style) comments from lines.""" + lineix = 0 + while lineix < len(lines): + lineix_begin = FindNextMultiLineCommentStart(lines, lineix) + if lineix_begin >= len(lines): + return + lineix_end = FindNextMultiLineCommentEnd(lines, lineix_begin) + if lineix_end >= len(lines): + error(filename, lineix_begin + 1, 'readability/multiline_comment', 5, + 'Could not find end of multi-line comment') + return + RemoveMultiLineCommentsFromRange(lines, lineix_begin, lineix_end + 1) + lineix = lineix_end + 1 + + +def CleanseComments(line): + """Removes //-comments and single-line C-style /* */ comments. + + Args: + line: A line of C++ source. + + Returns: + The line with single-line comments removed. + """ + commentpos = line.find('//') + if commentpos != -1 and not IsCppString(line[:commentpos]): + line = line[:commentpos].rstrip() + # get rid of /* ... */ + return _RE_PATTERN_CLEANSE_LINE_C_COMMENTS.sub('', line) + + +class CleansedLines(object): + """Holds 4 copies of all lines with different preprocessing applied to them. + + 1) elided member contains lines without strings and comments. + 2) lines member contains lines without comments. + 3) raw_lines member contains all the lines without processing. + 4) lines_without_raw_strings member is same as raw_lines, but with C++11 raw + strings removed. + All these members are of , and of the same length. + """ + + def __init__(self, lines): + self.elided = [] + self.lines = [] + self.raw_lines = lines + self.num_lines = len(lines) + self.lines_without_raw_strings = CleanseRawStrings(lines) + for linenum in range(len(self.lines_without_raw_strings)): + self.lines.append(CleanseComments( + self.lines_without_raw_strings[linenum])) + elided = self._CollapseStrings(self.lines_without_raw_strings[linenum]) + self.elided.append(CleanseComments(elided)) + + def NumLines(self): + """Returns the number of lines represented.""" + return self.num_lines + + @staticmethod + def _CollapseStrings(elided): + """Collapses strings and chars on a line to simple "" or '' blocks. + + We nix strings first so we're not fooled by text like '"http://"' + + Args: + elided: The line being processed. + + Returns: + The line with collapsed strings. + """ + if _RE_PATTERN_INCLUDE.match(elided): + return elided + + # Remove escaped characters first to make quote/single quote collapsing + # basic. Things that look like escaped characters shouldn't occur + # outside of strings and chars. + elided = _RE_PATTERN_CLEANSE_LINE_ESCAPES.sub('', elided) + + # Replace quoted strings and digit separators. Both single quotes + # and double quotes are processed in the same loop, otherwise + # nested quotes wouldn't work. + collapsed = '' + while True: + # Find the first quote character + match = Match(r'^([^\'"]*)([\'"])(.*)$', elided) + if not match: + collapsed += elided + break + head, quote, tail = match.groups() + + if quote == '"': + # Collapse double quoted strings + second_quote = tail.find('"') + if second_quote >= 0: + collapsed += head + '""' + elided = tail[second_quote + 1:] + else: + # Unmatched double quote, don't bother processing the rest + # of the line since this is probably a multiline string. + collapsed += elided + break + else: + # Found single quote, check nearby text to eliminate digit separators. + # + # There is no special handling for floating point here, because + # the integer/fractional/exponent parts would all be parsed + # correctly as long as there are digits on both sides of the + # separator. So we are fine as long as we don't see something + # like "0.'3" (gcc 4.9.0 will not allow this literal). + if Search(r'\b(?:0[bBxX]?|[1-9])[0-9a-fA-F]*$', head): + match_literal = Match(r'^((?:\'?[0-9a-zA-Z_])*)(.*)$', "'" + tail) + collapsed += head + match_literal.group(1).replace("'", '') + elided = match_literal.group(2) + else: + second_quote = tail.find('\'') + if second_quote >= 0: + collapsed += head + "''" + elided = tail[second_quote + 1:] + else: + # Unmatched single quote + collapsed += elided + break + + return collapsed + + +def FindEndOfExpressionInLine(line, startpos, stack): + """Find the position just after the end of current parenthesized expression. + + Args: + line: a CleansedLines line. + startpos: start searching at this position. + stack: nesting stack at startpos. + + Returns: + On finding matching end: (index just after matching end, None) + On finding an unclosed expression: (-1, None) + Otherwise: (-1, new stack at end of this line) + """ + for i in xrange(startpos, len(line)): + char = line[i] + if char in '([{': + # Found start of parenthesized expression, push to expression stack + stack.append(char) + elif char == '<': + # Found potential start of template argument list + if i > 0 and line[i - 1] == '<': + # Left shift operator + if stack and stack[-1] == '<': + stack.pop() + if not stack: + return (-1, None) + elif i > 0 and Search(r'\boperator\s*$', line[0:i]): + # operator<, don't add to stack + continue + else: + # Tentative start of template argument list + stack.append('<') + elif char in ')]}': + # Found end of parenthesized expression. + # + # If we are currently expecting a matching '>', the pending '<' + # must have been an operator. Remove them from expression stack. + while stack and stack[-1] == '<': + stack.pop() + if not stack: + return (-1, None) + if ((stack[-1] == '(' and char == ')') or + (stack[-1] == '[' and char == ']') or + (stack[-1] == '{' and char == '}')): + stack.pop() + if not stack: + return (i + 1, None) + else: + # Mismatched parentheses + return (-1, None) + elif char == '>': + # Found potential end of template argument list. + + # Ignore "->" and operator functions + if (i > 0 and + (line[i - 1] == '-' or Search(r'\boperator\s*$', line[0:i - 1]))): + continue + + # Pop the stack if there is a matching '<'. Otherwise, ignore + # this '>' since it must be an operator. + if stack: + if stack[-1] == '<': + stack.pop() + if not stack: + return (i + 1, None) + elif char == ';': + # Found something that look like end of statements. If we are currently + # expecting a '>', the matching '<' must have been an operator, since + # template argument list should not contain statements. + while stack and stack[-1] == '<': + stack.pop() + if not stack: + return (-1, None) + + # Did not find end of expression or unbalanced parentheses on this line + return (-1, stack) + + +def CloseExpression(clean_lines, linenum, pos): + """If input points to ( or { or [ or <, finds the position that closes it. + + If lines[linenum][pos] points to a '(' or '{' or '[' or '<', finds the + linenum/pos that correspond to the closing of the expression. + + TODO(unknown): cpplint spends a fair bit of time matching parentheses. + Ideally we would want to index all opening and closing parentheses once + and have CloseExpression be just a simple lookup, but due to preprocessor + tricks, this is not so easy. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + pos: A position on the line. + + Returns: + A tuple (line, linenum, pos) pointer *past* the closing brace, or + (line, len(lines), -1) if we never find a close. Note we ignore + strings and comments when matching; and the line we return is the + 'cleansed' line at linenum. + """ + + line = clean_lines.elided[linenum] + if (line[pos] not in '({[<') or Match(r'<[<=]', line[pos:]): + return (line, clean_lines.NumLines(), -1) + + # Check first line + (end_pos, stack) = FindEndOfExpressionInLine(line, pos, []) + if end_pos > -1: + return (line, linenum, end_pos) + + # Continue scanning forward + while stack and linenum < clean_lines.NumLines() - 1: + linenum += 1 + line = clean_lines.elided[linenum] + (end_pos, stack) = FindEndOfExpressionInLine(line, 0, stack) + if end_pos > -1: + return (line, linenum, end_pos) + + # Did not find end of expression before end of file, give up + return (line, clean_lines.NumLines(), -1) + + +def FindStartOfExpressionInLine(line, endpos, stack): + """Find position at the matching start of current expression. + + This is almost the reverse of FindEndOfExpressionInLine, but note + that the input position and returned position differs by 1. + + Args: + line: a CleansedLines line. + endpos: start searching at this position. + stack: nesting stack at endpos. + + Returns: + On finding matching start: (index at matching start, None) + On finding an unclosed expression: (-1, None) + Otherwise: (-1, new stack at beginning of this line) + """ + i = endpos + while i >= 0: + char = line[i] + if char in ')]}': + # Found end of expression, push to expression stack + stack.append(char) + elif char == '>': + # Found potential end of template argument list. + # + # Ignore it if it's a "->" or ">=" or "operator>" + if (i > 0 and + (line[i - 1] == '-' or + Match(r'\s>=\s', line[i - 1:]) or + Search(r'\boperator\s*$', line[0:i]))): + i -= 1 + else: + stack.append('>') + elif char == '<': + # Found potential start of template argument list + if i > 0 and line[i - 1] == '<': + # Left shift operator + i -= 1 + else: + # If there is a matching '>', we can pop the expression stack. + # Otherwise, ignore this '<' since it must be an operator. + if stack and stack[-1] == '>': + stack.pop() + if not stack: + return (i, None) + elif char in '([{': + # Found start of expression. + # + # If there are any unmatched '>' on the stack, they must be + # operators. Remove those. + while stack and stack[-1] == '>': + stack.pop() + if not stack: + return (-1, None) + if ((char == '(' and stack[-1] == ')') or + (char == '[' and stack[-1] == ']') or + (char == '{' and stack[-1] == '}')): + stack.pop() + if not stack: + return (i, None) + else: + # Mismatched parentheses + return (-1, None) + elif char == ';': + # Found something that look like end of statements. If we are currently + # expecting a '<', the matching '>' must have been an operator, since + # template argument list should not contain statements. + while stack and stack[-1] == '>': + stack.pop() + if not stack: + return (-1, None) + + i -= 1 + + return (-1, stack) + + +def ReverseCloseExpression(clean_lines, linenum, pos): + """If input points to ) or } or ] or >, finds the position that opens it. + + If lines[linenum][pos] points to a ')' or '}' or ']' or '>', finds the + linenum/pos that correspond to the opening of the expression. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + pos: A position on the line. + + Returns: + A tuple (line, linenum, pos) pointer *at* the opening brace, or + (line, 0, -1) if we never find the matching opening brace. Note + we ignore strings and comments when matching; and the line we + return is the 'cleansed' line at linenum. + """ + line = clean_lines.elided[linenum] + if line[pos] not in ')}]>': + return (line, 0, -1) + + # Check last line + (start_pos, stack) = FindStartOfExpressionInLine(line, pos, []) + if start_pos > -1: + return (line, linenum, start_pos) + + # Continue scanning backward + while stack and linenum > 0: + linenum -= 1 + line = clean_lines.elided[linenum] + (start_pos, stack) = FindStartOfExpressionInLine(line, len(line) - 1, stack) + if start_pos > -1: + return (line, linenum, start_pos) + + # Did not find start of expression before beginning of file, give up + return (line, 0, -1) + + +def CheckForCopyright(filename, lines, error): + """Logs an error if no Copyright message appears at the top of the file.""" + + # We'll say it should occur by line 10. Don't forget there's a + # dummy line at the front. + for line in xrange(1, min(len(lines), 11)): + if re.search(r'Copyright', lines[line], re.I): break + else: # means no copyright line was found + error(filename, 0, 'legal/copyright', 5, + 'No copyright message found. ' + 'You should have a line: "Copyright [year] "') + + +def GetIndentLevel(line): + """Return the number of leading spaces in line. + + Args: + line: A string to check. + + Returns: + An integer count of leading spaces, possibly zero. + """ + indent = Match(r'^( *)\S', line) + if indent: + return len(indent.group(1)) + else: + return 0 + + +def GetHeaderGuardCPPVariable(filename): + """Returns the CPP variable that should be used as a header guard. + + Args: + filename: The name of a C++ header file. + + Returns: + The CPP variable that should be used as a header guard in the + named file. + + """ + + # Restores original filename in case that cpplint is invoked from Emacs's + # flymake. + filename = re.sub(r'_flymake\.h$', '.h', filename) + filename = re.sub(r'/\.flymake/([^/]*)$', r'/\1', filename) + # Replace 'c++' with 'cpp'. + filename = filename.replace('C++', 'cpp').replace('c++', 'cpp') + + fileinfo = FileInfo(filename) + file_path_from_root = fileinfo.RepositoryName() + if _root: + suffix = os.sep + # On Windows using directory separator will leave us with + # "bogus escape error" unless we properly escape regex. + if suffix == '\\': + suffix += '\\' + file_path_from_root = re.sub('^' + _root + suffix, '', file_path_from_root) + return re.sub(r'[^a-zA-Z0-9]', '_', file_path_from_root).upper() + '_' + + +def CheckForHeaderGuard(filename, clean_lines, error): + """Checks that the file contains a header guard. + + Logs an error if no #ifndef header guard is present. For other + headers, checks that the full pathname is used. + + Args: + filename: The name of the C++ header file. + clean_lines: A CleansedLines instance containing the file. + error: The function to call with any errors found. + """ + + # Don't check for header guards if there are error suppression + # comments somewhere in this file. + # + # Because this is silencing a warning for a nonexistent line, we + # only support the very specific NOLINT(build/header_guard) syntax, + # and not the general NOLINT or NOLINT(*) syntax. + raw_lines = clean_lines.lines_without_raw_strings + for i in raw_lines: + if Search(r'//\s*NOLINT\(build/header_guard\)', i): + return + + cppvar = GetHeaderGuardCPPVariable(filename) + + ifndef = '' + ifndef_linenum = 0 + define = '' + endif = '' + endif_linenum = 0 + for linenum, line in enumerate(raw_lines): + linesplit = line.split() + if len(linesplit) >= 2: + # find the first occurrence of #ifndef and #define, save arg + if not ifndef and linesplit[0] == '#ifndef': + # set ifndef to the header guard presented on the #ifndef line. + ifndef = linesplit[1] + ifndef_linenum = linenum + if not define and linesplit[0] == '#define': + define = linesplit[1] + # find the last occurrence of #endif, save entire line + if line.startswith('#endif'): + endif = line + endif_linenum = linenum + + if not ifndef or not define or ifndef != define: + error(filename, 0, 'build/header_guard', 5, + 'No #ifndef header guard found, suggested CPP variable is: %s' % + cppvar) + return + + # The guard should be PATH_FILE_H_, but we also allow PATH_FILE_H__ + # for backward compatibility. + if ifndef != cppvar: + error_level = 0 + if ifndef != cppvar + '_': + error_level = 5 + + ParseNolintSuppressions(filename, raw_lines[ifndef_linenum], ifndef_linenum, + error) + error(filename, ifndef_linenum, 'build/header_guard', error_level, + '#ifndef header guard has wrong style, please use: %s' % cppvar) + + # Check for "//" comments on endif line. + ParseNolintSuppressions(filename, raw_lines[endif_linenum], endif_linenum, + error) + match = Match(r'#endif\s*//\s*' + cppvar + r'(_)?\b', endif) + if match: + if match.group(1) == '_': + # Issue low severity warning for deprecated double trailing underscore + error(filename, endif_linenum, 'build/header_guard', 0, + '#endif line should be "#endif // %s"' % cppvar) + return + + # Didn't find the corresponding "//" comment. If this file does not + # contain any "//" comments at all, it could be that the compiler + # only wants "/**/" comments, look for those instead. + no_single_line_comments = True + for i in xrange(1, len(raw_lines) - 1): + line = raw_lines[i] + if Match(r'^(?:(?:\'(?:\.|[^\'])*\')|(?:"(?:\.|[^"])*")|[^\'"])*//', line): + no_single_line_comments = False + break + + if no_single_line_comments: + match = Match(r'#endif\s*/\*\s*' + cppvar + r'(_)?\s*\*/', endif) + if match: + if match.group(1) == '_': + # Low severity warning for double trailing underscore + error(filename, endif_linenum, 'build/header_guard', 0, + '#endif line should be "#endif /* %s */"' % cppvar) + return + + # Didn't find anything + error(filename, endif_linenum, 'build/header_guard', 5, + '#endif line should be "#endif // %s"' % cppvar) + + +def CheckHeaderFileIncluded(filename, include_state, error): + """Logs an error if a .cc file does not include its header.""" + + # Do not check test files + fileinfo = FileInfo(filename) + if Search(_TEST_FILE_SUFFIX, fileinfo.BaseName()): + return + + headerfile = filename[0:len(filename) - len(fileinfo.Extension())] + '.h' + if not os.path.exists(headerfile): + return + headername = FileInfo(headerfile).RepositoryName() + first_include = 0 + for section_list in include_state.include_list: + for f in section_list: + if headername in f[0] or f[0] in headername: + return + if not first_include: + first_include = f[1] + + error(filename, first_include, 'build/include', 5, + '%s should include its header file %s' % (fileinfo.RepositoryName(), + headername)) + + +def CheckForBadCharacters(filename, lines, error): + """Logs an error for each line containing bad characters. + + Two kinds of bad characters: + + 1. Unicode replacement characters: These indicate that either the file + contained invalid UTF-8 (likely) or Unicode replacement characters (which + it shouldn't). Note that it's possible for this to throw off line + numbering if the invalid UTF-8 occurred adjacent to a newline. + + 2. NUL bytes. These are problematic for some tools. + + Args: + filename: The name of the current file. + lines: An array of strings, each representing a line of the file. + error: The function to call with any errors found. + """ + for linenum, line in enumerate(lines): + if u'\ufffd' in line: + error(filename, linenum, 'readability/utf8', 5, + 'Line contains invalid UTF-8 (or Unicode replacement character).') + if '\0' in line: + error(filename, linenum, 'readability/nul', 5, 'Line contains NUL byte.') + + +def CheckForNewlineAtEOF(filename, lines, error): + """Logs an error if there is no newline char at the end of the file. + + Args: + filename: The name of the current file. + lines: An array of strings, each representing a line of the file. + error: The function to call with any errors found. + """ + + # The array lines() was created by adding two newlines to the + # original file (go figure), then splitting on \n. + # To verify that the file ends in \n, we just have to make sure the + # last-but-two element of lines() exists and is empty. + if len(lines) < 3 or lines[-2]: + error(filename, len(lines) - 2, 'whitespace/ending_newline', 5, + 'Could not find a newline character at the end of the file.') + + +def CheckForMultilineCommentsAndStrings(filename, clean_lines, linenum, error): + """Logs an error if we see /* ... */ or "..." that extend past one line. + + /* ... */ comments are legit inside macros, for one line. + Otherwise, we prefer // comments, so it's ok to warn about the + other. Likewise, it's ok for strings to extend across multiple + lines, as long as a line continuation character (backslash) + terminates each line. Although not currently prohibited by the C++ + style guide, it's ugly and unnecessary. We don't do well with either + in this lint program, so we warn about both. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # Remove all \\ (escaped backslashes) from the line. They are OK, and the + # second (escaped) slash may trigger later \" detection erroneously. + line = line.replace('\\\\', '') + + if line.count('/*') > line.count('*/'): + error(filename, linenum, 'readability/multiline_comment', 5, + 'Complex multi-line /*...*/-style comment found. ' + 'Lint may give bogus warnings. ' + 'Consider replacing these with //-style comments, ' + 'with #if 0...#endif, ' + 'or with more clearly structured multi-line comments.') + + if (line.count('"') - line.count('\\"')) % 2: + error(filename, linenum, 'readability/multiline_string', 5, + 'Multi-line string ("...") found. This lint script doesn\'t ' + 'do well with such strings, and may give bogus warnings. ' + 'Use C++11 raw strings or concatenation instead.') + + +# (non-threadsafe name, thread-safe alternative, validation pattern) +# +# The validation pattern is used to eliminate false positives such as: +# _rand(); // false positive due to substring match. +# ->rand(); // some member function rand(). +# ACMRandom rand(seed); // some variable named rand. +# ISAACRandom rand(); // another variable named rand. +# +# Basically we require the return value of these functions to be used +# in some expression context on the same line by matching on some +# operator before the function name. This eliminates constructors and +# member function calls. +_UNSAFE_FUNC_PREFIX = r'(?:[-+*/=%^&|(<]\s*|>\s+)' +_THREADING_LIST = ( + ('asctime(', 'asctime_r(', _UNSAFE_FUNC_PREFIX + r'asctime\([^)]+\)'), + ('ctime(', 'ctime_r(', _UNSAFE_FUNC_PREFIX + r'ctime\([^)]+\)'), + ('getgrgid(', 'getgrgid_r(', _UNSAFE_FUNC_PREFIX + r'getgrgid\([^)]+\)'), + ('getgrnam(', 'getgrnam_r(', _UNSAFE_FUNC_PREFIX + r'getgrnam\([^)]+\)'), + ('getlogin(', 'getlogin_r(', _UNSAFE_FUNC_PREFIX + r'getlogin\(\)'), + ('getpwnam(', 'getpwnam_r(', _UNSAFE_FUNC_PREFIX + r'getpwnam\([^)]+\)'), + ('getpwuid(', 'getpwuid_r(', _UNSAFE_FUNC_PREFIX + r'getpwuid\([^)]+\)'), + ('gmtime(', 'gmtime_r(', _UNSAFE_FUNC_PREFIX + r'gmtime\([^)]+\)'), + ('localtime(', 'localtime_r(', _UNSAFE_FUNC_PREFIX + r'localtime\([^)]+\)'), + ('rand(', 'rand_r(', _UNSAFE_FUNC_PREFIX + r'rand\(\)'), + ('strtok(', 'strtok_r(', + _UNSAFE_FUNC_PREFIX + r'strtok\([^)]+\)'), + ('ttyname(', 'ttyname_r(', _UNSAFE_FUNC_PREFIX + r'ttyname\([^)]+\)'), + ) + + +def CheckPosixThreading(filename, clean_lines, linenum, error): + """Checks for calls to thread-unsafe functions. + + Much code has been originally written without consideration of + multi-threading. Also, engineers are relying on their old experience; + they have learned posix before threading extensions were added. These + tests guide the engineers to use thread-safe functions (when using + posix directly). + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + for single_thread_func, multithread_safe_func, pattern in _THREADING_LIST: + # Additional pattern matching check to confirm that this is the + # function we are looking for + if Search(pattern, line): + error(filename, linenum, 'runtime/threadsafe_fn', 2, + 'Consider using ' + multithread_safe_func + + '...) instead of ' + single_thread_func + + '...) for improved thread safety.') + + +def CheckVlogArguments(filename, clean_lines, linenum, error): + """Checks that VLOG() is only used for defining a logging level. + + For example, VLOG(2) is correct. VLOG(INFO), VLOG(WARNING), VLOG(ERROR), and + VLOG(FATAL) are not. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + if Search(r'\bVLOG\((INFO|ERROR|WARNING|DFATAL|FATAL)\)', line): + error(filename, linenum, 'runtime/vlog', 5, + 'VLOG() should be used with numeric verbosity level. ' + 'Use LOG() if you want symbolic severity levels.') + +# Matches invalid increment: *count++, which moves pointer instead of +# incrementing a value. +_RE_PATTERN_INVALID_INCREMENT = re.compile( + r'^\s*\*\w+(\+\+|--);') + + +def CheckInvalidIncrement(filename, clean_lines, linenum, error): + """Checks for invalid increment *count++. + + For example following function: + void increment_counter(int* count) { + *count++; + } + is invalid, because it effectively does count++, moving pointer, and should + be replaced with ++*count, (*count)++ or *count += 1. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + if _RE_PATTERN_INVALID_INCREMENT.match(line): + error(filename, linenum, 'runtime/invalid_increment', 5, + 'Changing pointer instead of value (or unused value of operator*).') + + +def IsMacroDefinition(clean_lines, linenum): + if Search(r'^#define', clean_lines[linenum]): + return True + + if linenum > 0 and Search(r'\\$', clean_lines[linenum - 1]): + return True + + return False + + +def IsForwardClassDeclaration(clean_lines, linenum): + return Match(r'^\s*(\btemplate\b)*.*class\s+\w+;\s*$', clean_lines[linenum]) + + +class _BlockInfo(object): + """Stores information about a generic block of code.""" + + def __init__(self, linenum, seen_open_brace): + self.starting_linenum = linenum + self.seen_open_brace = seen_open_brace + self.open_parentheses = 0 + self.inline_asm = _NO_ASM + self.check_namespace_indentation = False + + def CheckBegin(self, filename, clean_lines, linenum, error): + """Run checks that applies to text up to the opening brace. + + This is mostly for checking the text after the class identifier + and the "{", usually where the base class is specified. For other + blocks, there isn't much to check, so we always pass. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + pass + + def CheckEnd(self, filename, clean_lines, linenum, error): + """Run checks that applies to text after the closing brace. + + This is mostly used for checking end of namespace comments. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + pass + + def IsBlockInfo(self): + """Returns true if this block is a _BlockInfo. + + This is convenient for verifying that an object is an instance of + a _BlockInfo, but not an instance of any of the derived classes. + + Returns: + True for this class, False for derived classes. + """ + return self.__class__ == _BlockInfo + + +class _ExternCInfo(_BlockInfo): + """Stores information about an 'extern "C"' block.""" + + def __init__(self, linenum): + _BlockInfo.__init__(self, linenum, True) + + +class _ClassInfo(_BlockInfo): + """Stores information about a class.""" + + def __init__(self, name, class_or_struct, clean_lines, linenum): + _BlockInfo.__init__(self, linenum, False) + self.name = name + self.is_derived = False + self.check_namespace_indentation = True + if class_or_struct == 'struct': + self.access = 'public' + self.is_struct = True + else: + self.access = 'private' + self.is_struct = False + + # Remember initial indentation level for this class. Using raw_lines here + # instead of elided to account for leading comments. + self.class_indent = GetIndentLevel(clean_lines.raw_lines[linenum]) + + # Try to find the end of the class. This will be confused by things like: + # class A { + # } *x = { ... + # + # But it's still good enough for CheckSectionSpacing. + self.last_line = 0 + depth = 0 + for i in range(linenum, clean_lines.NumLines()): + line = clean_lines.elided[i] + depth += line.count('{') - line.count('}') + if not depth: + self.last_line = i + break + + def CheckBegin(self, filename, clean_lines, linenum, error): + # Look for a bare ':' + if Search('(^|[^:]):($|[^:])', clean_lines.elided[linenum]): + self.is_derived = True + + def CheckEnd(self, filename, clean_lines, linenum, error): + # If there is a DISALLOW macro, it should appear near the end of + # the class. + seen_last_thing_in_class = False + for i in xrange(linenum - 1, self.starting_linenum, -1): + match = Search( + r'\b(DISALLOW_COPY_AND_ASSIGN|DISALLOW_IMPLICIT_CONSTRUCTORS)\(' + + self.name + r'\)', + clean_lines.elided[i]) + if match: + if seen_last_thing_in_class: + error(filename, i, 'readability/constructors', 3, + match.group(1) + ' should be the last thing in the class') + break + + if not Match(r'^\s*$', clean_lines.elided[i]): + seen_last_thing_in_class = True + + # Check that closing brace is aligned with beginning of the class. + # Only do this if the closing brace is indented by only whitespaces. + # This means we will not check single-line class definitions. + indent = Match(r'^( *)\}', clean_lines.elided[linenum]) + if indent and len(indent.group(1)) != self.class_indent: + if self.is_struct: + parent = 'struct ' + self.name + else: + parent = 'class ' + self.name + error(filename, linenum, 'whitespace/indent', 3, + 'Closing brace should be aligned with beginning of %s' % parent) + + +class _NamespaceInfo(_BlockInfo): + """Stores information about a namespace.""" + + def __init__(self, name, linenum): + _BlockInfo.__init__(self, linenum, False) + self.name = name or '' + self.check_namespace_indentation = True + + def CheckEnd(self, filename, clean_lines, linenum, error): + """Check end of namespace comments.""" + line = clean_lines.raw_lines[linenum] + + # Check how many lines is enclosed in this namespace. Don't issue + # warning for missing namespace comments if there aren't enough + # lines. However, do apply checks if there is already an end of + # namespace comment and it's incorrect. + # + # TODO(unknown): We always want to check end of namespace comments + # if a namespace is large, but sometimes we also want to apply the + # check if a short namespace contained nontrivial things (something + # other than forward declarations). There is currently no logic on + # deciding what these nontrivial things are, so this check is + # triggered by namespace size only, which works most of the time. + if (linenum - self.starting_linenum < 10 + and not Match(r'^\s*};*\s*(//|/\*).*\bnamespace\b', line)): + return + + # Look for matching comment at end of namespace. + # + # Note that we accept C style "/* */" comments for terminating + # namespaces, so that code that terminate namespaces inside + # preprocessor macros can be cpplint clean. + # + # We also accept stuff like "// end of namespace ." with the + # period at the end. + # + # Besides these, we don't accept anything else, otherwise we might + # get false negatives when existing comment is a substring of the + # expected namespace. + if self.name: + # Named namespace + if not Match((r'^\s*};*\s*(//|/\*).*\bnamespace\s+' + + re.escape(self.name) + r'[\*/\.\\\s]*$'), + line): + error(filename, linenum, 'readability/namespace', 5, + 'Namespace should be terminated with "// namespace %s"' % + self.name) + else: + # Anonymous namespace + if not Match(r'^\s*};*\s*(//|/\*).*\bnamespace[\*/\.\\\s]*$', line): + # If "// namespace anonymous" or "// anonymous namespace (more text)", + # mention "// anonymous namespace" as an acceptable form + if Match(r'^\s*}.*\b(namespace anonymous|anonymous namespace)\b', line): + error(filename, linenum, 'readability/namespace', 5, + 'Anonymous namespace should be terminated with "// namespace"' + ' or "// anonymous namespace"') + else: + error(filename, linenum, 'readability/namespace', 5, + 'Anonymous namespace should be terminated with "// namespace"') + + +class _PreprocessorInfo(object): + """Stores checkpoints of nesting stacks when #if/#else is seen.""" + + def __init__(self, stack_before_if): + # The entire nesting stack before #if + self.stack_before_if = stack_before_if + + # The entire nesting stack up to #else + self.stack_before_else = [] + + # Whether we have already seen #else or #elif + self.seen_else = False + + +class NestingState(object): + """Holds states related to parsing braces.""" + + def __init__(self): + # Stack for tracking all braces. An object is pushed whenever we + # see a "{", and popped when we see a "}". Only 3 types of + # objects are possible: + # - _ClassInfo: a class or struct. + # - _NamespaceInfo: a namespace. + # - _BlockInfo: some other type of block. + self.stack = [] + + # Top of the previous stack before each Update(). + # + # Because the nesting_stack is updated at the end of each line, we + # had to do some convoluted checks to find out what is the current + # scope at the beginning of the line. This check is simplified by + # saving the previous top of nesting stack. + # + # We could save the full stack, but we only need the top. Copying + # the full nesting stack would slow down cpplint by ~10%. + self.previous_stack_top = [] + + # Stack of _PreprocessorInfo objects. + self.pp_stack = [] + + def SeenOpenBrace(self): + """Check if we have seen the opening brace for the innermost block. + + Returns: + True if we have seen the opening brace, False if the innermost + block is still expecting an opening brace. + """ + return (not self.stack) or self.stack[-1].seen_open_brace + + def InNamespaceBody(self): + """Check if we are currently one level inside a namespace body. + + Returns: + True if top of the stack is a namespace block, False otherwise. + """ + return self.stack and isinstance(self.stack[-1], _NamespaceInfo) + + def InExternC(self): + """Check if we are currently one level inside an 'extern "C"' block. + + Returns: + True if top of the stack is an extern block, False otherwise. + """ + return self.stack and isinstance(self.stack[-1], _ExternCInfo) + + def InClassDeclaration(self): + """Check if we are currently one level inside a class or struct declaration. + + Returns: + True if top of the stack is a class/struct, False otherwise. + """ + return self.stack and isinstance(self.stack[-1], _ClassInfo) + + def InAsmBlock(self): + """Check if we are currently one level inside an inline ASM block. + + Returns: + True if the top of the stack is a block containing inline ASM. + """ + return self.stack and self.stack[-1].inline_asm != _NO_ASM + + def InTemplateArgumentList(self, clean_lines, linenum, pos): + """Check if current position is inside template argument list. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + pos: position just after the suspected template argument. + Returns: + True if (linenum, pos) is inside template arguments. + """ + while linenum < clean_lines.NumLines(): + # Find the earliest character that might indicate a template argument + line = clean_lines.elided[linenum] + match = Match(r'^[^{};=\[\]\.<>]*(.)', line[pos:]) + if not match: + linenum += 1 + pos = 0 + continue + token = match.group(1) + pos += len(match.group(0)) + + # These things do not look like template argument list: + # class Suspect { + # class Suspect x; } + if token in ('{', '}', ';'): return False + + # These things look like template argument list: + # template + # template + # template + # template + if token in ('>', '=', '[', ']', '.'): return True + + # Check if token is an unmatched '<'. + # If not, move on to the next character. + if token != '<': + pos += 1 + if pos >= len(line): + linenum += 1 + pos = 0 + continue + + # We can't be sure if we just find a single '<', and need to + # find the matching '>'. + (_, end_line, end_pos) = CloseExpression(clean_lines, linenum, pos - 1) + if end_pos < 0: + # Not sure if template argument list or syntax error in file + return False + linenum = end_line + pos = end_pos + return False + + def UpdatePreprocessor(self, line): + """Update preprocessor stack. + + We need to handle preprocessors due to classes like this: + #ifdef SWIG + struct ResultDetailsPageElementExtensionPoint { + #else + struct ResultDetailsPageElementExtensionPoint : public Extension { + #endif + + We make the following assumptions (good enough for most files): + - Preprocessor condition evaluates to true from #if up to first + #else/#elif/#endif. + + - Preprocessor condition evaluates to false from #else/#elif up + to #endif. We still perform lint checks on these lines, but + these do not affect nesting stack. + + Args: + line: current line to check. + """ + if Match(r'^\s*#\s*(if|ifdef|ifndef)\b', line): + # Beginning of #if block, save the nesting stack here. The saved + # stack will allow us to restore the parsing state in the #else case. + self.pp_stack.append(_PreprocessorInfo(copy.deepcopy(self.stack))) + elif Match(r'^\s*#\s*(else|elif)\b', line): + # Beginning of #else block + if self.pp_stack: + if not self.pp_stack[-1].seen_else: + # This is the first #else or #elif block. Remember the + # whole nesting stack up to this point. This is what we + # keep after the #endif. + self.pp_stack[-1].seen_else = True + self.pp_stack[-1].stack_before_else = copy.deepcopy(self.stack) + + # Restore the stack to how it was before the #if + self.stack = copy.deepcopy(self.pp_stack[-1].stack_before_if) + else: + # TODO(unknown): unexpected #else, issue warning? + pass + elif Match(r'^\s*#\s*endif\b', line): + # End of #if or #else blocks. + if self.pp_stack: + # If we saw an #else, we will need to restore the nesting + # stack to its former state before the #else, otherwise we + # will just continue from where we left off. + if self.pp_stack[-1].seen_else: + # Here we can just use a shallow copy since we are the last + # reference to it. + self.stack = self.pp_stack[-1].stack_before_else + # Drop the corresponding #if + self.pp_stack.pop() + else: + # TODO(unknown): unexpected #endif, issue warning? + pass + + # TODO(unknown): Update() is too long, but we will refactor later. + def Update(self, filename, clean_lines, linenum, error): + """Update nesting state with current line. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # Remember top of the previous nesting stack. + # + # The stack is always pushed/popped and not modified in place, so + # we can just do a shallow copy instead of copy.deepcopy. Using + # deepcopy would slow down cpplint by ~28%. + if self.stack: + self.previous_stack_top = self.stack[-1] + else: + self.previous_stack_top = None + + # Update pp_stack + self.UpdatePreprocessor(line) + + # Count parentheses. This is to avoid adding struct arguments to + # the nesting stack. + if self.stack: + inner_block = self.stack[-1] + depth_change = line.count('(') - line.count(')') + inner_block.open_parentheses += depth_change + + # Also check if we are starting or ending an inline assembly block. + if inner_block.inline_asm in (_NO_ASM, _END_ASM): + if (depth_change != 0 and + inner_block.open_parentheses == 1 and + _MATCH_ASM.match(line)): + # Enter assembly block + inner_block.inline_asm = _INSIDE_ASM + else: + # Not entering assembly block. If previous line was _END_ASM, + # we will now shift to _NO_ASM state. + inner_block.inline_asm = _NO_ASM + elif (inner_block.inline_asm == _INSIDE_ASM and + inner_block.open_parentheses == 0): + # Exit assembly block + inner_block.inline_asm = _END_ASM + + # Consume namespace declaration at the beginning of the line. Do + # this in a loop so that we catch same line declarations like this: + # namespace proto2 { namespace bridge { class MessageSet; } } + while True: + # Match start of namespace. The "\b\s*" below catches namespace + # declarations even if it weren't followed by a whitespace, this + # is so that we don't confuse our namespace checker. The + # missing spaces will be flagged by CheckSpacing. + namespace_decl_match = Match(r'^\s*namespace\b\s*([:\w]+)?(.*)$', line) + if not namespace_decl_match: + break + + new_namespace = _NamespaceInfo(namespace_decl_match.group(1), linenum) + self.stack.append(new_namespace) + + line = namespace_decl_match.group(2) + if line.find('{') != -1: + new_namespace.seen_open_brace = True + line = line[line.find('{') + 1:] + + # Look for a class declaration in whatever is left of the line + # after parsing namespaces. The regexp accounts for decorated classes + # such as in: + # class LOCKABLE API Object { + # }; + class_decl_match = Match( + r'^(\s*(?:template\s*<[\w\s<>,:]*>\s*)?' + r'(class|struct)\s+(?:[A-Z_]+\s+)*(\w+(?:::\w+)*))' + r'(.*)$', line) + if (class_decl_match and + (not self.stack or self.stack[-1].open_parentheses == 0)): + # We do not want to accept classes that are actually template arguments: + # template , + # template class Ignore3> + # void Function() {}; + # + # To avoid template argument cases, we scan forward and look for + # an unmatched '>'. If we see one, assume we are inside a + # template argument list. + end_declaration = len(class_decl_match.group(1)) + if not self.InTemplateArgumentList(clean_lines, linenum, end_declaration): + self.stack.append(_ClassInfo( + class_decl_match.group(3), class_decl_match.group(2), + clean_lines, linenum)) + line = class_decl_match.group(4) + + # If we have not yet seen the opening brace for the innermost block, + # run checks here. + if not self.SeenOpenBrace(): + self.stack[-1].CheckBegin(filename, clean_lines, linenum, error) + + # Update access control if we are inside a class/struct + if self.stack and isinstance(self.stack[-1], _ClassInfo): + classinfo = self.stack[-1] + access_match = Match( + r'^(.*)\b(public|private|protected|signals)(\s+(?:slots\s*)?)?' + r':(?:[^:]|$)', + line) + if access_match: + classinfo.access = access_match.group(2) + + # Check that access keywords are indented +1 space. Skip this + # check if the keywords are not preceded by whitespaces. + indent = access_match.group(1) + if (len(indent) != classinfo.class_indent + 1 and + Match(r'^\s*$', indent)): + if classinfo.is_struct: + parent = 'struct ' + classinfo.name + else: + parent = 'class ' + classinfo.name + slots = '' + if access_match.group(3): + slots = access_match.group(3) + error(filename, linenum, 'whitespace/indent', 3, + '%s%s: should be indented +1 space inside %s' % ( + access_match.group(2), slots, parent)) + + # Consume braces or semicolons from what's left of the line + while True: + # Match first brace, semicolon, or closed parenthesis. + matched = Match(r'^[^{;)}]*([{;)}])(.*)$', line) + if not matched: + break + + token = matched.group(1) + if token == '{': + # If namespace or class hasn't seen a opening brace yet, mark + # namespace/class head as complete. Push a new block onto the + # stack otherwise. + if not self.SeenOpenBrace(): + self.stack[-1].seen_open_brace = True + elif Match(r'^extern\s*"[^"]*"\s*\{', line): + self.stack.append(_ExternCInfo(linenum)) + else: + self.stack.append(_BlockInfo(linenum, True)) + if _MATCH_ASM.match(line): + self.stack[-1].inline_asm = _BLOCK_ASM + + elif token == ';' or token == ')': + # If we haven't seen an opening brace yet, but we already saw + # a semicolon, this is probably a forward declaration. Pop + # the stack for these. + # + # Similarly, if we haven't seen an opening brace yet, but we + # already saw a closing parenthesis, then these are probably + # function arguments with extra "class" or "struct" keywords. + # Also pop these stack for these. + if not self.SeenOpenBrace(): + self.stack.pop() + else: # token == '}' + # Perform end of block checks and pop the stack. + if self.stack: + self.stack[-1].CheckEnd(filename, clean_lines, linenum, error) + self.stack.pop() + line = matched.group(2) + + def InnermostClass(self): + """Get class info on the top of the stack. + + Returns: + A _ClassInfo object if we are inside a class, or None otherwise. + """ + for i in range(len(self.stack), 0, -1): + classinfo = self.stack[i - 1] + if isinstance(classinfo, _ClassInfo): + return classinfo + return None + + def CheckCompletedBlocks(self, filename, error): + """Checks that all classes and namespaces have been completely parsed. + + Call this when all lines in a file have been processed. + Args: + filename: The name of the current file. + error: The function to call with any errors found. + """ + # Note: This test can result in false positives if #ifdef constructs + # get in the way of brace matching. See the testBuildClass test in + # cpplint_unittest.py for an example of this. + for obj in self.stack: + if isinstance(obj, _ClassInfo): + error(filename, obj.starting_linenum, 'build/class', 5, + 'Failed to find complete declaration of class %s' % + obj.name) + elif isinstance(obj, _NamespaceInfo): + error(filename, obj.starting_linenum, 'build/namespaces', 5, + 'Failed to find complete declaration of namespace %s' % + obj.name) + + +def CheckForNonStandardConstructs(filename, clean_lines, linenum, + nesting_state, error): + r"""Logs an error if we see certain non-ANSI constructs ignored by gcc-2. + + Complain about several constructs which gcc-2 accepts, but which are + not standard C++. Warning about these in lint is one way to ease the + transition to new compilers. + - put storage class first (e.g. "static const" instead of "const static"). + - "%lld" instead of %qd" in printf-type functions. + - "%1$d" is non-standard in printf-type functions. + - "\%" is an undefined character escape sequence. + - text after #endif is not allowed. + - invalid inner-style forward declaration. + - >? and ?= and )\?=?\s*(\w+|[+-]?\d+)(\.\d*)?', + line): + error(filename, linenum, 'build/deprecated', 3, + '>? and ))?' + # r'\s*const\s*' + type_name + '\s*&\s*\w+\s*;' + error(filename, linenum, 'runtime/member_string_references', 2, + 'const string& members are dangerous. It is much better to use ' + 'alternatives, such as pointers or simple constants.') + + # Everything else in this function operates on class declarations. + # Return early if the top of the nesting stack is not a class, or if + # the class head is not completed yet. + classinfo = nesting_state.InnermostClass() + if not classinfo or not classinfo.seen_open_brace: + return + + # The class may have been declared with namespace or classname qualifiers. + # The constructor and destructor will not have those qualifiers. + base_classname = classinfo.name.split('::')[-1] + + # Look for single-argument constructors that aren't marked explicit. + # Technically a valid construct, but against style. + explicit_constructor_match = Match( + r'\s+(?:(?:inline|constexpr)\s+)*(explicit\s+)?' + r'(?:(?:inline|constexpr)\s+)*%s\s*' + r'\(((?:[^()]|\([^()]*\))*)\)' + % re.escape(base_classname), + line) + + if explicit_constructor_match: + is_marked_explicit = explicit_constructor_match.group(1) + + if not explicit_constructor_match.group(2): + constructor_args = [] + else: + constructor_args = explicit_constructor_match.group(2).split(',') + + # collapse arguments so that commas in template parameter lists and function + # argument parameter lists don't split arguments in two + i = 0 + while i < len(constructor_args): + constructor_arg = constructor_args[i] + while (constructor_arg.count('<') > constructor_arg.count('>') or + constructor_arg.count('(') > constructor_arg.count(')')): + constructor_arg += ',' + constructor_args[i + 1] + del constructor_args[i + 1] + constructor_args[i] = constructor_arg + i += 1 + + defaulted_args = [arg for arg in constructor_args if '=' in arg] + noarg_constructor = (not constructor_args or # empty arg list + # 'void' arg specifier + (len(constructor_args) == 1 and + constructor_args[0].strip() == 'void')) + onearg_constructor = ((len(constructor_args) == 1 and # exactly one arg + not noarg_constructor) or + # all but at most one arg defaulted + (len(constructor_args) >= 1 and + not noarg_constructor and + len(defaulted_args) >= len(constructor_args) - 1)) + initializer_list_constructor = bool( + onearg_constructor and + Search(r'\bstd\s*::\s*initializer_list\b', constructor_args[0])) + copy_constructor = bool( + onearg_constructor and + Match(r'(const\s+)?%s(\s*<[^>]*>)?(\s+const)?\s*(?:<\w+>\s*)?&' + % re.escape(base_classname), constructor_args[0].strip())) + + if (not is_marked_explicit and + onearg_constructor and + not initializer_list_constructor and + not copy_constructor): + if defaulted_args: + error(filename, linenum, 'runtime/explicit', 5, + 'Constructors callable with one argument ' + 'should be marked explicit.') + else: + error(filename, linenum, 'runtime/explicit', 5, + 'Single-parameter constructors should be marked explicit.') + elif is_marked_explicit and not onearg_constructor: + if noarg_constructor: + error(filename, linenum, 'runtime/explicit', 5, + 'Zero-parameter constructors should not be marked explicit.') + + +def CheckSpacingForFunctionCall(filename, clean_lines, linenum, error): + """Checks for the correctness of various spacing around function calls. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # Since function calls often occur inside if/for/while/switch + # expressions - which have their own, more liberal conventions - we + # first see if we should be looking inside such an expression for a + # function call, to which we can apply more strict standards. + fncall = line # if there's no control flow construct, look at whole line + for pattern in (r'\bif\s*\((.*)\)\s*{', + r'\bfor\s*\((.*)\)\s*{', + r'\bwhile\s*\((.*)\)\s*[{;]', + r'\bswitch\s*\((.*)\)\s*{'): + match = Search(pattern, line) + if match: + fncall = match.group(1) # look inside the parens for function calls + break + + # Except in if/for/while/switch, there should never be space + # immediately inside parens (eg "f( 3, 4 )"). We make an exception + # for nested parens ( (a+b) + c ). Likewise, there should never be + # a space before a ( when it's a function argument. I assume it's a + # function argument when the char before the whitespace is legal in + # a function name (alnum + _) and we're not starting a macro. Also ignore + # pointers and references to arrays and functions coz they're too tricky: + # we use a very simple way to recognize these: + # " (something)(maybe-something)" or + # " (something)(maybe-something," or + # " (something)[something]" + # Note that we assume the contents of [] to be short enough that + # they'll never need to wrap. + if ( # Ignore control structures. + not Search(r'\b(if|for|while|switch|return|new|delete|catch|sizeof)\b', + fncall) and + # Ignore pointers/references to functions. + not Search(r' \([^)]+\)\([^)]*(\)|,$)', fncall) and + # Ignore pointers/references to arrays. + not Search(r' \([^)]+\)\[[^\]]+\]', fncall)): + if Search(r'\w\s*\(\s(?!\s*\\$)', fncall): # a ( used for a fn call + error(filename, linenum, 'whitespace/parens', 4, + 'Extra space after ( in function call') + elif Search(r'\(\s+(?!(\s*\\)|\()', fncall): + error(filename, linenum, 'whitespace/parens', 2, + 'Extra space after (') + if (Search(r'\w\s+\(', fncall) and + not Search(r'_{0,2}asm_{0,2}\s+_{0,2}volatile_{0,2}\s+\(', fncall) and + not Search(r'#\s*define|typedef|using\s+\w+\s*=', fncall) and + not Search(r'\w\s+\((\w+::)*\*\w+\)\(', fncall) and + not Search(r'\bcase\s+\(', fncall)): + # TODO(unknown): Space after an operator function seem to be a common + # error, silence those for now by restricting them to highest verbosity. + if Search(r'\boperator_*\b', line): + error(filename, linenum, 'whitespace/parens', 0, + 'Extra space before ( in function call') + else: + error(filename, linenum, 'whitespace/parens', 4, + 'Extra space before ( in function call') + # If the ) is followed only by a newline or a { + newline, assume it's + # part of a control statement (if/while/etc), and don't complain + if Search(r'[^)]\s+\)\s*[^{\s]', fncall): + # If the closing parenthesis is preceded by only whitespaces, + # try to give a more descriptive error message. + if Search(r'^\s+\)', fncall): + error(filename, linenum, 'whitespace/parens', 2, + 'Closing ) should be moved to the previous line') + else: + error(filename, linenum, 'whitespace/parens', 2, + 'Extra space before )') + + +def IsBlankLine(line): + """Returns true if the given line is blank. + + We consider a line to be blank if the line is empty or consists of + only white spaces. + + Args: + line: A line of a string. + + Returns: + True, if the given line is blank. + """ + return not line or line.isspace() + + +def CheckForNamespaceIndentation(filename, nesting_state, clean_lines, line, + error): + is_namespace_indent_item = ( + len(nesting_state.stack) > 1 and + nesting_state.stack[-1].check_namespace_indentation and + isinstance(nesting_state.previous_stack_top, _NamespaceInfo) and + nesting_state.previous_stack_top == nesting_state.stack[-2]) + + if ShouldCheckNamespaceIndentation(nesting_state, is_namespace_indent_item, + clean_lines.elided, line): + CheckItemIndentationInNamespace(filename, clean_lines.elided, + line, error) + + +def CheckForFunctionLengths(filename, clean_lines, linenum, + function_state, error): + """Reports for long function bodies. + + For an overview why this is done, see: + https://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Write_Short_Functions + + Uses a simplistic algorithm assuming other style guidelines + (especially spacing) are followed. + Only checks unindented functions, so class members are unchecked. + Trivial bodies are unchecked, so constructors with huge initializer lists + may be missed. + Blank/comment lines are not counted so as to avoid encouraging the removal + of vertical space and comments just to get through a lint check. + NOLINT *on the last line of a function* disables this check. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + function_state: Current function name and lines in body so far. + error: The function to call with any errors found. + """ + lines = clean_lines.lines + line = lines[linenum] + joined_line = '' + + starting_func = False + regexp = r'(\w(\w|::|\*|\&|\s)*)\(' # decls * & space::name( ... + match_result = Match(regexp, line) + if match_result: + # If the name is all caps and underscores, figure it's a macro and + # ignore it, unless it's TEST or TEST_F. + function_name = match_result.group(1).split()[-1] + if function_name == 'TEST' or function_name == 'TEST_F' or ( + not Match(r'[A-Z_]+$', function_name)): + starting_func = True + + if starting_func: + body_found = False + for start_linenum in xrange(linenum, clean_lines.NumLines()): + start_line = lines[start_linenum] + joined_line += ' ' + start_line.lstrip() + if Search(r'(;|})', start_line): # Declarations and trivial functions + body_found = True + break # ... ignore + elif Search(r'{', start_line): + body_found = True + function = Search(r'((\w|:)*)\(', line).group(1) + if Match(r'TEST', function): # Handle TEST... macros + parameter_regexp = Search(r'(\(.*\))', joined_line) + if parameter_regexp: # Ignore bad syntax + function += parameter_regexp.group(1) + else: + function += '()' + function_state.Begin(function) + break + if not body_found: + # No body for the function (or evidence of a non-function) was found. + error(filename, linenum, 'readability/fn_size', 5, + 'Lint failed to find start of function body.') + elif Match(r'^\}\s*$', line): # function end + function_state.Check(error, filename, linenum) + function_state.End() + elif not Match(r'^\s*$', line): + function_state.Count() # Count non-blank/non-comment lines. + + +_RE_PATTERN_TODO = re.compile(r'^//(\s*)TODO(\(.+?\))?:?(\s|$)?') + + +def CheckComment(line, filename, linenum, next_line_start, error): + """Checks for common mistakes in comments. + + Args: + line: The line in question. + filename: The name of the current file. + linenum: The number of the line to check. + next_line_start: The first non-whitespace column of the next line. + error: The function to call with any errors found. + """ + commentpos = line.find('//') + if commentpos != -1: + # Check if the // may be in quotes. If so, ignore it + if re.sub(r'\\.', '', line[0:commentpos]).count('"') % 2 == 0: + # Allow one space for new scopes, two spaces otherwise: + if (not (Match(r'^.*{ *//', line) and next_line_start == commentpos) and + ((commentpos >= 1 and + line[commentpos-1] not in string.whitespace) or + (commentpos >= 2 and + line[commentpos-2] not in string.whitespace))): + error(filename, linenum, 'whitespace/comments', 2, + 'At least two spaces is best between code and comments') + + # Checks for common mistakes in TODO comments. + comment = line[commentpos:] + match = _RE_PATTERN_TODO.match(comment) + if match: + # One whitespace is correct; zero whitespace is handled elsewhere. + leading_whitespace = match.group(1) + if len(leading_whitespace) > 1: + error(filename, linenum, 'whitespace/todo', 2, + 'Too many spaces before TODO') + + username = match.group(2) + if not username: + error(filename, linenum, 'readability/todo', 2, + 'Missing username in TODO; it should look like ' + '"// TODO(my_username): Stuff."') + + middle_whitespace = match.group(3) + # Comparisons made explicit for correctness -- pylint: disable=g-explicit-bool-comparison + if middle_whitespace != ' ' and middle_whitespace != '': + error(filename, linenum, 'whitespace/todo', 2, + 'TODO(my_username) should be followed by a space') + + # If the comment contains an alphanumeric character, there + # should be a space somewhere between it and the // unless + # it's a /// or //! Doxygen comment. + if (Match(r'//[^ ]*\w', comment) and + not Match(r'(///|//\!)(\s+|$)', comment)): + error(filename, linenum, 'whitespace/comments', 4, + 'Should have a space between // and comment') + + +def CheckSpacing(filename, clean_lines, linenum, nesting_state, error): + """Checks for the correctness of various spacing issues in the code. + + Things we check for: spaces around operators, spaces after + if/for/while/switch, no spaces around parens in function calls, two + spaces between code and comment, don't start a block with a blank + line, don't end a function with a blank line, don't add a blank line + after public/protected/private, don't have too many blank lines in a row. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + nesting_state: A NestingState instance which maintains information about + the current stack of nested blocks being parsed. + error: The function to call with any errors found. + """ + + # Don't use "elided" lines here, otherwise we can't check commented lines. + # Don't want to use "raw" either, because we don't want to check inside C++11 + # raw strings, + raw = clean_lines.lines_without_raw_strings + line = raw[linenum] + + # Before nixing comments, check if the line is blank for no good + # reason. This includes the first line after a block is opened, and + # blank lines at the end of a function (ie, right before a line like '}' + # + # Skip all the blank line checks if we are immediately inside a + # namespace body. In other words, don't issue blank line warnings + # for this block: + # namespace { + # + # } + # + # A warning about missing end of namespace comments will be issued instead. + # + # Also skip blank line checks for 'extern "C"' blocks, which are formatted + # like namespaces. + if (IsBlankLine(line) and + not nesting_state.InNamespaceBody() and + not nesting_state.InExternC()): + elided = clean_lines.elided + prev_line = elided[linenum - 1] + prevbrace = prev_line.rfind('{') + # TODO(unknown): Don't complain if line before blank line, and line after, + # both start with alnums and are indented the same amount. + # This ignores whitespace at the start of a namespace block + # because those are not usually indented. + if prevbrace != -1 and prev_line[prevbrace:].find('}') == -1: + # OK, we have a blank line at the start of a code block. Before we + # complain, we check if it is an exception to the rule: The previous + # non-empty line has the parameters of a function header that are indented + # 4 spaces (because they did not fit in a 80 column line when placed on + # the same line as the function name). We also check for the case where + # the previous line is indented 6 spaces, which may happen when the + # initializers of a constructor do not fit into a 80 column line. + exception = False + if Match(r' {6}\w', prev_line): # Initializer list? + # We are looking for the opening column of initializer list, which + # should be indented 4 spaces to cause 6 space indentation afterwards. + search_position = linenum-2 + while (search_position >= 0 + and Match(r' {6}\w', elided[search_position])): + search_position -= 1 + exception = (search_position >= 0 + and elided[search_position][:5] == ' :') + else: + # Search for the function arguments or an initializer list. We use a + # simple heuristic here: If the line is indented 4 spaces; and we have a + # closing paren, without the opening paren, followed by an opening brace + # or colon (for initializer lists) we assume that it is the last line of + # a function header. If we have a colon indented 4 spaces, it is an + # initializer list. + exception = (Match(r' {4}\w[^\(]*\)\s*(const\s*)?(\{\s*$|:)', + prev_line) + or Match(r' {4}:', prev_line)) + + if not exception: + error(filename, linenum, 'whitespace/blank_line', 2, + 'Redundant blank line at the start of a code block ' + 'should be deleted.') + # Ignore blank lines at the end of a block in a long if-else + # chain, like this: + # if (condition1) { + # // Something followed by a blank line + # + # } else if (condition2) { + # // Something else + # } + if linenum + 1 < clean_lines.NumLines(): + next_line = raw[linenum + 1] + if (next_line + and Match(r'\s*}', next_line) + and next_line.find('} else ') == -1): + error(filename, linenum, 'whitespace/blank_line', 3, + 'Redundant blank line at the end of a code block ' + 'should be deleted.') + + matched = Match(r'\s*(public|protected|private):', prev_line) + if matched: + error(filename, linenum, 'whitespace/blank_line', 3, + 'Do not leave a blank line after "%s:"' % matched.group(1)) + + # Next, check comments + next_line_start = 0 + if linenum + 1 < clean_lines.NumLines(): + next_line = raw[linenum + 1] + next_line_start = len(next_line) - len(next_line.lstrip()) + CheckComment(line, filename, linenum, next_line_start, error) + + # get rid of comments and strings + line = clean_lines.elided[linenum] + + # You shouldn't have spaces before your brackets, except maybe after + # 'delete []' or 'return []() {};' + if Search(r'\w\s+\[', line) and not Search(r'(?:delete|return)\s+\[', line): + error(filename, linenum, 'whitespace/braces', 5, + 'Extra space before [') + + # In range-based for, we wanted spaces before and after the colon, but + # not around "::" tokens that might appear. + if (Search(r'for *\(.*[^:]:[^: ]', line) or + Search(r'for *\(.*[^: ]:[^:]', line)): + error(filename, linenum, 'whitespace/forcolon', 2, + 'Missing space around colon in range-based for loop') + + +def CheckOperatorSpacing(filename, clean_lines, linenum, error): + """Checks for horizontal spacing around operators. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # Don't try to do spacing checks for operator methods. Do this by + # replacing the troublesome characters with something else, + # preserving column position for all other characters. + # + # The replacement is done repeatedly to avoid false positives from + # operators that call operators. + while True: + match = Match(r'^(.*\boperator\b)(\S+)(\s*\(.*)$', line) + if match: + line = match.group(1) + ('_' * len(match.group(2))) + match.group(3) + else: + break + + # We allow no-spaces around = within an if: "if ( (a=Foo()) == 0 )". + # Otherwise not. Note we only check for non-spaces on *both* sides; + # sometimes people put non-spaces on one side when aligning ='s among + # many lines (not that this is behavior that I approve of...) + if ((Search(r'[\w.]=', line) or + Search(r'=[\w.]', line)) + and not Search(r'\b(if|while|for) ', line) + # Operators taken from [lex.operators] in C++11 standard. + and not Search(r'(>=|<=|==|!=|&=|\^=|\|=|\+=|\*=|\/=|\%=)', line) + and not Search(r'operator=', line)): + error(filename, linenum, 'whitespace/operators', 4, + 'Missing spaces around =') + + # It's ok not to have spaces around binary operators like + - * /, but if + # there's too little whitespace, we get concerned. It's hard to tell, + # though, so we punt on this one for now. TODO. + + # You should always have whitespace around binary operators. + # + # Check <= and >= first to avoid false positives with < and >, then + # check non-include lines for spacing around < and >. + # + # If the operator is followed by a comma, assume it's be used in a + # macro context and don't do any checks. This avoids false + # positives. + # + # Note that && is not included here. This is because there are too + # many false positives due to RValue references. + match = Search(r'[^<>=!\s](==|!=|<=|>=|\|\|)[^<>=!\s,;\)]', line) + if match: + error(filename, linenum, 'whitespace/operators', 3, + 'Missing spaces around %s' % match.group(1)) + elif not Match(r'#.*include', line): + # Look for < that is not surrounded by spaces. This is only + # triggered if both sides are missing spaces, even though + # technically should should flag if at least one side is missing a + # space. This is done to avoid some false positives with shifts. + match = Match(r'^(.*[^\s<])<[^\s=<,]', line) + if match: + (_, _, end_pos) = CloseExpression( + clean_lines, linenum, len(match.group(1))) + if end_pos <= -1: + error(filename, linenum, 'whitespace/operators', 3, + 'Missing spaces around <') + + # Look for > that is not surrounded by spaces. Similar to the + # above, we only trigger if both sides are missing spaces to avoid + # false positives with shifts. + match = Match(r'^(.*[^-\s>])>[^\s=>,]', line) + if match: + (_, _, start_pos) = ReverseCloseExpression( + clean_lines, linenum, len(match.group(1))) + if start_pos <= -1: + error(filename, linenum, 'whitespace/operators', 3, + 'Missing spaces around >') + + # We allow no-spaces around << when used like this: 10<<20, but + # not otherwise (particularly, not when used as streams) + # + # We also allow operators following an opening parenthesis, since + # those tend to be macros that deal with operators. + match = Search(r'(operator|[^\s(<])(?:L|UL|LL|ULL|l|ul|ll|ull)?<<([^\s,=<])', line) + if (match and not (match.group(1).isdigit() and match.group(2).isdigit()) and + not (match.group(1) == 'operator' and match.group(2) == ';')): + error(filename, linenum, 'whitespace/operators', 3, + 'Missing spaces around <<') + + # We allow no-spaces around >> for almost anything. This is because + # C++11 allows ">>" to close nested templates, which accounts for + # most cases when ">>" is not followed by a space. + # + # We still warn on ">>" followed by alpha character, because that is + # likely due to ">>" being used for right shifts, e.g.: + # value >> alpha + # + # When ">>" is used to close templates, the alphanumeric letter that + # follows would be part of an identifier, and there should still be + # a space separating the template type and the identifier. + # type> alpha + match = Search(r'>>[a-zA-Z_]', line) + if match: + error(filename, linenum, 'whitespace/operators', 3, + 'Missing spaces around >>') + + # There shouldn't be space around unary operators + match = Search(r'(!\s|~\s|[\s]--[\s;]|[\s]\+\+[\s;])', line) + if match: + error(filename, linenum, 'whitespace/operators', 4, + 'Extra space for operator %s' % match.group(1)) + + +def CheckParenthesisSpacing(filename, clean_lines, linenum, error): + """Checks for horizontal spacing around parentheses. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # No spaces after an if, while, switch, or for + match = Search(r' (if\(|for\(|while\(|switch\()', line) + if match: + error(filename, linenum, 'whitespace/parens', 5, + 'Missing space before ( in %s' % match.group(1)) + + # For if/for/while/switch, the left and right parens should be + # consistent about how many spaces are inside the parens, and + # there should either be zero or one spaces inside the parens. + # We don't want: "if ( foo)" or "if ( foo )". + # Exception: "for ( ; foo; bar)" and "for (foo; bar; )" are allowed. + match = Search(r'\b(if|for|while|switch)\s*' + r'\(([ ]*)(.).*[^ ]+([ ]*)\)\s*{\s*$', + line) + if match: + if len(match.group(2)) != len(match.group(4)): + if not (match.group(3) == ';' and + len(match.group(2)) == 1 + len(match.group(4)) or + not match.group(2) and Search(r'\bfor\s*\(.*; \)', line)): + error(filename, linenum, 'whitespace/parens', 5, + 'Mismatching spaces inside () in %s' % match.group(1)) + if len(match.group(2)) not in [0, 1]: + error(filename, linenum, 'whitespace/parens', 5, + 'Should have zero or one spaces inside ( and ) in %s' % + match.group(1)) + + +def CheckCommaSpacing(filename, clean_lines, linenum, error): + """Checks for horizontal spacing near commas and semicolons. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + raw = clean_lines.lines_without_raw_strings + line = clean_lines.elided[linenum] + + # You should always have a space after a comma (either as fn arg or operator) + # + # This does not apply when the non-space character following the + # comma is another comma, since the only time when that happens is + # for empty macro arguments. + # + # We run this check in two passes: first pass on elided lines to + # verify that lines contain missing whitespaces, second pass on raw + # lines to confirm that those missing whitespaces are not due to + # elided comments. + if (Search(r',[^,\s]', ReplaceAll(r'\boperator\s*,\s*\(', 'F(', line)) and + Search(r',[^,\s]', raw[linenum])): + error(filename, linenum, 'whitespace/comma', 3, + 'Missing space after ,') + + # You should always have a space after a semicolon + # except for few corner cases + # TODO(unknown): clarify if 'if (1) { return 1;}' is requires one more + # space after ; + if Search(r';[^\s};\\)/]', line): + error(filename, linenum, 'whitespace/semicolon', 3, + 'Missing space after ;') + + +def _IsType(clean_lines, nesting_state, expr): + """Check if expression looks like a type name, returns true if so. + + Args: + clean_lines: A CleansedLines instance containing the file. + nesting_state: A NestingState instance which maintains information about + the current stack of nested blocks being parsed. + expr: The expression to check. + Returns: + True, if token looks like a type. + """ + # Keep only the last token in the expression + last_word = Match(r'^.*(\b\S+)$', expr) + if last_word: + token = last_word.group(1) + else: + token = expr + + # Match native types and stdint types + if _TYPES.match(token): + return True + + # Try a bit harder to match templated types. Walk up the nesting + # stack until we find something that resembles a typename + # declaration for what we are looking for. + typename_pattern = (r'\b(?:typename|class|struct)\s+' + re.escape(token) + + r'\b') + block_index = len(nesting_state.stack) - 1 + while block_index >= 0: + if isinstance(nesting_state.stack[block_index], _NamespaceInfo): + return False + + # Found where the opening brace is. We want to scan from this + # line up to the beginning of the function, minus a few lines. + # template + # class C + # : public ... { // start scanning here + last_line = nesting_state.stack[block_index].starting_linenum + + next_block_start = 0 + if block_index > 0: + next_block_start = nesting_state.stack[block_index - 1].starting_linenum + first_line = last_line + while first_line >= next_block_start: + if clean_lines.elided[first_line].find('template') >= 0: + break + first_line -= 1 + if first_line < next_block_start: + # Didn't find any "template" keyword before reaching the next block, + # there are probably no template things to check for this block + block_index -= 1 + continue + + # Look for typename in the specified range + for i in xrange(first_line, last_line + 1, 1): + if Search(typename_pattern, clean_lines.elided[i]): + return True + block_index -= 1 + + return False + + +def CheckBracesSpacing(filename, clean_lines, linenum, nesting_state, error): + """Checks for horizontal spacing near commas. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + nesting_state: A NestingState instance which maintains information about + the current stack of nested blocks being parsed. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # Except after an opening paren, or after another opening brace (in case of + # an initializer list, for instance), you should have spaces before your + # braces when they are delimiting blocks, classes, namespaces etc. + # And since you should never have braces at the beginning of a line, + # this is an easy test. Except that braces used for initialization don't + # follow the same rule; we often don't want spaces before those. + match = Match(r'^(.*[^ ({>]){', line) + + if match: + # Try a bit harder to check for brace initialization. This + # happens in one of the following forms: + # Constructor() : initializer_list_{} { ... } + # Constructor{}.MemberFunction() + # Type variable{}; + # FunctionCall(type{}, ...); + # LastArgument(..., type{}); + # LOG(INFO) << type{} << " ..."; + # map_of_type[{...}] = ...; + # ternary = expr ? new type{} : nullptr; + # OuterTemplate{}> + # + # We check for the character following the closing brace, and + # silence the warning if it's one of those listed above, i.e. + # "{.;,)<>]:". + # + # To account for nested initializer list, we allow any number of + # closing braces up to "{;,)<". We can't simply silence the + # warning on first sight of closing brace, because that would + # cause false negatives for things that are not initializer lists. + # Silence this: But not this: + # Outer{ if (...) { + # Inner{...} if (...){ // Missing space before { + # }; } + # + # There is a false negative with this approach if people inserted + # spurious semicolons, e.g. "if (cond){};", but we will catch the + # spurious semicolon with a separate check. + leading_text = match.group(1) + (endline, endlinenum, endpos) = CloseExpression( + clean_lines, linenum, len(match.group(1))) + trailing_text = '' + if endpos > -1: + trailing_text = endline[endpos:] + for offset in xrange(endlinenum + 1, + min(endlinenum + 3, clean_lines.NumLines() - 1)): + trailing_text += clean_lines.elided[offset] + # We also suppress warnings for `uint64_t{expression}` etc., as the style + # guide recommends brace initialization for integral types to avoid + # overflow/truncation. + if (not Match(r'^[\s}]*[{.;,)<>\]:]', trailing_text) + and not _IsType(clean_lines, nesting_state, leading_text)): + error(filename, linenum, 'whitespace/braces', 5, + 'Missing space before {') + + # Make sure '} else {' has spaces. + if Search(r'}else', line): + error(filename, linenum, 'whitespace/braces', 5, + 'Missing space before else') + + # You shouldn't have a space before a semicolon at the end of the line. + # There's a special case for "for" since the style guide allows space before + # the semicolon there. + if Search(r':\s*;\s*$', line): + error(filename, linenum, 'whitespace/semicolon', 5, + 'Semicolon defining empty statement. Use {} instead.') + elif Search(r'^\s*;\s*$', line): + error(filename, linenum, 'whitespace/semicolon', 5, + 'Line contains only semicolon. If this should be an empty statement, ' + 'use {} instead.') + elif (Search(r'\s+;\s*$', line) and + not Search(r'\bfor\b', line)): + error(filename, linenum, 'whitespace/semicolon', 5, + 'Extra space before last semicolon. If this should be an empty ' + 'statement, use {} instead.') + + +def IsDecltype(clean_lines, linenum, column): + """Check if the token ending on (linenum, column) is decltype(). + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: the number of the line to check. + column: end column of the token to check. + Returns: + True if this token is decltype() expression, False otherwise. + """ + (text, _, start_col) = ReverseCloseExpression(clean_lines, linenum, column) + if start_col < 0: + return False + if Search(r'\bdecltype\s*$', text[0:start_col]): + return True + return False + + +def CheckSectionSpacing(filename, clean_lines, class_info, linenum, error): + """Checks for additional blank line issues related to sections. + + Currently the only thing checked here is blank line before protected/private. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + class_info: A _ClassInfo objects. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + # Skip checks if the class is small, where small means 25 lines or less. + # 25 lines seems like a good cutoff since that's the usual height of + # terminals, and any class that can't fit in one screen can't really + # be considered "small". + # + # Also skip checks if we are on the first line. This accounts for + # classes that look like + # class Foo { public: ... }; + # + # If we didn't find the end of the class, last_line would be zero, + # and the check will be skipped by the first condition. + if (class_info.last_line - class_info.starting_linenum <= 24 or + linenum <= class_info.starting_linenum): + return + + matched = Match(r'\s*(public|protected|private):', clean_lines.lines[linenum]) + if matched: + # Issue warning if the line before public/protected/private was + # not a blank line, but don't do this if the previous line contains + # "class" or "struct". This can happen two ways: + # - We are at the beginning of the class. + # - We are forward-declaring an inner class that is semantically + # private, but needed to be public for implementation reasons. + # Also ignores cases where the previous line ends with a backslash as can be + # common when defining classes in C macros. + prev_line = clean_lines.lines[linenum - 1] + if (not IsBlankLine(prev_line) and + not Search(r'\b(class|struct)\b', prev_line) and + not Search(r'\\$', prev_line)): + # Try a bit harder to find the beginning of the class. This is to + # account for multi-line base-specifier lists, e.g.: + # class Derived + # : public Base { + end_class_head = class_info.starting_linenum + for i in range(class_info.starting_linenum, linenum): + if Search(r'\{\s*$', clean_lines.lines[i]): + end_class_head = i + break + if end_class_head < linenum - 1: + error(filename, linenum, 'whitespace/blank_line', 3, + '"%s:" should be preceded by a blank line' % matched.group(1)) + + +def GetPreviousNonBlankLine(clean_lines, linenum): + """Return the most recent non-blank line and its line number. + + Args: + clean_lines: A CleansedLines instance containing the file contents. + linenum: The number of the line to check. + + Returns: + A tuple with two elements. The first element is the contents of the last + non-blank line before the current line, or the empty string if this is the + first non-blank line. The second is the line number of that line, or -1 + if this is the first non-blank line. + """ + + prevlinenum = linenum - 1 + while prevlinenum >= 0: + prevline = clean_lines.elided[prevlinenum] + if not IsBlankLine(prevline): # if not a blank line... + return (prevline, prevlinenum) + prevlinenum -= 1 + return ('', -1) + + +def CheckBraces(filename, clean_lines, linenum, error): + """Looks for misplaced braces (e.g. at the end of line). + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + + line = clean_lines.elided[linenum] # get rid of comments and strings + + if Match(r'\s*{\s*$', line): + # We allow an open brace to start a line in the case where someone is using + # braces in a block to explicitly create a new scope, which is commonly used + # to control the lifetime of stack-allocated variables. Braces are also + # used for brace initializers inside function calls. We don't detect this + # perfectly: we just don't complain if the last non-whitespace character on + # the previous non-blank line is ',', ';', ':', '(', '{', or '}', or if the + # previous line starts a preprocessor block. We also allow a brace on the + # following line if it is part of an array initialization and would not fit + # within the 80 character limit of the preceding line. + prevline = GetPreviousNonBlankLine(clean_lines, linenum)[0] + if (not Search(r'[,;:}{(]\s*$', prevline) and + not Match(r'\s*#', prevline) and + not (GetLineWidth(prevline) > _line_length - 2 and '[]' in prevline)): + error(filename, linenum, 'whitespace/braces', 4, + '{ should almost always be at the end of the previous line') + + # An else clause should be on the same line as the preceding closing brace. + if Match(r'\s*else\b\s*(?:if\b|\{|$)', line): + prevline = GetPreviousNonBlankLine(clean_lines, linenum)[0] + if Match(r'\s*}\s*$', prevline): + error(filename, linenum, 'whitespace/newline', 4, + 'An else should appear on the same line as the preceding }') + + # If braces come on one side of an else, they should be on both. + # However, we have to worry about "else if" that spans multiple lines! + if Search(r'else if\s*\(', line): # could be multi-line if + brace_on_left = bool(Search(r'}\s*else if\s*\(', line)) + # find the ( after the if + pos = line.find('else if') + pos = line.find('(', pos) + if pos > 0: + (endline, _, endpos) = CloseExpression(clean_lines, linenum, pos) + brace_on_right = endline[endpos:].find('{') != -1 + if brace_on_left != brace_on_right: # must be brace after if + error(filename, linenum, 'readability/braces', 5, + 'If an else has a brace on one side, it should have it on both') + elif Search(r'}\s*else[^{]*$', line) or Match(r'[^}]*else\s*{', line): + error(filename, linenum, 'readability/braces', 5, + 'If an else has a brace on one side, it should have it on both') + + # Likewise, an else should never have the else clause on the same line + if Search(r'\belse [^\s{]', line) and not Search(r'\belse if\b', line): + error(filename, linenum, 'whitespace/newline', 4, + 'Else clause should never be on same line as else (use 2 lines)') + + # In the same way, a do/while should never be on one line + if Match(r'\s*do [^\s{]', line): + error(filename, linenum, 'whitespace/newline', 4, + 'do/while clauses should not be on a single line') + + # Check single-line if/else bodies. The style guide says 'curly braces are not + # required for single-line statements'. We additionally allow multi-line, + # single statements, but we reject anything with more than one semicolon in + # it. This means that the first semicolon after the if should be at the end of + # its line, and the line after that should have an indent level equal to or + # lower than the if. We also check for ambiguous if/else nesting without + # braces. + if_else_match = Search(r'\b(if\s*\(|else\b)', line) + if if_else_match and not Match(r'\s*#', line): + if_indent = GetIndentLevel(line) + endline, endlinenum, endpos = line, linenum, if_else_match.end() + if_match = Search(r'\bif\s*\(', line) + if if_match: + # This could be a multiline if condition, so find the end first. + pos = if_match.end() - 1 + (endline, endlinenum, endpos) = CloseExpression(clean_lines, linenum, pos) + # Check for an opening brace, either directly after the if or on the next + # line. If found, this isn't a single-statement conditional. + if (not Match(r'\s*{', endline[endpos:]) + and not (Match(r'\s*$', endline[endpos:]) + and endlinenum < (len(clean_lines.elided) - 1) + and Match(r'\s*{', clean_lines.elided[endlinenum + 1]))): + while (endlinenum < len(clean_lines.elided) + and ';' not in clean_lines.elided[endlinenum][endpos:]): + endlinenum += 1 + endpos = 0 + if endlinenum < len(clean_lines.elided): + endline = clean_lines.elided[endlinenum] + # We allow a mix of whitespace and closing braces (e.g. for one-liner + # methods) and a single \ after the semicolon (for macros) + endpos = endline.find(';') + if not Match(r';[\s}]*(\\?)$', endline[endpos:]): + # Semicolon isn't the last character, there's something trailing. + # Output a warning if the semicolon is not contained inside + # a lambda expression. + if not Match(r'^[^{};]*\[[^\[\]]*\][^{}]*\{[^{}]*\}\s*\)*[;,]\s*$', + endline): + error(filename, linenum, 'readability/braces', 4, + 'If/else bodies with multiple statements require braces') + elif endlinenum < len(clean_lines.elided) - 1: + # Make sure the next line is dedented + next_line = clean_lines.elided[endlinenum + 1] + next_indent = GetIndentLevel(next_line) + # With ambiguous nested if statements, this will error out on the + # if that *doesn't* match the else, regardless of whether it's the + # inner one or outer one. + if (if_match and Match(r'\s*else\b', next_line) + and next_indent != if_indent): + error(filename, linenum, 'readability/braces', 4, + 'Else clause should be indented at the same level as if. ' + 'Ambiguous nested if/else chains require braces.') + elif next_indent > if_indent: + error(filename, linenum, 'readability/braces', 4, + 'If/else bodies with multiple statements require braces') + + +def CheckTrailingSemicolon(filename, clean_lines, linenum, error): + """Looks for redundant trailing semicolon. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + + line = clean_lines.elided[linenum] + + # Block bodies should not be followed by a semicolon. Due to C++11 + # brace initialization, there are more places where semicolons are + # required than not, so we use a whitelist approach to check these + # rather than a blacklist. These are the places where "};" should + # be replaced by just "}": + # 1. Some flavor of block following closing parenthesis: + # for (;;) {}; + # while (...) {}; + # switch (...) {}; + # Function(...) {}; + # if (...) {}; + # if (...) else if (...) {}; + # + # 2. else block: + # if (...) else {}; + # + # 3. const member function: + # Function(...) const {}; + # + # 4. Block following some statement: + # x = 42; + # {}; + # + # 5. Block at the beginning of a function: + # Function(...) { + # {}; + # } + # + # Note that naively checking for the preceding "{" will also match + # braces inside multi-dimensional arrays, but this is fine since + # that expression will not contain semicolons. + # + # 6. Block following another block: + # while (true) {} + # {}; + # + # 7. End of namespaces: + # namespace {}; + # + # These semicolons seems far more common than other kinds of + # redundant semicolons, possibly due to people converting classes + # to namespaces. For now we do not warn for this case. + # + # Try matching case 1 first. + match = Match(r'^(.*\)\s*)\{', line) + if match: + # Matched closing parenthesis (case 1). Check the token before the + # matching opening parenthesis, and don't warn if it looks like a + # macro. This avoids these false positives: + # - macro that defines a base class + # - multi-line macro that defines a base class + # - macro that defines the whole class-head + # + # But we still issue warnings for macros that we know are safe to + # warn, specifically: + # - TEST, TEST_F, TEST_P, MATCHER, MATCHER_P + # - TYPED_TEST + # - INTERFACE_DEF + # - EXCLUSIVE_LOCKS_REQUIRED, SHARED_LOCKS_REQUIRED, LOCKS_EXCLUDED: + # + # We implement a whitelist of safe macros instead of a blacklist of + # unsafe macros, even though the latter appears less frequently in + # google code and would have been easier to implement. This is because + # the downside for getting the whitelist wrong means some extra + # semicolons, while the downside for getting the blacklist wrong + # would result in compile errors. + # + # In addition to macros, we also don't want to warn on + # - Compound literals + # - Lambdas + # - alignas specifier with anonymous structs + # - decltype + closing_brace_pos = match.group(1).rfind(')') + opening_parenthesis = ReverseCloseExpression( + clean_lines, linenum, closing_brace_pos) + if opening_parenthesis[2] > -1: + line_prefix = opening_parenthesis[0][0:opening_parenthesis[2]] + macro = Search(r'\b([A-Z_][A-Z0-9_]*)\s*$', line_prefix) + func = Match(r'^(.*\])\s*$', line_prefix) + if ((macro and + macro.group(1) not in ( + 'TEST', 'TEST_F', 'MATCHER', 'MATCHER_P', 'TYPED_TEST', + 'EXCLUSIVE_LOCKS_REQUIRED', 'SHARED_LOCKS_REQUIRED', + 'LOCKS_EXCLUDED', 'INTERFACE_DEF')) or + (func and not Search(r'\boperator\s*\[\s*\]', func.group(1))) or + Search(r'\b(?:struct|union)\s+alignas\s*$', line_prefix) or + Search(r'\bdecltype$', line_prefix) or + Search(r'\s+=\s*$', line_prefix)): + match = None + if (match and + opening_parenthesis[1] > 1 and + Search(r'\]\s*$', clean_lines.elided[opening_parenthesis[1] - 1])): + # Multi-line lambda-expression + match = None + + else: + # Try matching cases 2-3. + match = Match(r'^(.*(?:else|\)\s*const)\s*)\{', line) + if not match: + # Try matching cases 4-6. These are always matched on separate lines. + # + # Note that we can't simply concatenate the previous line to the + # current line and do a single match, otherwise we may output + # duplicate warnings for the blank line case: + # if (cond) { + # // blank line + # } + prevline = GetPreviousNonBlankLine(clean_lines, linenum)[0] + if prevline and Search(r'[;{}]\s*$', prevline): + match = Match(r'^(\s*)\{', line) + + # Check matching closing brace + if match: + (endline, endlinenum, endpos) = CloseExpression( + clean_lines, linenum, len(match.group(1))) + if endpos > -1 and Match(r'^\s*;', endline[endpos:]): + # Current {} pair is eligible for semicolon check, and we have found + # the redundant semicolon, output warning here. + # + # Note: because we are scanning forward for opening braces, and + # outputting warnings for the matching closing brace, if there are + # nested blocks with trailing semicolons, we will get the error + # messages in reversed order. + + # We need to check the line forward for NOLINT + raw_lines = clean_lines.raw_lines + ParseNolintSuppressions(filename, raw_lines[endlinenum-1], endlinenum-1, + error) + ParseNolintSuppressions(filename, raw_lines[endlinenum], endlinenum, + error) + + error(filename, endlinenum, 'readability/braces', 4, + "You don't need a ; after a }") + + +def CheckEmptyBlockBody(filename, clean_lines, linenum, error): + """Look for empty loop/conditional body with only a single semicolon. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + + # Search for loop keywords at the beginning of the line. Because only + # whitespaces are allowed before the keywords, this will also ignore most + # do-while-loops, since those lines should start with closing brace. + # + # We also check "if" blocks here, since an empty conditional block + # is likely an error. + line = clean_lines.elided[linenum] + matched = Match(r'\s*(for|while|if)\s*\(', line) + if matched: + # Find the end of the conditional expression. + (end_line, end_linenum, end_pos) = CloseExpression( + clean_lines, linenum, line.find('(')) + + # Output warning if what follows the condition expression is a semicolon. + # No warning for all other cases, including whitespace or newline, since we + # have a separate check for semicolons preceded by whitespace. + if end_pos >= 0 and Match(r';', end_line[end_pos:]): + if matched.group(1) == 'if': + error(filename, end_linenum, 'whitespace/empty_conditional_body', 5, + 'Empty conditional bodies should use {}') + else: + error(filename, end_linenum, 'whitespace/empty_loop_body', 5, + 'Empty loop bodies should use {} or continue') + + # Check for if statements that have completely empty bodies (no comments) + # and no else clauses. + if end_pos >= 0 and matched.group(1) == 'if': + # Find the position of the opening { for the if statement. + # Return without logging an error if it has no brackets. + opening_linenum = end_linenum + opening_line_fragment = end_line[end_pos:] + # Loop until EOF or find anything that's not whitespace or opening {. + while not Search(r'^\s*\{', opening_line_fragment): + if Search(r'^(?!\s*$)', opening_line_fragment): + # Conditional has no brackets. + return + opening_linenum += 1 + if opening_linenum == len(clean_lines.elided): + # Couldn't find conditional's opening { or any code before EOF. + return + opening_line_fragment = clean_lines.elided[opening_linenum] + # Set opening_line (opening_line_fragment may not be entire opening line). + opening_line = clean_lines.elided[opening_linenum] + + # Find the position of the closing }. + opening_pos = opening_line_fragment.find('{') + if opening_linenum == end_linenum: + # We need to make opening_pos relative to the start of the entire line. + opening_pos += end_pos + (closing_line, closing_linenum, closing_pos) = CloseExpression( + clean_lines, opening_linenum, opening_pos) + if closing_pos < 0: + return + + # Now construct the body of the conditional. This consists of the portion + # of the opening line after the {, all lines until the closing line, + # and the portion of the closing line before the }. + if (clean_lines.raw_lines[opening_linenum] != + CleanseComments(clean_lines.raw_lines[opening_linenum])): + # Opening line ends with a comment, so conditional isn't empty. + return + if closing_linenum > opening_linenum: + # Opening line after the {. Ignore comments here since we checked above. + body = list(opening_line[opening_pos+1:]) + # All lines until closing line, excluding closing line, with comments. + body.extend(clean_lines.raw_lines[opening_linenum+1:closing_linenum]) + # Closing line before the }. Won't (and can't) have comments. + body.append(clean_lines.elided[closing_linenum][:closing_pos-1]) + body = '\n'.join(body) + else: + # If statement has brackets and fits on a single line. + body = opening_line[opening_pos+1:closing_pos-1] + + # Check if the body is empty + if not _EMPTY_CONDITIONAL_BODY_PATTERN.search(body): + return + # The body is empty. Now make sure there's not an else clause. + current_linenum = closing_linenum + current_line_fragment = closing_line[closing_pos:] + # Loop until EOF or find anything that's not whitespace or else clause. + while Search(r'^\s*$|^(?=\s*else)', current_line_fragment): + if Search(r'^(?=\s*else)', current_line_fragment): + # Found an else clause, so don't log an error. + return + current_linenum += 1 + if current_linenum == len(clean_lines.elided): + break + current_line_fragment = clean_lines.elided[current_linenum] + + # The body is empty and there's no else clause until EOF or other code. + error(filename, end_linenum, 'whitespace/empty_if_body', 4, + ('If statement had no body and no else clause')) + + +def FindCheckMacro(line): + """Find a replaceable CHECK-like macro. + + Args: + line: line to search on. + Returns: + (macro name, start position), or (None, -1) if no replaceable + macro is found. + """ + for macro in _CHECK_MACROS: + i = line.find(macro) + if i >= 0: + # Find opening parenthesis. Do a regular expression match here + # to make sure that we are matching the expected CHECK macro, as + # opposed to some other macro that happens to contain the CHECK + # substring. + matched = Match(r'^(.*\b' + macro + r'\s*)\(', line) + if not matched: + continue + return (macro, len(matched.group(1))) + return (None, -1) + + +def CheckCheck(filename, clean_lines, linenum, error): + """Checks the use of CHECK and EXPECT macros. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + + # Decide the set of replacement macros that should be suggested + lines = clean_lines.elided + (check_macro, start_pos) = FindCheckMacro(lines[linenum]) + if not check_macro: + return + + # Find end of the boolean expression by matching parentheses + (last_line, end_line, end_pos) = CloseExpression( + clean_lines, linenum, start_pos) + if end_pos < 0: + return + + # If the check macro is followed by something other than a + # semicolon, assume users will log their own custom error messages + # and don't suggest any replacements. + if not Match(r'\s*;', last_line[end_pos:]): + return + + if linenum == end_line: + expression = lines[linenum][start_pos + 1:end_pos - 1] + else: + expression = lines[linenum][start_pos + 1:] + for i in xrange(linenum + 1, end_line): + expression += lines[i] + expression += last_line[0:end_pos - 1] + + # Parse expression so that we can take parentheses into account. + # This avoids false positives for inputs like "CHECK((a < 4) == b)", + # which is not replaceable by CHECK_LE. + lhs = '' + rhs = '' + operator = None + while expression: + matched = Match(r'^\s*(<<|<<=|>>|>>=|->\*|->|&&|\|\||' + r'==|!=|>=|>|<=|<|\()(.*)$', expression) + if matched: + token = matched.group(1) + if token == '(': + # Parenthesized operand + expression = matched.group(2) + (end, _) = FindEndOfExpressionInLine(expression, 0, ['(']) + if end < 0: + return # Unmatched parenthesis + lhs += '(' + expression[0:end] + expression = expression[end:] + elif token in ('&&', '||'): + # Logical and/or operators. This means the expression + # contains more than one term, for example: + # CHECK(42 < a && a < b); + # + # These are not replaceable with CHECK_LE, so bail out early. + return + elif token in ('<<', '<<=', '>>', '>>=', '->*', '->'): + # Non-relational operator + lhs += token + expression = matched.group(2) + else: + # Relational operator + operator = token + rhs = matched.group(2) + break + else: + # Unparenthesized operand. Instead of appending to lhs one character + # at a time, we do another regular expression match to consume several + # characters at once if possible. Trivial benchmark shows that this + # is more efficient when the operands are longer than a single + # character, which is generally the case. + matched = Match(r'^([^-=!<>()&|]+)(.*)$', expression) + if not matched: + matched = Match(r'^(\s*\S)(.*)$', expression) + if not matched: + break + lhs += matched.group(1) + expression = matched.group(2) + + # Only apply checks if we got all parts of the boolean expression + if not (lhs and operator and rhs): + return + + # Check that rhs do not contain logical operators. We already know + # that lhs is fine since the loop above parses out && and ||. + if rhs.find('&&') > -1 or rhs.find('||') > -1: + return + + # At least one of the operands must be a constant literal. This is + # to avoid suggesting replacements for unprintable things like + # CHECK(variable != iterator) + # + # The following pattern matches decimal, hex integers, strings, and + # characters (in that order). + lhs = lhs.strip() + rhs = rhs.strip() + match_constant = r'^([-+]?(\d+|0[xX][0-9a-fA-F]+)[lLuU]{0,3}|".*"|\'.*\')$' + if Match(match_constant, lhs) or Match(match_constant, rhs): + # Note: since we know both lhs and rhs, we can provide a more + # descriptive error message like: + # Consider using CHECK_EQ(x, 42) instead of CHECK(x == 42) + # Instead of: + # Consider using CHECK_EQ instead of CHECK(a == b) + # + # We are still keeping the less descriptive message because if lhs + # or rhs gets long, the error message might become unreadable. + error(filename, linenum, 'readability/check', 2, + 'Consider using %s instead of %s(a %s b)' % ( + _CHECK_REPLACEMENT[check_macro][operator], + check_macro, operator)) + + +def CheckAltTokens(filename, clean_lines, linenum, error): + """Check alternative keywords being used in boolean expressions. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # Avoid preprocessor lines + if Match(r'^\s*#', line): + return + + # Last ditch effort to avoid multi-line comments. This will not help + # if the comment started before the current line or ended after the + # current line, but it catches most of the false positives. At least, + # it provides a way to workaround this warning for people who use + # multi-line comments in preprocessor macros. + # + # TODO(unknown): remove this once cpplint has better support for + # multi-line comments. + if line.find('/*') >= 0 or line.find('*/') >= 0: + return + + for match in _ALT_TOKEN_REPLACEMENT_PATTERN.finditer(line): + error(filename, linenum, 'readability/alt_tokens', 2, + 'Use operator %s instead of %s' % ( + _ALT_TOKEN_REPLACEMENT[match.group(1)], match.group(1))) + + +def GetLineWidth(line): + """Determines the width of the line in column positions. + + Args: + line: A string, which may be a Unicode string. + + Returns: + The width of the line in column positions, accounting for Unicode + combining characters and wide characters. + """ + if isinstance(line, unicode): + width = 0 + for uc in unicodedata.normalize('NFC', line): + if unicodedata.east_asian_width(uc) in ('W', 'F'): + width += 2 + elif not unicodedata.combining(uc): + width += 1 + return width + else: + return len(line) + + +def CheckStyle(filename, clean_lines, linenum, file_extension, nesting_state, + error): + """Checks rules from the 'C++ style rules' section of cppguide.html. + + Most of these rules are hard to test (naming, comment style), but we + do what we can. In particular we check for 2-space indents, line lengths, + tab usage, spaces inside code, etc. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + file_extension: The extension (without the dot) of the filename. + nesting_state: A NestingState instance which maintains information about + the current stack of nested blocks being parsed. + error: The function to call with any errors found. + """ + + # Don't use "elided" lines here, otherwise we can't check commented lines. + # Don't want to use "raw" either, because we don't want to check inside C++11 + # raw strings, + raw_lines = clean_lines.lines_without_raw_strings + line = raw_lines[linenum] + prev = raw_lines[linenum - 1] if linenum > 0 else '' + + if line.find('\t') != -1: + error(filename, linenum, 'whitespace/tab', 1, + 'Tab found; better to use spaces') + + # One or three blank spaces at the beginning of the line is weird; it's + # hard to reconcile that with 2-space indents. + # NOTE: here are the conditions rob pike used for his tests. Mine aren't + # as sophisticated, but it may be worth becoming so: RLENGTH==initial_spaces + # if(RLENGTH > 20) complain = 0; + # if(match($0, " +(error|private|public|protected):")) complain = 0; + # if(match(prev, "&& *$")) complain = 0; + # if(match(prev, "\\|\\| *$")) complain = 0; + # if(match(prev, "[\",=><] *$")) complain = 0; + # if(match($0, " <<")) complain = 0; + # if(match(prev, " +for \\(")) complain = 0; + # if(prevodd && match(prevprev, " +for \\(")) complain = 0; + scope_or_label_pattern = r'\s*\w+\s*:\s*\\?$' + classinfo = nesting_state.InnermostClass() + initial_spaces = 0 + cleansed_line = clean_lines.elided[linenum] + while initial_spaces < len(line) and line[initial_spaces] == ' ': + initial_spaces += 1 + # There are certain situations we allow one space, notably for + # section labels, and also lines containing multi-line raw strings. + # We also don't check for lines that look like continuation lines + # (of lines ending in double quotes, commas, equals, or angle brackets) + # because the rules for how to indent those are non-trivial. + if (not Search(r'[",=><] *$', prev) and + (initial_spaces == 1 or initial_spaces == 3) and + not Match(scope_or_label_pattern, cleansed_line) and + not (clean_lines.raw_lines[linenum] != line and + Match(r'^\s*""', line))): + error(filename, linenum, 'whitespace/indent', 3, + 'Weird number of spaces at line-start. ' + 'Are you using a 2-space indent?') + + if line and line[-1].isspace(): + error(filename, linenum, 'whitespace/end_of_line', 4, + 'Line ends in whitespace. Consider deleting these extra spaces.') + + # Check if the line is a header guard. + is_header_guard = False + if IsHeaderExtension(file_extension): + cppvar = GetHeaderGuardCPPVariable(filename) + if (line.startswith('#ifndef %s' % cppvar) or + line.startswith('#define %s' % cppvar) or + line.startswith('#endif // %s' % cppvar)): + is_header_guard = True + # #include lines and header guards can be long, since there's no clean way to + # split them. + # + # URLs can be long too. It's possible to split these, but it makes them + # harder to cut&paste. + # + # The "$Id:...$" comment may also get very long without it being the + # developers fault. + if (not line.startswith('#include') and not is_header_guard and + not Match(r'^\s*//.*http(s?)://\S*$', line) and + not Match(r'^\s*//\s*[^\s]*$', line) and + not Match(r'^// \$Id:.*#[0-9]+ \$$', line)): + line_width = GetLineWidth(line) + if line_width > _line_length: + error(filename, linenum, 'whitespace/line_length', 2, + 'Lines should be <= %i characters long' % _line_length) + + if (cleansed_line.count(';') > 1 and + # for loops are allowed two ;'s (and may run over two lines). + cleansed_line.find('for') == -1 and + (GetPreviousNonBlankLine(clean_lines, linenum)[0].find('for') == -1 or + GetPreviousNonBlankLine(clean_lines, linenum)[0].find(';') != -1) and + # It's ok to have many commands in a switch case that fits in 1 line + not ((cleansed_line.find('case ') != -1 or + cleansed_line.find('default:') != -1) and + cleansed_line.find('break;') != -1)): + error(filename, linenum, 'whitespace/newline', 0, + 'More than one command on the same line') + + # Some more style checks + CheckBraces(filename, clean_lines, linenum, error) + CheckTrailingSemicolon(filename, clean_lines, linenum, error) + CheckEmptyBlockBody(filename, clean_lines, linenum, error) + CheckSpacing(filename, clean_lines, linenum, nesting_state, error) + CheckOperatorSpacing(filename, clean_lines, linenum, error) + CheckParenthesisSpacing(filename, clean_lines, linenum, error) + CheckCommaSpacing(filename, clean_lines, linenum, error) + CheckBracesSpacing(filename, clean_lines, linenum, nesting_state, error) + CheckSpacingForFunctionCall(filename, clean_lines, linenum, error) + CheckCheck(filename, clean_lines, linenum, error) + CheckAltTokens(filename, clean_lines, linenum, error) + classinfo = nesting_state.InnermostClass() + if classinfo: + CheckSectionSpacing(filename, clean_lines, classinfo, linenum, error) + + +_RE_PATTERN_INCLUDE = re.compile(r'^\s*#\s*include\s*([<"])([^>"]*)[>"].*$') +# Matches the first component of a filename delimited by -s and _s. That is: +# _RE_FIRST_COMPONENT.match('foo').group(0) == 'foo' +# _RE_FIRST_COMPONENT.match('foo.cc').group(0) == 'foo' +# _RE_FIRST_COMPONENT.match('foo-bar_baz.cc').group(0) == 'foo' +# _RE_FIRST_COMPONENT.match('foo_bar-baz.cc').group(0) == 'foo' +_RE_FIRST_COMPONENT = re.compile(r'^[^-_.]+') + + +def _DropCommonSuffixes(filename): + """Drops common suffixes like _test.cc or -inl.h from filename. + + For example: + >>> _DropCommonSuffixes('foo/foo-inl.h') + 'foo/foo' + >>> _DropCommonSuffixes('foo/bar/foo.cc') + 'foo/bar/foo' + >>> _DropCommonSuffixes('foo/foo_internal.h') + 'foo/foo' + >>> _DropCommonSuffixes('foo/foo_unusualinternal.h') + 'foo/foo_unusualinternal' + + Args: + filename: The input filename. + + Returns: + The filename with the common suffix removed. + """ + for suffix in ('test.cc', 'regtest.cc', 'unittest.cc', + 'inl.h', 'impl.h', 'internal.h'): + if (filename.endswith(suffix) and len(filename) > len(suffix) and + filename[-len(suffix) - 1] in ('-', '_')): + return filename[:-len(suffix) - 1] + return os.path.splitext(filename)[0] + + +def _ClassifyInclude(fileinfo, include, is_system): + """Figures out what kind of header 'include' is. + + Args: + fileinfo: The current file cpplint is running over. A FileInfo instance. + include: The path to a #included file. + is_system: True if the #include used <> rather than "". + + Returns: + One of the _XXX_HEADER constants. + + For example: + >>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'stdio.h', True) + _C_SYS_HEADER + >>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'string', True) + _CPP_SYS_HEADER + >>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'foo/foo.h', False) + _LIKELY_MY_HEADER + >>> _ClassifyInclude(FileInfo('foo/foo_unknown_extension.cc'), + ... 'bar/foo_other_ext.h', False) + _POSSIBLE_MY_HEADER + >>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'foo/bar.h', False) + _OTHER_HEADER + """ + # This is a list of all standard c++ header files, except + # those already checked for above. + is_cpp_h = include in _CPP_HEADERS + + if is_system: + if is_cpp_h: + return _CPP_SYS_HEADER + else: + return _C_SYS_HEADER + + # If the target file and the include we're checking share a + # basename when we drop common extensions, and the include + # lives in . , then it's likely to be owned by the target file. + target_dir, target_base = ( + os.path.split(_DropCommonSuffixes(fileinfo.RepositoryName()))) + include_dir, include_base = os.path.split(_DropCommonSuffixes(include)) + if target_base == include_base and ( + include_dir == target_dir or + include_dir == os.path.normpath(target_dir + '/../public')): + return _LIKELY_MY_HEADER + + # If the target and include share some initial basename + # component, it's possible the target is implementing the + # include, so it's allowed to be first, but we'll never + # complain if it's not there. + target_first_component = _RE_FIRST_COMPONENT.match(target_base) + include_first_component = _RE_FIRST_COMPONENT.match(include_base) + if (target_first_component and include_first_component and + target_first_component.group(0) == + include_first_component.group(0)): + return _POSSIBLE_MY_HEADER + + return _OTHER_HEADER + + + +def CheckIncludeLine(filename, clean_lines, linenum, include_state, error): + """Check rules that are applicable to #include lines. + + Strings on #include lines are NOT removed from elided line, to make + certain tasks easier. However, to prevent false positives, checks + applicable to #include lines in CheckLanguage must be put here. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + include_state: An _IncludeState instance in which the headers are inserted. + error: The function to call with any errors found. + """ + fileinfo = FileInfo(filename) + line = clean_lines.lines[linenum] + + # "include" should use the new style "foo/bar.h" instead of just "bar.h" + # Only do this check if the included header follows google naming + # conventions. If not, assume that it's a 3rd party API that + # requires special include conventions. + # + # We also make an exception for Lua headers, which follow google + # naming convention but not the include convention. + match = Match(r'#include\s*"([^/]+\.h)"', line) + if match and not _THIRD_PARTY_HEADERS_PATTERN.match(match.group(1)): + error(filename, linenum, 'build/include', 4, + 'Include the directory when naming .h files') + + # we shouldn't include a file more than once. actually, there are a + # handful of instances where doing so is okay, but in general it's + # not. + match = _RE_PATTERN_INCLUDE.search(line) + if match: + include = match.group(2) + is_system = (match.group(1) == '<') + duplicate_line = include_state.FindHeader(include) + if duplicate_line >= 0: + error(filename, linenum, 'build/include', 4, + '"%s" already included at %s:%s' % + (include, filename, duplicate_line)) + elif (include.endswith('.cc') and + os.path.dirname(fileinfo.RepositoryName()) != os.path.dirname(include)): + error(filename, linenum, 'build/include', 4, + 'Do not include .cc files from other packages') + elif not _THIRD_PARTY_HEADERS_PATTERN.match(include): + include_state.include_list[-1].append((include, linenum)) + + # We want to ensure that headers appear in the right order: + # 1) for foo.cc, foo.h (preferred location) + # 2) c system files + # 3) cpp system files + # 4) for foo.cc, foo.h (deprecated location) + # 5) other google headers + # + # We classify each include statement as one of those 5 types + # using a number of techniques. The include_state object keeps + # track of the highest type seen, and complains if we see a + # lower type after that. + error_message = include_state.CheckNextIncludeOrder( + _ClassifyInclude(fileinfo, include, is_system)) + if error_message: + error(filename, linenum, 'build/include_order', 4, + '%s. Should be: %s.h, c system, c++ system, other.' % + (error_message, fileinfo.BaseName())) + canonical_include = include_state.CanonicalizeAlphabeticalOrder(include) + if not include_state.IsInAlphabeticalOrder( + clean_lines, linenum, canonical_include): + error(filename, linenum, 'build/include_alpha', 4, + 'Include "%s" not in alphabetical order' % include) + include_state.SetLastHeader(canonical_include) + + + +def _GetTextInside(text, start_pattern): + r"""Retrieves all the text between matching open and close parentheses. + + Given a string of lines and a regular expression string, retrieve all the text + following the expression and between opening punctuation symbols like + (, [, or {, and the matching close-punctuation symbol. This properly nested + occurrences of the punctuations, so for the text like + printf(a(), b(c())); + a call to _GetTextInside(text, r'printf\(') will return 'a(), b(c())'. + start_pattern must match string having an open punctuation symbol at the end. + + Args: + text: The lines to extract text. Its comments and strings must be elided. + It can be single line and can span multiple lines. + start_pattern: The regexp string indicating where to start extracting + the text. + Returns: + The extracted text. + None if either the opening string or ending punctuation could not be found. + """ + # TODO(unknown): Audit cpplint.py to see what places could be profitably + # rewritten to use _GetTextInside (and use inferior regexp matching today). + + # Give opening punctuations to get the matching close-punctuations. + matching_punctuation = {'(': ')', '{': '}', '[': ']'} + closing_punctuation = set(matching_punctuation.itervalues()) + + # Find the position to start extracting text. + match = re.search(start_pattern, text, re.M) + if not match: # start_pattern not found in text. + return None + start_position = match.end(0) + + assert start_position > 0, ( + 'start_pattern must ends with an opening punctuation.') + assert text[start_position - 1] in matching_punctuation, ( + 'start_pattern must ends with an opening punctuation.') + # Stack of closing punctuations we expect to have in text after position. + punctuation_stack = [matching_punctuation[text[start_position - 1]]] + position = start_position + while punctuation_stack and position < len(text): + if text[position] == punctuation_stack[-1]: + punctuation_stack.pop() + elif text[position] in closing_punctuation: + # A closing punctuation without matching opening punctuations. + return None + elif text[position] in matching_punctuation: + punctuation_stack.append(matching_punctuation[text[position]]) + position += 1 + if punctuation_stack: + # Opening punctuations left without matching close-punctuations. + return None + # punctuations match. + return text[start_position:position - 1] + + +# Patterns for matching call-by-reference parameters. +# +# Supports nested templates up to 2 levels deep using this messy pattern: +# < (?: < (?: < [^<>]* +# > +# | [^<>] )* +# > +# | [^<>] )* +# > +_RE_PATTERN_IDENT = r'[_a-zA-Z]\w*' # =~ [[:alpha:]][[:alnum:]]* +_RE_PATTERN_TYPE = ( + r'(?:const\s+)?(?:typename\s+|class\s+|struct\s+|union\s+|enum\s+)?' + r'(?:\w|' + r'\s*<(?:<(?:<[^<>]*>|[^<>])*>|[^<>])*>|' + r'::)+') +# A call-by-reference parameter ends with '& identifier'. +_RE_PATTERN_REF_PARAM = re.compile( + r'(' + _RE_PATTERN_TYPE + r'(?:\s*(?:\bconst\b|[*]))*\s*' + r'&\s*' + _RE_PATTERN_IDENT + r')\s*(?:=[^,()]+)?[,)]') +# A call-by-const-reference parameter either ends with 'const& identifier' +# or looks like 'const type& identifier' when 'type' is atomic. +_RE_PATTERN_CONST_REF_PARAM = ( + r'(?:.*\s*\bconst\s*&\s*' + _RE_PATTERN_IDENT + + r'|const\s+' + _RE_PATTERN_TYPE + r'\s*&\s*' + _RE_PATTERN_IDENT + r')') +# Stream types. +_RE_PATTERN_REF_STREAM_PARAM = ( + r'(?:.*stream\s*&\s*' + _RE_PATTERN_IDENT + r')') + + +def CheckLanguage(filename, clean_lines, linenum, file_extension, + include_state, nesting_state, error): + """Checks rules from the 'C++ language rules' section of cppguide.html. + + Some of these rules are hard to test (function overloading, using + uint32 inappropriately), but we do the best we can. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + file_extension: The extension (without the dot) of the filename. + include_state: An _IncludeState instance in which the headers are inserted. + nesting_state: A NestingState instance which maintains information about + the current stack of nested blocks being parsed. + error: The function to call with any errors found. + """ + # If the line is empty or consists of entirely a comment, no need to + # check it. + line = clean_lines.elided[linenum] + if not line: + return + + match = _RE_PATTERN_INCLUDE.search(line) + if match: + CheckIncludeLine(filename, clean_lines, linenum, include_state, error) + return + + # Reset include state across preprocessor directives. This is meant + # to silence warnings for conditional includes. + match = Match(r'^\s*#\s*(if|ifdef|ifndef|elif|else|endif)\b', line) + if match: + include_state.ResetSection(match.group(1)) + + # Make Windows paths like Unix. + fullname = os.path.abspath(filename).replace('\\', '/') + + # Perform other checks now that we are sure that this is not an include line + CheckCasts(filename, clean_lines, linenum, error) + CheckGlobalStatic(filename, clean_lines, linenum, error) + CheckPrintf(filename, clean_lines, linenum, error) + + if IsHeaderExtension(file_extension): + # TODO(unknown): check that 1-arg constructors are explicit. + # How to tell it's a constructor? + # (handled in CheckForNonStandardConstructs for now) + # TODO(unknown): check that classes declare or disable copy/assign + # (level 1 error) + pass + + # Check if people are using the verboten C basic types. The only exception + # we regularly allow is "unsigned short port" for port. + if Search(r'\bshort port\b', line): + if not Search(r'\bunsigned short port\b', line): + error(filename, linenum, 'runtime/int', 4, + 'Use "unsigned short" for ports, not "short"') + else: + match = Search(r'\b(short|long(?! +double)|long long)\b', line) + if match: + error(filename, linenum, 'runtime/int', 4, + 'Use int16/int64/etc, rather than the C type %s' % match.group(1)) + + # Check if some verboten operator overloading is going on + # TODO(unknown): catch out-of-line unary operator&: + # class X {}; + # int operator&(const X& x) { return 42; } // unary operator& + # The trick is it's hard to tell apart from binary operator&: + # class Y { int operator&(const Y& x) { return 23; } }; // binary operator& + if Search(r'\boperator\s*&\s*\(\s*\)', line): + error(filename, linenum, 'runtime/operator', 4, + 'Unary operator& is dangerous. Do not use it.') + + # Check for suspicious usage of "if" like + # } if (a == b) { + if Search(r'\}\s*if\s*\(', line): + error(filename, linenum, 'readability/braces', 4, + 'Did you mean "else if"? If not, start a new line for "if".') + + # Check for potential format string bugs like printf(foo). + # We constrain the pattern not to pick things like DocidForPrintf(foo). + # Not perfect but it can catch printf(foo.c_str()) and printf(foo->c_str()) + # TODO(unknown): Catch the following case. Need to change the calling + # convention of the whole function to process multiple line to handle it. + # printf( + # boy_this_is_a_really_long_variable_that_cannot_fit_on_the_prev_line); + printf_args = _GetTextInside(line, r'(?i)\b(string)?printf\s*\(') + if printf_args: + match = Match(r'([\w.\->()]+)$', printf_args) + if match and match.group(1) != '__VA_ARGS__': + function_name = re.search(r'\b((?:string)?printf)\s*\(', + line, re.I).group(1) + error(filename, linenum, 'runtime/printf', 4, + 'Potential format string bug. Do %s("%%s", %s) instead.' + % (function_name, match.group(1))) + + # Check for potential memset bugs like memset(buf, sizeof(buf), 0). + match = Search(r'memset\s*\(([^,]*),\s*([^,]*),\s*0\s*\)', line) + if match and not Match(r"^''|-?[0-9]+|0x[0-9A-Fa-f]$", match.group(2)): + error(filename, linenum, 'runtime/memset', 4, + 'Did you mean "memset(%s, 0, %s)"?' + % (match.group(1), match.group(2))) + + if Search(r'\busing namespace\b', line): + error(filename, linenum, 'build/namespaces', 5, + 'Do not use namespace using-directives. ' + 'Use using-declarations instead.') + + # Detect variable-length arrays. + match = Match(r'\s*(.+::)?(\w+) [a-z]\w*\[(.+)];', line) + if (match and match.group(2) != 'return' and match.group(2) != 'delete' and + match.group(3).find(']') == -1): + # Split the size using space and arithmetic operators as delimiters. + # If any of the resulting tokens are not compile time constants then + # report the error. + tokens = re.split(r'\s|\+|\-|\*|\/|<<|>>]', match.group(3)) + is_const = True + skip_next = False + for tok in tokens: + if skip_next: + skip_next = False + continue + + if Search(r'sizeof\(.+\)', tok): continue + if Search(r'arraysize\(\w+\)', tok): continue + + tok = tok.lstrip('(') + tok = tok.rstrip(')') + if not tok: continue + if Match(r'\d+', tok): continue + if Match(r'0[xX][0-9a-fA-F]+', tok): continue + if Match(r'k[A-Z0-9]\w*', tok): continue + if Match(r'(.+::)?k[A-Z0-9]\w*', tok): continue + if Match(r'(.+::)?[A-Z][A-Z0-9_]*', tok): continue + # A catch all for tricky sizeof cases, including 'sizeof expression', + # 'sizeof(*type)', 'sizeof(const type)', 'sizeof(struct StructName)' + # requires skipping the next token because we split on ' ' and '*'. + if tok.startswith('sizeof'): + skip_next = True + continue + is_const = False + break + if not is_const: + error(filename, linenum, 'runtime/arrays', 1, + 'Do not use variable-length arrays. Use an appropriately named ' + "('k' followed by CamelCase) compile-time constant for the size.") + + # Check for use of unnamed namespaces in header files. Registration + # macros are typically OK, so we allow use of "namespace {" on lines + # that end with backslashes. + if (IsHeaderExtension(file_extension) + and Search(r'\bnamespace\s*{', line) + and line[-1] != '\\'): + error(filename, linenum, 'build/namespaces', 4, + 'Do not use unnamed namespaces in header files. See ' + 'https://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Namespaces' + ' for more information.') + + +def CheckGlobalStatic(filename, clean_lines, linenum, error): + """Check for unsafe global or static objects. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # Match two lines at a time to support multiline declarations + if linenum + 1 < clean_lines.NumLines() and not Search(r'[;({]', line): + line += clean_lines.elided[linenum + 1].strip() + + # Check for people declaring static/global STL strings at the top level. + # This is dangerous because the C++ language does not guarantee that + # globals with constructors are initialized before the first access, and + # also because globals can be destroyed when some threads are still running. + # TODO(unknown): Generalize this to also find static unique_ptr instances. + # TODO(unknown): File bugs for clang-tidy to find these. + match = Match( + r'((?:|static +)(?:|const +))(?::*std::)?string( +const)? +' + r'([a-zA-Z0-9_:]+)\b(.*)', + line) + + # Remove false positives: + # - String pointers (as opposed to values). + # string *pointer + # const string *pointer + # string const *pointer + # string *const pointer + # + # - Functions and template specializations. + # string Function(... + # string Class::Method(... + # + # - Operators. These are matched separately because operator names + # cross non-word boundaries, and trying to match both operators + # and functions at the same time would decrease accuracy of + # matching identifiers. + # string Class::operator*() + if (match and + not Search(r'\bstring\b(\s+const)?\s*[\*\&]\s*(const\s+)?\w', line) and + not Search(r'\boperator\W', line) and + not Match(r'\s*(<.*>)?(::[a-zA-Z0-9_]+)*\s*\(([^"]|$)', match.group(4))): + if Search(r'\bconst\b', line): + error(filename, linenum, 'runtime/string', 4, + 'For a static/global string constant, use a C style string ' + 'instead: "%schar%s %s[]".' % + (match.group(1), match.group(2) or '', match.group(3))) + else: + error(filename, linenum, 'runtime/string', 4, + 'Static/global string variables are not permitted.') + + if (Search(r'\b([A-Za-z0-9_]*_)\(\1\)', line) or + Search(r'\b([A-Za-z0-9_]*_)\(CHECK_NOTNULL\(\1\)\)', line)): + error(filename, linenum, 'runtime/init', 4, + 'You seem to be initializing a member variable with itself.') + + +def CheckPrintf(filename, clean_lines, linenum, error): + """Check for printf related issues. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # When snprintf is used, the second argument shouldn't be a literal. + match = Search(r'snprintf\s*\(([^,]*),\s*([0-9]*)\s*,', line) + if match and match.group(2) != '0': + # If 2nd arg is zero, snprintf is used to calculate size. + error(filename, linenum, 'runtime/printf', 3, + 'If you can, use sizeof(%s) instead of %s as the 2nd arg ' + 'to snprintf.' % (match.group(1), match.group(2))) + + # Check if some verboten C functions are being used. + if Search(r'\bsprintf\s*\(', line): + error(filename, linenum, 'runtime/printf', 5, + 'Never use sprintf. Use snprintf instead.') + match = Search(r'\b(strcpy|strcat)\s*\(', line) + if match: + error(filename, linenum, 'runtime/printf', 4, + 'Almost always, snprintf is better than %s' % match.group(1)) + + +def IsDerivedFunction(clean_lines, linenum): + """Check if current line contains an inherited function. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + Returns: + True if current line contains a function with "override" + virt-specifier. + """ + # Scan back a few lines for start of current function + for i in xrange(linenum, max(-1, linenum - 10), -1): + match = Match(r'^([^()]*\w+)\(', clean_lines.elided[i]) + if match: + # Look for "override" after the matching closing parenthesis + line, _, closing_paren = CloseExpression( + clean_lines, i, len(match.group(1))) + return (closing_paren >= 0 and + Search(r'\boverride\b', line[closing_paren:])) + return False + + +def IsOutOfLineMethodDefinition(clean_lines, linenum): + """Check if current line contains an out-of-line method definition. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + Returns: + True if current line contains an out-of-line method definition. + """ + # Scan back a few lines for start of current function + for i in xrange(linenum, max(-1, linenum - 10), -1): + if Match(r'^([^()]*\w+)\(', clean_lines.elided[i]): + return Match(r'^[^()]*\w+::\w+\(', clean_lines.elided[i]) is not None + return False + + +def IsInitializerList(clean_lines, linenum): + """Check if current line is inside constructor initializer list. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + Returns: + True if current line appears to be inside constructor initializer + list, False otherwise. + """ + for i in xrange(linenum, 1, -1): + line = clean_lines.elided[i] + if i == linenum: + remove_function_body = Match(r'^(.*)\{\s*$', line) + if remove_function_body: + line = remove_function_body.group(1) + + if Search(r'\s:\s*\w+[({]', line): + # A lone colon tend to indicate the start of a constructor + # initializer list. It could also be a ternary operator, which + # also tend to appear in constructor initializer lists as + # opposed to parameter lists. + return True + if Search(r'\}\s*,\s*$', line): + # A closing brace followed by a comma is probably the end of a + # brace-initialized member in constructor initializer list. + return True + if Search(r'[{};]\s*$', line): + # Found one of the following: + # - A closing brace or semicolon, probably the end of the previous + # function. + # - An opening brace, probably the start of current class or namespace. + # + # Current line is probably not inside an initializer list since + # we saw one of those things without seeing the starting colon. + return False + + # Got to the beginning of the file without seeing the start of + # constructor initializer list. + return False + + +def CheckForNonConstReference(filename, clean_lines, linenum, + nesting_state, error): + """Check for non-const references. + + Separate from CheckLanguage since it scans backwards from current + line, instead of scanning forward. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + nesting_state: A NestingState instance which maintains information about + the current stack of nested blocks being parsed. + error: The function to call with any errors found. + """ + # Do nothing if there is no '&' on current line. + line = clean_lines.elided[linenum] + if '&' not in line: + return + + # If a function is inherited, current function doesn't have much of + # a choice, so any non-const references should not be blamed on + # derived function. + if IsDerivedFunction(clean_lines, linenum): + return + + # Don't warn on out-of-line method definitions, as we would warn on the + # in-line declaration, if it isn't marked with 'override'. + if IsOutOfLineMethodDefinition(clean_lines, linenum): + return + + # Long type names may be broken across multiple lines, usually in one + # of these forms: + # LongType + # ::LongTypeContinued &identifier + # LongType:: + # LongTypeContinued &identifier + # LongType< + # ...>::LongTypeContinued &identifier + # + # If we detected a type split across two lines, join the previous + # line to current line so that we can match const references + # accordingly. + # + # Note that this only scans back one line, since scanning back + # arbitrary number of lines would be expensive. If you have a type + # that spans more than 2 lines, please use a typedef. + if linenum > 1: + previous = None + if Match(r'\s*::(?:[\w<>]|::)+\s*&\s*\S', line): + # previous_line\n + ::current_line + previous = Search(r'\b((?:const\s*)?(?:[\w<>]|::)+[\w<>])\s*$', + clean_lines.elided[linenum - 1]) + elif Match(r'\s*[a-zA-Z_]([\w<>]|::)+\s*&\s*\S', line): + # previous_line::\n + current_line + previous = Search(r'\b((?:const\s*)?(?:[\w<>]|::)+::)\s*$', + clean_lines.elided[linenum - 1]) + if previous: + line = previous.group(1) + line.lstrip() + else: + # Check for templated parameter that is split across multiple lines + endpos = line.rfind('>') + if endpos > -1: + (_, startline, startpos) = ReverseCloseExpression( + clean_lines, linenum, endpos) + if startpos > -1 and startline < linenum: + # Found the matching < on an earlier line, collect all + # pieces up to current line. + line = '' + for i in xrange(startline, linenum + 1): + line += clean_lines.elided[i].strip() + + # Check for non-const references in function parameters. A single '&' may + # found in the following places: + # inside expression: binary & for bitwise AND + # inside expression: unary & for taking the address of something + # inside declarators: reference parameter + # We will exclude the first two cases by checking that we are not inside a + # function body, including one that was just introduced by a trailing '{'. + # TODO(unknown): Doesn't account for 'catch(Exception& e)' [rare]. + if (nesting_state.previous_stack_top and + not (isinstance(nesting_state.previous_stack_top, _ClassInfo) or + isinstance(nesting_state.previous_stack_top, _NamespaceInfo))): + # Not at toplevel, not within a class, and not within a namespace + return + + # Avoid initializer lists. We only need to scan back from the + # current line for something that starts with ':'. + # + # We don't need to check the current line, since the '&' would + # appear inside the second set of parentheses on the current line as + # opposed to the first set. + if linenum > 0: + for i in xrange(linenum - 1, max(0, linenum - 10), -1): + previous_line = clean_lines.elided[i] + if not Search(r'[),]\s*$', previous_line): + break + if Match(r'^\s*:\s+\S', previous_line): + return + + # Avoid preprocessors + if Search(r'\\\s*$', line): + return + + # Avoid constructor initializer lists + if IsInitializerList(clean_lines, linenum): + return + + # We allow non-const references in a few standard places, like functions + # called "swap()" or iostream operators like "<<" or ">>". Do not check + # those function parameters. + # + # We also accept & in static_assert, which looks like a function but + # it's actually a declaration expression. + whitelisted_functions = (r'(?:[sS]wap(?:<\w:+>)?|' + r'operator\s*[<>][<>]|' + r'static_assert|COMPILE_ASSERT' + r')\s*\(') + if Search(whitelisted_functions, line): + return + elif not Search(r'\S+\([^)]*$', line): + # Don't see a whitelisted function on this line. Actually we + # didn't see any function name on this line, so this is likely a + # multi-line parameter list. Try a bit harder to catch this case. + for i in xrange(2): + if (linenum > i and + Search(whitelisted_functions, clean_lines.elided[linenum - i - 1])): + return + + decls = ReplaceAll(r'{[^}]*}', ' ', line) # exclude function body + for parameter in re.findall(_RE_PATTERN_REF_PARAM, decls): + if (not Match(_RE_PATTERN_CONST_REF_PARAM, parameter) and + not Match(_RE_PATTERN_REF_STREAM_PARAM, parameter)): + error(filename, linenum, 'runtime/references', 2, + 'Is this a non-const reference? ' + 'If so, make const or use a pointer: ' + + ReplaceAll(' *<', '<', parameter)) + + +def CheckCasts(filename, clean_lines, linenum, error): + """Various cast related checks. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # Check to see if they're using an conversion function cast. + # I just try to capture the most common basic types, though there are more. + # Parameterless conversion functions, such as bool(), are allowed as they are + # probably a member operator declaration or default constructor. + match = Search( + r'(\bnew\s+(?:const\s+)?|\S<\s*(?:const\s+)?)?\b' + r'(int|float|double|bool|char|int32|uint32|int64|uint64)' + r'(\([^)].*)', line) + expecting_function = ExpectingFunctionArgs(clean_lines, linenum) + if match and not expecting_function: + matched_type = match.group(2) + + # matched_new_or_template is used to silence two false positives: + # - New operators + # - Template arguments with function types + # + # For template arguments, we match on types immediately following + # an opening bracket without any spaces. This is a fast way to + # silence the common case where the function type is the first + # template argument. False negative with less-than comparison is + # avoided because those operators are usually followed by a space. + # + # function // bracket + no space = false positive + # value < double(42) // bracket + space = true positive + matched_new_or_template = match.group(1) + + # Avoid arrays by looking for brackets that come after the closing + # parenthesis. + if Match(r'\([^()]+\)\s*\[', match.group(3)): + return + + # Other things to ignore: + # - Function pointers + # - Casts to pointer types + # - Placement new + # - Alias declarations + matched_funcptr = match.group(3) + if (matched_new_or_template is None and + not (matched_funcptr and + (Match(r'\((?:[^() ]+::\s*\*\s*)?[^() ]+\)\s*\(', + matched_funcptr) or + matched_funcptr.startswith('(*)'))) and + not Match(r'\s*using\s+\S+\s*=\s*' + matched_type, line) and + not Search(r'new\(\S+\)\s*' + matched_type, line)): + error(filename, linenum, 'readability/casting', 4, + 'Using deprecated casting style. ' + 'Use static_cast<%s>(...) instead' % + matched_type) + + if not expecting_function: + CheckCStyleCast(filename, clean_lines, linenum, 'static_cast', + r'\((int|float|double|bool|char|u?int(16|32|64))\)', error) + + # This doesn't catch all cases. Consider (const char * const)"hello". + # + # (char *) "foo" should always be a const_cast (reinterpret_cast won't + # compile). + if CheckCStyleCast(filename, clean_lines, linenum, 'const_cast', + r'\((char\s?\*+\s?)\)\s*"', error): + pass + else: + # Check pointer casts for other than string constants + CheckCStyleCast(filename, clean_lines, linenum, 'reinterpret_cast', + r'\((\w+\s?\*+\s?)\)', error) + + # In addition, we look for people taking the address of a cast. This + # is dangerous -- casts can assign to temporaries, so the pointer doesn't + # point where you think. + # + # Some non-identifier character is required before the '&' for the + # expression to be recognized as a cast. These are casts: + # expression = &static_cast(temporary()); + # function(&(int*)(temporary())); + # + # This is not a cast: + # reference_type&(int* function_param); + match = Search( + r'(?:[^\w]&\(([^)*][^)]*)\)[\w(])|' + r'(?:[^\w]&(static|dynamic|down|reinterpret)_cast\b)', line) + if match: + # Try a better error message when the & is bound to something + # dereferenced by the casted pointer, as opposed to the casted + # pointer itself. + parenthesis_error = False + match = Match(r'^(.*&(?:static|dynamic|down|reinterpret)_cast\b)<', line) + if match: + _, y1, x1 = CloseExpression(clean_lines, linenum, len(match.group(1))) + if x1 >= 0 and clean_lines.elided[y1][x1] == '(': + _, y2, x2 = CloseExpression(clean_lines, y1, x1) + if x2 >= 0: + extended_line = clean_lines.elided[y2][x2:] + if y2 < clean_lines.NumLines() - 1: + extended_line += clean_lines.elided[y2 + 1] + if Match(r'\s*(?:->|\[)', extended_line): + parenthesis_error = True + + if parenthesis_error: + error(filename, linenum, 'readability/casting', 4, + ('Are you taking an address of something dereferenced ' + 'from a cast? Wrapping the dereferenced expression in ' + 'parentheses will make the binding more obvious')) + else: + error(filename, linenum, 'runtime/casting', 4, + ('Are you taking an address of a cast? ' + 'This is dangerous: could be a temp var. ' + 'Take the address before doing the cast, rather than after')) + + +def CheckCStyleCast(filename, clean_lines, linenum, cast_type, pattern, error): + """Checks for a C-style cast by looking for the pattern. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + cast_type: The string for the C++ cast to recommend. This is either + reinterpret_cast, static_cast, or const_cast, depending. + pattern: The regular expression used to find C-style casts. + error: The function to call with any errors found. + + Returns: + True if an error was emitted. + False otherwise. + """ + line = clean_lines.elided[linenum] + match = Search(pattern, line) + if not match: + return False + + # Exclude lines with keywords that tend to look like casts + context = line[0:match.start(1) - 1] + if Match(r'.*\b(?:sizeof|alignof|alignas|[_A-Z][_A-Z0-9]*)\s*$', context): + return False + + # Try expanding current context to see if we one level of + # parentheses inside a macro. + if linenum > 0: + for i in xrange(linenum - 1, max(0, linenum - 5), -1): + context = clean_lines.elided[i] + context + if Match(r'.*\b[_A-Z][_A-Z0-9]*\s*\((?:\([^()]*\)|[^()])*$', context): + return False + + # operator++(int) and operator--(int) + if context.endswith(' operator++') or context.endswith(' operator--'): + return False + + # A single unnamed argument for a function tends to look like old style cast. + # If we see those, don't issue warnings for deprecated casts. + remainder = line[match.end(0):] + if Match(r'^\s*(?:;|const\b|throw\b|final\b|override\b|[=>{),]|->)', + remainder): + return False + + # At this point, all that should be left is actual casts. + error(filename, linenum, 'readability/casting', 4, + 'Using C-style cast. Use %s<%s>(...) instead' % + (cast_type, match.group(1))) + + return True + + +def ExpectingFunctionArgs(clean_lines, linenum): + """Checks whether where function type arguments are expected. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + + Returns: + True if the line at 'linenum' is inside something that expects arguments + of function types. + """ + line = clean_lines.elided[linenum] + return (Match(r'^\s*MOCK_(CONST_)?METHOD\d+(_T)?\(', line) or + (linenum >= 2 and + (Match(r'^\s*MOCK_(?:CONST_)?METHOD\d+(?:_T)?\((?:\S+,)?\s*$', + clean_lines.elided[linenum - 1]) or + Match(r'^\s*MOCK_(?:CONST_)?METHOD\d+(?:_T)?\(\s*$', + clean_lines.elided[linenum - 2]) or + Search(r'\bstd::m?function\s*\<\s*$', + clean_lines.elided[linenum - 1])))) + + +_HEADERS_CONTAINING_TEMPLATES = ( + ('', ('deque',)), + ('', ('unary_function', 'binary_function', + 'plus', 'minus', 'multiplies', 'divides', 'modulus', + 'negate', + 'equal_to', 'not_equal_to', 'greater', 'less', + 'greater_equal', 'less_equal', + 'logical_and', 'logical_or', 'logical_not', + 'unary_negate', 'not1', 'binary_negate', 'not2', + 'bind1st', 'bind2nd', + 'pointer_to_unary_function', + 'pointer_to_binary_function', + 'ptr_fun', + 'mem_fun_t', 'mem_fun', 'mem_fun1_t', 'mem_fun1_ref_t', + 'mem_fun_ref_t', + 'const_mem_fun_t', 'const_mem_fun1_t', + 'const_mem_fun_ref_t', 'const_mem_fun1_ref_t', + 'mem_fun_ref', + )), + ('', ('numeric_limits',)), + ('', ('list',)), + ('', ('map', 'multimap',)), + ('', ('allocator', 'make_shared', 'make_unique', 'shared_ptr', + 'unique_ptr', 'weak_ptr')), + ('', ('queue', 'priority_queue',)), + ('', ('set', 'multiset',)), + ('', ('stack',)), + ('', ('char_traits', 'basic_string',)), + ('', ('tuple',)), + ('', ('unordered_map', 'unordered_multimap')), + ('', ('unordered_set', 'unordered_multiset')), + ('', ('pair',)), + ('', ('vector',)), + + # gcc extensions. + # Note: std::hash is their hash, ::hash is our hash + ('', ('hash_map', 'hash_multimap',)), + ('', ('hash_set', 'hash_multiset',)), + ('', ('slist',)), + ) + +_HEADERS_MAYBE_TEMPLATES = ( + ('', ('copy', 'max', 'min', 'min_element', 'sort', + 'transform', + )), + ('', ('forward', 'make_pair', 'move', 'swap')), + ) + +_RE_PATTERN_STRING = re.compile(r'\bstring\b') + +_re_pattern_headers_maybe_templates = [] +for _header, _templates in _HEADERS_MAYBE_TEMPLATES: + for _template in _templates: + # Match max(..., ...), max(..., ...), but not foo->max, foo.max or + # type::max(). + _re_pattern_headers_maybe_templates.append( + (re.compile(r'[^>.]\b' + _template + r'(<.*?>)?\([^\)]'), + _template, + _header)) + +# Other scripts may reach in and modify this pattern. +_re_pattern_templates = [] +for _header, _templates in _HEADERS_CONTAINING_TEMPLATES: + for _template in _templates: + _re_pattern_templates.append( + (re.compile(r'(\<|\b)' + _template + r'\s*\<'), + _template + '<>', + _header)) + + +def FilesBelongToSameModule(filename_cc, filename_h): + """Check if these two filenames belong to the same module. + + The concept of a 'module' here is a as follows: + foo.h, foo-inl.h, foo.cc, foo_test.cc and foo_unittest.cc belong to the + same 'module' if they are in the same directory. + some/path/public/xyzzy and some/path/internal/xyzzy are also considered + to belong to the same module here. + + If the filename_cc contains a longer path than the filename_h, for example, + '/absolute/path/to/base/sysinfo.cc', and this file would include + 'base/sysinfo.h', this function also produces the prefix needed to open the + header. This is used by the caller of this function to more robustly open the + header file. We don't have access to the real include paths in this context, + so we need this guesswork here. + + Known bugs: tools/base/bar.cc and base/bar.h belong to the same module + according to this implementation. Because of this, this function gives + some false positives. This should be sufficiently rare in practice. + + Args: + filename_cc: is the path for the .cc file + filename_h: is the path for the header path + + Returns: + Tuple with a bool and a string: + bool: True if filename_cc and filename_h belong to the same module. + string: the additional prefix needed to open the header file. + """ + + fileinfo = FileInfo(filename_cc) + if not fileinfo.IsSource(): + return (False, '') + filename_cc = filename_cc[:-len(fileinfo.Extension())] + matched_test_suffix = Search(_TEST_FILE_SUFFIX, fileinfo.BaseName()) + if matched_test_suffix: + filename_cc = filename_cc[:-len(matched_test_suffix.group(1))] + filename_cc = filename_cc.replace('/public/', '/') + filename_cc = filename_cc.replace('/internal/', '/') + + if not filename_h.endswith('.h'): + return (False, '') + filename_h = filename_h[:-len('.h')] + if filename_h.endswith('-inl'): + filename_h = filename_h[:-len('-inl')] + filename_h = filename_h.replace('/public/', '/') + filename_h = filename_h.replace('/internal/', '/') + + files_belong_to_same_module = filename_cc.endswith(filename_h) + common_path = '' + if files_belong_to_same_module: + common_path = filename_cc[:-len(filename_h)] + return files_belong_to_same_module, common_path + + +def UpdateIncludeState(filename, include_dict, io=codecs): + """Fill up the include_dict with new includes found from the file. + + Args: + filename: the name of the header to read. + include_dict: a dictionary in which the headers are inserted. + io: The io factory to use to read the file. Provided for testability. + + Returns: + True if a header was successfully added. False otherwise. + """ + headerfile = None + try: + headerfile = io.open(filename, 'r', 'utf8', 'replace') + except IOError: + return False + linenum = 0 + for line in headerfile: + linenum += 1 + clean_line = CleanseComments(line) + match = _RE_PATTERN_INCLUDE.search(clean_line) + if match: + include = match.group(2) + include_dict.setdefault(include, linenum) + return True + + +def CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error, + io=codecs): + """Reports for missing stl includes. + + This function will output warnings to make sure you are including the headers + necessary for the stl containers and functions that you use. We only give one + reason to include a header. For example, if you use both equal_to<> and + less<> in a .h file, only one (the latter in the file) of these will be + reported as a reason to include the . + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + include_state: An _IncludeState instance. + error: The function to call with any errors found. + io: The IO factory to use to read the header file. Provided for unittest + injection. + """ + required = {} # A map of header name to linenumber and the template entity. + # Example of required: { '': (1219, 'less<>') } + + for linenum in xrange(clean_lines.NumLines()): + line = clean_lines.elided[linenum] + if not line or line[0] == '#': + continue + + # String is special -- it is a non-templatized type in STL. + matched = _RE_PATTERN_STRING.search(line) + if matched: + # Don't warn about strings in non-STL namespaces: + # (We check only the first match per line; good enough.) + prefix = line[:matched.start()] + if prefix.endswith('std::') or not prefix.endswith('::'): + required[''] = (linenum, 'string') + + for pattern, template, header in _re_pattern_headers_maybe_templates: + if pattern.search(line): + required[header] = (linenum, template) + + # The following function is just a speed up, no semantics are changed. + if not '<' in line: # Reduces the cpu time usage by skipping lines. + continue + + for pattern, template, header in _re_pattern_templates: + matched = pattern.search(line) + if matched: + # Don't warn about IWYU in non-STL namespaces: + # (We check only the first match per line; good enough.) + prefix = line[:matched.start()] + if prefix.endswith('std::') or not prefix.endswith('::'): + required[header] = (linenum, template) + + # The policy is that if you #include something in foo.h you don't need to + # include it again in foo.cc. Here, we will look at possible includes. + # Let's flatten the include_state include_list and copy it into a dictionary. + include_dict = dict([item for sublist in include_state.include_list + for item in sublist]) + + # Did we find the header for this file (if any) and successfully load it? + header_found = False + + # Use the absolute path so that matching works properly. + abs_filename = FileInfo(filename).FullName() + + # For Emacs's flymake. + # If cpplint is invoked from Emacs's flymake, a temporary file is generated + # by flymake and that file name might end with '_flymake.cc'. In that case, + # restore original file name here so that the corresponding header file can be + # found. + # e.g. If the file name is 'foo_flymake.cc', we should search for 'foo.h' + # instead of 'foo_flymake.h' + abs_filename = re.sub(r'_flymake\.cc$', '.cc', abs_filename) + + # include_dict is modified during iteration, so we iterate over a copy of + # the keys. + header_keys = include_dict.keys() + for header in header_keys: + (same_module, common_path) = FilesBelongToSameModule(abs_filename, header) + fullpath = common_path + header + if same_module and UpdateIncludeState(fullpath, include_dict, io): + header_found = True + + # If we can't find the header file for a .cc, assume it's because we don't + # know where to look. In that case we'll give up as we're not sure they + # didn't include it in the .h file. + # TODO(unknown): Do a better job of finding .h files so we are confident that + # not having the .h file means there isn't one. + if filename.endswith('.cc') and not header_found: + return + + # All the lines have been processed, report the errors found. + for required_header_unstripped in required: + template = required[required_header_unstripped][1] + if required_header_unstripped.strip('<>"') not in include_dict: + error(filename, required[required_header_unstripped][0], + 'build/include_what_you_use', 4, + 'Add #include ' + required_header_unstripped + ' for ' + template) + + +_RE_PATTERN_EXPLICIT_MAKEPAIR = re.compile(r'\bmake_pair\s*<') + + +def CheckMakePairUsesDeduction(filename, clean_lines, linenum, error): + """Check that make_pair's template arguments are deduced. + + G++ 4.6 in C++11 mode fails badly if make_pair's template arguments are + specified explicitly, and such use isn't intended in any case. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + match = _RE_PATTERN_EXPLICIT_MAKEPAIR.search(line) + if match: + error(filename, linenum, 'build/explicit_make_pair', + 4, # 4 = high confidence + 'For C++11-compatibility, omit template arguments from make_pair' + ' OR use pair directly OR if appropriate, construct a pair directly') + + +def CheckRedundantVirtual(filename, clean_lines, linenum, error): + """Check if line contains a redundant "virtual" function-specifier. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + # Look for "virtual" on current line. + line = clean_lines.elided[linenum] + virtual = Match(r'^(.*)(\bvirtual\b)(.*)$', line) + if not virtual: return + + # Ignore "virtual" keywords that are near access-specifiers. These + # are only used in class base-specifier and do not apply to member + # functions. + if (Search(r'\b(public|protected|private)\s+$', virtual.group(1)) or + Match(r'^\s+(public|protected|private)\b', virtual.group(3))): + return + + # Ignore the "virtual" keyword from virtual base classes. Usually + # there is a column on the same line in these cases (virtual base + # classes are rare in google3 because multiple inheritance is rare). + if Match(r'^.*[^:]:[^:].*$', line): return + + # Look for the next opening parenthesis. This is the start of the + # parameter list (possibly on the next line shortly after virtual). + # TODO(unknown): doesn't work if there are virtual functions with + # decltype() or other things that use parentheses, but csearch suggests + # that this is rare. + end_col = -1 + end_line = -1 + start_col = len(virtual.group(2)) + for start_line in xrange(linenum, min(linenum + 3, clean_lines.NumLines())): + line = clean_lines.elided[start_line][start_col:] + parameter_list = Match(r'^([^(]*)\(', line) + if parameter_list: + # Match parentheses to find the end of the parameter list + (_, end_line, end_col) = CloseExpression( + clean_lines, start_line, start_col + len(parameter_list.group(1))) + break + start_col = 0 + + if end_col < 0: + return # Couldn't find end of parameter list, give up + + # Look for "override" or "final" after the parameter list + # (possibly on the next few lines). + for i in xrange(end_line, min(end_line + 3, clean_lines.NumLines())): + line = clean_lines.elided[i][end_col:] + match = Search(r'\b(override|final)\b', line) + if match: + error(filename, linenum, 'readability/inheritance', 4, + ('"virtual" is redundant since function is ' + 'already declared as "%s"' % match.group(1))) + + # Set end_col to check whole lines after we are done with the + # first line. + end_col = 0 + if Search(r'[^\w]\s*$', line): + break + + +def CheckRedundantOverrideOrFinal(filename, clean_lines, linenum, error): + """Check if line contains a redundant "override" or "final" virt-specifier. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + # Look for closing parenthesis nearby. We need one to confirm where + # the declarator ends and where the virt-specifier starts to avoid + # false positives. + line = clean_lines.elided[linenum] + declarator_end = line.rfind(')') + if declarator_end >= 0: + fragment = line[declarator_end:] + else: + if linenum > 1 and clean_lines.elided[linenum - 1].rfind(')') >= 0: + fragment = line + else: + return + + # Check that at most one of "override" or "final" is present, not both + if Search(r'\boverride\b', fragment) and Search(r'\bfinal\b', fragment): + error(filename, linenum, 'readability/inheritance', 4, + ('"override" is redundant since function is ' + 'already declared as "final"')) + + + + +# Returns true if we are at a new block, and it is directly +# inside of a namespace. +def IsBlockInNameSpace(nesting_state, is_forward_declaration): + """Checks that the new block is directly in a namespace. + + Args: + nesting_state: The _NestingState object that contains info about our state. + is_forward_declaration: If the class is a forward declared class. + Returns: + Whether or not the new block is directly in a namespace. + """ + if is_forward_declaration: + if len(nesting_state.stack) >= 1 and ( + isinstance(nesting_state.stack[-1], _NamespaceInfo)): + return True + else: + return False + + return (len(nesting_state.stack) > 1 and + nesting_state.stack[-1].check_namespace_indentation and + isinstance(nesting_state.stack[-2], _NamespaceInfo)) + + +def ShouldCheckNamespaceIndentation(nesting_state, is_namespace_indent_item, + raw_lines_no_comments, linenum): + """This method determines if we should apply our namespace indentation check. + + Args: + nesting_state: The current nesting state. + is_namespace_indent_item: If we just put a new class on the stack, True. + If the top of the stack is not a class, or we did not recently + add the class, False. + raw_lines_no_comments: The lines without the comments. + linenum: The current line number we are processing. + + Returns: + True if we should apply our namespace indentation check. Currently, it + only works for classes and namespaces inside of a namespace. + """ + + is_forward_declaration = IsForwardClassDeclaration(raw_lines_no_comments, + linenum) + + if not (is_namespace_indent_item or is_forward_declaration): + return False + + # If we are in a macro, we do not want to check the namespace indentation. + if IsMacroDefinition(raw_lines_no_comments, linenum): + return False + + return IsBlockInNameSpace(nesting_state, is_forward_declaration) + + +# Call this method if the line is directly inside of a namespace. +# If the line above is blank (excluding comments) or the start of +# an inner namespace, it cannot be indented. +def CheckItemIndentationInNamespace(filename, raw_lines_no_comments, linenum, + error): + line = raw_lines_no_comments[linenum] + if Match(r'^\s+', line): + error(filename, linenum, 'runtime/indentation_namespace', 4, + 'Do not indent within a namespace') + + +def ProcessLine(filename, file_extension, clean_lines, line, + include_state, function_state, nesting_state, error, + extra_check_functions=[]): + """Processes a single line in the file. + + Args: + filename: Filename of the file that is being processed. + file_extension: The extension (dot not included) of the file. + clean_lines: An array of strings, each representing a line of the file, + with comments stripped. + line: Number of line being processed. + include_state: An _IncludeState instance in which the headers are inserted. + function_state: A _FunctionState instance which counts function lines, etc. + nesting_state: A NestingState instance which maintains information about + the current stack of nested blocks being parsed. + error: A callable to which errors are reported, which takes 4 arguments: + filename, line number, error level, and message + extra_check_functions: An array of additional check functions that will be + run on each source line. Each function takes 4 + arguments: filename, clean_lines, line, error + """ + raw_lines = clean_lines.raw_lines + ParseNolintSuppressions(filename, raw_lines[line], line, error) + nesting_state.Update(filename, clean_lines, line, error) + CheckForNamespaceIndentation(filename, nesting_state, clean_lines, line, + error) + if nesting_state.InAsmBlock(): return + CheckForFunctionLengths(filename, clean_lines, line, function_state, error) + CheckForMultilineCommentsAndStrings(filename, clean_lines, line, error) + CheckStyle(filename, clean_lines, line, file_extension, nesting_state, error) + CheckLanguage(filename, clean_lines, line, file_extension, include_state, + nesting_state, error) + CheckForNonConstReference(filename, clean_lines, line, nesting_state, error) + CheckForNonStandardConstructs(filename, clean_lines, line, + nesting_state, error) + CheckVlogArguments(filename, clean_lines, line, error) + CheckPosixThreading(filename, clean_lines, line, error) + CheckInvalidIncrement(filename, clean_lines, line, error) + CheckMakePairUsesDeduction(filename, clean_lines, line, error) + CheckRedundantVirtual(filename, clean_lines, line, error) + CheckRedundantOverrideOrFinal(filename, clean_lines, line, error) + for check_fn in extra_check_functions: + check_fn(filename, clean_lines, line, error) + +def FlagCxx11Features(filename, clean_lines, linenum, error): + """Flag those c++11 features that we only allow in certain places. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + include = Match(r'\s*#\s*include\s+[<"]([^<"]+)[">]', line) + + # Flag unapproved C++ TR1 headers. + if include and include.group(1).startswith('tr1/'): + error(filename, linenum, 'build/c++tr1', 5, + ('C++ TR1 headers such as <%s> are unapproved.') % include.group(1)) + + # Flag unapproved C++11 headers. + if include and include.group(1) in ('cfenv', + 'condition_variable', + 'fenv.h', + 'future', + 'mutex', + 'thread', + 'chrono', + 'ratio', + 'regex', + 'system_error', + ): + error(filename, linenum, 'build/c++11', 5, + ('<%s> is an unapproved C++11 header.') % include.group(1)) + + # The only place where we need to worry about C++11 keywords and library + # features in preprocessor directives is in macro definitions. + if Match(r'\s*#', line) and not Match(r'\s*#\s*define\b', line): return + + # These are classes and free functions. The classes are always + # mentioned as std::*, but we only catch the free functions if + # they're not found by ADL. They're alphabetical by header. + for top_name in ( + # type_traits + 'alignment_of', + 'aligned_union', + ): + if Search(r'\bstd::%s\b' % top_name, line): + error(filename, linenum, 'build/c++11', 5, + ('std::%s is an unapproved C++11 class or function. Send c-style ' + 'an example of where it would make your code more readable, and ' + 'they may let you use it.') % top_name) + + +def FlagCxx14Features(filename, clean_lines, linenum, error): + """Flag those C++14 features that we restrict. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + include = Match(r'\s*#\s*include\s+[<"]([^<"]+)[">]', line) + + # Flag unapproved C++14 headers. + if include and include.group(1) in ('scoped_allocator', 'shared_mutex'): + error(filename, linenum, 'build/c++14', 5, + ('<%s> is an unapproved C++14 header.') % include.group(1)) + + +def ProcessFileData(filename, file_extension, lines, error, + extra_check_functions=[]): + """Performs lint checks and reports any errors to the given error function. + + Args: + filename: Filename of the file that is being processed. + file_extension: The extension (dot not included) of the file. + lines: An array of strings, each representing a line of the file, with the + last element being empty if the file is terminated with a newline. + error: A callable to which errors are reported, which takes 4 arguments: + filename, line number, error level, and message + extra_check_functions: An array of additional check functions that will be + run on each source line. Each function takes 4 + arguments: filename, clean_lines, line, error + """ + lines = (['// marker so line numbers and indices both start at 1'] + lines + + ['// marker so line numbers end in a known way']) + + include_state = _IncludeState() + function_state = _FunctionState() + nesting_state = NestingState() + + ResetNolintSuppressions() + + CheckForCopyright(filename, lines, error) + ProcessGlobalSuppresions(lines) + RemoveMultiLineComments(filename, lines, error) + clean_lines = CleansedLines(lines) + + if IsHeaderExtension(file_extension): + CheckForHeaderGuard(filename, clean_lines, error) + + for line in xrange(clean_lines.NumLines()): + ProcessLine(filename, file_extension, clean_lines, line, + include_state, function_state, nesting_state, error, + extra_check_functions) + FlagCxx11Features(filename, clean_lines, line, error) + nesting_state.CheckCompletedBlocks(filename, error) + + CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error) + + # Check that the .cc file has included its header if it exists. + if _IsSourceExtension(file_extension): + CheckHeaderFileIncluded(filename, include_state, error) + + # We check here rather than inside ProcessLine so that we see raw + # lines rather than "cleaned" lines. + CheckForBadCharacters(filename, lines, error) + + CheckForNewlineAtEOF(filename, lines, error) + +def ProcessConfigOverrides(filename): + """ Loads the configuration files and processes the config overrides. + + Args: + filename: The name of the file being processed by the linter. + + Returns: + False if the current |filename| should not be processed further. + """ + + abs_filename = os.path.abspath(filename) + cfg_filters = [] + keep_looking = True + while keep_looking: + abs_path, base_name = os.path.split(abs_filename) + if not base_name: + break # Reached the root directory. + + cfg_file = os.path.join(abs_path, "CPPLINT.cfg") + abs_filename = abs_path + if not os.path.isfile(cfg_file): + continue + + try: + with open(cfg_file) as file_handle: + for line in file_handle: + line, _, _ = line.partition('#') # Remove comments. + if not line.strip(): + continue + + name, _, val = line.partition('=') + name = name.strip() + val = val.strip() + if name == 'set noparent': + keep_looking = False + elif name == 'filter': + cfg_filters.append(val) + elif name == 'exclude_files': + # When matching exclude_files pattern, use the base_name of + # the current file name or the directory name we are processing. + # For example, if we are checking for lint errors in /foo/bar/baz.cc + # and we found the .cfg file at /foo/CPPLINT.cfg, then the config + # file's "exclude_files" filter is meant to be checked against "bar" + # and not "baz" nor "bar/baz.cc". + if base_name: + pattern = re.compile(val) + if pattern.match(base_name): + sys.stderr.write('Ignoring "%s": file excluded by "%s". ' + 'File path component "%s" matches ' + 'pattern "%s"\n' % + (filename, cfg_file, base_name, val)) + return False + elif name == 'linelength': + global _line_length + try: + _line_length = int(val) + except ValueError: + sys.stderr.write('Line length must be numeric.') + elif name == 'root': + global _root + _root = val + elif name == 'headers': + ProcessHppHeadersOption(val) + else: + sys.stderr.write( + 'Invalid configuration option (%s) in file %s\n' % + (name, cfg_file)) + + except IOError: + sys.stderr.write( + "Skipping config file '%s': Can't open for reading\n" % cfg_file) + keep_looking = False + + # Apply all the accumulated filters in reverse order (top-level directory + # config options having the least priority). + for filter in reversed(cfg_filters): + _AddFilters(filter) + + return True + + +def ProcessFile(filename, vlevel, extra_check_functions=[]): + """Does google-lint on a single file. + + Args: + filename: The name of the file to parse. + + vlevel: The level of errors to report. Every error of confidence + >= verbose_level will be reported. 0 is a good default. + + extra_check_functions: An array of additional check functions that will be + run on each source line. Each function takes 4 + arguments: filename, clean_lines, line, error + """ + + _SetVerboseLevel(vlevel) + _BackupFilters() + + if not ProcessConfigOverrides(filename): + _RestoreFilters() + return + + lf_lines = [] + crlf_lines = [] + try: + # Support the UNIX convention of using "-" for stdin. Note that + # we are not opening the file with universal newline support + # (which codecs doesn't support anyway), so the resulting lines do + # contain trailing '\r' characters if we are reading a file that + # has CRLF endings. + # If after the split a trailing '\r' is present, it is removed + # below. + if filename == '-': + lines = codecs.StreamReaderWriter(sys.stdin, + codecs.getreader('utf8'), + codecs.getwriter('utf8'), + 'replace').read().split('\n') + else: + lines = codecs.open(filename, 'r', 'utf8', 'replace').read().split('\n') + + # Remove trailing '\r'. + # The -1 accounts for the extra trailing blank line we get from split() + for linenum in range(len(lines) - 1): + if lines[linenum].endswith('\r'): + lines[linenum] = lines[linenum].rstrip('\r') + crlf_lines.append(linenum + 1) + else: + lf_lines.append(linenum + 1) + + except IOError: + sys.stderr.write( + "Skipping input '%s': Can't open for reading\n" % filename) + _RestoreFilters() + return + + # Note, if no dot is found, this will give the entire filename as the ext. + file_extension = filename[filename.rfind('.') + 1:] + + # When reading from stdin, the extension is unknown, so no cpplint tests + # should rely on the extension. + if filename != '-' and file_extension not in _valid_extensions: + sys.stderr.write('Ignoring %s; not a valid file name ' + '(%s)\n' % (filename, ', '.join(_valid_extensions))) + else: + ProcessFileData(filename, file_extension, lines, Error, + extra_check_functions) + + # If end-of-line sequences are a mix of LF and CR-LF, issue + # warnings on the lines with CR. + # + # Don't issue any warnings if all lines are uniformly LF or CR-LF, + # since critique can handle these just fine, and the style guide + # doesn't dictate a particular end of line sequence. + # + # We can't depend on os.linesep to determine what the desired + # end-of-line sequence should be, since that will return the + # server-side end-of-line sequence. + if lf_lines and crlf_lines: + # Warn on every line with CR. An alternative approach might be to + # check whether the file is mostly CRLF or just LF, and warn on the + # minority, we bias toward LF here since most tools prefer LF. + for linenum in crlf_lines: + Error(filename, linenum, 'whitespace/newline', 1, + 'Unexpected \\r (^M) found; better to use only \\n') + + sys.stdout.write('Done processing %s\n' % filename) + _RestoreFilters() + + +def PrintUsage(message): + """Prints a brief usage string and exits, optionally with an error message. + + Args: + message: The optional error message. + """ + sys.stderr.write(_USAGE) + if message: + sys.exit('\nFATAL ERROR: ' + message) + else: + sys.exit(1) + + +def PrintCategories(): + """Prints a list of all the error-categories used by error messages. + + These are the categories used to filter messages via --filter. + """ + sys.stderr.write(''.join(' %s\n' % cat for cat in _ERROR_CATEGORIES)) + sys.exit(0) + + +def ParseArguments(args): + """Parses the command line arguments. + + This may set the output format and verbosity level as side-effects. + + Args: + args: The command line arguments: + + Returns: + The list of filenames to lint. + """ + try: + (opts, filenames) = getopt.getopt(args, '', ['help', 'output=', 'verbose=', + 'counting=', + 'filter=', + 'root=', + 'linelength=', + 'extensions=', + 'headers=']) + except getopt.GetoptError: + PrintUsage('Invalid arguments.') + + verbosity = _VerboseLevel() + output_format = _OutputFormat() + filters = '' + counting_style = '' + + for (opt, val) in opts: + if opt == '--help': + PrintUsage(None) + elif opt == '--output': + if val not in ('emacs', 'vs7', 'eclipse'): + PrintUsage('The only allowed output formats are emacs, vs7 and eclipse.') + output_format = val + elif opt == '--verbose': + verbosity = int(val) + elif opt == '--filter': + filters = val + if not filters: + PrintCategories() + elif opt == '--counting': + if val not in ('total', 'toplevel', 'detailed'): + PrintUsage('Valid counting options are total, toplevel, and detailed') + counting_style = val + elif opt == '--root': + global _root + _root = val + elif opt == '--linelength': + global _line_length + try: + _line_length = int(val) + except ValueError: + PrintUsage('Line length must be digits.') + elif opt == '--extensions': + global _valid_extensions + try: + _valid_extensions = set(val.split(',')) + except ValueError: + PrintUsage('Extensions must be comma seperated list.') + elif opt == '--headers': + ProcessHppHeadersOption(val) + + if not filenames: + PrintUsage('No files were specified.') + + _SetOutputFormat(output_format) + _SetVerboseLevel(verbosity) + _SetFilters(filters) + _SetCountingStyle(counting_style) + + return filenames + + +def main(): + filenames = ParseArguments(sys.argv[1:]) + + # Change stderr to write with replacement characters so we don't die + # if we try to print something containing non-ASCII characters. + sys.stderr = codecs.StreamReaderWriter(sys.stderr, + codecs.getreader('utf8'), + codecs.getwriter('utf8'), + 'replace') + + _cpplint_state.ResetErrorCounts() + for filename in filenames: + ProcessFile(filename, _cpplint_state.verbose_level) + _cpplint_state.PrintErrorCounts() + + sys.exit(_cpplint_state.error_count > 0) + + +if __name__ == '__main__': + main() diff --git a/src/esplusplayer/CMakeLists.txt b/src/esplusplayer/CMakeLists.txt new file mode 100755 index 0000000..73a51ad --- /dev/null +++ b/src/esplusplayer/CMakeLists.txt @@ -0,0 +1,67 @@ +PROJECT(esplusplayer) + +SET(fw_name "${PROJECT_NAME}") +SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib) +SET(${fw_name}_LDFLAGS) + +IF(${PRODUCT_TYPE_AUDIO} STREQUAL "no") +SET(ADD_LIBS + "espplayer-core" + "trackrenderer" + "mixer" +) +ELSE(${PRODUCT_TYPE_AUDIO} STREQUAL "no") +SET(ADD_LIBS + "espplayer-core" + "trackrenderer" +) +ENDIF(${PRODUCT_TYPE_AUDIO} STREQUAL "no") + +SET(${fw_name}_CXXFLAGS "-Wall -Werror -std=c++11 -fPIC -fno-lto -Wl,-z,relro -fstack-protector -DEFL_BETA_API_SUPPORT") + +SET(dependents "gstreamer-1.0 glib-2.0 dlog" + "boost" + "tv-resource-manager" + "elementary ecore ecore-wl2" + "jsoncpp") + +INCLUDE(FindPkgConfig) + +IF(CMAKE_SYSTEM_PROCESSOR STREQUAL armv7l) +pkg_check_modules(${fw_name} REQUIRED ${dependents}) +ELSE(CMAKE_SYSTEM_PROCESSOR STREQUAL armv7l) +pkg_check_modules(${fw_name} REQUIRED ${dependents}) +ENDIF(CMAKE_SYSTEM_PROCESSOR STREQUAL armv7l) + +FOREACH(flag ${${fw_name}_CFLAGS}) +SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${flag}") +ENDFOREACH(flag) + +FOREACH(flag ${${fw_name}_CXXFLAGS}) +SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_C_FLAGS} ${flag}") +ENDFOREACH(flag) +GET_FILENAME_COMPONENT(PARENT_DIR ${PROJECT_SOURCE_DIR} DIRECTORY) +INCLUDE_DIRECTORIES( + ${PROJECT_SOURCE_DIR}/include_internal + ${PARENT_DIR}/plusplayer-core/include_internal +) + +SET(CC_SRCS + ${PROJECT_SOURCE_DIR}/src/espacket_logger.cpp + ${PROJECT_SOURCE_DIR}/src/esplayer_drm.cpp + ${PROJECT_SOURCE_DIR}/src/esplusplayer.cpp + ${PROJECT_SOURCE_DIR}/src/esplayer.cpp + ${PROJECT_SOURCE_DIR}/src/elementary_stream.cpp + ${PROJECT_SOURCE_DIR}/src/espacket.cpp + ${PROJECT_SOURCE_DIR}/src/esplusplayer_capi.cpp +) + +ADD_LIBRARY(${fw_name} SHARED ${CC_SRCS}) + +SET_TARGET_PROPERTIES(${fw_name} PROPERTIES LINKER_LANGUAGE CXX) +TARGET_LINK_LIBRARIES(${fw_name} ${CMAKE_THREAD_LIBS_INIT} ${${fw_name}_LDFLAGS} ${ADD_LIBS}) + +INSTALL(TARGETS ${fw_name} DESTINATION ${LIB_INSTALL_DIR}) +INSTALL( + DIRECTORY ${INC_DIR}/ DESTINATION include/ +) diff --git a/src/esplusplayer/include_internal/esplayer/decoded_pkt_list.h b/src/esplusplayer/include_internal/esplayer/decoded_pkt_list.h new file mode 100755 index 0000000..ab15164 --- /dev/null +++ b/src/esplusplayer/include_internal/esplayer/decoded_pkt_list.h @@ -0,0 +1,271 @@ +// +// @ Copyright [2019] +// + +#ifndef __PLUSPLAYER_SRC_ESPLAYER_DECODED_PACKET_LIST_H__ +#define __PLUSPLAYER_SRC_ESPLAYER_DECODED_PACKET_LIST_H__ + +#include + +#include +#include +#include +#include + +#include "core/utils/plusplayer_log.h" +#include "plusplayer/types/buffer.h" + +namespace { +void DecodedPacketDeleter(esplusplayer_decoded_video_packet* packet) { + if (packet == nullptr || packet->surface_data == nullptr) return; + // LOG_DEBUG("packet[%p] deleted", packet); + tbm_surface_destroy(static_cast(packet->surface_data)); + packet->surface_data = NULL; +} +} // namespace + +namespace plusplayer { +struct DecodedPacketManagerInterface { + virtual ~DecodedPacketManagerInterface() = default; + virtual bool TryToAdd(esplusplayer_decoded_video_packet* packet) = 0; + virtual void Remove(esplusplayer_decoded_video_packet* packet) = 0; + virtual void Clear() = 0; + virtual void GetFreeTbmSurface(void** ptr, bool is_scale_change) = 0; +}; +struct CmaBufferInfo { + void* tbm_surf = nullptr; + bool tbm_is_free = true; + bool destroy_flag = false; +}; + +class AbstractDecodedPacketList : public DecodedPacketManagerInterface { + public: + explicit AbstractDecodedPacketList() = default; + virtual ~AbstractDecodedPacketList() = default; + using DecodedPacketPtr = + std::unique_ptr>; + + public: + virtual bool TryToAdd(esplusplayer_decoded_video_packet* packet) override { + if (packet == nullptr) { + LOG_ERROR("packet is nullptr"); + return false; + } + std::unique_lock lk(mtx_); + auto pkt_ptr = DecodedPacketPtr( + packet, [this](esplusplayer_decoded_video_packet* pkt) { + DecodedPacketDeleter(pkt); + delete pkt; + }); + if (IsAvailableInternal() == false) { + LOG_ERROR("not available to add a packet"); + return false; + } + // LOG_DEBUG("packet[%p] added", packet); + list_.emplace_back(std::move(pkt_ptr)); + return true; + } + virtual void Remove(esplusplayer_decoded_video_packet* packet) override { + if (packet == nullptr) return; + std::unique_lock lk(mtx_); + list_.remove_if([&packet](const DecodedPacketPtr& cur) { + if (cur.get() == packet) return true; + return false; + }); + } + virtual void Clear() override { + std::unique_lock lk(mtx_); + LOG_DEBUG("all packets are cleared"); + list_.clear(); + } + + virtual void GetFreeTbmSurface(void** ptr, bool is_scale_change) override { + return; + } + + protected: + const std::list& GetList() { return list_; } + + protected: + virtual bool IsAvailableInternal() = 0; + virtual void DecodedPacketDeleter( + esplusplayer_decoded_video_packet* packet) = 0; + + protected: + std::mutex mtx_; + std::list list_; +}; + +class DecodedReferencePacketList : public AbstractDecodedPacketList { + public: + explicit DecodedReferencePacketList() { LOG_DEBUG("created"); } + virtual ~DecodedReferencePacketList() { LOG_DEBUG("destroyed"); } + + protected: + virtual bool IsAvailableInternal() override { + if (GetList().size() > kMaxAvailableSize_) return false; + return true; + } + virtual void DecodedPacketDeleter( + esplusplayer_decoded_video_packet* packet) override { + ::DecodedPacketDeleter(packet); + } + + private: + const std::uint32_t kMaxAvailableSize_ = 5; +}; + +class DecodedCopiedPacketList : public AbstractDecodedPacketList { + public: + explicit DecodedCopiedPacketList() { LOG_DEBUG("created"); } + virtual ~DecodedCopiedPacketList() { LOG_DEBUG("destroyed"); } + + protected: + virtual bool IsAvailableInternal() override { return true; } + virtual void DecodedPacketDeleter( + esplusplayer_decoded_video_packet* packet) override { + ::DecodedPacketDeleter(packet); + } +}; + +class DecodedScaledPacketList : public AbstractDecodedPacketList { + public: + explicit DecodedScaledPacketList() { LOG_DEBUG("created"); } + virtual ~DecodedScaledPacketList() { LOG_DEBUG("destroyed"); } + virtual void GetFreeTbmSurface(void** ptr, bool is_scale_change) { + std::unique_lock lk(mtx_); + if (is_scale_change) { + for (std::vector::iterator it = tbm_mgr_.begin(); + it != tbm_mgr_.end();) { + if (it->tbm_is_free == true) { + LOG_ERROR("scale size changed, destroy the free tbm %p", + it->tbm_surf); + tbm_surface_destroy(static_cast(it->tbm_surf)); + it = tbm_mgr_.erase(it); + } else { + LOG_ERROR("scale size changed, using tbm will be destroy later%p", + it->tbm_surf); + it->destroy_flag = true; + it++; + } + } + *ptr = nullptr; + } else { + auto tbm_is_free = [](const CmaBufferInfo& item) -> bool { + return item.tbm_is_free == true; + }; + auto target = std::find_if(tbm_mgr_.begin(), tbm_mgr_.end(), tbm_is_free); + if (target != tbm_mgr_.end()) { + *ptr = target->tbm_surf; + } + } + return; + } + + virtual bool TryToAdd(esplusplayer_decoded_video_packet* packet) override { + if (packet == nullptr) { + LOG_ERROR("packet is nullptr"); + return false; + } + std::unique_lock lk(mtx_); + auto pkt_ptr = DecodedPacketPtr( + packet, [this](esplusplayer_decoded_video_packet* pkt) { + DecodedPacketDeleter(pkt); + delete pkt; + }); + if (IsAvailableInternal() == false) { + LOG_ERROR("not available to add a packet"); + return false; + } + // traverse tbm_mgr to find the same tbm surf,if find, change that,if not, + // new one and add to vector. + void* tbm_ptr = packet->surface_data; + auto has_tbm = [tbm_ptr](const CmaBufferInfo& item) -> bool { + return item.tbm_surf == tbm_ptr; + }; + auto target = std::find_if(tbm_mgr_.begin(), tbm_mgr_.end(), has_tbm); + if (target == tbm_mgr_.end()) { + CmaBufferInfo tmp_tbm_buffer; + tmp_tbm_buffer.tbm_surf = packet->surface_data; + tmp_tbm_buffer.tbm_is_free = false; + tmp_tbm_buffer.destroy_flag = false; + tbm_mgr_.push_back(tmp_tbm_buffer); + LOG_ERROR("add tbm surface %p to list", tmp_tbm_buffer.tbm_surf); + } else { + target->tbm_is_free = false; + } + list_.emplace_back(std::move(pkt_ptr)); + return true; + } + + virtual void Remove(esplusplayer_decoded_video_packet* packet) override { + if (packet == nullptr) return; + std::unique_lock lk(mtx_); + // LOG_ERROR("Remove pkt %p ", packet->surface_data); + // before remove packet, set the tbm surf free. + void* tbm_ptr = packet->surface_data; + auto has_tbm = [tbm_ptr](const CmaBufferInfo& item) -> bool { + return item.tbm_surf == tbm_ptr; + }; + auto target = std::find_if(tbm_mgr_.begin(), tbm_mgr_.end(), has_tbm); + if (target != tbm_mgr_.end()) { + if (target->destroy_flag == true) { + LOG_ERROR("destroy tbm surface %p", target->tbm_surf); + tbm_surface_destroy(static_cast(target->tbm_surf)); + tbm_mgr_.erase(target); + } else { + target->tbm_is_free = true; + } + } + list_.remove_if([&packet](const DecodedPacketPtr& cur) { + if (cur.get() == packet) return true; + return false; + }); + } + + virtual void Clear() override { + std::unique_lock lk(mtx_); + LOG_DEBUG("all packets are cleared"); + list_.clear(); + for (auto& iter : tbm_mgr_) { + LOG_ERROR("destroy tbm buffer in list %p", iter.tbm_surf); + tbm_surface_destroy(static_cast(iter.tbm_surf)); + } + std::vector tmp; + tbm_mgr_.swap(tmp); + } + + protected: + virtual bool IsAvailableInternal() override { + if (GetList().size() > kMaxAvailableSize_) return false; + return true; + } + + virtual void DecodedPacketDeleter( + esplusplayer_decoded_video_packet* packet) override { + // redesign, when pkt list is full, which means new tbm ptr can't add, so + // destroy it after create in trackrender. + if (GetList().size() > kMaxAvailableSize_) { + if (packet == nullptr || packet->surface_data == nullptr) return; + // only destroy the tbm surface new created but not added in tbm list + void* tbm_ptr = packet->surface_data; + auto has_tbm = [tbm_ptr](const CmaBufferInfo& item) -> bool { + return item.tbm_surf == tbm_ptr; + }; + auto target = std::find_if(tbm_mgr_.begin(), tbm_mgr_.end(), has_tbm); + if (target == tbm_mgr_.end()) { + LOG_ERROR("tbm_surface_destroy[%p] deleted", packet->surface_data); + tbm_surface_destroy(static_cast(packet->surface_data)); + packet->surface_data = NULL; + } + } + } + + private: + const std::uint32_t kMaxAvailableSize_ = 5; + std::vector tbm_mgr_; +}; +} // namespace plusplayer + +#endif // __PLUSPLAYER_SRC_ESPLAYER_DECODED_PACKET_LIST_H__ \ No newline at end of file diff --git a/src/esplusplayer/include_internal/esplayer/espacket_logger.h b/src/esplusplayer/include_internal/esplayer/espacket_logger.h new file mode 100755 index 0000000..8a4678d --- /dev/null +++ b/src/esplusplayer/include_internal/esplayer/espacket_logger.h @@ -0,0 +1,43 @@ +// +// @ Copyright [2019] +// + +#ifndef __PLUSPLAYER_SRC_ESPLAYER_ESPACEKT_LOGGER__H__ +#define __PLUSPLAYER_SRC_ESPLAYER_ESPACEKT_LOGGER__H__ + +#include +#include + +#include "plusplayer/espacket.h" + +namespace plusplayer { +class EsPacketLogger { + public: + EsPacketLogger() = default; + virtual ~EsPacketLogger() = default; + EsPacketLogger(const EsPacketLogger&) = delete; + EsPacketLogger(EsPacketLogger&&) = delete; + + void StorePacketInfo(const EsPacketPtr& packet); + void PrintStoredPacketInfo(const StreamType type, bool force = false); + void ResetLog(const StreamType type); + + private: + constexpr static int kStreamTypeMax = static_cast(StreamType::kMax); + constexpr static int kLogBufferThreshold = 60; + struct LightWeightEsPacketInfo { + bool valid = false; + std::uint32_t size = 0; + std::uint64_t pts = 0; + std::uint64_t duration = 0; + bool is_eos = false; + }; + std::string GetStringFromLastPacketInfo_(const StreamType type) const; + + LightWeightEsPacketInfo last_packet_info_[kStreamTypeMax]; + std::uint64_t last_log_packet_count_[kStreamTypeMax] = {0}; + std::mutex mtx_; +}; +} // namespace plusplayer + +#endif \ No newline at end of file diff --git a/src/esplusplayer/include_internal/esplayer/esplayer.h b/src/esplusplayer/include_internal/esplayer/esplayer.h new file mode 100755 index 0000000..2ce81cf --- /dev/null +++ b/src/esplusplayer/include_internal/esplayer/esplayer.h @@ -0,0 +1,323 @@ +// +// @ Copyright [2018] +// + +#ifndef __PLUSPLAYER_SRC_ESPLAYER_ESPLAYER__H__ +#define __PLUSPLAYER_SRC_ESPLAYER_ESPLAYER__H__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core/decoderinputbuffer.h" +#include "core/kpi.h" +#include "core/trackrendereradapter.h" +#ifndef IS_AUDIO_PRODUCT +#include "mixer/mixerticket.h" +#include "mixer/mixerticket_eventlistener.h" +#endif +#include "esplayer/espacket_logger.h" +#include "esplayer/message.hpp" +#include "esplayer/state_manager.hpp" +#include "plusplayer/drm.h" +#include "plusplayer/esplusplayer.h" +#include "plusplayer/track.h" +#include "plusplayer/types/buffer.h" +#include "plusplayer/types/display.h" +#include "plusplayer/types/error.h" +#include "plusplayer/types/stream.h" + +namespace plusplayer { + +enum EosStatus { + kNone = 0x0000, + kAudioEos = 0x0010, + kVideoEos = 0x0100, + kAllEos = (kAudioEos | kVideoEos) +}; + +static constexpr unsigned long kNeedDataMaskNone = 0x00; +static constexpr unsigned long kNeedDataMaskBySeek = 0x01; +static constexpr unsigned long kNeedDataMaskByPrepare = 0x02; + +class EsPlayer : public EsPlusPlayer { + public: + EsPlayer(); + ~EsPlayer(); + + bool Open() override; + bool Close() override; + bool PrepareAsync() override; + bool Deactivate(const StreamType type) override; + bool Activate(const StreamType type) override; + bool Start() override; + bool Stop() override; + bool Pause() override; + bool Resume() override; + bool Seek(const uint64_t time_millisecond) override; + void SetAppInfo(const PlayerAppInfo& app_info) override; + bool SetPlaybackRate(const double rate, const bool audio_mute) override; + bool SetDisplay(const DisplayType& type, void* obj) override; +#ifndef IS_AUDIO_PRODUCT + bool SetDisplay(const DisplayType& type, MixerTicket* handle) override; +#endif + bool SetDisplay(const DisplayType& type, void* ecore_wl2_window, const int x, + const int y, const int w, const int h) override; + bool SetDisplaySubsurface(const DisplayType& type, void* ecore_wl2_subsurface, + const int x, const int y, const int w, + const int h) override; + bool SetDisplay(const DisplayType& type, unsigned int surface_id, const int x, + const int y, const int w, const int h) override; + bool SetDisplayMode(const DisplayMode& mode) override; + bool SetDisplayRoi(const Geometry& roi) override; + bool SetVideoRoi(const CropArea& area) override; + bool ResizeRenderRect(const RenderRect& rect) override; + bool SetDisplayRotate(const DisplayRotation& rotate) override; + bool GetDisplayRotate(DisplayRotation* rotate) override; + bool SetDisplayVisible(bool is_visible) override; + bool SetTrustZoneUse(bool is_using_tz) override; + bool SetSubmitDataType(SubmitDataType type) override; + bool SetStream(const AudioStreamPtr& stream) override; + bool SetStream(const VideoStreamPtr& stream) override; + PacketSubmitStatus SubmitPacket(const EsPacketPtr& packet) override; + PacketSubmitStatus SubmitTrustZonePacket(const EsPacketPtr& packet, + uint32_t tz_handle = 0) override; + PacketSubmitStatus SubmitEncryptedPacket( + const EsPacketPtr& packet, + const drm::EsPlayerEncryptedInfo& drm_info) override; + EsState GetState() override; + bool GetPlayingTime(uint64_t* time_in_milliseconds) override; + bool SetAudioMute(bool is_mute) override; + bool SetVideoFrameBufferType(DecodedVideoFrameBufferType type) override; + bool SetVideoFrameBufferScaleResolution( + const uint32_t& target_width, const uint32_t& target_height) override; + bool SetDecodedVideoFrameRate(const Rational& request_framerate) override; + void RegisterListener(EsEventListener* listener, + EsEventListener::UserData userdata) override; + bool GetAdaptiveInfo(void* padaptive_info, + const PlayerAdaptiveInfo& adaptive_type) override; + bool SetVolume(const int& volume) override; + bool GetVolume(int* volume) override; + bool Flush(const StreamType& type) override; + void SetBufferSize(const BufferOption& option, uint64_t size) override; + bool SetLowLatencyMode(const PlayerLowLatencyMode& mode) override; + bool SetVideoFramePeekMode() override; + bool RenderVideoFrame() override; + bool SetUnlimitedMaxBufferMode() override; + bool SetFmmMode() override; + bool SetAudioCodecType(const PlayerAudioCodecType& type) override; + bool SetVideoCodecType(const PlayerVideoCodecType& type) override; + bool SetRenderTimeOffset(const StreamType type, int64_t offset) override; + bool GetRenderTimeOffset(const StreamType type, int64_t* offset) override; + bool SetAlternativeVideoResource(unsigned int rsc_type) override; + bool SwitchAudioStreamOnTheFly(const AudioStreamPtr& stream) override; + bool SetAiFilter(void* aifilter) override; + bool SetCatchUpSpeed(const CatchUpSpeed& level) override; + bool GetVideoLatencyStatus(LatencyStatus* status) override; + bool GetAudioLatencyStatus(LatencyStatus* status) override; + bool SetVideoMidLatencyThreshold(const unsigned int threshold) override; + bool SetAudioMidLatencyThreshold(const unsigned int threshold) override; + bool SetVideoHighLatencyThreshold(const unsigned int threshold) override; + bool SetAudioHighLatencyThreshold(const unsigned int threshold) override; + bool InitAudioEasingInfo(const uint32_t init_volume, + const uint32_t init_elapsed_time, + const AudioEasingInfo& easing_info) override; + bool UpdateAudioEasingInfo(const AudioEasingInfo& easing_info) override; + bool GetAudioEasingInfo(uint32_t* current_volume, uint32_t* elapsed_time, + AudioEasingInfo* easing_info) override; + bool StartAudioEasing() override; + bool StopAudioEasing() override; + bool GetVirtualRscId(const RscType type, int* virtual_id) override; + bool SetAdvancedPictureQualityType(const AdvPictureQualityType type) override; + bool SetResourceAllocatePolicy(const RscAllocPolicy policy) override; + + private: + using SubmitPacketOperator = + std::function; + + struct NeedData { + std::bitset<2> mask = kNeedDataMaskNone; + uint64_t seek_offset = 0; + }; + + struct ResumeTime { + bool is_set = false; + uint64_t time = 0; + }; + + enum class MakeBufferStatus { + kEos, // eos packet + kOutOfMemory, // out of memory + kSuccess // success + }; + struct SrcQueueSize { + std::uint64_t kMaxByteOfVideoSrcQueue = 70 * 1024 * 1024; // 70 MB + std::uint64_t kMaxByteOfAudioSrcQueue = 10 * 1024 * 1024; // 10 MB + std::uint32_t kMinByteThresholdOfVideoSrcQueue = 0; + std::uint32_t kMinByteThresholdOfAudioSrcQueue = 0; + + std::uint64_t kMaxTimeOfVideoSrcQueue = 0; + std::uint64_t kMaxTimeOfAudioSrcQueue = 0; + std::uint32_t kMinTimeThresholdOfVideoSrcQueue = 0; + std::uint32_t kMinTimeThresholdOfAudioSrcQueue = 0; + }; + + private: + void Init_(); + void MsgTask_(); + bool Prepare_(); + void PrepareTask_(); + void SetTrackRendererAttributes_(); + GstBuffer* GetGstBuffer_(const EsPacketPtr& packet, MakeBufferStatus* status); + void UnsetTzQdata_(const DecoderInputBufferPtr& buffer); + void MakeGstBufferForTzHandle_(GstBuffer* gstbuffer, const TrackType& type, + const uint32_t& tz_handle, + const uint32_t& packet_size); + void MakeGstBufferForEncryptedPacket_( + GstBuffer* gstbuffer, const EsPacketPtr& packet, + const drm::EsPlayerEncryptedInfo& drm_info); + PacketSubmitStatus SubmitPacketCommon_(const EsPacketPtr& packet, + SubmitPacketOperator op); + PacketSubmitStatus SubmitEosPacket_(const TrackType& type); + PacketSubmitStatus SubmitDecoderInputBuffer_( + const DecoderInputBufferPtr& buffer); + bool SetTrack_(const Track& track); + bool ChangeStream_(const Track& track); + bool SetStream_(const Track& track); + void ResetContextForClose_(); + void ResetContextForStop_(); + void GetSrcQueueCurrentSize_(const TrackType& type, uint64_t* byte_size, + uint64_t* time_size); + kpi::EsCodecLoggerKeys MakeKpiKeys_(); +#ifndef IS_AUDIO_PRODUCT + bool PrepareVideoMixingMode_(std::vector* tracks); +#endif + + private: // private types + class TrackRendererEventListener + : public TrackRendererAdapter::EventListener { + public: + explicit TrackRendererEventListener(EsPlayer* handler) : handler_(handler) { + assert(handler); + } + void OnError(const ErrorType& error_code) override; + void OnErrorMsg(const ErrorType& error_code, char* error_msg) override; + void OnResourceConflicted() override; + void OnEos() override; + void OnSeekDone() override; + void OnBufferStatus(const TrackType& type, + const BufferStatus& status) override; + void OnSeekData(const TrackType& type, const uint64_t offset) override; + void OnClosedCaptionData(const char* data, const int size) override; + void OnFlushDone() override; + void OnEvent(const EventType& event_type, + const EventMsg& msg_data) override; + void OnFirstDecodingDone() override; + void OnVideoDecoderUnderrun() override; + void OnVideoLatencyStatus(const LatencyStatus& latency_status) override; + void OnAudioLatencyStatus(const LatencyStatus& latency_status) override; + void OnVideoHighLatency() override; + void OnAudioHighLatency() override; +#ifndef IS_AUDIO_PRODUCT + void OnMediaPacketVideoRawDecoded( + const DecodedVideoRawModePacket& packet) override; +#endif + + private: + void ReadyToPrepare_(const TrackType& type); + void ReadyToSeek_(const TrackType& type); + void BufferStatus_(const TrackType& type, const BufferStatus& status); + void OnMediaPacketVideoDecoded(const DecodedVideoPacket& packet); + void OnMediaPacketGetTbmBufPtr(void** ptr, bool is_scale_change); + + private: + EsPlayer* handler_ = nullptr; + }; // class TrackRendererEventListener + +#ifndef IS_AUDIO_PRODUCT + class MixerListener : public MixerTicketEventListener { + public: + explicit MixerListener(EsPlayer* handler) : handler_(handler) { + assert(handler); + } + bool OnAudioFocusChanged(bool active) override; + bool OnUpdateDisplayInfo(const DisplayInfo& cur_info, + DisplayInfo* new_info) override; + + private: + EsPlayer* handler_{nullptr}; + }; +#endif + + private: + std::vector track_; + NeedData need_data_[kTrackTypeMax]; + int eos_status_ = EosStatus::kAllEos; + std::mutex eos_mutex_; + EsEventListener* eventlistener_ = nullptr; + EsEventListener::UserData eventlistener_userdata_ = nullptr; + + EsStateManager state_manager_; + + SubmitDataType submit_data_type_ = SubmitDataType::kCleanData; + drm::Property drm_property_; + std::uint32_t low_latency_mode_ = 0; + std::uint32_t video_frame_peek_mode_ = 0; + std::uint32_t unlimited_max_buffer_mode_ = 0; + std::uint32_t accurate_seek_mode_ = 1; + std::uint32_t video_pre_display_mode_ = 1; + std::uint32_t fmm_mode_ = 0; + PlayerAudioCodecType audio_codec_type_ = kPlayerAudioCodecTypeHW; + PlayerVideoCodecType video_codec_type_ = kPlayerVideoCodecTypeHW; + bool force_audio_swdecoder_use_ = false; + std::uint32_t video_decoding_mode_ = 0x02; // seamless mode + std::uint32_t alternaltive_video_resource_ = 0; + ResumeTime resume_time_; + + bool is_msg_task_stop_ = false; + std::mutex msg_task_mutex_; + std::mutex submit_mutex_; + std::condition_variable msg_task_cv_; + std::queue msg_queue_; + std::future msg_handler_task_; + + std::unique_ptr trackrenderer_event_listener_{ + new TrackRendererEventListener(this)}; +#ifndef IS_AUDIO_PRODUCT + std::unique_ptr mixer_event_listener_{new MixerListener(this)}; +#endif + std::unique_ptr trackrenderer_; + std::future preparetask_; + PlayerAppInfo app_info_; + double current_playback_rate_ = 1.0; + bool current_audio_mute_ = false; + bool is_resource_conflicted_ = false; + bool is_stopped_ = false; + bool is_preparing_ = false; + bool is_seek_done_need_drop = false; + SrcQueueSize src_queue_size_; + + std::string caf_unique_number; +#ifndef IS_AUDIO_PRODUCT + std::unique_ptr mixer_ticket_; + bool is_audio_focused_ = true; + Geometry mixerticket_roi_; + bool is_visible_ = true; + std::mutex audio_focus_m_; + bool enable_audio_pipeline_handle_ = true; + bool enable_rsc_alloc_handle_ = true; +#endif + DecodedVideoFrameBufferType vidoe_frame_buffer_type_ = + DecodedVideoFrameBufferType::kNone; + // for debugging + EsPacketLogger es_packet_logger_; +}; + +} // namespace plusplayer +#endif // __PLUSPLAYER_SRC_ESPLAYER_ESPLAYER__H__ diff --git a/src/esplusplayer/include_internal/esplayer/esplayer_drm.h b/src/esplusplayer/include_internal/esplayer/esplayer_drm.h new file mode 100755 index 0000000..78e260e --- /dev/null +++ b/src/esplusplayer/include_internal/esplayer/esplayer_drm.h @@ -0,0 +1,20 @@ +// +// @ Copyright [2018] +// + +#ifndef __PLUSPLAYER_SRC_ESPLAYER_ESPLAYER_DRM__H__ +#define __PLUSPLAYER_SRC_ESPLAYER_ESPLAYER_DRM__H__ + +#include "core/gstobject_guard.h" +#include "plusplayer/espacket.h" +#include "plusplayer/external_drm.h" + +namespace plusplayer { +namespace esplayer_drm { +using GBytesPtr = gstguard::GstGuardPtr; +GBytesPtr Serialize(const EsPacketPtr &packet, + const drm::EsPlayerEncryptedInfo &drm_info); +} // namespace esplayer_drm +} // namespace plusplayer + +#endif // __PLUSPLAYER_SRC_ESPLAYER_ESPLAYER_DRM__H__ \ No newline at end of file diff --git a/src/esplusplayer/include_internal/esplayer/message.hpp b/src/esplusplayer/include_internal/esplayer/message.hpp new file mode 100755 index 0000000..bdf19c6 --- /dev/null +++ b/src/esplusplayer/include_internal/esplayer/message.hpp @@ -0,0 +1,369 @@ +// +// @ Copyright [2018] +// + +#ifndef __PLUSPLAYER_SRC_ESPLAYER_MESSAGE_H__ +#define __PLUSPLAYER_SRC_ESPLAYER_MESSAGE_H__ + +#include +#include +#include +#include + +#include "core/utils/plusplayer_log.h" +#include "plusplayer/track.h" +#include "plusplayer/types/buffer.h" +#include "plusplayer/types/error.h" +#include "plusplayer/types/stream.h" + +// Restructuring points +// - remove listener handler ptr from msg class member +// - no RTTI +// - no need to know message type in mskTask +// - seperate configurations into each messages +// Check point +// - design issues? delivering op breaks encapsulation ? +// -> related modules : pipeline , statemanager , msghandler. + +namespace plusplayer { + +// messages +namespace es_msg { + +struct Base : private boost::noncopyable { + using Ptr = std::unique_ptr; + using UserData = void*; + virtual ~Base() {} + virtual void Execute() = 0; + + protected: + Base() = default; + Base(const UserData userdata) : userdata_(userdata) {} + UserData userdata_ = nullptr; +}; // class Msg + +struct Simple : Base { + using Ptr = std::unique_ptr; + using Operator = std::function; + + static Base::Ptr Make(const Operator& _op, const Base::UserData userdata) { + return Base::Ptr(new Simple(_op, userdata)); + } + void Execute() override { + if (!op) return; + op(userdata_); + } + const Operator op; + + private: + explicit Simple(const Operator& _op, const Base::UserData userdata) + : Base(userdata), op(_op) {} +}; + +struct Error : Base { + using Ptr = std::unique_ptr; + using Operator = std::function; + + static Base::Ptr Make(const ErrorType& error_type, const Operator& op, + const Base::UserData userdata) { + return Base::Ptr(new Error(error_type, op, userdata)); + } + void Execute() override { + if (!op) return; + op(type, userdata_); + } + + const ErrorType type = ErrorType::kNone; + const Operator op; + + private: + explicit Error(const ErrorType& _type, const Operator& _op, + const Base::UserData userdata) + : Base(userdata), type(_type), op(_op) {} +}; + +struct ErrorMsg : Base { + using Ptr = std::unique_ptr; + using Operator = + std::function; + + static Base::Ptr Make(const ErrorType& error_type, const char* error_msg, + size_t size, const Operator& op, + const Base::UserData userdata) { + return Base::Ptr(new ErrorMsg(error_type, error_msg, size, op, userdata)); + } + void Execute() override { + if (!op) return; + op(type, message, userdata_); + } + + const ErrorType type = ErrorType::kNone; + const char* message = nullptr; + const Operator op; + + private: + explicit ErrorMsg(const ErrorType& _type, const char* _msg, size_t _size, + const Operator& _op, const Base::UserData userdata) + : Base(userdata), type(_type), op(_op) { + message = new const char[_size + 1]{0}; + memcpy((void*)message, _msg, _size); + } + + ~ErrorMsg() { + delete[] message; + message = nullptr; + } +}; + +struct Bufferstatus : Base { + using Ptr = std::unique_ptr; + using Operator = std::function; + + static Base::Ptr Make(const StreamType type, const BufferStatus status, + const uint64_t byte_size, const uint64_t time_size, + const Operator& op, const Base::UserData userdata) { + return Base::Ptr( + new Bufferstatus(type, status, byte_size, time_size, op, userdata)); + } + void Execute() override { + if (!op) return; + op(type, status, byte_size, time_size, userdata_); + } + + const StreamType type = StreamType::kMax; + const BufferStatus status = BufferStatus::kUnderrun; + const uint64_t byte_size; + const uint64_t time_size; + const Operator op; + + private: + explicit Bufferstatus(const StreamType _type, const BufferStatus _status, + const uint64_t _byte_size, const uint64_t _time_size, + const Operator& _op, const Base::UserData userdata) + : Base(userdata), + type(_type), + status(_status), + byte_size(_byte_size), + time_size(_time_size), + op(_op) {} +}; + +struct ReadyToPrepare : Base { + using Ptr = std::unique_ptr; + using Operator = std::function; + + static Base::Ptr Make(const StreamType type, const Operator& op, + const Base::UserData userdata) { + return Base::Ptr(new ReadyToPrepare(type, op, userdata)); + } + void Execute() override { + if (!op) return; + op(type, userdata_); + } + + const StreamType type = StreamType::kMax; + const Operator op; + + private: + explicit ReadyToPrepare(const StreamType _type, const Operator& _op, + const Base::UserData userdata) + : Base(userdata), type(_type), op(_op) {} +}; + +struct ReadyToSeek : Base { + using Ptr = std::unique_ptr; + using Operator = std::function; + + static Base::Ptr Make(const StreamType type, const uint64_t offset, + const Operator& op, const Base::UserData userdata) { + return Base::Ptr(new ReadyToSeek(type, offset, op, userdata)); + } + void Execute() override { + if (!op) return; + op(type, offset, userdata_); + } + + const StreamType type = StreamType::kMax; + const uint64_t offset = 0; + const Operator op; + + private: + explicit ReadyToSeek(const StreamType _type, const uint64_t _offset, + const Operator& _op, const Base::UserData userdata) + : Base(userdata), type(_type), offset(_offset), op(_op) {} +}; + +struct ResourceConflict : Base { + using Ptr = std::unique_ptr; + using Operator = std::function; + + static Base::Ptr Make(const Operator& _op, const Base::UserData userdata) { + return Base::Ptr(new ResourceConflict(_op, userdata)); + } + void Execute() override { + if (!op) return; + op(userdata_); + } + const Operator op; + + private: + explicit ResourceConflict(const Operator& _op, const Base::UserData userdata) + : Base(userdata), op(_op) {} +}; + +struct Eos : Base { + using Ptr = std::unique_ptr; + using Operator = std::function; + + static Base::Ptr Make(const Operator& _op, const Base::UserData userdata) { + return Base::Ptr(new Eos(_op, userdata)); + } + void Execute() override { + if (!op) return; + op(userdata_); + } + + const Operator op; + + private: + explicit Eos(const Operator& _op, const Base::UserData userdata) + : Base(userdata), op(_op) {} +}; + +struct SeekDone : Base { + using Ptr = std::unique_ptr; + using Operator = std::function; + + static Base::Ptr Make(const Operator& _op, const Base::UserData userdata) { + return Base::Ptr(new SeekDone(_op, userdata)); + } + void Execute() override { + if (!op) return; + op(userdata_); + } + + const Operator op; + + private: + explicit SeekDone(const Operator& _op, const Base::UserData userdata) + : Base(userdata), op(_op) {} +}; + +struct FlushDone : Base { + using Ptr = std::unique_ptr; + using Operator = std::function; + + static Base::Ptr Make(const Operator& _op, const Base::UserData userdata) { + return Base::Ptr(new FlushDone(_op, userdata)); + } + void Execute() override { + if (!op) return; + op(userdata_); + } + + const Operator op; + + private: + explicit FlushDone(const Operator& _op, const Base::UserData userdata) + : Base(userdata), op(_op) {} +}; + +struct OnEvent : Base { + using Ptr = std::unique_ptr; + using Operator = + std::function; + + static Base::Ptr Make(const EventType& event, const EventMsg& msg_data, + const Operator& op, const Base::UserData userdata) { + return Base::Ptr(new OnEvent(event, msg_data, op, userdata)); + } + void Execute() override { + if (!op) return; + op(event, msg_data, userdata_); + } + + const EventType event = EventType::kNone; + EventMsg msg_data; + const Operator op; + + private: + explicit OnEvent(const EventType& _event, const EventMsg& _msg_data, + const Operator& _op, const Base::UserData userdata) + : Base(userdata), event(_event), msg_data(_msg_data), op(_op) {} +}; + +struct FirstDecodingDone : Base { + using Ptr = std::unique_ptr; + using Operator = std::function; + + static Base::Ptr Make(const Operator& _op, const Base::UserData userdata) { + return Base::Ptr(new FirstDecodingDone(_op, userdata)); + } + void Execute() override { + if (!op) return; + op(userdata_); + } + + const Operator op; + + private: + explicit FirstDecodingDone(const Operator& _op, const Base::UserData userdata) + : Base(userdata), op(_op) {} +}; + +struct ClosedCaption : Base { + using Ptr = std::unique_ptr; + using Operator = + std::function, int, Base::UserData)>; + + static Base::Ptr Make(const char* _data, const int _size, const Operator& _op, + const Base::UserData userdata) { + return Base::Ptr(new ClosedCaption(_data, _size, _op, userdata)); + } + void Execute() override { + if (!op) return; + op(std::move(data), size, userdata_); + } + + std::unique_ptr data; + const int size = 0; + const Operator op; + + private: + explicit ClosedCaption(const char* _data, const int _size, + const Operator& _op, const Base::UserData userdata) + : Base(userdata), size(_size), op(_op) { + data.reset(new char[size]); + memcpy(data.get(), _data, size); + } +}; + +struct PacketLatencyStatus : Base { + using Ptr = std::unique_ptr; + using Operator = std::function; + + static Base::Ptr Make(const LatencyStatus latency_status, const Operator& op, + const Base::UserData userdata) { + return Base::Ptr(new PacketLatencyStatus(latency_status, op, userdata)); + } + void Execute() override { + if (!op) return; + op(latency_status, userdata_); + } + + const LatencyStatus latency_status = LatencyStatus::kLow; + const Operator op; + + private: + explicit PacketLatencyStatus(const LatencyStatus _latency_status, + const Operator& _op, + const Base::UserData userdata) + : Base(userdata), latency_status(_latency_status), op(_op) {} +}; + +} // namespace es_msg + +} // namespace plusplayer + +#endif // __PLUSPLAYER_SRC_ESPLAYER_MESSAGE_H__ diff --git a/src/esplusplayer/include_internal/esplayer/state_manager.hpp b/src/esplusplayer/include_internal/esplayer/state_manager.hpp new file mode 100755 index 0000000..90e6f04 --- /dev/null +++ b/src/esplusplayer/include_internal/esplayer/state_manager.hpp @@ -0,0 +1,387 @@ +// +// @ Copyright [2018] +// + +// +// StateMachine using boost::msm (meta state machine) +// +// * Sequence +// 1. generate Event (by user) +// 2. call process_event(Event) +// 3. check transition_table +// 3-1. return if no transition defined +// 3-2. process_event() return if other event is being processed(deferred) +// 4. check Guard(Event const&) +// 5. do Action(Event const&) if Guard succeeded +// 6. Transition updated(to "next") +// (4-6) is atomically serialized in boost::msm +// +// * Return value of process_event() +// . failed(HANDLED_FALSE=0) if no transition defined in transition_table +// . HANDLED_GUARD_REJECT if Guard(Event const&) returned false +// + +#ifndef __PLUSPLAYER_SRC_ESPLAYER_STATE_MANAGER_H__ +#define __PLUSPLAYER_SRC_ESPLAYER_STATE_MANAGER_H__ + +#include +#include +#define BOOST_MPL_CFG_NO_PREPROCESSED_HEADERS +// max lines of transition table +#define BOOST_MPL_LIMIT_VECTOR_SIZE 50 // or whatever you need +#define BOOST_MPL_LIMIT_MAP_SIZE 50 // or whatever you need +// max number of fsm states +#define FUSION_MAX_VECTOR_SIZE 20 // or whatever you need +#include // back-end +#include // functors +#include // front-end +#include +#include + +#include "core/utils/plusplayer_log.h" +#include "plusplayer/esplusplayer.h" + +#define DEBUG_STATE_MANAGER +#ifdef DEBUG_STATE_MANAGER +#define STATE_TRACE(fmt, arg...) LOG_DEBUG(fmt, ##arg) +#define STATE_TRACE_P(id, fmt, arg...) LOG_DEBUG_P(id, fmt, ##arg) +#else +#define STATE_TRACE(fmt, arg...) +#endif + +namespace plusplayer { + +namespace msm = boost::msm; +namespace mpl = boost::mpl; +// to make transition_table cleaner +// to use 'Row' 'Defer' +using namespace msm::front; + +namespace es_event { + +using Operator = std::function; +struct Open { + Open() = default; + explicit Open(const Operator _op) : op(_op) {} + const Operator op; +}; +struct Close { + Close() = default; + explicit Close(const Operator _op) : op(_op) {} + const Operator op; +}; +struct Start { + Start() = default; + explicit Start(const Operator _op) : op(_op) {} + const Operator op; +}; +struct Stop { + Stop() = default; + explicit Stop(const Operator _op) : op(_op) {} + const Operator op; +}; +struct SetStream { + SetStream() = default; + explicit SetStream(const Operator _op) : op(_op) {} + const Operator op; +}; +struct Prepare { + Prepare() = default; + explicit Prepare(const Operator _op) : op(_op) {} + const Operator op; +}; +struct ResConflict { + ResConflict() = default; + explicit ResConflict(const Operator _op) : op(_op) {} + const Operator op; +}; +struct Pause { + Pause() = default; + explicit Pause(const Operator _op) : op(_op) {} + const Operator op; +}; +struct Resume { + Resume() = default; + explicit Resume(const Operator _op) : op(_op) {} + const Operator op; +}; +struct Seek { + Seek() = default; + explicit Seek(const Operator _op) : op(_op) {} + const Operator op; +}; +struct PlaybackRate { + PlaybackRate() = default; + explicit PlaybackRate(const Operator _op) : op(_op) {} + const Operator op; +}; + +} // namespace es_event + +// +// transition actions +// +struct ResetMaskStop { + template + void operator()(EVT const&, FSM& fsm, SourceState&, TargetState&) { + STATE_TRACE("entering Action : ResetMaskStop"); + fsm.event_stop_mask = false; + } +}; + +using namespace es_event; +struct EsStateMachine : msm::front::state_machine_def { + // + // Customizing a state machine / Getting more speed + // -http://www.boost.org/doc/libs/1_57_0/libs/msm/doc/HTML/ch03s02.html#d0e1147 + // + typedef int no_exception_thrown; // no need for exception handling + // typedef int no_message_queue; // no need for message queue + // we need this, check ut. + typedef int activate_deferred_events; // manually enable deferred events + + // when a transition is about to be taken, we already update our currently + // active state(s) + typedef msm::active_state_switch_before_transition active_state_switch_policy; + + template + void on_entry(Event const&, FSM&) { + STATE_TRACE("entering: EsPlayer StateMachine"); + } + template + void on_exit(Event const&, FSM&) { + STATE_TRACE("leaving: EsPlayer StateMachine"); + } + + // + // The list of FSM states + // + struct None : public msm::front::state<> {}; + struct Idle : public msm::front::state<> {}; + struct StreamReady : public msm::front::state<> {}; + struct Ready : public msm::front::state<> {}; + struct Playing : public msm::front::state<> {}; + struct Paused : public msm::front::state<> {}; + + // + // guard conditions + // + struct AlwaysTrue { + template + bool operator()(EVT const& evt, FSM&, SourceState& from, TargetState& to) { + return true; + } + }; + + struct CheckOp { + template + bool operator()(EVT const& evt, FSM& fsm, SourceState& from, + TargetState& to) { + if (fsm.event_stop_mask) return false; + return evt.op ? evt.op() : true; + } + }; + + struct AlwaysRun { + template + bool operator()(EVT const& evt, FSM& fsm, SourceState& from, + TargetState& to) { + return evt.op ? evt.op() : true; + } + }; + + // + // the initial state of the StateMachine. Must be defined + // + typedef None initial_state; + + // + // Transition table + // + struct transition_table : mpl::vector< + // Start Event Next Action Guard + // +------------+----------------+-------------+------+--------+ + Row< None , Open , Idle , ResetMaskStop, AlwaysRun >, + Row< Idle , Close , None , none, AlwaysRun >, + // Start Event Next Action Guard + // +------------+----------------+-------------+------------+--------+ + Row< Idle , SetStream , StreamReady , ResetMaskStop, CheckOp >, + Row< StreamReady , SetStream , StreamReady , ResetMaskStop, CheckOp >, + Row< StreamReady , Prepare , Ready , none, CheckOp >, + // Start Event Next Action Guard + // +------------+----------------+-------------+-----+--------+ + Row< Ready , Start , Playing , none, CheckOp >, + // Start Event Next Action Guard + // +------------+----------------+-------------+-----+---------+ + Row< Ready , Pause , Paused , none, CheckOp >, + Row< Playing , Pause , Paused , none, CheckOp >, + Row< Paused , Resume , Playing , none, CheckOp >, + // Start Event Next Action Guard + // +------------+----------------+-------------+-----+--------+ + Row< None , Close , None , none, AlwaysTrue >, + Row< Paused , Pause , Paused , none, AlwaysTrue >, + Row< Playing , Resume , Playing , none, AlwaysTrue >, + Row< Idle , Stop , Idle , none, AlwaysTrue >, + // Start Event Next Action Guard + // +-------------+--------------+--------------+-----+--------+ + Row< StreamReady , Stop , Idle , none , none >, + Row< Ready , Stop , Idle , none , none >, + Row< Playing , Stop , Idle , none , none >, + Row< Paused , Stop , Idle , none , none >, + // Start Event Next Action Guard + // +-------------+----------------+----------+---------+-------+ + Row< Ready , Seek , Ready , none, CheckOp >, + Row< Playing , Seek , Playing , none, CheckOp >, + Row< Paused , Seek , Paused , none, CheckOp >, + // Start Event Next Action Guard + // +-------------+----------------+----------+---------+-------+ + Row< Ready , PlaybackRate , Ready , none , CheckOp >, + Row< Paused , PlaybackRate , Paused , none , CheckOp >, + Row< Playing , PlaybackRate , Playing , none , CheckOp > + // +-------------+----------------+--------------+---------+-------+ + > {}; + + // Replaces the default no-transition response. + template + void no_transition(Event const& e, FSM& fsm, int state) { + LOG_ERROR("no transition on event[%s], check transition_table current[%d]", + typeid(e).name(), *fsm.current_state()); + } + + // Additional data section for customization + bool event_stop_mask = false; + bool is_preparing = false; +}; // struct StateMachine + +class EsStateManager : private boost::noncopyable { + public: + EsStateManager() noexcept {} + ~EsStateManager() {} + void Start() { + std::lock_guard lock(control_m_); + msm_.start(); + stopped_ = false; + } + void* log_id_ = nullptr; + + void Stop() { + // TODO(js4716.chun) : + // - this is for ut_meta_state_machine.cpp MessageQueueTest. + // - find out better solution. + while (GetState() != EsState::kNone) { + LOG_ERROR_P(this, + "waiting the state to none before stopping state-machine"); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + { + std::lock_guard lock(control_m_); + stopped_ = true; + // need to check & wait message queue is empty + msm_.stop(); + } + } + + template + bool ProcessEvent(const T& es_event) { + std::lock_guard lock(control_m_); + if (stopped_) return false; + + ProcessEventRet ret = msm::back::HANDLED_FALSE; + STATE_TRACE_P(this, "Transition Request, Current[%d]", GetStateEnum()); + if (!(ret = msm_.process_event(es_event))) { + STATE_TRACE_P(this, "process_event failed ret[%d], Current[%d]", ret, + GetStateEnum()); + return false; + } + if (ret == msm::back::HANDLED_GUARD_REJECT) { + STATE_TRACE_P(this, + "Guard Rejected, probably event.op failed Current[%d]", + GetStateEnum()); + return false; + } else if (ret == msm::back::HANDLED_DEFERRED) { + STATE_TRACE_P(this, "Process_event Deffered"); + // TODO(js4716.chun) : return값을 어떻게 해야하는가. + // - deferred된 이후 gaurd fail시 return값이 잘못 될 수 있다. + // - 이게 중요한가? ê¼­ 처리해야 하는가? + } + STATE_TRACE_P(this, "Process_event done Current[%d],ret[%d]", + GetStateEnum(), ret); + return true; + } + + bool ProcessEventStop(const es_event::Stop& stop_event) { + std::unique_lock lock(control_m_, std::defer_lock); + lock.try_lock(); + if (stopped_) return false; + STATE_TRACE_P(this, "Transition Stop Requested, Current[%d]", + GetStateEnum()); + msm_.event_stop_mask = true; + msm_.is_preparing = false; + if (!stop_event.op()) { + LOG_ERROR("Stop Operation failed"); + return false; + } + ProcessEventRet ret = msm::back::HANDLED_FALSE; + STATE_TRACE_P(this, "Transition Request, Current[%d]", GetStateEnum()); + if (!lock.owns_lock()) lock.lock(); + if (!(ret = msm_.process_event(stop_event))) { + STATE_TRACE_P(this, "process_event_stop failed ret[%d], current[%d]", ret, + GetStateEnum()); + return false; + } + STATE_TRACE_P(this, "Process_event done Current[%d],ret[%d]", + GetStateEnum(), ret); + return true; + } + + EsState GetState() { + if (stopped_) return EsState::kNone; + + unsigned int index = *msm_.current_state(); + if (index >= state_vector_.size()) { + assert(0 && "invalid state id returned, something went wrong"); + return EsState::kNone; + } + return state_vector_[index]; + } + + int GetStateEnum() { + if (stopped_) return static_cast(EsState::kNone); + + unsigned int index = *msm_.current_state(); + if (index >= state_vector_.size()) { + assert(0 && "invalid state id returned, something went wrong"); + return static_cast(EsState::kNone); + } + return static_cast(state_vector_[index]); + } + + void SetPreparingState(bool val) { msm_.is_preparing = val; } + + bool GetPreparingState() { return msm_.is_preparing; } + + private: + std::mutex control_m_; // TODO(js4716.chun) : remove this if possible + bool stopped_ = true; // TODO(js4716.chun) : remove this if possible + + // typedef enum { + // HANDLED_FALSE = 0, + // HANDLED_TRUE = 1, + // HANDLED_GUARD_REJECT = 2, + // HANDLED_DEFERRED = 4 + // } HandledEnum; + using ProcessEventRet = msm::back::HandledEnum; + // Generating rules of state ids + // http://www.boost.org/doc/libs/1_65_1/libs/msm/doc/HTML/ch06s03.html#internals-state-id + const std::vector state_vector_ = {EsState::kNone, // None + EsState::kIdle, // Idle + EsState::kIdle, // StreamReady + EsState::kReady, // Ready + EsState::kPlaying, // Playing + EsState::kPaused}; // Paused + msm::back::state_machine msm_; +}; // class StateManager + +} // namespace plusplayer + +#endif // __PLUSPLAYER_SRC_ESPLAYER_STATE_MANAGER_H__ \ No newline at end of file diff --git a/src/esplusplayer/src/elementary_stream.cpp b/src/esplusplayer/src/elementary_stream.cpp new file mode 100755 index 0000000..9de2d37 --- /dev/null +++ b/src/esplusplayer/src/elementary_stream.cpp @@ -0,0 +1,217 @@ +// +// @ Copyright [2018] +// + +#include "plusplayer/elementary_stream.h" + +namespace { + +constexpr int kLittleEdian = 1234; +constexpr int kBigEdian = 4321; + +} // namespace + +namespace plusplayer { + +AudioStream::AudioStream() noexcept { + track_.type = kTrackTypeAudio; + track_.index = 0; + track_.active = true; +} + +void AudioStream::SetMimeType(AudioMimeType mimetype) { + mimetype_ = mimetype; + SetMimeType_(mimetype); +} + +void AudioStream::SetMimeType_(AudioMimeType mimetype) { + mimetype_ = mimetype; + switch (mimetype) { + case AudioMimeType::kAAC: { + track_.mimetype = "audio/mpeg"; + track_.version = 2; + break; + } + case AudioMimeType::kMP2: { + track_.mimetype = "audio/mpeg"; + track_.version = 1; + track_.layer = 2; + break; + } + case AudioMimeType::kMP3: { + track_.mimetype = "audio/mpeg"; + track_.version = 1; + track_.layer = 3; + break; + } + case AudioMimeType::kAC3: { + track_.mimetype = "audio/x-ac3"; + break; + } + case AudioMimeType::kEAC3: { + track_.mimetype = "audio/x-eac3"; + break; + } + case AudioMimeType::kVORBIS: { + track_.mimetype = "audio/x-vorbis"; + break; + } + case AudioMimeType::kOPUS: { + track_.mimetype = "audio/x-opus"; + break; + } + case AudioMimeType::kPCM_S16LE: { + track_.mimetype = "audio/x-raw"; + track_.is_signed = true; + track_.endianness = kLittleEdian; + track_.sample_format = 16; + break; + } + case AudioMimeType::kPCM_S16BE: { + track_.mimetype = "audio/x-raw"; + track_.is_signed = true; + track_.endianness = kBigEdian; + track_.sample_format = 16; + break; + } + case AudioMimeType::kPCM_U16LE: { + track_.mimetype = "audio/x-raw"; + track_.is_signed = false; + track_.endianness = kLittleEdian; + track_.sample_format = 16; + break; + } + case AudioMimeType::kPCM_U16BE: { + track_.mimetype = "audio/x-raw"; + track_.is_signed = false; + track_.endianness = kBigEdian; + track_.sample_format = 16; + break; + } + case AudioMimeType::kPCM_S24LE: { + track_.mimetype = "audio/x-raw"; + track_.is_signed = true; + track_.endianness = kLittleEdian; + track_.sample_format = 24; + break; + } + case AudioMimeType::kPCM_S24BE: { + track_.mimetype = "audio/x-raw"; + track_.is_signed = true; + track_.endianness = kBigEdian; + track_.sample_format = 24; + break; + } + case AudioMimeType::kPCM_U24LE: { + track_.mimetype = "audio/x-raw"; + track_.is_signed = false; + track_.endianness = kLittleEdian; + track_.sample_format = 24; + break; + } + case AudioMimeType::kPCM_U24BE: { + track_.mimetype = "audio/x-raw"; + track_.is_signed = false; + track_.endianness = kBigEdian; + track_.sample_format = 24; + break; + } + case AudioMimeType::kPCM_S32LE: { + track_.mimetype = "audio/x-raw"; + track_.is_signed = true; + track_.endianness = kLittleEdian; + track_.sample_format = 32; + break; + } + case AudioMimeType::kPCM_S32BE: { + track_.mimetype = "audio/x-raw"; + track_.is_signed = true; + track_.endianness = kBigEdian; + track_.sample_format = 32; + break; + } + case AudioMimeType::kPCM_U32LE: { + track_.mimetype = "audio/x-raw"; + track_.is_signed = false; + track_.endianness = kLittleEdian; + track_.sample_format = 32; + break; + } + case AudioMimeType::kPCM_U32BE: { + track_.mimetype = "audio/x-raw"; + track_.is_signed = false; + track_.endianness = kBigEdian; + track_.sample_format = 32; + break; + } + case AudioMimeType::kG711_MULAW: { + track_.mimetype = "audio/x-mulaw"; + force_swdecoder_use_ = true; + break; + } + default: + track_.mimetype = "unknown"; + break; + } +} + +VideoStream::VideoStream() noexcept { + track_.type = kTrackTypeVideo; + track_.index = 0; + track_.active = true; +} + +void VideoStream::SetMimeType(VideoMimeType mimetype) { + mimetype_ = mimetype; + SetMimeType_(mimetype); +} + +void VideoStream::SetMimeType_(VideoMimeType mimetype) { + switch (mimetype) { + case VideoMimeType::kH263: + track_.mimetype = "video/x-h263"; + break; + case VideoMimeType::kH264: + track_.mimetype = "video/x-h264"; + break; + case VideoMimeType::kHEVC: + track_.mimetype = "video/x-h265"; + break; + case VideoMimeType::kMPEG1: { + track_.mimetype = "video/mpeg"; + track_.version = 1; + break; + } + case VideoMimeType::kMPEG2: { + track_.mimetype = "video/mpeg"; + track_.version = 2; + break; + } + case VideoMimeType::kMPEG4: { + track_.mimetype = "video/mpeg"; + track_.version = 4; + break; + } + case VideoMimeType::kVP8: + track_.mimetype = "video/x-vp8"; + break; + case VideoMimeType::kVP9: + track_.mimetype = "video/x-vp9"; + break; + case VideoMimeType::kWMV3: + track_.mimetype = "video/x-wmv"; + track_.version = 3; + break; + case VideoMimeType::kAV1: + track_.mimetype = "video/x-av1"; + break; + case VideoMimeType::kMJPEG: + track_.mimetype = "video/x-jpeg"; + break; + default: + track_.mimetype = "unknown"; + break; + } +} + +} // namespace plusplayer diff --git a/src/esplusplayer/src/espacket.cpp b/src/esplusplayer/src/espacket.cpp new file mode 100755 index 0000000..c9bf579 --- /dev/null +++ b/src/esplusplayer/src/espacket.cpp @@ -0,0 +1,37 @@ +// +// @ Copyright [2018] +// +#include "plusplayer/espacket.h" + +namespace plusplayer { +EsPacketPtr EsPacket::Create(const StreamType type, + std::shared_ptr buffer, + const uint32_t buffer_size, const uint64_t pts, + const uint64_t duration, + const uint32_t hdr10p_size, + std::shared_ptr hdr10p_data) { + return Ptr(new EsPacket(type, buffer, buffer_size, pts, duration, hdr10p_size, + hdr10p_data)); +} + +EsPacketPtr EsPacket::CreateEos(const StreamType type) { + return Ptr(new EsPacket(type, nullptr, 0, 0, 0, 0, nullptr)); +} + +EsPacket::EsPacket(const StreamType type, std::shared_ptr buffer, + const uint32_t buffer_size, const uint64_t pts, + const uint64_t duration, const uint32_t hdr10p_size, + std::shared_ptr hdr10p_data) + : type_(type), + buffer_size_(buffer_size), + pts_(pts), + duration_(duration), + hdr10p_metadata_size_(hdr10p_size) { + if (buffer) { + buffer_ = buffer; + } + if (hdr10p_data) { + hdr10p_metadata_ = hdr10p_data; + } +} +} // namespace plusplayer diff --git a/src/esplusplayer/src/espacket_logger.cpp b/src/esplusplayer/src/espacket_logger.cpp new file mode 100755 index 0000000..19cd11c --- /dev/null +++ b/src/esplusplayer/src/espacket_logger.cpp @@ -0,0 +1,91 @@ +// +// @ Copyright [2019] +// + +#include "esplayer/espacket_logger.h" + +#include +#include +#include + +#include "core/utils/plusplayer_log.h" + +namespace internal { +enum class TizenBuiltImageType { Unknown, Debug, Release, Perf }; +static TizenBuiltImageType GetBuiltImageType() { + static TizenBuiltImageType image_type; + if (image_type != TizenBuiltImageType::Unknown) return image_type; + if (access("/etc/debug", F_OK) == 0) { + image_type = TizenBuiltImageType::Debug; + LOG_DEBUG("Debug Image"); + } else if (access("/etc/release", F_OK) == 0) { + image_type = TizenBuiltImageType::Release; + LOG_DEBUG("Release Image"); + } else if (access("/etc/perf", F_OK) == 0) { + image_type = TizenBuiltImageType::Perf; + LOG_DEBUG("Perf Image"); + } + return image_type; +} +} // namespace internal + +namespace plusplayer { +std::string EsPacketLogger::GetStringFromLastPacketInfo_( + const StreamType type) const { + const int track_index = static_cast(type); + const auto& pkt_info = last_packet_info_[track_index]; + std::ostringstream oss; + oss << "valid? [" << (pkt_info.valid ? 'T' : 'F') << "], "; + oss << "size [" << (pkt_info.size) << "], "; + oss << "pts [" << (pkt_info.pts) << "], "; + oss << "duration [" << (pkt_info.duration) << "], "; + oss << "is_eos? [" << (pkt_info.is_eos ? 'T' : 'F') << "]"; + return oss.str(); +} + +void EsPacketLogger::StorePacketInfo(const EsPacketPtr& packet) { + if (packet == nullptr) return; + const int track_index = static_cast(packet->GetType()); + if (track_index >= static_cast(StreamType::kMax)) return; + + std::lock_guard lk(mtx_); + + auto& count = last_log_packet_count_[track_index]; + count++; + + auto& last_pkt_info = last_packet_info_[track_index]; + last_pkt_info.valid = true; + last_pkt_info.size = packet->GetSize(); + last_pkt_info.pts = packet->GetPts(); + last_pkt_info.duration = packet->GetDuration(); + last_pkt_info.is_eos = packet->IsEosPacket(); +} + +void EsPacketLogger::PrintStoredPacketInfo(const StreamType type, bool force) { + const bool printable = ((force) || (internal::GetBuiltImageType() == + internal::TizenBuiltImageType::Debug)); + if (printable == false) return; + + const int track_index = static_cast(type); + if (track_index >= static_cast(StreamType::kMax)) return; + + std::lock_guard lk(mtx_); + + const auto& count = last_log_packet_count_[track_index]; + const bool in_timing = + (force) || (count == 1) || (count % kLogBufferThreshold == 0); + if (in_timing) { + const auto logmsg = GetStringFromLastPacketInfo_(type); + LOG_INFO(" [%s] %s", count, (force ? ": last" : ""), + (type == StreamType::kAudio ? "AUDIO" : "VIDEO"), logmsg.c_str()); + } +} + +void EsPacketLogger::ResetLog(const StreamType type) { + std::lock_guard lk(mtx_); + + last_packet_info_[static_cast(type)].valid = false; + last_log_packet_count_[static_cast(type)] = 0; +} + +} // namespace plusplayer \ No newline at end of file diff --git a/src/esplusplayer/src/esplayer.cpp b/src/esplusplayer/src/esplayer.cpp new file mode 100755 index 0000000..dfecffc --- /dev/null +++ b/src/esplusplayer/src/esplayer.cpp @@ -0,0 +1,2580 @@ +// +// @ Copyright [2018] +// +// to manipulate TrackRenderer objects +// to provide optimized control of TrackRenderer + +#include "esplayer/esplayer.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core/gst_utils.h" +#include "core/track_util.h" +#include "core/utils/caf_logger.h" +#include "core/utils/performance_checker.h" +#include "core/utils/plusplayer_cfg.h" +#include "core/utils/plusplayer_log.h" +#include "core/videoframetypestrategy.h" +#include "esplayer/esplayer_drm.h" +#include "json/json.h" + +namespace plusplayer { + +namespace es_conf { + +// get values from /etc/multimedia/esplusplayer.ini +static std::once_flag loaded; +static std::map ini_property; + +bool LoadIniFile(); +void LoadIniProperty(const Json::Value& root); + +} // namespace es_conf + +namespace util { + +std::uint64_t ConvertMsToNs(std::uint64_t ms) { + constexpr std::uint64_t ns_unit = 1000000; + if (ms * ns_unit > G_MAXUINT64) return G_MAXUINT64; + return ms * ns_unit; +} +std::uint64_t ConvertNsToMs(std::uint64_t ns) { + constexpr std::uint64_t ms_unit = 1000000; + return ns / ms_unit; +} + +std::int64_t ConvertMsToNs(std::int64_t ms) { + constexpr std::int64_t ns_unit = 1000000; + if (ms * ns_unit > G_MAXINT64) return G_MAXINT64; + return ms * ns_unit; +} +std::int64_t ConvertNsToMs(std::int64_t ns) { + constexpr std::int64_t ms_unit = 1000000; + return ns / ms_unit; +} + +std::string GetStringFromMatroskaColor(const MatroskaColor& color_info) { + std::ostringstream oss; + oss << "matrixCoefficients:" << color_info.matrix_coefficients + << " bitsPerChannel:" << color_info.bits_per_channel + << " chromaSubsamplingHorz:" << color_info.chroma_subsampling_horizontal + << " chromaSubsamplingVert:" << color_info.chroma_subsampling_vertical + << " cbSubsamplingHorz:" << color_info.cb_subsampling_horizontal + << " cbSubsamplingVert:" << color_info.cb_subsampling_vertical + << " chromaSitingHorz:" << color_info.chroma_siting_horizontal + << " chromaSitingVert:" << color_info.chroma_siting_vertical + << " range:" << color_info.range + << " transferCharacteristics:" << color_info.transfer_characteristics + << " primaries:" << color_info.primaries + << " maxCLL:" << color_info.max_cll << " maxFALL:" << color_info.max_fall + << " RX:" << color_info.metadata.primary_r_chromaticity_x + << " RY:" << color_info.metadata.primary_r_chromaticity_y + << " GX:" << color_info.metadata.primary_g_chromaticity_x + << " GY:" << color_info.metadata.primary_g_chromaticity_y + << " BX:" << color_info.metadata.primary_b_chromaticity_x + << " BY:" << color_info.metadata.primary_b_chromaticity_y + << " wX:" << color_info.metadata.white_point_chromaticity_x + << " wY:" << color_info.metadata.white_point_chromaticity_y + << " luminanceMax:" << color_info.metadata.luminance_max + << " luminanceMin:" << color_info.metadata.luminance_min + << " isHDR10p:" << color_info.is_hdr_10p; + return oss.str(); +} + +} // namespace util + +namespace internal { +// const std::uint64_t kMaxByteOfVideoSrcQueue = 70 * 1024 * 1024; // 70 MB +// const std::uint64_t kMaxByteOfAudioSrcQueue = 10 * 1024 * 1024; // 10 MB +// const std::uint64_t kMaxTimeOfVideoSrcQueue = 10000000000; // 10 s +// const std::uint64_t kMaxTimeOfAudioSrcQueue = 10000000000; // 10 s + +constexpr uint32_t kNdecodingMode = 0x04; + +enum VolumeLevel { kVolumeMin = 0, kVolumeMax = 100 }; +constexpr int kMaxFhdWidth = 1920; +constexpr int kMaxFhdHeight = 1080; + +inline bool IsPcmMimeType(const std::string& mimetype) { + return (mimetype.find("audio/x-raw") != std::string::npos); +} +inline bool IsForcedUnsetTz(const Track& track, const std::string& id) { + if ((track.type == kTrackTypeAudio) && strstr(id.c_str(), "netflix")) { + return true; + } + return false; +} +inline bool IsAacCodec(const Track& track) { + return (track.mimetype.find("audio/mpeg") != std::string::npos && + track.version == 2); +} +inline bool IsEac3Codec(const std::string& mimetype) { + return mimetype.find("audio/x-eac3") != std::string::npos; +} +inline bool IsAc3Codec(const std::string& mimetype) { + return mimetype.find("audio/x-ac3") != std::string::npos; +} +inline bool IsAvailableCodecSwitch(const Track& track) { + if (internal::IsAacCodec(track) || internal::IsAc3Codec(track.mimetype) || + internal::IsEac3Codec(track.mimetype)) + return true; + return false; +} + +int ResetEosStatus(const TrackType& type, int eos_status) { + if (type == kTrackTypeVideo) + eos_status &= ~EosStatus::kVideoEos; + else if (type == kTrackTypeAudio) + eos_status &= ~EosStatus::kAudioEos; + return eos_status; +} + +void MakeTrustZoneTracks(std::vector& tracks, + const std::string& app_id) { + for (auto& track : tracks) { + const bool is_already_tz_type = + (track.mimetype.find("_tz", track.mimetype.length() - 3) != + std::string::npos); + if (is_already_tz_type) { + continue; + } + track.streamtype = track.mimetype; + if (track.use_swdecoder || IsPcmMimeType(track.mimetype) || + IsForcedUnsetTz(track, app_id)) + continue; + else + track.mimetype = track.mimetype + "_tz"; + } +} + +void UpdateCodecTypeTracks(std::vector& tracks, + const PlayerAudioCodecType& audio_codec_type, + const PlayerVideoCodecType& video_codec_type, + bool force_audio_swdecoder_use) { + for (auto& track : tracks) { + switch (track.type) { + case kTrackTypeAudio: { + if (audio_codec_type == kPlayerAudioCodecTypeSW || + force_audio_swdecoder_use) + track.use_swdecoder = true; + else + track.use_swdecoder = false; + break; + } + case kTrackTypeVideo: { + if (video_codec_type == kPlayerVideoCodecTypeSW) + track.use_swdecoder = true; + else + track.use_swdecoder = false; + break; + } + default: + break; + } + } +} +inline bool IsSupportedDrmType(const drm::Type& drm_type) { + static const std::map kSupportedDrmType = { + {drm::Type::kPlayready, true}, + }; + if (kSupportedDrmType.count(drm_type) == 0) return false; + return kSupportedDrmType.at(drm_type); +} +inline void ResetDrmProperty(drm::Property& drm_property) { + drm_property = drm::Property(); +} +inline StreamType ConvertToStreamType(TrackType type) { + return (type == kTrackTypeAudio) ? StreamType::kAudio : StreamType::kVideo; +} +struct AppsrcQueueSizeOption { + std::uint64_t current_size = 0; + std::uint64_t max_size = 0; + std::uint32_t threshold = 0; +}; +inline bool IsUnderRun(AppsrcQueueSizeOption& byte_based, + AppsrcQueueSizeOption& time_based) { + bool need_data_by_byte = false, need_data_by_time = false; + need_data_by_byte = (byte_based.max_size > 0) && + (byte_based.current_size * 100 / byte_based.max_size <= + byte_based.threshold); + need_data_by_time = (time_based.max_size > 0) && + (time_based.current_size * 100 / time_based.max_size <= + time_based.threshold); + if (need_data_by_byte || need_data_by_time) + return true; + else + return false; +} +inline bool IsLowLatencyModeDisableAVSync(std::uint32_t mode) { + constexpr std::uint32_t kAVSync = 0x0100; + return (mode & kAVSync) ? true : false; +} +inline bool IsLowLatencyModeDisablePreroll(std::uint32_t mode) { + constexpr std::uint32_t kPreroll = 0x0200; + return (mode & kPreroll) ? true : false; +} +inline bool IsLowLatencyMode(std::uint32_t mode) { + return (mode != static_cast(kLowLatencyModeNone)) ? true + : false; +} +inline bool IsSupportedTsOffset(std::uint32_t mode) { + return IsLowLatencyMode(mode) && !IsLowLatencyModeDisableAVSync(mode) ? true + : false; +} +inline bool IsSetLowLatencyModeForCatchUp(std::uint32_t low_latency_mode) { + std::uint32_t precondition = 0; + precondition |= static_cast(kLowLatencyModeAudio); + precondition |= static_cast(kLowLatencyModeVideo); + precondition |= static_cast(kLowLatencyModeDisableAVSync); + precondition |= + static_cast(kLowLatencyModeDisableVideoQuality); + return (low_latency_mode == precondition) ? true : false; +} + +} // namespace internal + +EsPlayer::EsPlayer() { + std::call_once(es_conf::loaded, [this]() { es_conf::LoadIniFile(); }); + if (CafLogger::Initialize() != true) { + LOG_INFO("CAF Dbus not connect."); + } +} + +EsPlayer::~EsPlayer() { + LOG_ENTER_P(this); + Close(); + LOG_LEAVE_P(this); +} + +bool EsPlayer::Open() { + LOG_INFO_P(this, "state manager > %p", &state_manager_); + Init_(); + state_manager_.Start(); + auto op = [this]() noexcept -> bool { + const auto start = performance_checker::Start(); + if (trackrenderer_) { + assert(0 && "trackrenderer already exist"); + } + msg_handler_task_ = + std::async(std::launch::async, &EsPlayer::MsgTask_, this); + trackrenderer_ = TrackRendererAdapter::Create(); + assert(trackrenderer_); + + trackrenderer_->RegisterListenerForEsplayer( + trackrenderer_event_listener_.get()); + performance_checker::End(start, "Open"); + return true; + }; + CafLogger::SetUniqueNumber(); + caf_unique_number = CafLogger::GetUniqueNumber(); + CafLogger::LogMessage(CafEventType::kIdle, caf_unique_number); + + es_event::Open event{op}; + return state_manager_.ProcessEvent(event); +} + +bool EsPlayer::Close() { + LOG_ENTER_P(this); + std::lock_guard lk(submit_mutex_); + if (state_manager_.GetState() >= EsState::kIdle) { + Stop(); + } + auto op = [this]() noexcept { + if (is_msg_task_stop_ == false) { + is_msg_task_stop_ = true; + msg_task_cv_.notify_one(); + if (msg_handler_task_.valid()) msg_handler_task_.wait(); + } + + if (trackrenderer_) trackrenderer_.reset(); + ResetContextForClose_(); + LOG_LEAVE_P(this); + return true; + }; + es_event::Close event{op}; + state_manager_.ProcessEvent(event); + state_manager_.Stop(); + return true; +} + +bool EsPlayer::Deactivate(const StreamType type) { + LOG_ENTER_P(this); + if (state_manager_.GetState() < EsState::kReady) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return false; + } +#ifndef IS_AUDIO_PRODUCT + if (!enable_audio_pipeline_handle_ && type == StreamType::kAudio) { + LOG_ERROR_P( + this, "can't deactivate audio stream, mixer will control audio stream"); + return false; + } +#endif + if (!trackrenderer_->Deactivate(static_cast(type))) { + return false; + } + { + std::lock_guard lock(eos_mutex_); + switch (type) { + case StreamType::kAudio: + eos_status_ |= EosStatus::kAudioEos; + break; + case StreamType::kVideo: + eos_status_ |= EosStatus::kVideoEos; + break; + default: + break; + } + } + for (auto& track : track_) { + if (track.type == static_cast(type)) { + track.active = false; + } + } + return true; +} + +bool EsPlayer::Activate(const StreamType type) { + LOG_ENTER_P(this); + if (state_manager_.GetState() < EsState::kReady) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return false; + } +#ifndef IS_AUDIO_PRODUCT + if (!enable_audio_pipeline_handle_ && type == StreamType::kAudio) { + LOG_ERROR_P(this, + "can't activate audio stream, mixer will control audio stream"); + return false; + } +#endif + if (!track_.empty()) { + auto has_track = [type](const Track& item) -> bool { + return item.type == static_cast(type); + }; + auto target = std::find_if(track_.begin(), track_.end(), has_track); + if (target == track_.end()) { + LOG_ERROR_P(this, "there is no track to activate"); + return false; + } + if (target->active != false) { + LOG_ERROR_P(this, "The track should be deactivated in advance."); + return false; + } + target->active = true; + internal::UpdateCodecTypeTracks(track_, audio_codec_type_, + video_codec_type_, + force_audio_swdecoder_use_); + if (drm_property_.external_decryption) { + internal::MakeTrustZoneTracks(track_, app_info_.id); + } + SetTrackRendererAttributes_(); +#ifndef IS_AUDIO_PRODUCT + if (type == StreamType::kVideo) { + if (mixer_ticket_) + trackrenderer_->SetVideoFrameBufferType( + VideoFrameTypeStrategyPtr(new RawVideoFrameTypeStrategy())); + else + trackrenderer_->SetVideoFrameBufferType(VideoFrameTypeStrategyPtr( + new DefaultVideoFrameTypeStrategy(vidoe_frame_buffer_type_))); + } +#endif + if (!trackrenderer_->Activate(target->type, *target)) { + target->active = false; + return false; + } + eos_status_ = + internal::ResetEosStatus(static_cast(type), eos_status_); + return true; + } + return false; +} + +bool EsPlayer::Start() { + LOG_ENTER_P(this); + if (is_stopped_) { + LOG_ERROR_P(this, "Stop already, no need to Start,leave..."); + return false; + } + auto op = [this]() noexcept { + if (!trackrenderer_->Start()) { + return false; + } + return true; + }; + + CafLogger::LogMessage(CafEventType::kPlaying, caf_unique_number); + + es_event::Start event{op}; + return state_manager_.ProcessEvent(event); +} + +bool EsPlayer::Stop() { + LOG_ENTER_P(this); + is_stopped_ = true; + auto stop = [this]() noexcept -> bool { + const auto start = performance_checker::Start(); + if (trackrenderer_) trackrenderer_->Stop(); + ResetContextForStop_(); +#ifndef IS_AUDIO_PRODUCT + if (mixer_ticket_) mixer_ticket_.reset(); +#endif + performance_checker::End(start, "Stop"); + return true; + }; + for (const auto& track : track_) { + es_packet_logger_.PrintStoredPacketInfo( + internal::ConvertToStreamType(track.type), true); + } + es_event::Stop event{stop}; + bool res = state_manager_.ProcessEventStop(event); + + if (preparetask_.valid()) { + LOG_INFO_P(this, "Stopped , Wait Prepare() finish..."); + preparetask_.wait(); + LOG_INFO_P(this, "Wait , Wait Prepare() Done..."); + } + + CafLogger::LogMessage(CafEventType::kIdle, caf_unique_number); + CafLogger::StopLoggingThread(); + + return res; +} + +void EsPlayer::SetTrackRendererAttributes_() { + if (trackrenderer_ == nullptr) return; + trackrenderer_->SetAttribute( + TrackRendererAdapter::Attribute::kVideoQueueMaxByte, + src_queue_size_.kMaxByteOfVideoSrcQueue); + trackrenderer_->SetAttribute( + TrackRendererAdapter::Attribute::kAudioQueueMaxByte, + src_queue_size_.kMaxByteOfAudioSrcQueue); + trackrenderer_->SetAttribute( + TrackRendererAdapter::Attribute::kVideoMinByteThreshold, + src_queue_size_.kMinByteThresholdOfVideoSrcQueue); + trackrenderer_->SetAttribute( + TrackRendererAdapter::Attribute::kAudioMinByteThreshold, + src_queue_size_.kMinByteThresholdOfAudioSrcQueue); + trackrenderer_->SetAttribute( + TrackRendererAdapter::Attribute::kVideoQueueMaxTime, + util::ConvertMsToNs(src_queue_size_.kMaxTimeOfVideoSrcQueue)); + trackrenderer_->SetAttribute( + TrackRendererAdapter::Attribute::kAudioQueueMaxTime, + util::ConvertMsToNs(src_queue_size_.kMaxTimeOfAudioSrcQueue)); + trackrenderer_->SetAttribute( + TrackRendererAdapter::Attribute::kVideoMinTimeThreshold, + src_queue_size_.kMinTimeThresholdOfVideoSrcQueue); + trackrenderer_->SetAttribute( + TrackRendererAdapter::Attribute::kAudioMinTimeThreshold, + src_queue_size_.kMinTimeThresholdOfAudioSrcQueue); + trackrenderer_->SetAttribute(TrackRendererAdapter::Attribute::kLowLatencyMode, + low_latency_mode_); + trackrenderer_->SetAttribute( + TrackRendererAdapter::Attribute::kVideoFramePeekMode, + video_frame_peek_mode_); + trackrenderer_->SetAttribute( + TrackRendererAdapter::Attribute::kUnlimitedMaxBufferMode, + unlimited_max_buffer_mode_); + trackrenderer_->SetAttribute( + TrackRendererAdapter::Attribute::kAccurateSeekMode, accurate_seek_mode_); + trackrenderer_->SetAttribute( + TrackRendererAdapter::Attribute::kVideoPreDisplayMode, + video_pre_display_mode_); + if (resume_time_.is_set) { + trackrenderer_->SetAttribute( + TrackRendererAdapter::Attribute::kStartRenderingTime, + resume_time_.time); + resume_time_.is_set = false; + } + trackrenderer_->SetAttribute(TrackRendererAdapter::Attribute::kFmmMode, + fmm_mode_); + trackrenderer_->SetAttribute( + TrackRendererAdapter::Attribute::kAlternativeVideoResource, + alternaltive_video_resource_); + trackrenderer_->SetAttribute( + TrackRendererAdapter::Attribute::kVideoDecodingMode, + video_decoding_mode_); +} + +#ifndef IS_AUDIO_PRODUCT +bool EsPlayer::PrepareVideoMixingMode_(std::vector* tracks) { + LOG_ENTER_P(this); + mixer_ticket_->Prepare(); + if (enable_rsc_alloc_handle_) return true; + ResourceType type = ResourceType::kHwMain; + if (!mixer_ticket_->GetAvailableResourceType(ResourceCategory::kVideoDecoder, + &type)) { + LOG_ERROR_P(this, "no available resource"); + return false; + } + if (!mixer_ticket_->Alloc(ResourceCategory::kVideoDecoder, type)) { + LOG_ERROR_P(this, "fail to alloc resource [%d]", static_cast(type)); + return false; + } + + if (type == ResourceType::kHwSub) { + alternaltive_video_resource_ = 1; + } else if (type == ResourceType::kSw) { + for (auto it = tracks->begin(); it != tracks->end(); ++it) { + if (it->type == kTrackTypeVideo) { + it->use_swdecoder = true; + break; + } + } + } else if (type == ResourceType::kNdecoder) { + video_decoding_mode_ = internal::kNdecodingMode; + } + return true; +} +#endif + +bool EsPlayer::Prepare_() { + LOG_ENTER_P(this); + if (is_stopped_) { + LOG_ERROR_P(this, "Stop already, no need to prepare,leave..."); + return false; + } + auto op = [this]() noexcept -> bool { + const auto start = performance_checker::Start(); + + internal::UpdateCodecTypeTracks(track_, audio_codec_type_, + video_codec_type_, + force_audio_swdecoder_use_); + if (drm_property_.external_decryption) { + internal::MakeTrustZoneTracks(track_, app_info_.id); + } + std::vector active_track; + if (!track_util::GetActiveTrackList(track_, active_track)) { + return false; + } + trackrenderer_->SetIniProperty(es_conf::ini_property); +#ifndef IS_AUDIO_PRODUCT + if (mixer_ticket_) { + trackrenderer_->SetVideoFrameBufferType( + VideoFrameTypeStrategyPtr(new RawVideoFrameTypeStrategy())); + if (!PrepareVideoMixingMode_(&active_track)) { + LOG_ERROR_P(this, "fail to prepare mixing mode"); + return false; + } + } + std::unique_lock lock(audio_focus_m_); + if (!enable_audio_pipeline_handle_ && !is_audio_focused_) { + for (auto it = active_track.begin(); it != active_track.end(); ++it) { + if (it->type == kTrackTypeAudio) { + active_track.erase(it); + LOG_INFO_P(this, "erase audio track is_audio_focused_ [%d]", + is_audio_focused_); + break; + } + } + } +#endif + for (const auto& track : active_track) { + switch (track.type) { + case kTrackTypeAudio: { + std::lock_guard lock2(eos_mutex_); + eos_status_ = internal::ResetEosStatus(kTrackTypeAudio, eos_status_); + need_data_[track.type].mask |= kNeedDataMaskByPrepare; + break; + } + case kTrackTypeVideo: { + std::lock_guard lock2(eos_mutex_); + eos_status_ = internal::ResetEosStatus(kTrackTypeVideo, eos_status_); + need_data_[track.type].mask |= kNeedDataMaskByPrepare; + break; + } + default: + break; + } + } + trackrenderer_->SetTrack(active_track); + SetTrackRendererAttributes_(); + if (!trackrenderer_->Prepare()) { + return false; + } + performance_checker::End(start, "Prepare"); + return true; + }; + + CafLogger::StartLoggingThread(); + CafLogger::LogMessage(CafEventType::kReady, caf_unique_number); + + es_event::Prepare event{op}; + if (!state_manager_.ProcessEvent(event)) { + return false; + } + LOG_LEAVE_P(this); + return true; +} + +void EsPlayer::PrepareTask_() { + bool ret = Prepare_(); + + state_manager_.SetPreparingState(false); + if (eventlistener_) { + LOG_INFO_P(this, "Prepare completely, call OnPrepareDone(%d)", ret); + eventlistener_->OnPrepareDone(ret, eventlistener_userdata_); + } + + kpi::CodecLogger logger; + kpi::EsCodecLoggerKeys event_keys = MakeKpiKeys_(); + logger.SendKpi(ret, event_keys); + LOG_LEAVE_P(this); +} + +bool EsPlayer::PrepareAsync() { + LOG_ENTER_P(this); + state_manager_.SetPreparingState(true); + preparetask_ = std::async(std::launch::async, &EsPlayer::PrepareTask_, this); + if (!preparetask_.valid()) { + state_manager_.SetPreparingState(false); + return false; + } + return true; +} + +bool EsPlayer::Pause() { + LOG_ENTER_P(this); + if (is_stopped_) { + LOG_ERROR_P(this, "Stop already, no need to pause,leave..."); + return false; + } + if (state_manager_.GetState() < EsState::kReady) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return false; + } + auto op = [this]() noexcept -> bool { + if (!trackrenderer_) return false; + if (!trackrenderer_->Pause()) { + return false; + } + return true; + }; + for (const auto& track : track_) { + es_packet_logger_.PrintStoredPacketInfo( + internal::ConvertToStreamType(track.type), true); + } + + CafLogger::LogMessage(CafEventType::kPaused, caf_unique_number); + + es_event::Pause event{op}; + return state_manager_.ProcessEvent(event); +} + +bool EsPlayer::Resume() { + LOG_ENTER_P(this); + if (is_stopped_) { + LOG_ERROR_P(this, "Stop already, no need to Resume,leave..."); + return false; + } + if (state_manager_.GetState() <= EsState::kReady) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return false; + } + if (is_resource_conflicted_) { + LOG_ERROR_P(this, "Resuem fail resource conflicted"); + return false; + } + auto op = [this]() noexcept -> bool { + if (!trackrenderer_) return false; + if (!trackrenderer_->Resume()) { + return false; + } + return true; + }; + for (const auto& track : track_) { + es_packet_logger_.PrintStoredPacketInfo( + internal::ConvertToStreamType(track.type), true); + } + + CafLogger::LogMessage(CafEventType::kPlaying, caf_unique_number); + + es_event::Resume event{op}; + return state_manager_.ProcessEvent(event); +} + +bool EsPlayer::Seek(const uint64_t time_millisecond) { + if (state_manager_.GetState() < EsState::kIdle) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return false; + } + if (state_manager_.GetState() == EsState::kIdle) { + if (state_manager_.GetPreparingState()) { + LOG_ERROR_P(this, "Invalid State , during preparing"); + return false; + } + LOG_ERROR_P(this, "resume time [%llu ms]", time_millisecond); + resume_time_.is_set = true; + resume_time_.time = time_millisecond; + return true; + } + is_seek_done_need_drop = true; + LOG_DEBUG_P(this, "[ENTER] seek time [%llu ms]", time_millisecond); + for (const auto& track : track_) { + eos_status_ = internal::ResetEosStatus(track.type, eos_status_); + es_packet_logger_.PrintStoredPacketInfo( + internal::ConvertToStreamType(track.type), true); + } + auto op = [this, time_millisecond]() -> bool { + if (!trackrenderer_->Seek(time_millisecond, current_playback_rate_, + current_audio_mute_)) { + return false; + } + return true; + }; + es_event::Seek event{op}; + bool ret = state_manager_.ProcessEvent(event); + is_seek_done_need_drop = false; + + if (eventlistener_) { + if (internal::IsLowLatencyModeDisableAVSync(low_latency_mode_) || + internal::IsLowLatencyModeDisablePreroll(low_latency_mode_)) { + auto listener = std::bind(&plusplayer::EsEventListener::OnSeekDone, + eventlistener_, std::placeholders::_1); + auto msg = es_msg::Simple::Make(listener, eventlistener_userdata_); + std::unique_lock msg_mutex(msg_task_mutex_); + msg_queue_.push(std::move(msg)); + msg_mutex.unlock(); + msg_task_cv_.notify_one(); + } + } + LOG_DEBUG_P(this, "[LEAVE] seek end [%llu ms]", time_millisecond); + return ret; +} + +void EsPlayer::SetAppInfo(const PlayerAppInfo& app_info) { + LOG_ENTER_P(this); + if (state_manager_.GetState() != EsState::kIdle) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return; + } + app_info_ = app_info; + trackrenderer_->SetAppInfo(app_info); + LOG_INFO("Appid [%s]", app_info.id.c_str()); + CafLogger::SetAppId(app_info.id); +} + +bool EsPlayer::SetPlaybackRate(const double rate, const bool audio_mute) { + LOG_ENTER_P(this); + + if (rate <= 0 || rate > 2.0) { + LOG_ERROR_P(this, "Not a valid PlaybackRate"); + return false; + } + + if (state_manager_.GetState() < EsState::kReady) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return false; + } + + auto op = [this, rate, audio_mute]() -> bool { + if (!trackrenderer_->SetPlaybackRate(rate, audio_mute)) { + return false; + } + current_playback_rate_ = rate; + current_audio_mute_ = audio_mute; + return true; + }; + es_event::PlaybackRate event{op}; + return state_manager_.ProcessEvent(event); + + LOG_LEAVE_P(this); + return true; +} + +bool EsPlayer::SetDisplay(const DisplayType& type, void* obj) { + if (state_manager_.GetState() != EsState::kIdle) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return false; + } +#ifndef IS_AUDIO_PRODUCT + if (mixer_ticket_) mixer_ticket_.reset(); +#endif + return trackrenderer_->SetDisplay(type, obj); +} + +#ifndef IS_AUDIO_PRODUCT +bool EsPlayer::SetDisplay(const DisplayType& type, MixerTicket* handle) { + if (type == DisplayType::kMixer) { + LOG_INFO_P(this, "Create MixerTicket"); + mixer_ticket_.reset(handle); + mixer_ticket_->RegisterListener(mixer_event_listener_.get()); + if (mixer_ticket_->IsAudioFocusHandler()) + enable_audio_pipeline_handle_ = false; + if (mixer_ticket_->IsRscAllocHandler()) enable_rsc_alloc_handle_ = false; + trackrenderer_->SetDisplay(DisplayType::kNone, nullptr); + } + return true; +} +#endif + +bool EsPlayer::SetDisplay(const DisplayType& type, void* ecore_wl2_window, + const int x, const int y, const int w, const int h) { + if (state_manager_.GetState() != EsState::kIdle) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return false; + } +#ifndef IS_AUDIO_PRODUCT + if (mixer_ticket_) mixer_ticket_.reset(); +#endif + return trackrenderer_->SetDisplay(type, ecore_wl2_window, x, y, w, h); +} + +bool EsPlayer::SetDisplaySubsurface(const DisplayType& type, + void* ecore_wl2_subsurface, const int x, + const int y, const int w, const int h) { + if (state_manager_.GetState() != EsState::kIdle) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return false; + } +#ifndef IS_AUDIO_PRODUCT + if (mixer_ticket_) mixer_ticket_.reset(); +#endif + return trackrenderer_->SetDisplaySubsurface(type, ecore_wl2_subsurface, x, y, + w, h); +} + +bool EsPlayer::SetDisplay(const DisplayType& type, unsigned int surface_id, + const int x, const int y, const int w, const int h) { + if (state_manager_.GetState() != EsState::kIdle) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return false; + } +#ifndef IS_AUDIO_PRODUCT + if (mixer_ticket_) mixer_ticket_.reset(); +#endif + return trackrenderer_->SetDisplay(type, surface_id, x, y, w, h); +} + +bool EsPlayer::SetDisplayMode(const DisplayMode& mode) { + if (state_manager_.GetState() < EsState::kIdle) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return false; + } + trackrenderer_->SetDisplayMode(mode); + return true; +} + +bool EsPlayer::SetDisplayRoi(const Geometry& roi) { + if (state_manager_.GetState() < EsState::kIdle) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return false; + } +#ifndef IS_AUDIO_PRODUCT + if (mixer_ticket_) { + LOG_INFO_P(this, "mixed player roi x[%d] y[%d] w[%d] h[%d]", roi.x, roi.y, + roi.w, roi.h); + mixerticket_roi_ = roi; + return true; + } +#endif + return trackrenderer_->SetDisplayRoi(roi); +} + +bool EsPlayer::SetVideoRoi(const CropArea& area) { + if (state_manager_.GetState() < EsState::kIdle) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return false; + } + return trackrenderer_->SetVideoRoi(area); +} + +bool EsPlayer::ResizeRenderRect(const RenderRect& rect) { + if (state_manager_.GetState() < EsState::kIdle) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return false; + } + return trackrenderer_->ResizeRenderRect(rect); +} + +bool EsPlayer::SetDisplayRotate(const DisplayRotation& rotate) { + if (state_manager_.GetState() < EsState::kIdle) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return false; + } + return trackrenderer_->SetDisplayRotate(rotate); +} + +bool EsPlayer::GetDisplayRotate(DisplayRotation* rotate) { + if (state_manager_.GetState() < EsState::kIdle) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return false; + } + if (trackrenderer_->GetDisplayRotate(rotate)) { + return true; + } + return false; +} + +bool EsPlayer::SetDisplayVisible(bool is_visible) { + if (state_manager_.GetState() < EsState::kIdle) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return false; + } +#ifndef IS_AUDIO_PRODUCT + if (mixer_ticket_) { + LOG_INFO_P(this, "mixed player is_visible [%d] -> [%d]", is_visible_, + is_visible); + is_visible_ = is_visible; + return true; + } +#endif + return trackrenderer_->SetDisplayVisible(is_visible); +} + +bool EsPlayer::SetTrustZoneUse(bool is_using_tz) { + if (state_manager_.GetState() != EsState::kIdle) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return false; + } + if (drm_property_.type != drm::Type::kNone) { + LOG_ERROR_P(this, "drm type is already set for sending encrypted packets"); + return false; + } + LOG_INFO_P(this, "set trust zone use [%d]", is_using_tz); + drm_property_.external_decryption = is_using_tz; + + drm::Property drm_property = drm_property_; + drm_property.type = drm::Type::kPlayready; + trackrenderer_->SetDrm(drm_property); + return true; +} + +bool EsPlayer::SetSubmitDataType(SubmitDataType type) { + if (state_manager_.GetState() != EsState::kIdle) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return false; + } + LOG_INFO_P(this, "set submit data type [%d]", static_cast(type)); + + if (type == SubmitDataType::kCleanData) return true; + submit_data_type_ = type; + drm_property_.type = drm::Type::kPlayready; + // TODO: following SubmitDataType, need to set external_decryption + drm_property_.external_decryption = true; + drm::Property drm_property = drm_property_; + trackrenderer_->SetDrm(drm_property); + return true; +} + +bool EsPlayer::SetTrack_(const Track& track) { + LOG_ENTER_P(this); + track_.push_back(std::move(track)); + return true; +} + +bool EsPlayer::ChangeStream_(const Track& track) { + TrackType type = track.type; + if (track_.empty()) return false; + auto has_track = [type](const Track& item) -> bool { + return item.type == type; + }; + std::lock_guard lk(submit_mutex_); + auto target = std::find_if(track_.begin(), track_.end(), has_track); + if (target == track_.end()) { + LOG_ERROR_P(this, "Add a new stream."); + SetTrack_(track); + return true; + } + if (target->active != false) { + LOG_ERROR_P(this, "The track should be deactivated in advance."); + return false; + } + track_.erase(target); + LOG_ERROR_P(this, "previously added %s stream is deleted", + (type == kTrackTypeAudio) ? "audio" : "video"); + return SetTrack_(track); +} + +bool EsPlayer::SetStream_(const Track& track) { + TrackType type = track.type; + if (!track_.empty()) { + auto has_track = [type](const Track& item) -> bool { + return item.type == type; + }; + auto target = std::find_if(track_.begin(), track_.end(), has_track); + if (target != track_.end()) { + LOG_ERROR_P(this, "Stream is already exist"); + return false; + } + } + auto op = [this, track]() noexcept { + if (!SetTrack_(track)) { + return false; + } + return true; + }; + + CafLogger::LogMessage(CafEventType::kStreamReady, caf_unique_number); + + es_event::SetStream event{op}; + return state_manager_.ProcessEvent(event); +} + +bool EsPlayer::SetStream(const AudioStreamPtr& stream) { + LOG_ENTER_P(this); + bool ret = false; + BOOST_SCOPE_EXIT(&ret, &stream, &force_audio_swdecoder_use_) { + if (ret) force_audio_swdecoder_use_ = stream->GetForceSwDecoderUse(); + } + BOOST_SCOPE_EXIT_END + if (state_manager_.GetState() < EsState::kIdle) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return ret; + } + Track track = stream->GetTrack_(); + if (state_manager_.GetState() >= EsState::kReady) { + track.active = false; + ret = ChangeStream_(track); + return ret; + } + ret = SetStream_(track); + return ret; +} + +bool EsPlayer::SetStream(const VideoStreamPtr& stream) { + LOG_ENTER_P(this); + if (state_manager_.GetState() < EsState::kIdle) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return false; + } + Track track = stream->GetTrack_(); + if (state_manager_.GetState() >= EsState::kReady) { + track.active = false; + return ChangeStream_(track); + } + return SetStream_(track); +} + +bool EsPlayer::SwitchAudioStreamOnTheFly(const AudioStreamPtr& stream) { + LOG_ENTER_P(this); + if (state_manager_.GetState() < EsState::kReady) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return false; + } + Track track = stream->GetTrack_(); + + if (!internal::IsAvailableCodecSwitch(track)) { + LOG_ERROR_P(this, "Invalid new mimetype [%s][%d]", track.mimetype.c_str(), + track.version); + return false; + } + for (auto& old_track : track_) { + if (old_track.type == TrackType::kTrackTypeAudio) { + if (!internal::IsAvailableCodecSwitch(old_track)) { + LOG_ERROR_P(this, "Invalid previous mimetype [%s][%d]", + old_track.mimetype.c_str(), old_track.version); + return false; + } + if (!Flush(StreamType::kAudio)) return false; + old_track.active = false; + break; + } + } + if (!ChangeStream_(track)) return false; + + trackrenderer_->SetTrack(track_); + return true; +} + +bool EsPlayer::SetAdvancedPictureQualityType(const AdvPictureQualityType type) { + LOG_ENTER_P(this); + if (state_manager_.GetState() != EsState::kIdle) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return false; + } + trackrenderer_->SetAdvancedPictureQualityType(type); + return true; +} + +bool EsPlayer::SetResourceAllocatePolicy(const RscAllocPolicy policy) { + LOG_ENTER_P(this); + if (state_manager_.GetState() != EsState::kIdle) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return false; + } + trackrenderer_->SetResourceAllocatePolicy(policy); + return true; +} + +void EsPlayer::ResetContextForClose_() { + internal::ResetDrmProperty(drm_property_); + track_.clear(); + submit_data_type_ = SubmitDataType::kCleanData; + low_latency_mode_ = 0; + resume_time_.is_set = false; + video_frame_peek_mode_ = 0; + unlimited_max_buffer_mode_ = 0; + fmm_mode_ = 0; + audio_codec_type_ = kPlayerAudioCodecTypeHW; + video_codec_type_ = kPlayerVideoCodecTypeHW; + is_resource_conflicted_ = false; + app_info_ = PlayerAppInfo(); + src_queue_size_ = SrcQueueSize(); + is_msg_task_stop_ = false; +} + +void EsPlayer::ResetContextForStop_() { + for (int i = 0; i < kTrackTypeMax; ++i) { + need_data_[i].mask = kNeedDataMaskNone; + need_data_[i].seek_offset = 0; + } + { + std::lock_guard lock(eos_mutex_); + eos_status_ = EosStatus::kAllEos; + } + current_playback_rate_ = 1.0; + current_audio_mute_ = false; +} + +void EsPlayer::GetSrcQueueCurrentSize_(const TrackType& type, + uint64_t* byte_size, + uint64_t* time_size) { + boost::any byte_size_, time_size_; + if (type == TrackType::kTrackTypeVideo) { + trackrenderer_->GetAttribute( + TrackRendererAdapter::Attribute::kVideoQueueCurrentLevelByte, + &byte_size_); + trackrenderer_->GetAttribute( + TrackRendererAdapter::Attribute::kVideoQueueCurrentLevelTime, + &time_size_); + } else if (type == TrackType::kTrackTypeAudio) { + trackrenderer_->GetAttribute( + TrackRendererAdapter::Attribute::kAudioQueueCurrentLevelByte, + &byte_size_); + trackrenderer_->GetAttribute( + TrackRendererAdapter::Attribute::kAudioQueueCurrentLevelTime, + &time_size_); + } else + return; + *byte_size = boost::any_cast(byte_size_); + *time_size = boost::any_cast(time_size_); + *time_size = util::ConvertNsToMs(*time_size); +} + +GstBuffer* EsPlayer::GetGstBuffer_(const EsPacketPtr& packet, + MakeBufferStatus* status) { + std::shared_ptr buffer = packet->GetBuffer(); + uint32_t size = packet->GetSize(); + if (packet->IsEosPacket()) { + *status = MakeBufferStatus::kEos; + return nullptr; + } + GstBuffer* gstbuffer = gst_buffer_new_and_alloc(size); + if (!gstbuffer) { + *status = MakeBufferStatus::kOutOfMemory; + return nullptr; + } + if (buffer != nullptr) { + GstMapInfo map; + gst_buffer_map(gstbuffer, &map, GST_MAP_WRITE); + memcpy(map.data, buffer.get(), size); + gst_buffer_unmap(gstbuffer, &map); + } + + uint64_t pts = packet->GetPts(); + uint64_t duration = packet->GetDuration(); + /* if pts or duration are -1(=GST_CLOCK_TIME_NONE), some of the elements don't + * adjust the buffer. */ + GST_BUFFER_PTS(gstbuffer) = (pts == GST_CLOCK_TIME_NONE) + ? GST_CLOCK_TIME_NONE + : (GstClockTime)util::ConvertMsToNs(pts); + GST_BUFFER_DURATION(gstbuffer) = + (duration == GST_CLOCK_TIME_NONE) + ? GST_CLOCK_TIME_NONE + : (GstClockTime)util::ConvertMsToNs(duration); + uint32_t hdr10p_size = packet->GetHdr10pSize(); + std::shared_ptr hdr10p_metadata = packet->GetHdr10pData(); + + if (hdr10p_size > 0 && hdr10p_metadata != nullptr) { + guint32* blockadditional_size = (guint32*)g_malloc(sizeof(uint32_t)); + *blockadditional_size = hdr10p_size; + gst_mini_object_set_qdata( + GST_MINI_OBJECT(gstbuffer), + g_quark_from_static_string("matroska_blockadditional_size"), + blockadditional_size, g_free); + + /* blockadditiona_data : the data sent to omx, size (4 bytes) + metadata */ + guint8* blockadditional_data = + (guint8*)g_malloc(((*blockadditional_size) + 4) * sizeof(guint8)); + memcpy(blockadditional_data, blockadditional_size, sizeof(uint32_t)); + memcpy(blockadditional_data + 4, hdr10p_metadata.get(), + (*blockadditional_size)); + gst_mini_object_set_qdata( + GST_MINI_OBJECT(gstbuffer), + g_quark_from_static_string("matroska_blockadditional_info"), + blockadditional_data, g_free); + } + *status = MakeBufferStatus::kSuccess; + return gstbuffer; +} + +PacketSubmitStatus EsPlayer::SubmitEosPacket_(const TrackType& type) { + PacketSubmitStatus submitstate = PacketSubmitStatus::kSuccess; + { + std::lock_guard lock(eos_mutex_); + switch (type) { + case kTrackTypeAudio: + eos_status_ |= EosStatus::kAudioEos; + break; + case kTrackTypeVideo: + eos_status_ |= EosStatus::kVideoEos; + break; + default: + break; + } + if (eos_status_ != EosStatus::kAllEos) { + return submitstate; + } + } + for (int tracktype = kTrackTypeAudio; tracktype < kTrackTypeMax; + ++tracktype) { + auto inbuffer = + DecoderInputBuffer::Create(static_cast(tracktype)); + if (!trackrenderer_->SubmitPacket2(inbuffer, nullptr)) { + std::lock_guard lock(eos_mutex_); + eos_status_ = EosStatus::kAllEos; + submitstate = PacketSubmitStatus::kNotPrepared; + return submitstate; + } + } + return submitstate; +} + +void EsPlayer::UnsetTzQdata_(const DecoderInputBufferPtr& buffer) { + const GstBuffer* gstbuf = buffer->Get(); + if (!gstbuf) return; + GstStructure* tzqdata = GST_STRUCTURE(gst_mini_object_steal_qdata( + GST_MINI_OBJECT(gstbuf), g_quark_from_static_string("GstTzHandleData"))); + if (tzqdata) gst_structure_free(tzqdata); +} + +PacketSubmitStatus EsPlayer::SubmitDecoderInputBuffer_( + const DecoderInputBufferPtr& buffer) { + PacketSubmitStatus status = PacketSubmitStatus::kSuccess; + TrackRendererAdapter::SubmitStatus submitstate = + TrackRendererAdapter::SubmitStatus::kSuccess; + trackrenderer_->SubmitPacket2(buffer, &submitstate); + + switch (submitstate) { + case TrackRendererAdapter::SubmitStatus::kSuccess: + case TrackRendererAdapter::SubmitStatus::kDrop: + status = PacketSubmitStatus::kSuccess; + break; + case TrackRendererAdapter::SubmitStatus::kFull: + UnsetTzQdata_(buffer); + trackrenderer_event_listener_->OnBufferStatus(buffer->GetType(), + BufferStatus::kOverrun); + status = PacketSubmitStatus::kFull; + break; + default: + UnsetTzQdata_(buffer); + status = PacketSubmitStatus::kNotPrepared; + break; + } + return status; +} + +void EsPlayer::MakeGstBufferForTzHandle_(GstBuffer* gstbuffer, + const TrackType& type, + const uint32_t& tz_handle, + const uint32_t& packet_size) { + GstStructure* gst_tz_handle_data_structure = + gst_structure_new("GstTzHandleData", "packet_handle", G_TYPE_UINT, + static_cast(tz_handle), "packet_size", + G_TYPE_UINT, static_cast(packet_size), + "secure", G_TYPE_BOOLEAN, true, nullptr); + gst_mini_object_set_qdata( + GST_MINI_OBJECT(gstbuffer), g_quark_from_string("GstTzHandleData"), + gst_tz_handle_data_structure, (GDestroyNotify)gst_structure_free); + + if (type == kTrackTypeAudio) { + Track audio_track; + bool has_active_audio_track = + track_util::GetActiveTrack(track_, kTrackTypeAudio, &audio_track); + if (has_active_audio_track) { + GstStructure* audio_info_structure = + gst_structure_new("AudioInfo", "mime_type", G_TYPE_STRING, + audio_track.mimetype.c_str(), nullptr); + gst_mini_object_set_qdata( + GST_MINI_OBJECT(gstbuffer), g_quark_from_string("AudioInfo"), + audio_info_structure, (GDestroyNotify)gst_structure_free); + } + } +} + +void EsPlayer::MakeGstBufferForEncryptedPacket_( + GstBuffer* gstbuffer, const EsPacketPtr& packet, + const drm::EsPlayerEncryptedInfo& drm_info) { + if (drm_info.handle == 0) return; + auto serialized_drm_info_ptr = esplayer_drm::Serialize(packet, drm_info); + GstStructure* gst_drm_info_structure = + gst_structure_new("drm_info", "drm_specific_info", G_TYPE_BYTES, + serialized_drm_info_ptr.get(), nullptr); + if (gst_drm_info_structure) { + gst_mini_object_set_qdata( + GST_MINI_OBJECT(gstbuffer), g_quark_from_static_string("drm_info"), + gst_drm_info_structure, (GDestroyNotify)gst_structure_free); + } +} + +PacketSubmitStatus EsPlayer::SubmitPacketCommon_(const EsPacketPtr& packet, + SubmitPacketOperator op) { + if (state_manager_.GetState() < EsState::kIdle) { + return PacketSubmitStatus::kNotPrepared; + } + if (!packet) return PacketSubmitStatus::kInvalidPacket; + + TrackType type = static_cast(packet->GetType()); + Track activated_track; + if (!track_util::GetActiveTrack(track_, type, &activated_track)) + return PacketSubmitStatus::kInvalidPacket; + + if (state_manager_.GetState() == EsState::kPaused || + state_manager_.GetState() == EsState::kReady) { + internal::AppsrcQueueSizeOption byte_based, time_based; + switch (type) { + case kTrackTypeAudio: + byte_based.max_size = src_queue_size_.kMaxByteOfAudioSrcQueue; + time_based.max_size = src_queue_size_.kMaxTimeOfAudioSrcQueue; + byte_based.threshold = src_queue_size_.kMinByteThresholdOfAudioSrcQueue; + time_based.threshold = src_queue_size_.kMinTimeThresholdOfAudioSrcQueue; + break; + case kTrackTypeVideo: + byte_based.max_size = src_queue_size_.kMaxByteOfVideoSrcQueue; + time_based.max_size = src_queue_size_.kMaxTimeOfVideoSrcQueue; + byte_based.threshold = src_queue_size_.kMinByteThresholdOfVideoSrcQueue; + time_based.threshold = src_queue_size_.kMinTimeThresholdOfVideoSrcQueue; + break; + default: + break; + } + GetSrcQueueCurrentSize_(type, &(byte_based.current_size), + &(time_based.current_size)); + if (internal::IsUnderRun(byte_based, time_based)) + trackrenderer_event_listener_->OnBufferStatus(type, + BufferStatus::kUnderrun); + } + + es_packet_logger_.StorePacketInfo(packet); + es_packet_logger_.PrintStoredPacketInfo(packet->GetType()); + + MakeBufferStatus make_buffer_status; + GstBuffer* gstbuffer = GetGstBuffer_(packet, &make_buffer_status); + if (!gstbuffer) { + if (make_buffer_status == MakeBufferStatus::kEos) + return SubmitEosPacket_(type); + else if (make_buffer_status == MakeBufferStatus::kOutOfMemory) + return PacketSubmitStatus::kOutOfMemory; + } + if (op != nullptr) { + PacketSubmitStatus op_status = op(gstbuffer); + if (op_status != PacketSubmitStatus::kSuccess) { + return op_status; + } + } + + auto inbuffer = DecoderInputBuffer::Create(type, 0, gstbuffer); + gst_buffer_unref(gstbuffer); + + if (packet->HasMatroskaColorInfo()) { + std::string color_info_str = + util::GetStringFromMatroskaColor(packet->GetMatroskaColorInfo()); + LOG_DEBUG_P(this, "Detected MatroskaColor : %s", color_info_str.c_str()); + if (trackrenderer_->SetMatroskaColorInfo(color_info_str) == false) + return PacketSubmitStatus::kNotPrepared; + } + + return SubmitDecoderInputBuffer_(inbuffer); +} + +PacketSubmitStatus EsPlayer::SubmitPacket(const EsPacketPtr& packet) { + std::lock_guard lk(submit_mutex_); + return SubmitPacketCommon_(packet, nullptr); +} + +PacketSubmitStatus EsPlayer::SubmitTrustZonePacket(const EsPacketPtr& packet, + uint32_t tz_handle) { + std::lock_guard lk(submit_mutex_); + if (submit_data_type_ != SubmitDataType::kTrustZoneData) + return PacketSubmitStatus::kInvalidPacket; + auto submitpacket_op = [this, &tz_handle, + &packet](GstBuffer* gstbuffer) -> PacketSubmitStatus { + if (tz_handle > 0) { + TrackType type = static_cast(packet->GetType()); + uint32_t packet_size = packet->GetSize(); + MakeGstBufferForTzHandle_(gstbuffer, type, tz_handle, packet_size); + } + return PacketSubmitStatus::kSuccess; + }; + return SubmitPacketCommon_(packet, submitpacket_op); +} + +PacketSubmitStatus EsPlayer::SubmitEncryptedPacket( + const EsPacketPtr& packet, const drm::EsPlayerEncryptedInfo& drm_info) { + std::lock_guard lk(submit_mutex_); + if (submit_data_type_ != SubmitDataType::kEncryptedData) + return PacketSubmitStatus::kInvalidPacket; + auto submitpacket_op = + [this, &packet, &drm_info](GstBuffer* gstbuffer) -> PacketSubmitStatus { + MakeGstBufferForEncryptedPacket_(gstbuffer, packet, drm_info); + return PacketSubmitStatus::kSuccess; + }; + return SubmitPacketCommon_(packet, submitpacket_op); +} + +EsState EsPlayer::GetState() { return state_manager_.GetState(); } + +bool EsPlayer::GetPlayingTime(uint64_t* time_in_milliseconds) { + if (!time_in_milliseconds) return false; + if (state_manager_.GetState() <= EsState::kReady) { + *time_in_milliseconds = 0; + return false; + } + return trackrenderer_->GetPlayingTime(time_in_milliseconds); +} + +bool EsPlayer::SetAudioMute(bool is_mute) { + if (state_manager_.GetState() < EsState::kIdle) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return false; + } + return trackrenderer_->SetAudioMute(is_mute); +} + +bool EsPlayer::SetVideoFrameBufferType(DecodedVideoFrameBufferType type) { + if ((state_manager_.GetState() != EsState::kIdle && + type != DecodedVideoFrameBufferType::kScale) || + (state_manager_.GetState() < EsState::kIdle && + type == DecodedVideoFrameBufferType::kScale)) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return false; + } + if (type == DecodedVideoFrameBufferType::kScale && + video_codec_type_ == kPlayerVideoCodecTypeSW) { + LOG_ERROR_P(this, "kScale is not supportted when using sw video decoder"); + return false; + } + trackrenderer_->SetVideoFrameBufferType( + VideoFrameTypeStrategyPtr(new DefaultVideoFrameTypeStrategy(type))); + vidoe_frame_buffer_type_ = type; + return true; +} + +bool EsPlayer::SetVideoFrameBufferScaleResolution( + const uint32_t& target_width, const uint32_t& target_height) { + if (state_manager_.GetState() < EsState::kIdle) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return false; + } + + return trackrenderer_->SetVideoFrameBufferScaleResolution(target_width, + target_height); +} + +bool EsPlayer::SetDecodedVideoFrameRate(const Rational& request_framerate) { + if (state_manager_.GetState() < EsState::kIdle) { + LOG_ERROR("Invalid State , current %d", state_manager_.GetStateEnum()); + return false; + } + + return trackrenderer_->SetDecodedVideoFrameRate(request_framerate); +} + +void EsPlayer::RegisterListener(EsEventListener* listener, + EsEventListener::UserData userdata) { + // assert(listener); // allow unregister by setting nullptr + assert(!eventlistener_); + eventlistener_ = listener; + eventlistener_userdata_ = userdata; +} + +bool EsPlayer::GetAdaptiveInfo(void* padaptive_info, + const PlayerAdaptiveInfo& adaptive_type) { + if (!padaptive_info || adaptive_type <= PlayerAdaptiveInfo::kMinType || + adaptive_type >= PlayerAdaptiveInfo::kMaxType) + return false; + switch (adaptive_type) { + case PlayerAdaptiveInfo::kVideoDroppedFrames: + if (state_manager_.GetState() < EsState::kReady) { + LOG_ERROR_P(this, "Wrong state, we aren't started yet"); + return false; + } + return trackrenderer_->GetDroppedFrames(padaptive_info); + case PlayerAdaptiveInfo::kDroppedVideoFramesForCatchup: + if (state_manager_.GetState() < EsState::kReady) { + LOG_ERROR_P(this, "Wrong state, we aren't started yet"); + return false; + } + return trackrenderer_->GetDroppedFramesForCatchup(kTrackTypeVideo, + padaptive_info); + case PlayerAdaptiveInfo::kDroppedAudioFramesForCatchup: + if (state_manager_.GetState() < EsState::kReady) { + LOG_ERROR_P(this, "Wrong state, we aren't started yet"); + return false; + } + return trackrenderer_->GetDroppedFramesForCatchup(kTrackTypeAudio, + padaptive_info); + default: + break; + } + return false; +} + +bool EsPlayer::SetVolume(const int& volume) { + if (volume < internal::kVolumeMin || volume > internal::kVolumeMax) { + LOG_ERROR_P(this, "Invalid volume level %d", volume); + return false; + } + if (state_manager_.GetState() < EsState::kIdle) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return false; + } + return trackrenderer_->SetVolume(volume); +} + +bool EsPlayer::GetVolume(int* volume) { + if (state_manager_.GetState() < EsState::kIdle) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return false; + } + return trackrenderer_->GetVolume(volume); +} + +bool EsPlayer::Flush(const StreamType& type) { + if (state_manager_.GetState() <= EsState::kIdle) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return false; + } + eos_status_ = + internal::ResetEosStatus(static_cast(type), eos_status_); + es_packet_logger_.ResetLog(type); + return trackrenderer_->Flush(type); +} + +void EsPlayer::SetBufferSize(const BufferOption& option, uint64_t size) { + if (state_manager_.GetState() != EsState::kIdle) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return; + } + switch (option) { + case BufferOption::kBufferAudioMaxByteSize: + src_queue_size_.kMaxByteOfAudioSrcQueue = size; + break; + case BufferOption::kBufferVideoMaxByteSize: + src_queue_size_.kMaxByteOfVideoSrcQueue = size; + break; + case BufferOption::kBufferAudioMinByteThreshold: + src_queue_size_.kMinByteThresholdOfAudioSrcQueue = + static_cast(size); + break; + case BufferOption::kBufferVideoMinByteThreshold: + src_queue_size_.kMinByteThresholdOfVideoSrcQueue = + static_cast(size); + break; + case BufferOption::kBufferAudioMaxTimeSize: + src_queue_size_.kMaxTimeOfAudioSrcQueue = size; + break; + case BufferOption::kBufferVideoMaxTimeSize: + src_queue_size_.kMaxTimeOfVideoSrcQueue = size; + break; + case BufferOption::kBufferAudioMinTimeThreshold: + src_queue_size_.kMinTimeThresholdOfAudioSrcQueue = + static_cast(size); + break; + case BufferOption::kBufferVideoMinTimeThreshold: + src_queue_size_.kMinTimeThresholdOfVideoSrcQueue = + static_cast(size); + break; + default: + LOG_ERROR_P(this, "Invalid option!!!"); + break; + } +} + +bool EsPlayer::SetLowLatencyMode(const PlayerLowLatencyMode& mode) { + if (state_manager_.GetState() != EsState::kIdle) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return false; + } + low_latency_mode_ |= static_cast(mode); + return true; +} + +bool EsPlayer::SetRenderTimeOffset(const StreamType type, int64_t offset) { + if (state_manager_.GetState() < EsState::kReady) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return false; + } + if (!internal::IsSupportedTsOffset(low_latency_mode_)) { + LOG_ERROR_P(this, + "low latency mode have to be set except disable_sync mode"); + return false; + } + constexpr std::int64_t ns_unit = 1000000; + if ((offset * ns_unit > G_MAXINT64) || (offset * ns_unit < G_MININT64)) { + LOG_ERROR_P( + this, "wrong value : G_MAXINT64 < offset[%lld] * 1000000 < G_MAXINT64", + offset); + return false; + } + if (type == StreamType::kMax) return false; + if (type == StreamType::kAudio) + trackrenderer_->SetAttribute( + TrackRendererAdapter::Attribute::kAudioRenderTimeOffset, + util::ConvertMsToNs(offset)); + else if (type == StreamType::kVideo) + trackrenderer_->SetAttribute( + TrackRendererAdapter::Attribute::kVideoRenderTimeOffset, + util::ConvertMsToNs(offset)); + return true; +} + +bool EsPlayer::GetRenderTimeOffset(const StreamType type, int64_t* offset) { + if (state_manager_.GetState() < EsState::kReady) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return false; + } + if (!internal::IsSupportedTsOffset(low_latency_mode_)) { + LOG_ERROR_P(this, "low latency mode have to be set"); + return false; + } + if (type == StreamType::kMax) return false; + boost::any off_set; + if (type == StreamType::kAudio) + trackrenderer_->GetAttribute( + TrackRendererAdapter::Attribute::kAudioRenderTimeOffset, &off_set); + else if (type == StreamType::kVideo) + trackrenderer_->GetAttribute( + TrackRendererAdapter::Attribute::kVideoRenderTimeOffset, &off_set); + + *offset = boost::any_cast(off_set); + *offset = util::ConvertNsToMs(*offset); + return true; +} + +bool EsPlayer::SetVideoFramePeekMode() { + if (state_manager_.GetState() != EsState::kIdle) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return false; + } + constexpr std::uint32_t peek_mode_on = 1; + video_frame_peek_mode_ = peek_mode_on; + return true; +} + +bool EsPlayer::RenderVideoFrame() { + if (!video_frame_peek_mode_) return false; + if (state_manager_.GetState() == EsState::kReady || + state_manager_.GetState() == EsState::kPaused) { + trackrenderer_->RenderVideoFrame(); + return true; + } + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return false; +} + +bool EsPlayer::SetUnlimitedMaxBufferMode() { + if (state_manager_.GetState() != EsState::kIdle) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return false; + } + constexpr std::uint32_t unlimited_max_buffer_mode_on = 1; + unlimited_max_buffer_mode_ = unlimited_max_buffer_mode_on; + return true; +} + +bool EsPlayer::SetFmmMode() { + if (state_manager_.GetState() != EsState::kIdle) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return false; + } + constexpr std::uint32_t fmm_mode_on = 1; + fmm_mode_ = fmm_mode_on; + return true; +} + +bool EsPlayer::SetAudioCodecType(const PlayerAudioCodecType& type) { + Track activated_track; + bool is_existed = + track_util::GetActiveTrack(track_, kTrackTypeAudio, &activated_track); + EsState state = state_manager_.GetState(); + if ((state < EsState::kIdle) || (state > EsState::kIdle && is_existed)) { + LOG_ERROR_P(this, + "Invalid State [state:%d] or audio stream already exists[%d],", + state_manager_.GetStateEnum(), is_existed); + return false; + } + if (force_audio_swdecoder_use_ && type == kPlayerAudioCodecTypeHW) { + LOG_ERROR_P(this, "Not support hw decoder"); + return false; + } + LOG_INFO_P(this, "PlayerAudioCodecType [%s]", + (type == kPlayerAudioCodecTypeHW) ? "hardware" : "software"); + audio_codec_type_ = type; + return true; +} + +bool EsPlayer::SetVideoCodecType(const PlayerVideoCodecType& type) { + Track activated_track; + bool is_existed = + track_util::GetActiveTrack(track_, kTrackTypeVideo, &activated_track); + EsState state = state_manager_.GetState(); + if ((state < EsState::kIdle) || (state > EsState::kIdle && is_existed)) { + LOG_ERROR_P(this, + "Invalid State [state:%d] or video stream already exists[%d],", + state_manager_.GetStateEnum(), is_existed); + return false; + } + if (type == kPlayerVideoCodecTypeSW && + vidoe_frame_buffer_type_ == DecodedVideoFrameBufferType::kScale) { + LOG_ERROR_P(this, + "sw video decoder is not supportted when video frame buffer " + "type is scale"); + return false; + } +#ifndef IS_AUDIO_PRODUCT + if (!enable_rsc_alloc_handle_) { + LOG_ERROR_P(this, "player can't control resource type, mixer will do it"); + return false; + } + if (type == kPlayerVideoCodecTypeHWNdecoding) { + LOG_INFO_P(this, "PlayerVideoCodecType HW N-decoding"); + video_decoding_mode_ = internal::kNdecodingMode; + return true; + } +#endif + LOG_INFO_P(this, "PlayerVideoCodecType [%s]", + (type == kPlayerVideoCodecTypeHW) ? "hardware" : "software"); + video_codec_type_ = type; + return true; +} + +bool EsPlayer::SetAiFilter(void* aifilter) { + if (state_manager_.GetState() != EsState::kIdle) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return false; + } + trackrenderer_->SetAiFilter(aifilter); + return true; +} + +bool EsPlayer::InitAudioEasingInfo(const uint32_t init_volume, + const uint32_t init_elapsed_time, + const AudioEasingInfo& easing_info) { + if (init_volume > internal::kVolumeMax || + easing_info.target_volume > internal::kVolumeMax) { + LOG_ERROR_P(this, "Invalid volume: init [%d] target [%d]", init_volume, + easing_info.target_volume); + return false; + } + if (state_manager_.GetState() < EsState::kIdle) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return false; + } + return trackrenderer_->InitAudioEasingInfo(init_volume, init_elapsed_time, + easing_info); +} + +bool EsPlayer::UpdateAudioEasingInfo(const AudioEasingInfo& easing_info) { + if (easing_info.target_volume > internal::kVolumeMax) { + LOG_ERROR_P(this, "Invalid volume level %d", easing_info.target_volume); + return false; + } + if (state_manager_.GetState() < EsState::kIdle) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return false; + } + return trackrenderer_->UpdateAudioEasingInfo(easing_info); +} + +bool EsPlayer::GetAudioEasingInfo(uint32_t* current_volume, + uint32_t* elapsed_time, + AudioEasingInfo* easing_info) { + if (state_manager_.GetState() < EsState::kIdle) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return false; + } + return trackrenderer_->GetAudioEasingInfo(current_volume, elapsed_time, + easing_info); +} + +bool EsPlayer::StartAudioEasing() { + if (state_manager_.GetState() < EsState::kReady) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return false; + } + return trackrenderer_->StartAudioEasing(); +} + +bool EsPlayer::StopAudioEasing() { + if (state_manager_.GetState() < EsState::kIdle) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return false; + } + return trackrenderer_->StopAudioEasing(); +} + +bool EsPlayer::SetAlternativeVideoResource(unsigned int rsc_type) { + if (state_manager_.GetState() == EsState::kNone) { + LOG_ERROR_P(this, "Invalid State"); + return false; + } +#ifndef IS_AUDIO_PRODUCT + if (!enable_rsc_alloc_handle_) { + LOG_ERROR_P(this, "player can't control resource type, mixer will do it"); + return false; + } +#endif + alternaltive_video_resource_ = static_cast(rsc_type); + return true; +} + +bool EsPlayer::SetCatchUpSpeed(const CatchUpSpeed& level) { + if (state_manager_.GetState() < EsState::kIdle) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return false; + } + + if (false == internal::IsSetLowLatencyModeForCatchUp(low_latency_mode_)) { + LOG_ERROR_P(this, "Do not set low latency mode, current[%u]", + static_cast(low_latency_mode_)); + return false; + } + + if (trackrenderer_->SetCatchUpSpeed(level)) { + return true; + } + return false; +} + +bool EsPlayer::GetVideoLatencyStatus(LatencyStatus* status) { + if (state_manager_.GetState() < EsState::kReady) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return false; + } + + if (trackrenderer_->GetVideoLatencyStatus(status)) { + return true; + } + return false; +} + +bool EsPlayer::GetAudioLatencyStatus(LatencyStatus* status) { + if (state_manager_.GetState() < EsState::kReady) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return false; + } + + if (trackrenderer_->GetAudioLatencyStatus(status)) { + return true; + } + return false; +} + +bool EsPlayer::SetVideoMidLatencyThreshold(const unsigned int threshold) { + if (state_manager_.GetState() < EsState::kIdle) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return false; + } + + if (false == internal::IsSetLowLatencyModeForCatchUp(low_latency_mode_)) { + LOG_ERROR_P(this, "Don't set low latency mode, current[%u]", + static_cast(low_latency_mode_)); + return false; + } + + LOG_INFO_P(this, "video_mid_latency_threshold : [%u]", threshold); + + trackrenderer_->SetVideoMidLatencyThreshold(threshold); + return true; +} + +bool EsPlayer::SetAudioMidLatencyThreshold(const unsigned int threshold) { + if (state_manager_.GetState() < EsState::kIdle) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return false; + } + + if (false == internal::IsSetLowLatencyModeForCatchUp(low_latency_mode_)) { + LOG_ERROR_P(this, "Don't set low latency mode, current[%u]", + static_cast(low_latency_mode_)); + return false; + } + + LOG_INFO_P(this, "audio_mid_latency_threshold : [%u]", threshold); + + trackrenderer_->SetAudioMidLatencyThreshold(threshold); + return true; +} + +bool EsPlayer::SetVideoHighLatencyThreshold(const unsigned int threshold) { + if (state_manager_.GetState() < EsState::kIdle) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return false; + } + + if (false == internal::IsSetLowLatencyModeForCatchUp(low_latency_mode_)) { + LOG_ERROR_P(this, "Don't set low latency mode, current[%u]", + static_cast(low_latency_mode_)); + return false; + } + + LOG_INFO_P(this, "video_high_latency_threshold : [%u]", threshold); + + trackrenderer_->SetVideoHighLatencyThreshold(threshold); + return true; +} + +bool EsPlayer::SetAudioHighLatencyThreshold(const unsigned int threshold) { + if (state_manager_.GetState() < EsState::kIdle) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + return false; + } + + if (false == internal::IsSetLowLatencyModeForCatchUp(low_latency_mode_)) { + LOG_ERROR_P(this, "Don't set low latency mode, current[%u]", + static_cast(low_latency_mode_)); + return false; + } + + LOG_INFO_P(this, "audio_high_latency_threshold : [%u]", threshold); + + trackrenderer_->SetAudioHighLatencyThreshold(threshold); + return true; +} + +bool EsPlayer::GetVirtualRscId(const RscType type, int* virtual_id) { + if (virtual_id == nullptr) return false; + if (state_manager_.GetState() < EsState::kReady) { + LOG_ERROR_P(this, "Invalid State , current %d", + state_manager_.GetStateEnum()); + *virtual_id = -1; + return false; + } + return trackrenderer_->GetVirtualRscId(type, virtual_id); +} + +void EsPlayer::Init_() { + track_.clear(); + is_stopped_ = false; + LOG_LEAVE_P(this); +} + +void EsPlayer::MsgTask_() { + while (!is_msg_task_stop_) { + std::unique_lock msg_mutex(msg_task_mutex_); + if (msg_queue_.empty()) { + msg_task_cv_.wait(msg_mutex); + } else { + msg_mutex.unlock(); + msg_queue_.front()->Execute(); + msg_mutex.lock(); + msg_queue_.pop(); + } + } + while (!msg_queue_.empty()) { + std::unique_lock msg_mutex(msg_task_mutex_); + msg_queue_.pop(); + } + LOG_INFO_P(this, "Stop MsgTask"); +} + +void EsPlayer::TrackRendererEventListener::OnEos() { + LOG_ENTER_P(handler_); + if (!handler_->eventlistener_) return; + auto listener = std::bind(&plusplayer::EsEventListener::OnEos, + handler_->eventlistener_, std::placeholders::_1); + auto msg = es_msg::Simple::Make(listener, handler_->eventlistener_userdata_); + std::unique_lock msg_mutex(handler_->msg_task_mutex_); + handler_->msg_queue_.push(std::move(msg)); + msg_mutex.unlock(); + handler_->msg_task_cv_.notify_one(); + LOG_LEAVE_P(handler_); +} + +void EsPlayer::TrackRendererEventListener::OnSeekDone() { + LOG_ENTER_P(handler_); + if (!handler_->eventlistener_) return; + if (handler_->is_seek_done_need_drop == true) return; + auto listener = std::bind(&plusplayer::EsEventListener::OnSeekDone, + handler_->eventlistener_, std::placeholders::_1); + auto msg = es_msg::Simple::Make(listener, handler_->eventlistener_userdata_); + std::unique_lock msg_mutex(handler_->msg_task_mutex_); + handler_->msg_queue_.push(std::move(msg)); + msg_mutex.unlock(); + handler_->msg_task_cv_.notify_one(); + LOG_LEAVE_P(handler_); +} + +void EsPlayer::TrackRendererEventListener::OnResourceConflicted() { + LOG_ENTER_P(handler_); + if (handler_->is_stopped_) { + LOG_INFO_P(handler_, "LEAVE ~ Stop is called already"); + return; + } + LOG_INFO_P(handler_, "Handling resource conflict..."); + handler_->is_resource_conflicted_ = true; + handler_->trackrenderer_->Stop(); + if (!handler_->eventlistener_ || handler_->is_stopped_) return; + auto listener = std::bind(&plusplayer::EsEventListener::OnResourceConflicted, + handler_->eventlistener_, std::placeholders::_1); + auto msg = es_msg::Simple::Make(listener, handler_->eventlistener_userdata_); + std::unique_lock msg_mutex(handler_->msg_task_mutex_); + handler_->msg_queue_.push(std::move(msg)); + msg_mutex.unlock(); + if (handler_->is_stopped_) { + LOG_LEAVE_P(handler_); + return; + } + handler_->msg_task_cv_.notify_one(); + LOG_LEAVE_P(handler_); +} + +void EsPlayer::TrackRendererEventListener::OnError( + const ErrorType& error_code) { + if (!handler_->eventlistener_) return; + if (error_code == ErrorType::kResourceLimit) return; + auto listener = + std::bind(&plusplayer::EsEventListener::OnError, handler_->eventlistener_, + std::placeholders::_1, std::placeholders::_2); + auto msg = es_msg::Error::Make(error_code, listener, + handler_->eventlistener_userdata_); + + std::unique_lock msg_mutex(handler_->msg_task_mutex_); + handler_->msg_queue_.push(std::move(msg)); + msg_mutex.unlock(); + handler_->msg_task_cv_.notify_one(); +} + +void EsPlayer::TrackRendererEventListener::OnErrorMsg( + const ErrorType& error_code, char* error_msg) { + if (!handler_->eventlistener_) return; + if (error_code == ErrorType::kResourceLimit) return; + + std::vector activeTracks; + track_util::GetActiveTrackList(handler_->track_, activeTracks); + + Json::Value message; + message["error_code"] = (int)error_code; + + switch (error_code) { + case ErrorType::kNotSupportedVideoCodec: + for (const auto& track : activeTracks) { + if (track.type == kTrackTypeVideo) { + message["codec"] = track.mimetype.c_str(); + message["demux"] = track.container_type.c_str(); + char json_string[20] = {0}; + int nLen = 19; + strncat(json_string, std::to_string(track.width).c_str(), nLen); + nLen = sizeof(json_string) - strlen(json_string) - 1; + if (nLen < 0) nLen = 0; + strncat(json_string, "*", nLen); + nLen = sizeof(json_string) - strlen(json_string) - 1; + if (nLen < 0) nLen = 0; + strncat(json_string, std::to_string(track.height).c_str(), nLen); + message["resolution"] = json_string; + memset(json_string, 0, sizeof(json_string)); + nLen = 19; + strncat(json_string, std::to_string(track.framerate_num).c_str(), + nLen); + nLen = sizeof(json_string) - strlen(json_string) - 1; + if (nLen < 0) nLen = 0; + strncat(json_string, "/", nLen); + nLen = sizeof(json_string) - strlen(json_string) - 1; + if (nLen < 0) nLen = 0; + strncat(json_string, std::to_string(track.framerate_den).c_str(), + nLen); + message["fps"] = json_string; + message["detail_info"] = error_msg; + break; + } + } + break; + case ErrorType::kNotSupportedAudioCodec: + + break; + default: + break; + } + + Json::FastWriter writer; + std::string str = writer.write(message); + LOG_INFO_P(handler_, "error message: %s", str.c_str()); + + auto listener = std::bind(&plusplayer::EsEventListener::OnErrorMsg, + handler_->eventlistener_, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3); + auto msg = + es_msg::ErrorMsg::Make(error_code, str.c_str(), str.size(), listener, + handler_->eventlistener_userdata_); + + std::unique_lock msg_mutex(handler_->msg_task_mutex_); + handler_->msg_queue_.push(std::move(msg)); + msg_mutex.unlock(); + handler_->msg_task_cv_.notify_one(); +} + +void EsPlayer::TrackRendererEventListener::ReadyToPrepare_( + const TrackType& type) { + LOG_INFO_P(handler_, "OnReadyToPrepare [%s]", + (type == kTrackTypeAudio) ? "audio" : "video"); + + handler_->need_data_[type].mask &= ~kNeedDataMaskByPrepare; + + auto listener = std::bind(&plusplayer::EsEventListener::OnReadyToPrepare, + handler_->eventlistener_, std::placeholders::_1, + std::placeholders::_2); + StreamType stream_type = internal::ConvertToStreamType(type); + auto msg = es_msg::ReadyToPrepare::Make(stream_type, listener, + handler_->eventlistener_userdata_); + std::unique_lock msg_mutex(handler_->msg_task_mutex_); + handler_->msg_queue_.push(std::move(msg)); + msg_mutex.unlock(); + handler_->msg_task_cv_.notify_one(); +} + +void EsPlayer::TrackRendererEventListener::ReadyToSeek_(const TrackType& type) { + uint64_t offset = handler_->need_data_[type].seek_offset; + + LOG_INFO_P(handler_, "OnReadyToSeek [%s] offset [%llu]ms", + (type == kTrackTypeAudio) ? "audio" : "video", offset); + + handler_->need_data_[type].mask &= ~kNeedDataMaskBySeek; + handler_->need_data_[type].seek_offset = 0; + + auto listener = std::bind(&plusplayer::EsEventListener::OnReadyToSeek, + handler_->eventlistener_, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3); + StreamType stream_type = internal::ConvertToStreamType(type); + handler_->es_packet_logger_.ResetLog(stream_type); + auto msg = es_msg::ReadyToSeek::Make(stream_type, offset, listener, + handler_->eventlistener_userdata_); + std::unique_lock msg_mutex(handler_->msg_task_mutex_); + handler_->msg_queue_.push(std::move(msg)); + msg_mutex.unlock(); + handler_->msg_task_cv_.notify_one(); +} + +void EsPlayer::TrackRendererEventListener::BufferStatus_( + const TrackType& type, const BufferStatus& status) { + uint64_t byte_size, time_size; + // LOG_INFO_P(handler_, "OnBufferStatus [%s] [%s]", + // (type == kTrackTypeAudio) ? "audio" : "video", + // (status == BufferStatus::kUnderrun) ? "underrun" : "overrun"); + handler_->GetSrcQueueCurrentSize_(type, &byte_size, &time_size); + auto listener = std::bind(&plusplayer::EsEventListener::OnBufferStatus, + handler_->eventlistener_, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3, + std::placeholders::_4, std::placeholders::_5); + StreamType stream_type = internal::ConvertToStreamType(type); + auto msg = + es_msg::Bufferstatus::Make(stream_type, status, byte_size, time_size, + listener, handler_->eventlistener_userdata_); + std::unique_lock msg_mutex(handler_->msg_task_mutex_); + handler_->msg_queue_.push(std::move(msg)); + msg_mutex.unlock(); + handler_->msg_task_cv_.notify_one(); +} + +void EsPlayer::TrackRendererEventListener::OnBufferStatus( + const TrackType& type, const BufferStatus& status) { + if (!handler_->eventlistener_) return; + if (internal::IsLowLatencyModeDisableAVSync(handler_->low_latency_mode_)) + return; + + if (handler_->need_data_[type].mask == kNeedDataMaskByPrepare && + status == BufferStatus::kUnderrun) { + ReadyToPrepare_(type); + } else if (handler_->need_data_[type].mask == kNeedDataMaskBySeek && + status == BufferStatus::kUnderrun) { + ReadyToSeek_(type); + } else { + BufferStatus_(type, status); + } +} + +void EsPlayer::TrackRendererEventListener::OnMediaPacketGetTbmBufPtr( + void** tbm_ptr, bool is_scale_change) { + if (!handler_->eventlistener_) return; + + handler_->eventlistener_->OnMediaPacketGetTbmBufPtr(tbm_ptr, is_scale_change); +} + +void EsPlayer::TrackRendererEventListener::OnMediaPacketVideoDecoded( + const DecodedVideoPacket& packet) { + if (!handler_->eventlistener_) return; + + handler_->eventlistener_->OnMediaPacketVideoDecoded(packet); +} + +#ifndef IS_AUDIO_PRODUCT +void EsPlayer::TrackRendererEventListener::OnMediaPacketVideoRawDecoded( + const DecodedVideoRawModePacket& packet) { + if (handler_->mixer_ticket_ == nullptr) return; + const auto& data = packet.data; + if (packet.type == DecodedVideoRawModePacketType::kPhysicalAddress) { + DecodedRawInfo info; + info.width = packet.width; + info.height = packet.height; + info.y_info.phyaddr = data.raw.y_phyaddr; + info.y_info.viraddr = data.raw.y_viraddr; + info.y_info.linesize = data.raw.y_linesize; + info.uv_info.phyaddr = data.raw.uv_phyaddr; + info.uv_info.viraddr = data.raw.uv_viraddr; + info.uv_info.linesize = data.raw.uv_linesize; + handler_->mixer_ticket_->Render(info); + } else if (packet.type == DecodedVideoRawModePacketType::kTizenBuffer) { + DecodedVideoKeyTypeInfo info; + info.width = packet.width; + info.height = packet.height; + info.key = packet.data.tbm.key; + handler_->mixer_ticket_->Render(info); + } +} + +bool EsPlayer::MixerListener::OnAudioFocusChanged(bool active) { + LOG_INFO_P(handler_, "focused [%d]", active); + std::unique_lock lock(handler_->audio_focus_m_); + if (handler_->state_manager_.GetState() < EsState::kReady) { + handler_->is_audio_focused_ = active; + return true; + } + if (active) { + Track activated_track; + track_util::GetActiveTrack(handler_->track_, kTrackTypeAudio, + &activated_track); + LOG_INFO_P(handler_, "Activate audio track"); + { + std::lock_guard lock2(handler_->eos_mutex_); + handler_->eos_status_ = + internal::ResetEosStatus(kTrackTypeAudio, handler_->eos_status_); + } + return handler_->trackrenderer_->Activate(kTrackTypeAudio, activated_track); + } + LOG_INFO_P(handler_, "Deactivate audio track"); + handler_->is_audio_focused_ = false; + { + std::lock_guard lock2(handler_->eos_mutex_); + handler_->eos_status_ |= EosStatus::kAudioEos; + } + return handler_->trackrenderer_->Deactivate(kTrackTypeAudio); +} + +bool EsPlayer::MixerListener::OnUpdateDisplayInfo(const DisplayInfo& cur_info, + DisplayInfo* new_info) { + new_info->geometry = handler_->mixerticket_roi_; + new_info->visible_status = + handler_->is_visible_ ? VisibleStatus::kVisible : VisibleStatus::kHide; + return true; +} +#endif + +void EsPlayer::TrackRendererEventListener::OnSeekData(const TrackType& type, + const uint64_t offset) { + if (!handler_->eventlistener_) return; + + if (handler_->need_data_[type].mask != kNeedDataMaskByPrepare) { + handler_->need_data_[type].mask |= kNeedDataMaskBySeek; + handler_->need_data_[type].seek_offset = offset; + } +} + +void EsPlayer::TrackRendererEventListener::OnClosedCaptionData(const char* data, + const int size) { + if (size <= 0) return; + if (!handler_->eventlistener_) return; + auto listener = std::bind(&plusplayer::EsEventListener::OnClosedCaptionData, + handler_->eventlistener_, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3); + auto msg = es_msg::ClosedCaption::Make(data, size, listener, + handler_->eventlistener_userdata_); + std::unique_lock msg_mutex(handler_->msg_task_mutex_); + handler_->msg_queue_.push(std::move(msg)); + msg_mutex.unlock(); + handler_->msg_task_cv_.notify_one(); +} + +void EsPlayer::TrackRendererEventListener::OnFlushDone() { + LOG_ENTER_P(handler_); + if (!handler_->eventlistener_) return; + + auto listener = std::bind(&plusplayer::EsEventListener::OnFlushDone, + handler_->eventlistener_, std::placeholders::_1); + auto msg = + es_msg::FlushDone::Make(listener, handler_->eventlistener_userdata_); + std::unique_lock msg_mutex(handler_->msg_task_mutex_); + handler_->msg_queue_.push(std::move(msg)); + msg_mutex.unlock(); + handler_->msg_task_cv_.notify_one(); + LOG_LEAVE_P(handler_); +} + +void EsPlayer::TrackRendererEventListener::OnEvent(const EventType& event, + const EventMsg& msg_data) { + if (!handler_->eventlistener_) return; + + auto listener = std::bind(&plusplayer::EsEventListener::OnEvent, + handler_->eventlistener_, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3); + auto msg = es_msg::OnEvent::Make(event, msg_data, listener, + handler_->eventlistener_userdata_); + std::unique_lock msg_mutex(handler_->msg_task_mutex_); + handler_->msg_queue_.push(std::move(msg)); + msg_mutex.unlock(); + handler_->msg_task_cv_.notify_one(); + LOG_LEAVE_P(handler_); +} + +void EsPlayer::TrackRendererEventListener::OnFirstDecodingDone() { + LOG_ENTER_P(handler_); + if (!handler_->eventlistener_) return; + auto listener = std::bind(&plusplayer::EsEventListener::OnFirstDecodingDone, + handler_->eventlistener_, std::placeholders::_1); + auto msg = es_msg::FirstDecodingDone::Make(listener, + handler_->eventlistener_userdata_); + std::unique_lock msg_mutex(handler_->msg_task_mutex_); + handler_->msg_queue_.push(std::move(msg)); + msg_mutex.unlock(); + handler_->msg_task_cv_.notify_one(); + LOG_LEAVE_P(handler_); +} + +void EsPlayer::TrackRendererEventListener::OnVideoDecoderUnderrun() { + LOG_ENTER_P(handler_); + if (!handler_->eventlistener_) return; + + if (handler_->state_manager_.GetState() != EsState::kPlaying && + handler_->state_manager_.GetState() != EsState::kPaused) { + return; + } + + auto listener = + std::bind(&plusplayer::EsEventListener::OnVideoDecoderUnderrun, + handler_->eventlistener_, std::placeholders::_1); + auto msg = es_msg::Simple::Make(listener, handler_->eventlistener_userdata_); + std::unique_lock msg_mutex(handler_->msg_task_mutex_); + handler_->msg_queue_.push(std::move(msg)); + msg_mutex.unlock(); + handler_->msg_task_cv_.notify_one(); + LOG_LEAVE_P(handler_); +} + +void EsPlayer::TrackRendererEventListener::OnVideoLatencyStatus( + const LatencyStatus& latency_status) { + LOG_ENTER_P(handler_); + if (!handler_->eventlistener_) return; + + if (handler_->state_manager_.GetState() != EsState::kPlaying && + handler_->state_manager_.GetState() != EsState::kPaused) { + return; + } + + auto listener = std::bind(&plusplayer::EsEventListener::OnVideoLatencyStatus, + handler_->eventlistener_, std::placeholders::_1, + std::placeholders::_2); + + auto msg = es_msg::PacketLatencyStatus::Make( + latency_status, listener, handler_->eventlistener_userdata_); + + std::unique_lock msg_mutex(handler_->msg_task_mutex_); + handler_->msg_queue_.push(std::move(msg)); + msg_mutex.unlock(); + handler_->msg_task_cv_.notify_one(); + LOG_LEAVE_P(handler_); +} + +void EsPlayer::TrackRendererEventListener::OnAudioLatencyStatus( + const LatencyStatus& latency_status) { + LOG_ENTER_P(handler_); + if (!handler_->eventlistener_) return; + + if (handler_->state_manager_.GetState() != EsState::kPlaying && + handler_->state_manager_.GetState() != EsState::kPaused) { + return; + } + + auto listener = std::bind(&plusplayer::EsEventListener::OnAudioLatencyStatus, + handler_->eventlistener_, std::placeholders::_1, + std::placeholders::_2); + + auto msg = es_msg::PacketLatencyStatus::Make( + latency_status, listener, handler_->eventlistener_userdata_); + + std::unique_lock msg_mutex(handler_->msg_task_mutex_); + handler_->msg_queue_.push(std::move(msg)); + msg_mutex.unlock(); + handler_->msg_task_cv_.notify_one(); + LOG_LEAVE_P(handler_); +} + +void EsPlayer::TrackRendererEventListener::OnVideoHighLatency() { + LOG_ENTER_P(handler_); + if (!handler_->eventlistener_) return; + + auto listener = std::bind(&plusplayer::EsEventListener::OnVideoHighLatency, + handler_->eventlistener_, std::placeholders::_1); + + auto msg = es_msg::Simple::Make(listener, handler_->eventlistener_userdata_); + + std::unique_lock msg_mutex(handler_->msg_task_mutex_); + handler_->msg_queue_.push(std::move(msg)); + msg_mutex.unlock(); + handler_->msg_task_cv_.notify_one(); + LOG_LEAVE_P(handler_); +} + +void EsPlayer::TrackRendererEventListener::OnAudioHighLatency() { + LOG_ENTER_P(handler_); + if (!handler_->eventlistener_) return; + + auto listener = std::bind(&plusplayer::EsEventListener::OnAudioHighLatency, + handler_->eventlistener_, std::placeholders::_1); + + auto msg = es_msg::Simple::Make(listener, handler_->eventlistener_userdata_); + + std::unique_lock msg_mutex(handler_->msg_task_mutex_); + handler_->msg_queue_.push(std::move(msg)); + msg_mutex.unlock(); + handler_->msg_task_cv_.notify_one(); + LOG_LEAVE_P(handler_); +} + +kpi::EsCodecLoggerKeys EsPlayer::MakeKpiKeys_() { + kpi::EsCodecLoggerKeys event_info; + event_info.app_id = app_info_.id; + if (submit_data_type_ == SubmitDataType::kCleanData) { + event_info.is_clean = true; + } else { + event_info.is_clean = false; + } + + for (const auto& track : track_) { + if (track.type == kTrackTypeVideo) { + event_info.v_codec = track.mimetype; + event_info.v_codec_version = track.version; + event_info.width = track.maxwidth; + event_info.height = track.maxheight; + } else if (track.type == kTrackTypeAudio) { + event_info.a_codec = track.mimetype; + } + } + return event_info; +} + +namespace es_conf { + +void LoadIniProperty(const Json::Value& root) { + gst_util::GstInit(root); + std::string key = "generate_dot"; + es_conf::ini_property[key] = root.get(key, "").asBool(); + LOG_DEBUG("[%s] : [%d]", key.c_str(), es_conf::ini_property[key]); +} + +bool LoadIniFile() { + const char* path = plusplayer_cfg::GetIniPath(); + LOG_INFO("path : %s", path); + std::streampos size; + char* buf = nullptr; + std::ifstream file(path, std::ios::binary | std::ios::ate); + if (file.is_open() == false) { + gst_util::GstInit(); + LOG_ERROR("Can't open file !!"); + return false; + } + BOOST_SCOPE_EXIT(&file) { file.close(); } + BOOST_SCOPE_EXIT_END + + size = file.tellg(); + if (size <= 0) { + LOG_ERROR("Wrong file size"); + return false; + } + size += 1; + buf = static_cast(calloc(size, sizeof(char))); + if (buf == nullptr) { + LOG_ERROR("Fail to calloc buf"); + return false; + } + BOOST_SCOPE_EXIT(&buf) { + if (buf) free(buf); + } + BOOST_SCOPE_EXIT_END + + file.seekg(0, std::ios::beg); + file.read(buf, size); + + std::string config = buf; + Json::Value root; + Json::Reader reader; + if (!reader.parse(config, root)) { + LOG_ERROR("Fail to parse configuration file %s", + (reader.getFormatedErrorMessages()).c_str()); + return false; + } + + es_conf::LoadIniProperty(root); + return true; +} + +} // namespace es_conf + +} // namespace plusplayer diff --git a/src/esplusplayer/src/esplayer_drm.cpp b/src/esplusplayer/src/esplayer_drm.cpp new file mode 100755 index 0000000..ef576c5 --- /dev/null +++ b/src/esplusplayer/src/esplayer_drm.cpp @@ -0,0 +1,84 @@ +// +// @ Copyright [2018] +// + +#include "esplayer/esplayer_drm.h" +#include "core/serializer.h" + +#include +#include +#include +#include + +namespace plusplayer { +namespace esplayer_drm { +GBytesPtr Serialize(const EsPacketPtr &packet, + const drm::EsPlayerEncryptedInfo &drm_info) { + Serializer s; + + // box + s.Put(0); // box_size : (lazy) + s.Put(true); // secure + s.Put(drm_info.handle); // handle + s.Put(0); // session_id + s.Put(0); // psshinfo_size + auto psa_size_offset = s.Put(0); // psa_size : (lazy) + + // box.pPSAParam + s.Put(drm_info.algorithm); // algorithm + s.Put(drm_info.format); // format + s.Put(drm_info.phase); // phase + s.Put(drm_info.use_out_buffer); // bUseOutBuf + s.Put(drm_info.kid.size()); // uKIDLen + s.Put(drm_info.kid); // pKID + s.Put(0); // uDataLen + s.Put(0); // uOutBufLen + s.Put(drm_info.initialization_vector.size()); // uIVLen + s.Put(drm_info.initialization_vector); // pIV + + // box.pPSAParam->pSubData + drm::DrmbEsFragmentedMp4Data *sub_data = + reinterpret_cast(drm_info.sub_data); + if (sub_data != nullptr && sub_data->sub_sample_info_vector.size() != 0) { + uint32_t subdata_size = + sizeof(uint32_t) + sub_data->sub_sample_info_vector.size() * + sizeof(drm::DrmbEsSubSampleInfo); + s.Put(subdata_size); // subData_size + const size_t sub_sample_count = sub_data->sub_sample_info_vector.size(); + s.Put(sub_sample_count); // uSubSampleCount + for (const auto &value : sub_data->sub_sample_info_vector) { + s.Put(value.bytes_of_clear_data); // uBytesOfClearData + s.Put(value.bytes_of_encrypted_data); // uBytesOfEncryptedData + } + } else { + s.Put(0); // subData_size + } + + // box.pPSAParam + s.Put( + reinterpret_cast(drm_info.split_offsets.data()), + sizeof(int) * drm_info.split_offsets.max_size()); // split_offsets + s.Put(drm_info.use_pattern); // use_pattern + s.Put(drm_info.crypt_byte_block); // crypt_byte_block + s.Put(drm_info.skip_byte_block); // skip_byte_block + + // box + auto reconfigure_pssh_offset = s.Put(0); // reconfigure_pssh + + const auto total_size = s.GetSize(); + const auto psa_size = reconfigure_pssh_offset - psa_size_offset; + + std::unique_ptr> + raw_data_ptr(new Serializer::Byte[total_size], + [](Serializer::Byte *data) { delete[] data; }); + s.Serialize(raw_data_ptr.get()); + auto raw_data = raw_data_ptr.get(); + + // fill size field + Serializer::Put(raw_data, total_size); // box_size + Serializer::Put(raw_data + psa_size_offset, psa_size); // psa_size + + return gstguard::make_guard(g_bytes_new(raw_data, total_size)); +} +} // namespace esplayer_drm +} // namespace plusplayer diff --git a/src/esplusplayer/src/esplusplayer.cpp b/src/esplusplayer/src/esplusplayer.cpp new file mode 100755 index 0000000..1b7038b --- /dev/null +++ b/src/esplusplayer/src/esplusplayer.cpp @@ -0,0 +1,18 @@ +// +// @ Copyright [2018] +// + +#include "plusplayer/esplusplayer.h" + +#include "core/utils/plusplayer_log.h" +#include "esplayer/esplayer.h" + +namespace plusplayer { + +std::unique_ptr EsPlusPlayer::Create() { + auto instance = std::unique_ptr(new EsPlayer); + LOG_INFO("Create Es Player [%p]", instance.get()); + return instance; +} + +} // namespace plusplayer diff --git a/src/esplusplayer/src/esplusplayer_capi.cpp b/src/esplusplayer/src/esplusplayer_capi.cpp new file mode 100755 index 0000000..045b444 --- /dev/null +++ b/src/esplusplayer/src/esplusplayer_capi.cpp @@ -0,0 +1,1901 @@ +#include "esplusplayer_capi/esplusplayer_capi.h" + +#include "plusplayer/esplusplayer.h" + +using plusplayer::EsPlusPlayer; +using plusplayer::Geometry; + +#include +#include + +#include +#include +#include +#include + +#include "core/utils/plusplayer_log.h" +#include "esplayer/decoded_pkt_list.h" +#include "esplusplayer_capi/esplusplayer_internal.h" +#ifndef IS_TOMATO +#include "mixer_capi/mixer_capi.h" +#endif +#include "plusplayer/appinfo.h" +#include "plusplayer/audioeasinginfo.h" +#include "plusplayer/drm.h" +#include "plusplayer/elementary_stream.h" +#include "plusplayer/espacket.h" +#include "plusplayer/esplusplayer.h" +#include "plusplayer/track.h" +#include "plusplayer/types/buffer.h" +#include "plusplayer/types/display.h" +#include "plusplayer/types/error.h" +#include "plusplayer/types/latency.h" +#include "plusplayer/types/picturequality.h" +#include "plusplayer/types/resource.h" +#include "plusplayer/types/stream.h" + +using plusplayer::AdvPictureQualityType; +using plusplayer::AudioEasingInfo; +using plusplayer::AudioEasingType; +using plusplayer::AudioMimeType; +using plusplayer::AudioStream; +using plusplayer::AudioStreamPtr; +using plusplayer::BufferStatus; +using plusplayer::CatchUpSpeed; +using plusplayer::CropArea; +using plusplayer::DecodedPacketManagerInterface; +using plusplayer::DisplayMode; +using plusplayer::DisplayRotation; +using plusplayer::DisplayType; +using plusplayer::ErrorType; +using plusplayer::EsPacket; +using plusplayer::EsPacketPtr; +using plusplayer::EsState; +using plusplayer::LatencyStatus; +using plusplayer::MatroskaColor; +using plusplayer::PlayerAdaptiveInfo; +using plusplayer::PlayerAppInfo; +using plusplayer::PlayerAudioCodecType; +using plusplayer::PlayerLowLatencyMode; +using plusplayer::PlayerVideoCodecType; +using plusplayer::Rational; +using plusplayer::RenderRect; +using plusplayer::RscAllocPolicy; +using plusplayer::RscType; +using plusplayer::StreamType; +using plusplayer::SubmitDataType; +using plusplayer::Track; +using plusplayer::TrackType; +using plusplayer::VideoMimeType; +using plusplayer::VideoStream; +using plusplayer::VideoStreamPtr; +using plusplayer::drm::EsPlayerEncryptedInfo; +using plusplayer::drm::Type; + +namespace util { +const std::unordered_map kErrorStringMap = + {{ESPLUSPLAYER_ERROR_TYPE_NONE, "ESPLUSPLAYER_ERROR_TYPE_NONE"}, + {ESPLUSPLAYER_ERROR_TYPE_OUT_OF_MEMORY, + "ESPLUSPLAYER_ERROR_TYPE_OUT_OF_MEMORY"}, + {ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER, + "ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER"}, + {ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION, + "ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION"}, + {ESPLUSPLAYER_ERROR_TYPE_INVALID_STATE, + "ESPLUSPLAYER_ERROR_TYPE_INVALID_STATE"}, + {ESPLUSPLAYER_ERROR_TYPE_NOT_SUPPORTED_AUDIO_CODEC, + "ESPLUSPLAYER_ERROR_TYPE_NOT_SUPPORTED_AUDIO_CODEC"}, + {ESPLUSPLAYER_ERROR_TYPE_NOT_SUPPORTED_VIDEO_CODEC, + "ESPLUSPLAYER_ERROR_TYPE_NOT_SUPPORTED_VIDEO_CODEC"}, + {ESPLUSPLAYER_ERROR_TYPE_NOT_SUPPORTED_FILE, + "ESPLUSPLAYER_ERROR_TYPE_NOT_SUPPORTED_FILE"}, + {ESPLUSPLAYER_ERROR_TYPE_CONNECTION_FAILED, + "ESPLUSPLAYER_ERROR_TYPE_CONNECTION_FAILED"}, + {ESPLUSPLAYER_ERROR_TYPE_DRM_EXPIRED, + "ESPLUSPLAYER_ERROR_TYPE_DRM_EXPIRED"}, + {ESPLUSPLAYER_ERROR_TYPE_DRM_NO_LICENSE, + "ESPLUSPLAYER_ERROR_TYPE_DRM_NO_LICENSE"}, + {ESPLUSPLAYER_ERROR_TYPE_DRM_FUTURE_USE, + "ESPLUSPLAYER_ERROR_TYPE_DRM_FUTURE_USE"}, + {ESPLUSPLAYER_ERROR_TYPE_NOT_PERMITTED, + "ESPLUSPLAYER_ERROR_TYPE_NOT_PERMITTED"}, + {ESPLUSPLAYER_ERROR_TYPE_DRM_DECRYPTION_FAILED, + "ESPLUSPLAYER_ERROR_TYPE_DRM_DECRYPTION_FAILED"}, + {ESPLUSPLAYER_ERROR_TYPE_NOT_SUPPORTED_FORMAT, + "ESPLUSPLAYER_ERROR_TYPE_NOT_SUPPORTED_FORMAT"}, + {ESPLUSPLAYER_ERROR_TYPE_UNKNOWN, "ESPLUSPLAYER_ERROR_TYPE_UNKNOWN"}}; + +const std::string kUnhandledErrorString = "Unhandled Error Type"; +static const std::string& ConvertErrorTypeToString( + esplusplayer_error_type type) { + return kErrorStringMap.count(type) > 0 ? kErrorStringMap.at(type) + : kUnhandledErrorString; +} +} // namespace util + +struct EsPlusPlayerPriv; + +class listener_bridge : public plusplayer::EsEventListener { + public: + listener_bridge() { LOG_ENTER } + ~listener_bridge() { LOG_ENTER } + + void ResetPacketList() { + if (decoded_pkt_mgr_) decoded_pkt_mgr_->Clear(); + } + + void ResetMultiSeekControl() { + std::unique_lock lock(multi_seek_control.lock); + multi_seek_control.is_offset_valid = false; + multi_seek_control.offset = 0; + } + + void Reset() { + LOG_ENTER + ResetPacketList(); + ResetMultiSeekControl(); + LOG_LEAVE + } + + esplusplayer_error_type ConvertErrorCode(const ErrorType& error_code) { + esplusplayer_error_type type = ESPLUSPLAYER_ERROR_TYPE_NONE; + switch (error_code) { + case ErrorType::kOutOfMemory: + type = ESPLUSPLAYER_ERROR_TYPE_OUT_OF_MEMORY; + break; + case ErrorType::kInvalidParameter: + type = ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + break; + case ErrorType::kInvalidOperation: + type = ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION; + break; + case ErrorType::kInvalidState: + type = ESPLUSPLAYER_ERROR_TYPE_INVALID_STATE; + break; + case ErrorType::kNotSupportedAudioCodec: + type = ESPLUSPLAYER_ERROR_TYPE_NOT_SUPPORTED_AUDIO_CODEC; + break; + case ErrorType::kNotSupportedVideoCodec: + type = ESPLUSPLAYER_ERROR_TYPE_NOT_SUPPORTED_VIDEO_CODEC; + break; + case ErrorType::kNotSupportedFile: + type = ESPLUSPLAYER_ERROR_TYPE_NOT_SUPPORTED_FILE; + break; + case ErrorType::kConnectionFailed: + type = ESPLUSPLAYER_ERROR_TYPE_CONNECTION_FAILED; + break; + case ErrorType::kDrmExpired: + type = ESPLUSPLAYER_ERROR_TYPE_DRM_EXPIRED; + break; + case ErrorType::kDrmNoLicense: + type = ESPLUSPLAYER_ERROR_TYPE_DRM_NO_LICENSE; + break; + case ErrorType::kDrmFutureUse: + type = ESPLUSPLAYER_ERROR_TYPE_DRM_FUTURE_USE; + break; + case ErrorType::kDrmNotPermitted: + type = ESPLUSPLAYER_ERROR_TYPE_NOT_PERMITTED; + break; + case ErrorType::kDrmInfo: + type = ESPLUSPLAYER_ERROR_TYPE_DRM_DECRYPTION_FAILED; + break; + case ErrorType::kNotSupportedFormat: + type = ESPLUSPLAYER_ERROR_TYPE_NOT_SUPPORTED_FORMAT; + break; + default: + LOG_ERROR("not defined error %x", static_cast(error_code)); + type = ESPLUSPLAYER_ERROR_TYPE_UNKNOWN; + break; + } + return type; + } + + virtual void OnError(const ErrorType& error_code, UserData userdata) { + LOG_ENTER + LOG_INFO("error code : %x", static_cast(error_code)); + if (this->error_cb_) + this->error_cb_(ConvertErrorCode(error_code), error_cb_userdata_); + } + + virtual void OnBufferStatus(const StreamType& type, + const BufferStatus& status, + const uint64_t byte_size, + const uint64_t time_size, UserData userdata) { + // LOG_ENTER + // LOG_INFO("stream type : %d, buffer status : %d", static_cast(type), + // static_cast(status)); + + if (this->buffer_status_cb_) + this->buffer_status_cb_(static_cast(type), + static_cast(status), + buffer_status_cb_userdata_); + if (this->buffer_byte_status_cb_) + this->buffer_byte_status_cb_( + static_cast(type), + static_cast(status), byte_size, + buffer_byte_status_cb_userdata_); + if (this->buffer_time_status_cb_) + this->buffer_time_status_cb_( + static_cast(type), + static_cast(status), time_size, + buffer_time_status_cb_userdata_); + } + + virtual void OnResourceConflicted(UserData userdata) { + LOG_ENTER + this->Reset(); + if (this->resource_conflicted_cb_) + this->resource_conflicted_cb_(resource_conflicted_cb_userdata_); + } + + virtual void OnEos(UserData userdata) { + LOG_ENTER + if (this->eos_cb_) this->eos_cb_(eos_cb_userdata_); + } + + virtual void OnPrepareDone(bool result, UserData userdata) { + LOG_ENTER + LOG_INFO("prepare done. result : %s", result ? "true" : "false"); + if (this->prepare_async_done_cb_) + this->prepare_async_done_cb_(result, prepare_async_done_cb_userdata_); + } + + virtual void OnReadyToPrepare(const StreamType& type, UserData userdata) { + LOG_ENTER + LOG_INFO("stream type : %d", static_cast(type)); + if (this->ready_to_prepare_cb_) + this->ready_to_prepare_cb_(static_cast(type), + ready_to_prepare_cb_userdata_); + } + + virtual void OnSeekDone(UserData userdata) { + LOG_ENTER + if (this->seek_done_cb_) this->seek_done_cb_(seek_done_cb_userdata_); + } + + virtual void OnReadyToSeek(const StreamType& type, const uint64_t offset, + UserData userdata) { + LOG_ENTER + LOG_INFO("offset : %llu", offset); + std::unique_lock lock(this->multi_seek_control.lock); + if (this->multi_seek_control.is_offset_valid == false || + this->multi_seek_control.offset != offset) { + LOG_ERROR("Invalid offset:%llu", this->multi_seek_control.offset); + return; + } + if (this->ready_to_seek_cb_) + this->ready_to_seek_cb_(static_cast(type), + offset, ready_to_seek_cb_userdata_); + } + + void SetDecodedPacketManager( + std::shared_ptr& mgr) { + decoded_pkt_mgr_ = mgr; + } + + virtual void OnMediaPacketGetTbmBufPtr(void** ptr, bool is_scale_change) { + // get one free point in current tbm list, send to trackrender, if can't + // find, set null + void* ptr1 = nullptr; + if (decoded_pkt_mgr_) + decoded_pkt_mgr_->GetFreeTbmSurface(&ptr1, is_scale_change); + *ptr = ptr1; + } + + virtual void OnMediaPacketVideoDecoded( + const plusplayer::DecodedVideoPacket& packet) { + if (this->media_packet_video_decoded_cb_ == nullptr) return; + + auto* _pkt = new esplusplayer_decoded_video_packet(); + _pkt->pts = packet.pts; + _pkt->duration = packet.duration; + _pkt->surface_data = static_cast(packet.surface_data); + _pkt->private_data = packet.scaler_index; + if (decoded_pkt_mgr_ && decoded_pkt_mgr_->TryToAdd(_pkt)) { + this->media_packet_video_decoded_cb_( + _pkt, media_packet_video_decoded_cb_userdata_); + } else { + LOG_ERROR("Too many buffers are not released. packet(%p) will be drop.", + _pkt); + } + } + + virtual void OnClosedCaptionData(std::unique_ptr data, const int size, + UserData userdata) { + LOG_ENTER + if (this->closed_caption_cb_) { + this->closed_caption_cb_(data.get(), size, closed_caption_cb_userdata_); + } + } + + virtual void OnFlushDone(UserData userdata) { + LOG_ENTER + if (this->flush_done_cb_) this->flush_done_cb_(flush_done_cb_userdata_); + } + + virtual void OnEvent(const plusplayer::EventType& event, + const plusplayer::EventMsg& msg_data, + UserData userdata) { + LOG_ENTER + esplusplayer_event_msg event_msg; + event_msg.data = const_cast(msg_data.data.c_str()); + event_msg.len = msg_data.len; + if (this->event_cb_) + this->event_cb_(static_cast(event), event_msg, + event_cb_userdata_); + } + + virtual void OnFirstDecodingDone(UserData userdata) { + LOG_ENTER + if (this->first_video_decoding_done_cb_) { + this->first_video_decoding_done_cb_( + first_video_decoding_done_cb_userdata_); + } + } + + virtual void OnVideoDecoderUnderrun(UserData userdata) { + LOG_ENTER + if (this->video_decoder_underrun_cb_) + this->video_decoder_underrun_cb_(video_decoder_underrun_cb_userdata_); + } + + virtual void OnVideoLatencyStatus(const LatencyStatus& latency_status, + UserData userdata) { + LOG_ENTER + if (this->video_latency_status_cb_) + this->video_latency_status_cb_( + static_cast(latency_status), + video_latency_status_cb_userdata_); + } + + virtual void OnAudioLatencyStatus(const LatencyStatus& latency_status, + UserData userdata) { + LOG_ENTER + if (this->audio_latency_status_cb_) + this->audio_latency_status_cb_( + static_cast(latency_status), + audio_latency_status_cb_userdata_); + } + + virtual void OnVideoHighLatency(UserData userdata) { + LOG_ENTER + if (this->video_high_latency_cb_) + this->video_high_latency_cb_(video_high_latency_cb_userdata_); + } + + virtual void OnAudioHighLatency(UserData userdata) { + LOG_ENTER + if (this->audio_high_latency_cb_) + this->audio_high_latency_cb_(audio_high_latency_cb_userdata_); + } + + private: + static void DecodedPacketDeleter(esplusplayer_decoded_video_packet* packet) { + if (packet->surface_data != nullptr) { + tbm_surface_destroy(static_cast(packet->surface_data)); + packet->surface_data = NULL; + } + delete packet; + } + + private: + esplusplayer_error_cb error_cb_ = nullptr; + void* error_cb_userdata_ = nullptr; + esplusplayer_buffer_status_cb buffer_status_cb_ = nullptr; + void* buffer_status_cb_userdata_ = nullptr; + esplusplayer_buffer_byte_status_cb buffer_byte_status_cb_ = nullptr; + void* buffer_byte_status_cb_userdata_ = nullptr; + esplusplayer_buffer_time_status_cb buffer_time_status_cb_ = nullptr; + void* buffer_time_status_cb_userdata_ = nullptr; + esplusplayer_resource_conflicted_cb resource_conflicted_cb_ = nullptr; + void* resource_conflicted_cb_userdata_ = nullptr; + esplusplayer_eos_cb eos_cb_ = nullptr; + void* eos_cb_userdata_ = nullptr; + esplusplayer_ready_to_prepare_cb ready_to_prepare_cb_ = nullptr; + void* ready_to_prepare_cb_userdata_ = nullptr; + esplusplayer_prepare_async_done_cb prepare_async_done_cb_ = nullptr; + void* prepare_async_done_cb_userdata_ = nullptr; + esplusplayer_seek_done_cb seek_done_cb_ = nullptr; + void* seek_done_cb_userdata_ = nullptr; + esplusplayer_ready_to_seek_cb ready_to_seek_cb_ = nullptr; + void* ready_to_seek_cb_userdata_ = nullptr; + esplusplayer_media_packet_video_decoded_cb media_packet_video_decoded_cb_ = + nullptr; + void* media_packet_video_decoded_cb_userdata_ = nullptr; + esplusplayer_closed_caption_cb closed_caption_cb_ = nullptr; + void* closed_caption_cb_userdata_ = nullptr; + esplusplayer_flush_done_cb flush_done_cb_ = nullptr; + void* flush_done_cb_userdata_ = nullptr; + esplusplayer_event_cb event_cb_ = nullptr; + void* event_cb_userdata_ = nullptr; + esplusplayer_first_video_decoding_done_cb first_video_decoding_done_cb_ = + nullptr; + void* first_video_decoding_done_cb_userdata_ = nullptr; + esplusplayer_decoder_underrun_cb video_decoder_underrun_cb_ = nullptr; + void* video_decoder_underrun_cb_userdata_ = nullptr; + esplusplayer_video_latency_status_cb video_latency_status_cb_ = nullptr; + esplusplayer_audio_latency_status_cb audio_latency_status_cb_ = nullptr; + void* video_latency_status_cb_userdata_ = nullptr; + void* audio_latency_status_cb_userdata_ = nullptr; + esplusplayer_video_high_latency_cb video_high_latency_cb_ = nullptr; + esplusplayer_audio_high_latency_cb audio_high_latency_cb_ = nullptr; + void* video_high_latency_cb_userdata_ = nullptr; + void* audio_high_latency_cb_userdata_ = nullptr; + + std::shared_ptr decoded_pkt_mgr_; + + struct MultiSeekControl { + std::mutex lock; + bool is_offset_valid = false; + uint64_t offset = 0; + }; + friend void update_ready_to_seek_callback( + esplusplayer_handle pp, esplusplayer_ready_to_seek_cb ready_to_seek_cb, + void* userdata); + friend void update_ready_to_seek_offset(esplusplayer_handle pp, + const uint64_t offset); + MultiSeekControl multi_seek_control; + + friend int esplusplayer_set_error_cb(esplusplayer_handle pp, + esplusplayer_error_cb error_cb, + void* userdata); + friend int esplusplayer_set_buffer_status_cb( + esplusplayer_handle pp, esplusplayer_buffer_status_cb buffer_status_cb, + void* userdata); + friend int esplusplayer_set_buffer_byte_status_cb( + esplusplayer_handle pp, + esplusplayer_buffer_byte_status_cb buffer_status_cb, void* userdata); + friend int esplusplayer_set_buffer_time_status_cb( + esplusplayer_handle pp, + esplusplayer_buffer_time_status_cb buffer_status_cb, void* userdata); + friend int esplusplayer_set_resource_conflicted_cb( + esplusplayer_handle pp, + esplusplayer_resource_conflicted_cb resource_conflicted_cb, + void* userdata); + friend int esplusplayer_set_eos_cb(esplusplayer_handle pp, + esplusplayer_eos_cb eos_cb, + void* userdata); + friend int esplusplayer_set_ready_to_prepare_cb( + esplusplayer_handle pp, + esplusplayer_ready_to_prepare_cb ready_to_prepare_cb, void* userdata); + friend int esplusplayer_set_prepare_async_done_cb( + esplusplayer_handle pp, + esplusplayer_prepare_async_done_cb prepare_async_done_cb, void* userdata); + friend int esplusplayer_set_seek_done_cb( + esplusplayer_handle pp, esplusplayer_seek_done_cb seek_done_cb, + void* userdata); + friend int esplusplayer_set_ready_to_seek_cb( + esplusplayer_handle pp, esplusplayer_ready_to_seek_cb ready_to_seek_cb, + void* userdata); + friend int esplusplayer_set_media_packet_video_decoded_cb( + esplusplayer_handle pp, + esplusplayer_media_packet_video_decoded_cb media_packet_video_decoded_cb, + void* userdata); + friend int esplusplayer_set_closed_caption_cb( + esplusplayer_handle handle, + esplusplayer_closed_caption_cb closed_caption_cb, void* userdata); + friend int esplusplayer_set_flush_done_cb( + esplusplayer_handle pp, esplusplayer_flush_done_cb flush_done_cb, + void* userdata); + friend int esplusplayer_set_event_cb(esplusplayer_handle pp, + esplusplayer_event_cb event_cb, + void* userdata); + friend int esplusplayer_set_first_video_decoding_done_cb( + esplusplayer_handle handle, + esplusplayer_first_video_decoding_done_cb first_video_decoding_done_cb, + void* userdata); + friend int esplusplayer_set_video_decoder_underrun_cb( + esplusplayer_handle handle, + esplusplayer_decoder_underrun_cb video_decoder_underrun_cb, + void* userdata); + friend int esplusplayer_set_video_latency_status_cb( + esplusplayer_handle pp, + esplusplayer_video_latency_status_cb video_latency_status_cb, + void* userdata); + friend int esplusplayer_set_audio_latency_status_cb( + esplusplayer_handle pp, + esplusplayer_audio_latency_status_cb audio_latency_status_cb, + void* userdata); + friend int esplusplayer_set_video_high_latency_cb( + esplusplayer_handle pp, + esplusplayer_video_high_latency_cb video_high_latency_cb, void* userdata); + friend int esplusplayer_set_audio_high_latency_cb( + esplusplayer_handle pp, + esplusplayer_audio_high_latency_cb audio_high_latency_cb, void* userdata); +}; + +struct EsPlusPlayerPriv { + std::unique_ptr player; + std::unique_ptr listener{new listener_bridge()}; + std::shared_ptr decoded_pkt_mgr; + + friend EsPlusPlayerPriv* EsPrivCreate(); + friend void EsPrivDestroy(EsPlusPlayerPriv*& instance); + + private: + EsPlusPlayerPriv() {} + ~EsPlusPlayerPriv() {} +}; + +EsPlusPlayerPriv* EsPrivCreate() { + EsPlusPlayerPriv* instance = new EsPlusPlayerPriv(); + instance->player = EsPlusPlayer::Create(); + instance->player->RegisterListener(instance->listener.get(), + instance->player.get()); + return instance; +} + +void EsPrivDestroy(EsPlusPlayerPriv*& instance) { + if (instance) delete instance; + instance = nullptr; +} + +inline bool is_null_(void* object) { return object == nullptr; } + +inline EsPlusPlayer* cast_(esplusplayer_handle pp) { + auto priv = static_cast(pp); + return priv ? priv->player.get() : nullptr; +} + +inline listener_bridge* listener_cast_(esplusplayer_handle pp) { + auto priv = static_cast(pp); + return priv->listener.get(); +} + +void update_ready_to_seek_callback( + esplusplayer_handle handle, esplusplayer_ready_to_seek_cb ready_to_seek_cb, + void* userdata) { + LOG_ENTER + listener_bridge* listener = nullptr; + if (is_null_(handle) || is_null_(listener = listener_cast_(handle))) { + LOG_ERROR("ESPlayer or Listener object is nil."); + return; + } + std::unique_lock lock(listener->multi_seek_control.lock); + listener->ready_to_seek_cb_ = ready_to_seek_cb; + listener->ready_to_seek_cb_userdata_ = userdata; + listener->multi_seek_control.is_offset_valid = false; +} +void update_ready_to_seek_offset(esplusplayer_handle handle, + const uint64_t offset) { + LOG_ENTER + listener_bridge* listener = nullptr; + if (is_null_(handle) || is_null_(listener = listener_cast_(handle))) { + LOG_ERROR("ESPlayer or Listener object is nil."); + return; + } + std::unique_lock lock(listener->multi_seek_control.lock); + listener->multi_seek_control.offset = offset; + listener->multi_seek_control.is_offset_valid = true; +} + +inline void convert_matroska_color_info_( + const esplusplayer_matroska_color* from, MatroskaColor* to) { + to->matrix_coefficients = from->matrix_coefficients; + to->bits_per_channel = from->bits_per_channel; + to->chroma_subsampling_horizontal = from->chroma_subsampling_horizontal; + to->chroma_subsampling_vertical = from->chroma_subsampling_vertical; + to->cb_subsampling_horizontal = from->cb_subsampling_horizontal; + to->cb_subsampling_vertical = from->cb_subsampling_vertical; + to->chroma_siting_horizontal = from->chroma_siting_horizontal; + to->chroma_siting_vertical = from->chroma_siting_vertical; + to->range = from->range; + to->transfer_characteristics = from->transfer_characteristics; + to->primaries = from->primaries; + to->max_cll = from->max_cll; + to->max_fall = from->max_fall; + to->is_hdr_10p = from->isHDR10p; + to->metadata.primary_r_chromaticity_x = + from->metadata.primary_r_chromaticity_x; + to->metadata.primary_r_chromaticity_y = + from->metadata.primary_r_chromaticity_y; + to->metadata.primary_g_chromaticity_x = + from->metadata.primary_g_chromaticity_x; + to->metadata.primary_g_chromaticity_y = + from->metadata.primary_g_chromaticity_y; + to->metadata.primary_b_chromaticity_x = + from->metadata.primary_b_chromaticity_x; + to->metadata.primary_b_chromaticity_y = + from->metadata.primary_b_chromaticity_y; + to->metadata.white_point_chromaticity_x = + from->metadata.white_point_chromaticity_x; + to->metadata.white_point_chromaticity_y = + from->metadata.white_point_chromaticity_y; + to->metadata.luminance_max = from->metadata.luminance_max; + to->metadata.luminance_min = from->metadata.luminance_min; +} + +inline EsPacketPtr convert_espacket_(esplusplayer_es_packet* from) { + std::shared_ptr buffer = nullptr; + std::shared_ptr hdr10p_metadata = nullptr; + if (from->buffer_size != 0 && from->buffer) { + buffer = std::shared_ptr(new char[from->buffer_size], + std::default_delete()); + memcpy(buffer.get(), from->buffer, from->buffer_size); + } + if (from->hdr10p_metadata_size != 0 && from->hdr10p_metadata) { + hdr10p_metadata = std::shared_ptr( + new char[from->hdr10p_metadata_size], std::default_delete()); + memcpy(hdr10p_metadata.get(), from->hdr10p_metadata, + from->hdr10p_metadata_size); + } + auto espacket = EsPacket::Create(static_cast(from->type), buffer, + from->buffer_size, from->pts, from->duration, + from->hdr10p_metadata_size, hdr10p_metadata); + + if (from->matroska_color_info != nullptr) { + MatroskaColor color_info; + convert_matroska_color_info_(from->matroska_color_info, &color_info); + bool ret = espacket->SetMatroskaColorInfo(color_info); + if (ret == false) return nullptr; + } + return std::move(espacket); +} + +using EncryptedInfoPtr = + std::unique_ptr>; +inline EncryptedInfoPtr convert_es_drm_info_(esplusplayer_drm_info* from) { + auto custom_deleter = [](EsPlayerEncryptedInfo* drm_info) { + if (drm_info == nullptr) return; + if (drm_info->sub_data != nullptr) { + delete reinterpret_cast( + drm_info->sub_data); + drm_info->sub_data = nullptr; + } + delete drm_info; + }; + + if (from == nullptr) return EncryptedInfoPtr(nullptr, custom_deleter); + + EncryptedInfoPtr drm_info = + EncryptedInfoPtr(new EsPlayerEncryptedInfo(), custom_deleter); + + drm_info->handle = from->handle; + drm_info->algorithm = + static_cast(from->algorithm); + drm_info->format = + static_cast(from->format); + drm_info->phase = + static_cast(from->phase); + + // kid + if (from->kid && from->kid_length > 0) { + drm_info->kid = std::move( + std::vector(from->kid, from->kid + from->kid_length)); + } + + // initialization_vector + if (from->iv && from->iv_length > 0) { + drm_info->initialization_vector = std::move( + std::vector(from->iv, from->iv + from->iv_length)); + } + + // sub_data + auto* from_sub_data = + reinterpret_cast(from->sub_data); + if (from_sub_data && from_sub_data->subsample_count > 0) { + drm_info->sub_data = new plusplayer::drm::DrmbEsFragmentedMp4Data; + auto* sub_data = + reinterpret_cast( + drm_info->sub_data); + for (uint32_t i = 0; i < from_sub_data->subsample_count; i++) { + auto& subsample_info = from_sub_data->subsample_infos[i]; + sub_data->sub_sample_info_vector.emplace_back( + subsample_info.bytes_of_clear_data, + subsample_info.bytes_of_encrypted_data); + } + } + + // split_offsets + if (from->split_offsets) { + const std::size_t kSplitOffsetMaxSize = 15 * sizeof(int); + std::memcpy(drm_info->split_offsets.data(), from->split_offsets, + kSplitOffsetMaxSize); + } + + drm_info->use_out_buffer = from->use_out_buffer; + drm_info->use_pattern = from->use_pattern; + drm_info->crypt_byte_block = from->crypt_byte_block; + drm_info->skip_byte_block = from->skip_byte_block; + + return std::move(drm_info); +} + +inline AudioStreamPtr convert_stream_ptr_( + esplusplayer_audio_stream_info* from) { + LOG_INFO("mime type : %d", static_cast(from->mime_type)); + LOG_INFO("from->bitrate : %d", from->bitrate); + LOG_INFO("from->channels : %d", from->channels); + LOG_INFO("from->sample_rate : %d", from->sample_rate); + LOG_INFO("from->codec_data_length : %d", from->codec_data_length); + + auto stream = AudioStream::Create(); + std::shared_ptr codec_data = nullptr; + + if (from->codec_data_length != 0) { + codec_data = std::shared_ptr(new char[from->codec_data_length], + std::default_delete()); + memcpy(codec_data.get(), from->codec_data, from->codec_data_length); + } + + stream->SetCodecData(codec_data, from->codec_data_length); + stream->SetMimeType(static_cast(from->mime_type)); + stream->SetBitrate(from->bitrate); + stream->SetChannels(from->channels); + stream->SetSamplerate(from->sample_rate); + + return std::move(stream); +} + +inline int convert_return_type_(bool ret) { + return ret ? ESPLUSPLAYER_ERROR_TYPE_NONE + : ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION; +} + +inline VideoStreamPtr convert_stream_ptr_( + esplusplayer_video_stream_info* from) { + LOG_INFO("mime type : %u", static_cast(from->mime_type)); + LOG_INFO("from->width : %u", from->width); + LOG_INFO("from->height : %u", from->height); + LOG_INFO("from->max_width : %u", from->max_width); + LOG_INFO("from->max_height : %u", from->max_height); + LOG_INFO("from->framerate_num : %u", from->framerate_num); + LOG_INFO("from->framerate_den : %u", from->framerate_den); + LOG_INFO("from->codec_data_length : %u", from->codec_data_length); + + auto stream = VideoStream::Create(); + std::shared_ptr codec_data = nullptr; + + if (from->codec_data_length != 0) { + codec_data = std::shared_ptr(new char[from->codec_data_length], + std::default_delete()); + memcpy(codec_data.get(), from->codec_data, from->codec_data_length); + } + + stream->SetCodecData(codec_data, from->codec_data_length); + stream->SetMimeType(static_cast(from->mime_type)); + stream->SetWidth(from->width); + stream->SetHeight(from->height); + stream->SetMaxWidth(from->max_width); + stream->SetMaxHeight(from->max_height); + stream->SetFramerate(from->framerate_num, from->framerate_den); + + return std::move(stream); +} + +esplusplayer_handle esplusplayer_create() { + esplusplayer_handle player = static_cast(EsPrivCreate()); + LOG_INFO("capi handle > [%p], cpp handle > [%p]", player, cast_(player)); + return player; +} + +int esplusplayer_open(esplusplayer_handle handle) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + return convert_return_type_(cast_(handle)->Open()); +} + +int esplusplayer_close(esplusplayer_handle handle) { + LOG_ENTER_P(cast_(handle)) + listener_bridge* listener = nullptr; + if (is_null_(handle) || is_null_(listener = listener_cast_(handle))) { + LOG_ERROR("ESPlayer or Listener object is nil."); + return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + } + + bool ret = cast_(handle)->Close(); + listener->Reset(); + return convert_return_type_(ret); +} + +int esplusplayer_destroy(esplusplayer_handle handle) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + + if (ESPLUSPLAYER_STATE_NONE != esplusplayer_get_state(handle)) + return ESPLUSPLAYER_ERROR_TYPE_INVALID_STATE; + + auto priv = static_cast(handle); + EsPrivDestroy(priv); + + return ESPLUSPLAYER_ERROR_TYPE_NONE; +} + +int esplusplayer_deactivate(esplusplayer_handle handle, + esplusplayer_stream_type type) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + + return convert_return_type_( + cast_(handle)->Deactivate(static_cast(type))); +} + +int esplusplayer_activate(esplusplayer_handle handle, + esplusplayer_stream_type type) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + + return convert_return_type_( + cast_(handle)->Activate(static_cast(type))); +} + +int esplusplayer_prepare_async(esplusplayer_handle handle) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + + return convert_return_type_(cast_(handle)->PrepareAsync()); +} + +int esplusplayer_start(esplusplayer_handle handle) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + + return convert_return_type_(cast_(handle)->Start()); +} + +int esplusplayer_stop(esplusplayer_handle handle) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + + return convert_return_type_(cast_(handle)->Stop()); +} + +int esplusplayer_pause(esplusplayer_handle handle) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + + return convert_return_type_(cast_(handle)->Pause()); +} + +int esplusplayer_resume(esplusplayer_handle handle) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + + return convert_return_type_(cast_(handle)->Resume()); +} + +int esplusplayer_set_playback_rate(esplusplayer_handle handle, + const double playback_rate, + const bool audio_mute) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + LOG_INFO_P(cast_(handle), "playback rate : %lf, audio mute : %d", + playback_rate, audio_mute); + return convert_return_type_( + cast_(handle)->SetPlaybackRate(playback_rate, audio_mute)); +} + +int esplusplayer_seek(esplusplayer_handle handle, uint64_t time_ms) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + LOG_INFO_P(cast_(handle), "time : %llu", time_ms); + update_ready_to_seek_offset(handle, time_ms); + + return convert_return_type_(cast_(handle)->Seek(time_ms)); +} + +int esplusplayer_set_app_info(esplusplayer_handle handle, + const esplusplayer_app_info* app_info) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + if (app_info == nullptr) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + + LOG_INFO_P(cast_(handle), "app id : %s ", app_info->id); + LOG_INFO_P(cast_(handle), "app version : %s ", app_info->version); + LOG_INFO_P(cast_(handle), "app type : %s", app_info->type); + + PlayerAppInfo info; + info.id = app_info->id; + info.version = app_info->version; + info.type = app_info->type; + cast_(handle)->SetAppInfo(info); + return convert_return_type_(true); +} + +int esplusplayer_set_display(esplusplayer_handle handle, + esplusplayer_display_type type, void* window) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + LOG_INFO_P(cast_(handle), "display type : %d, object : %p", + static_cast(type), window); + +#if (!IS_AUDIO_PRODUCT) && (!IS_TOMATO) + if (type == ESPLUSPLAYER_DISPLAY_TYPE_MIXER) { + mixer_handle mixer_h = window; + plusplayer::MixerTicket* ticket = + (plusplayer::MixerTicket*)mixer_create_ticket(mixer_h, handle); + if (is_null_(ticket)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + return convert_return_type_( + cast_(handle)->SetDisplay(static_cast(type), ticket)); + } +#endif + return convert_return_type_( + cast_(handle)->SetDisplay(static_cast(type), window)); +} + +int esplusplayer_set_ecore_display(esplusplayer_handle handle, + esplusplayer_display_type type, void* window, + int x, int y, int width, int height) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + LOG_INFO_P(cast_(handle), "display type : %d, object : %p", + static_cast(type), window); + + return convert_return_type_(cast_(handle)->SetDisplay( + static_cast(type), window, x, y, width, height)); +} + +int esplusplayer_set_display_ecore_subsurface(esplusplayer_handle handle, + esplusplayer_display_type type, + void* subsurface, int x, int y, + int width, int height) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle) || is_null_(subsurface)) + return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + LOG_INFO_P(cast_(handle), "display type : %d, object : %p", + static_cast(type), subsurface); + + return convert_return_type_(cast_(handle)->SetDisplaySubsurface( + static_cast(type), subsurface, x, y, width, height)); +} + +int esplusplayer_set_surface_display(esplusplayer_handle handle, + esplusplayer_display_type type, + unsigned int surface_id, int x, int y, + int width, int height) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + LOG_INFO_P(cast_(handle), "display type : %d, object : %u", + static_cast(type), surface_id); + + return convert_return_type_(cast_(handle)->SetDisplay( + static_cast(type), surface_id, x, y, width, height)); +} + +int esplusplayer_set_display_mode(esplusplayer_handle handle, + esplusplayer_display_mode mode) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + LOG_INFO_P(cast_(handle), "display mode : %d", static_cast(mode)); + + return convert_return_type_( + cast_(handle)->SetDisplayMode(static_cast(mode))); +} + +int esplusplayer_set_display_roi(esplusplayer_handle handle, int x, int y, + int width, int height) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + LOG_INFO_P(cast_(handle), "x : %d, y: %d, width : %d, height : %d", x, y, + width, height); + + Geometry roi; + roi.x = x; + roi.y = y; + roi.w = width; + roi.h = height; + + return convert_return_type_(cast_(handle)->SetDisplayRoi(roi)); +} + +int esplusplayer_set_video_roi(esplusplayer_handle handle, double scale_x, + double scale_y, double scale_w, double scale_h) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + LOG_INFO_P(cast_(handle), + "scale-x : %lf, scale-y: %lf, scale-w : %lf, scale-h : %lf", + scale_x, scale_y, scale_w, scale_h); + + CropArea rio_area; + rio_area.scale_x = scale_x; + rio_area.scale_y = scale_y; + rio_area.scale_w = scale_w; + rio_area.scale_h = scale_h; + + return convert_return_type_(cast_(handle)->SetVideoRoi(rio_area)); +} + +int esplusplayer_resize_render_rect(esplusplayer_handle handle, int x, int y, + int width, int height) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + LOG_INFO_P(cast_(handle), "x : %d, y: %d, width : %d, height : %d", x, y, + width, height); + + RenderRect rect; + rect.x = x; + rect.y = y; + rect.w = width; + rect.h = height; + + return convert_return_type_(cast_(handle)->ResizeRenderRect(rect)); +} + +int esplusplayer_set_display_rotation( + esplusplayer_handle handle, esplusplayer_display_rotation_type rotation) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + LOG_INFO_P(cast_(handle), "display rotate angle : %d", + static_cast(rotation)); + return convert_return_type_( + cast_(handle)->SetDisplayRotate(static_cast(rotation))); +} + +int esplusplayer_get_display_rotation( + esplusplayer_handle handle, esplusplayer_display_rotation_type* rotation) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle) || is_null_(rotation)) + return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + return convert_return_type_(cast_(handle)->GetDisplayRotate( + reinterpret_cast(rotation))); +} + +int esplusplayer_set_display_visible(esplusplayer_handle handle, bool visible) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + LOG_INFO_P(cast_(handle), "visible : %s", visible ? "true" : "false"); + return convert_return_type_(cast_(handle)->SetDisplayVisible(visible)); +} + +int esplusplayer_set_tz_use(esplusplayer_handle handle, bool using_tz) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + LOG_INFO_P(cast_(handle), "using_tz : %s", using_tz ? "true" : "false"); + return convert_return_type_(cast_(handle)->SetTrustZoneUse(using_tz)); +} + +int esplusplayer_set_submit_data_type(esplusplayer_handle handle, + esplusplayer_submit_data_type type) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + LOG_INFO_P(cast_(handle), "type : %d", type); + return convert_return_type_( + cast_(handle)->SetSubmitDataType(static_cast(type))); +} + +int esplusplayer_set_audio_mute(esplusplayer_handle handle, bool mute) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + LOG_INFO_P(cast_(handle), "mute : %s", mute ? "true" : "false"); + return convert_return_type_(cast_(handle)->SetAudioMute(mute)); +} + +esplusplayer_state esplusplayer_get_state(esplusplayer_handle handle) { + // LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return esplusplayer_state::ESPLUSPLAYER_STATE_NONE; + auto current_state = + static_cast(cast_(handle)->GetState()); + // LOG_INFO_P(cast_(handle), "state : %d", static_cast(current_state)); + + return current_state; +} + +esplusplayer_submit_status esplusplayer_submit_packet( + esplusplayer_handle handle, esplusplayer_es_packet* packet) { + if (is_null_(handle)) return ESPLUSPLAYER_SUBMIT_STATUS_NOT_PREPARED; + auto packetptr = convert_espacket_(packet); + if (packetptr == nullptr) { + LOG_ERROR("packet converting failed"); + return ESPLUSPLAYER_SUBMIT_STATUS_INVALID_PACKET; + } + auto status = cast_(handle)->SubmitPacket(packetptr); + if (status != plusplayer::PacketSubmitStatus::kSuccess) { + LOG_ERROR("SubmitPacket status isn't SUCCESS [%d]", + static_cast(status)); + } + return static_cast(status); +} + +esplusplayer_submit_status esplusplayer_submit_trust_zone_packet( + esplusplayer_handle handle, esplusplayer_es_packet* packet, + uint32_t tz_handle) { + if (is_null_(handle)) return ESPLUSPLAYER_SUBMIT_STATUS_NOT_PREPARED; + auto packetptr = convert_espacket_(packet); + if (packetptr == nullptr) { + LOG_ERROR("packet converting failed"); + return ESPLUSPLAYER_SUBMIT_STATUS_INVALID_PACKET; + } + auto status = cast_(handle)->SubmitTrustZonePacket(packetptr, tz_handle); + if (status != plusplayer::PacketSubmitStatus::kSuccess) { + LOG_ERROR("SubmitPacket status isn't SUCCESS [%d]", + static_cast(status)); + } + return static_cast(status); +} + +esplusplayer_submit_status esplusplayer_submit_encrypted_packet( + esplusplayer_handle handle, esplusplayer_es_packet* packet, + esplusplayer_drm_info* drm_info) { + if (is_null_(handle)) return ESPLUSPLAYER_SUBMIT_STATUS_NOT_PREPARED; + auto packetptr = convert_espacket_(packet); + if (packetptr == nullptr) { + LOG_ERROR("packet converting failed"); + return ESPLUSPLAYER_SUBMIT_STATUS_INVALID_PACKET; + } + auto status = plusplayer::PacketSubmitStatus::kSuccess; + if (drm_info == nullptr) { + status = cast_(handle)->SubmitPacket(packetptr); + } else { + auto encrypted_info = convert_es_drm_info_(drm_info); + status = cast_(handle)->SubmitEncryptedPacket(packetptr, *encrypted_info); + } + if (status != plusplayer::PacketSubmitStatus::kSuccess) { + LOG_ERROR("SubmitPacket status isn't SUCCESS [%d]", + static_cast(status)); + } + return static_cast(status); +} + +esplusplayer_submit_status esplusplayer_submit_eos_packet( + esplusplayer_handle handle, esplusplayer_stream_type type) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_SUBMIT_STATUS_NOT_PREPARED; + + auto status = cast_(handle)->SubmitPacket( + std::move(EsPacket::CreateEos(static_cast(type)))); + return static_cast(status); +} + +int esplusplayer_set_audio_stream_info(esplusplayer_handle handle, + esplusplayer_audio_stream_info* info) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle) || is_null_(info)) + return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + + auto stream = convert_stream_ptr_(info); + return convert_return_type_(cast_(handle)->SetStream(std::move(stream))); +} + +int esplusplayer_set_video_stream_info(esplusplayer_handle handle, + esplusplayer_video_stream_info* info) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle) || is_null_(info)) + return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + + auto stream = convert_stream_ptr_(info); + return convert_return_type_(cast_(handle)->SetStream(std::move(stream))); +} + +int esplusplayer_get_playing_time(esplusplayer_handle handle, uint64_t* ms) { + // LOG_ENTER_P(cast_(handle)) + if (is_null_(handle) || is_null_(ms)) + return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + + auto ret = cast_(handle)->GetPlayingTime(ms); + // LOG_INFO_P(cast_(handle), "playing time : %llu", *ms); + return convert_return_type_(ret); +} + +namespace { +std::shared_ptr CreateDecodedPacketManager( + esplusplayer_decoded_video_frame_buffer_type type) { + std::shared_ptr mgr = nullptr; + if (type == ESPLUSPLAYER_DECODED_VIDEO_FRAME_BUFFER_TYPE_COPY) + mgr = std::make_shared(); + else if (type == ESPLUSPLAYER_DECODED_VIDEO_FRAME_BUFFER_TYPE_REFERENCE) + mgr = std::make_shared(); + else if (type == ESPLUSPLAYER_DECODED_VIDEO_FRAME_BUFFER_TYPE_SCALE) + mgr = std::make_shared(); + return mgr; +} +} // namespace + +int esplusplayer_set_video_frame_buffer_type( + esplusplayer_handle handle, + esplusplayer_decoded_video_frame_buffer_type type) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle) || is_null_(listener_cast_(handle))) + return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + + auto priv = static_cast(handle); + priv->decoded_pkt_mgr = ::CreateDecodedPacketManager(type); + priv->listener->SetDecodedPacketManager(priv->decoded_pkt_mgr); + + auto ret = cast_(handle)->SetVideoFrameBufferType( + static_cast(type)); + return convert_return_type_(ret); +} + +int esplusplayer_set_video_frame_buffer_scale_resolution( + esplusplayer_handle handle, uint32_t target_width, uint32_t target_height) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle) || !target_width || !target_height) + return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + + LOG_INFO_P(cast_(handle), "target_width : %d, target_height: %d", + target_width, target_height); + return convert_return_type_(cast_(handle)->SetVideoFrameBufferScaleResolution( + target_width, target_height)); +} + +int esplusplayer_set_decoded_video_frame_rate( + esplusplayer_handle handle, esplusplayer_rational request_framerate) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + LOG_INFO("request decoded video frame rate : %d/%d", request_framerate.num, + request_framerate.den); + + Rational request_fps; + request_fps.num = request_framerate.num; + request_fps.den = request_framerate.den; + return convert_return_type_( + cast_(handle)->SetDecodedVideoFrameRate(request_fps)); +} + +int esplusplayer_get_adaptive_info( + esplusplayer_handle handle, void* padaptive_info, + esplusplayer_adaptive_info_type adaptive_type) { + // LOG_ENTER_P(cast_(handle)) + if (is_null_(handle) || is_null_(padaptive_info)) + return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + + auto ret = cast_(handle)->GetAdaptiveInfo( + padaptive_info, static_cast(adaptive_type)); + return convert_return_type_(ret); +} + +int esplusplayer_set_volume(esplusplayer_handle handle, const int volume) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + + auto ret = cast_(handle)->SetVolume(volume); + return convert_return_type_(ret); +} + +int esplusplayer_get_volume(esplusplayer_handle handle, int* volume) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle) || is_null_(volume)) + return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + + auto ret = cast_(handle)->GetVolume(volume); + return convert_return_type_(ret); +} + +int esplusplayer_flush(esplusplayer_handle handle, + esplusplayer_stream_type type) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + auto ret = cast_(handle)->Flush(static_cast(type)); + return convert_return_type_(ret); +} + +const char* esplusplayer_get_error_string(esplusplayer_error_type type) { + LOG_ENTER + return util::ConvertErrorTypeToString(type).c_str(); +} + +int esplusplayer_set_error_cb(esplusplayer_handle handle, + esplusplayer_error_cb error_cb, void* userdata) { + LOG_ENTER_P(cast_(handle)) + listener_bridge* listener = nullptr; + if (is_null_(handle) || is_null_(listener = listener_cast_(handle))) { + LOG_ERROR("ESPlayer or Listener object is nil."); + return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + } + + listener->error_cb_ = error_cb; + listener->error_cb_userdata_ = userdata; + return convert_return_type_(true); +} + +int esplusplayer_set_buffer_status_cb( + esplusplayer_handle handle, esplusplayer_buffer_status_cb buffer_status_cb, + void* userdata) { + LOG_ENTER_P(cast_(handle)) + listener_bridge* listener = nullptr; + if (is_null_(handle) || is_null_(listener = listener_cast_(handle))) { + LOG_ERROR("ESPlayer or Listener object is nil."); + return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + } + + listener->buffer_status_cb_ = buffer_status_cb; + listener->buffer_status_cb_userdata_ = userdata; + return convert_return_type_(true); +} + +int esplusplayer_set_buffer_byte_status_cb( + esplusplayer_handle handle, + esplusplayer_buffer_byte_status_cb buffer_status_cb, void* userdata) { + LOG_ENTER_P(cast_(handle)) + listener_bridge* listener = nullptr; + if (is_null_(handle) || is_null_(listener = listener_cast_(handle))) { + LOG_ERROR("ESPlayer or Listener object is nil."); + return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + } + + listener->buffer_byte_status_cb_ = buffer_status_cb; + listener->buffer_byte_status_cb_userdata_ = userdata; + return convert_return_type_(true); +} + +int esplusplayer_set_buffer_time_status_cb( + esplusplayer_handle handle, + esplusplayer_buffer_time_status_cb buffer_status_cb, void* userdata) { + LOG_ENTER_P(cast_(handle)) + listener_bridge* listener = nullptr; + if (is_null_(handle) || is_null_(listener = listener_cast_(handle))) { + LOG_ERROR("ESPlayer or Listener object is nil."); + return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + } + + listener->buffer_time_status_cb_ = buffer_status_cb; + listener->buffer_time_status_cb_userdata_ = userdata; + return convert_return_type_(true); +} + +int esplusplayer_set_buffer_size(esplusplayer_handle handle, + esplusplayer_buffer_option option, + uint64_t size) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + LOG_INFO_P(cast_(handle), "option: %d, size: %lld", static_cast(option), + size); + cast_(handle)->SetBufferSize(static_cast(option), + size); + return convert_return_type_(true); +} + +int esplusplayer_set_resource_conflicted_cb( + esplusplayer_handle handle, + esplusplayer_resource_conflicted_cb resource_conflicted_cb, + void* userdata) { + LOG_ENTER_P(cast_(handle)) + listener_bridge* listener = nullptr; + if (is_null_(handle) || is_null_(listener = listener_cast_(handle))) { + LOG_ERROR("ESPlayer or Listener object is nil."); + return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + } + + listener->resource_conflicted_cb_ = resource_conflicted_cb; + listener->resource_conflicted_cb_userdata_ = userdata; + return convert_return_type_(true); +} + +int esplusplayer_set_eos_cb(esplusplayer_handle handle, + esplusplayer_eos_cb eos_cb, void* userdata) { + LOG_ENTER_P(cast_(handle)) + listener_bridge* listener = nullptr; + if (is_null_(handle) || is_null_(listener = listener_cast_(handle))) { + LOG_ERROR("ESPlayer or Listener object is nil."); + return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + } + + listener->eos_cb_ = eos_cb; + listener->eos_cb_userdata_ = userdata; + return convert_return_type_(true); +} + +int esplusplayer_set_ready_to_prepare_cb( + esplusplayer_handle handle, + esplusplayer_ready_to_prepare_cb ready_to_prepare_cb, void* userdata) { + LOG_ENTER_P(cast_(handle)) + listener_bridge* listener = nullptr; + if (is_null_(handle) || is_null_(listener = listener_cast_(handle))) { + LOG_ERROR("ESPlayer or Listener object is nil."); + return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + } + + listener->ready_to_prepare_cb_ = ready_to_prepare_cb; + listener->ready_to_prepare_cb_userdata_ = userdata; + return convert_return_type_(true); +} + +int esplusplayer_set_prepare_async_done_cb( + esplusplayer_handle handle, + esplusplayer_prepare_async_done_cb prepare_async_done_cb, void* userdata) { + LOG_ENTER_P(cast_(handle)) + listener_bridge* listener = nullptr; + if (is_null_(handle) || is_null_(listener = listener_cast_(handle))) { + LOG_ERROR("ESPlayer or Listener object is nil."); + return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + } + + listener->prepare_async_done_cb_ = prepare_async_done_cb; + listener->prepare_async_done_cb_userdata_ = userdata; + return convert_return_type_(true); +} + +int esplusplayer_set_seek_done_cb(esplusplayer_handle handle, + esplusplayer_seek_done_cb seek_done_cb, + void* userdata) { + LOG_ENTER_P(cast_(handle)) + listener_bridge* listener = nullptr; + if (is_null_(handle) || is_null_(listener = listener_cast_(handle))) { + LOG_ERROR("ESPlayer or Listener object is nil."); + return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + } + + listener->seek_done_cb_ = seek_done_cb; + listener->seek_done_cb_userdata_ = userdata; + return convert_return_type_(true); +} + +int esplusplayer_set_ready_to_seek_cb( + esplusplayer_handle handle, esplusplayer_ready_to_seek_cb ready_to_seek_cb, + void* userdata) { + LOG_ENTER_P(cast_(handle)) + listener_bridge* listener = nullptr; + if (is_null_(handle) || is_null_(listener = listener_cast_(handle))) { + LOG_ERROR("ESPlayer or Listener object is nil."); + return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + } + update_ready_to_seek_callback(handle, ready_to_seek_cb, userdata); + return convert_return_type_(true); +} + +int esplusplayer_set_media_packet_video_decoded_cb( + esplusplayer_handle handle, + esplusplayer_media_packet_video_decoded_cb media_packet_video_decoded_cb, + void* userdata) { + LOG_ENTER_P(cast_(handle)) + listener_bridge* listener = nullptr; + if (is_null_(handle) || is_null_(listener = listener_cast_(handle))) { + LOG_ERROR("ESPlayer or Listener object is nil."); + return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + } + + listener->media_packet_video_decoded_cb_ = media_packet_video_decoded_cb; + listener->media_packet_video_decoded_cb_userdata_ = userdata; + return convert_return_type_(true); +} + +int esplusplayer_set_closed_caption_cb( + esplusplayer_handle handle, + esplusplayer_closed_caption_cb closed_caption_cb, void* userdata) { + LOG_ENTER_P(cast_(handle)) + listener_bridge* listener = nullptr; + if (is_null_(handle) || is_null_(listener = listener_cast_(handle))) { + LOG_ERROR("ESPlayer or Listener object is nil."); + return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + } + + listener->closed_caption_cb_ = closed_caption_cb; + listener->closed_caption_cb_userdata_ = userdata; + return convert_return_type_(true); +} + +int esplusplayer_set_flush_done_cb(esplusplayer_handle handle, + esplusplayer_flush_done_cb flush_done_cb, + void* userdata) { + LOG_ENTER_P(cast_(handle)) + listener_bridge* listener = nullptr; + if (is_null_(handle) || is_null_(listener = listener_cast_(handle))) { + LOG_ERROR("ESPlayer or Listener object is nil."); + return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + } + + listener->flush_done_cb_ = flush_done_cb; + listener->flush_done_cb_userdata_ = userdata; + return convert_return_type_(true); +} + +int esplusplayer_set_event_cb(esplusplayer_handle handle, + esplusplayer_event_cb event_cb, void* userdata) { + LOG_ENTER_P(cast_(handle)) + listener_bridge* listener = nullptr; + if (is_null_(handle) || is_null_(listener = listener_cast_(handle))) { + LOG_ERROR("ESPlayer or Listener object is nil."); + return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + } + listener->event_cb_ = event_cb; + listener->event_cb_userdata_ = userdata; + + return convert_return_type_(true); +} + +int esplusplayer_set_first_video_decoding_done_cb( + esplusplayer_handle handle, + esplusplayer_first_video_decoding_done_cb first_video_decoding_done_cb, + void* userdata) { + LOG_ENTER_P(cast_(handle)) + listener_bridge* listener = nullptr; + if (is_null_(handle) || is_null_(listener = listener_cast_(handle))) { + LOG_ERROR("ESPlayer or Listener object is nil."); + return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + } + listener->first_video_decoding_done_cb_ = first_video_decoding_done_cb; + listener->first_video_decoding_done_cb_userdata_ = userdata; + return convert_return_type_(true); +} + +int esplusplayer_set_video_decoder_underrun_cb( + esplusplayer_handle handle, + esplusplayer_decoder_underrun_cb video_decoder_underrun_cb, + void* userdata) { + LOG_ENTER_P(cast_(handle)) + listener_bridge* listener = nullptr; + if (is_null_(handle) || is_null_(listener = listener_cast_(handle))) { + LOG_ERROR("ESPlayer or Listener object is nil."); + return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + } + listener->video_decoder_underrun_cb_ = video_decoder_underrun_cb; + listener->video_decoder_underrun_cb_userdata_ = userdata; + + return convert_return_type_(true); +} +int esplusplayer_set_video_latency_status_cb( + esplusplayer_handle handle, + esplusplayer_video_latency_status_cb video_latency_status_cb, + void* userdata) { + LOG_ENTER_P(cast_(handle)) + listener_bridge* listener = nullptr; + if (is_null_(handle) || is_null_(listener = listener_cast_(handle))) { + LOG_ERROR("ESPlayer or Listener object is nil."); + return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + } + + listener->video_latency_status_cb_ = video_latency_status_cb; + listener->video_latency_status_cb_userdata_ = userdata; + return convert_return_type_(true); +} + +int esplusplayer_set_audio_latency_status_cb( + esplusplayer_handle handle, + esplusplayer_audio_latency_status_cb audio_latency_status_cb, + void* userdata) { + LOG_ENTER_P(cast_(handle)) + listener_bridge* listener = nullptr; + if (is_null_(handle) || is_null_(listener = listener_cast_(handle))) { + LOG_ERROR("ESPlayer or Listener object is nil."); + return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + } + + listener->audio_latency_status_cb_ = audio_latency_status_cb; + listener->audio_latency_status_cb_userdata_ = userdata; + return convert_return_type_(true); +} + +int esplusplayer_set_video_high_latency_cb( + esplusplayer_handle handle, + esplusplayer_video_high_latency_cb video_high_latency_cb, void* userdata) { + LOG_ENTER_P(cast_(handle)) + listener_bridge* listener = nullptr; + if (is_null_(handle) || is_null_(listener = listener_cast_(handle))) { + LOG_ERROR("ESPlayer or Listener object is nil."); + return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + } + + listener->video_high_latency_cb_ = video_high_latency_cb; + listener->video_high_latency_cb_userdata_ = userdata; + return convert_return_type_(true); +} + +int esplusplayer_set_audio_high_latency_cb( + esplusplayer_handle handle, + esplusplayer_audio_high_latency_cb audio_high_latency_cb, void* userdata) { + LOG_ENTER_P(cast_(handle)) + listener_bridge* listener = nullptr; + if (is_null_(handle) || is_null_(listener = listener_cast_(handle))) { + LOG_ERROR("ESPlayer or Listener object is nil."); + return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + } + + listener->audio_high_latency_cb_ = audio_high_latency_cb; + listener->audio_high_latency_cb_userdata_ = userdata; + return convert_return_type_(true); +} + +int esplusplayer_decoded_buffer_destroy( + esplusplayer_handle handle, esplusplayer_decoded_video_packet* packet) { + if (is_null_(handle)) { + LOG_ERROR("ESPlayer object is nil."); + return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + } + auto priv = static_cast(handle); + auto& mgr = priv->decoded_pkt_mgr; + if (mgr == nullptr) { + LOG_ERROR("DecodedPacketManager object is nil."); + return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + } + mgr->Remove(packet); + return convert_return_type_(true); +} + +int esplusplayer_set_low_latency_mode(esplusplayer_handle handle, + esplusplayer_low_latency_mode mode) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + auto ret = + cast_(handle)->SetLowLatencyMode(static_cast(mode)); + return convert_return_type_(ret); +} + +int esplusplayer_set_video_frame_peek_mode(esplusplayer_handle handle) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + auto ret = cast_(handle)->SetVideoFramePeekMode(); + return convert_return_type_(ret); +} + +int esplusplayer_render_video_frame(esplusplayer_handle handle) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + auto ret = cast_(handle)->RenderVideoFrame(); + return convert_return_type_(ret); +} + +int esplusplayer_set_unlimited_max_buffer_mode(esplusplayer_handle handle) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + auto ret = cast_(handle)->SetUnlimitedMaxBufferMode(); + return convert_return_type_(ret); +} + +int esplusplayer_set_fmm_mode(esplusplayer_handle handle) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + auto ret = cast_(handle)->SetFmmMode(); + return convert_return_type_(ret); +} + +int esplusplayer_set_audio_codec_type(esplusplayer_handle handle, + esplusplayer_audio_codec_type type) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + auto ret = + cast_(handle)->SetAudioCodecType(static_cast(type)); + return convert_return_type_(ret); +} + +int esplusplayer_set_aifilter(esplusplayer_handle handle, void* aifilter) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle) || is_null_(aifilter)) + return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + auto ret = cast_(handle)->SetAiFilter(aifilter); + return convert_return_type_(ret); +} + +int esplusplayer_set_video_codec_type(esplusplayer_handle handle, + esplusplayer_video_codec_type type) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + auto ret = + cast_(handle)->SetVideoCodecType(static_cast(type)); + return convert_return_type_(ret); +} + +int esplusplayer_set_alternative_video_resource(esplusplayer_handle handle, + unsigned int rsc_type) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + auto ret = cast_(handle)->SetAlternativeVideoResource(rsc_type); + return convert_return_type_(ret); +} + +int esplusplayer_set_render_time_offset(esplusplayer_handle handle, + esplusplayer_stream_type type, + int64_t offset) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + auto ret = + cast_(handle)->SetRenderTimeOffset(static_cast(type), offset); + return convert_return_type_(ret); +} + +int esplusplayer_get_render_time_offset(esplusplayer_handle handle, + esplusplayer_stream_type type, + int64_t* offset) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle) || is_null_(offset)) + return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + auto ret = + cast_(handle)->GetRenderTimeOffset(static_cast(type), offset); + return convert_return_type_(ret); +} + +int esplusplayer_switch_audio_stream_onthefly( + esplusplayer_handle handle, esplusplayer_audio_stream_info* info) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle) || is_null_(info)) + return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + + auto stream = convert_stream_ptr_(info); + auto ret = cast_(handle)->SwitchAudioStreamOnTheFly(std::move(stream)); + return convert_return_type_(ret); +} + +int esplusplayer_init_audio_easing_info( + esplusplayer_handle handle, uint32_t init_volume, uint32_t elapsed_time, + const esplusplayer_target_audio_easing_info* easing_info) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + if (easing_info == nullptr) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + + AudioEasingInfo info; + info.target_volume = easing_info->volume; + info.duration = easing_info->duration; + info.type = static_cast(easing_info->type); + auto ret = + cast_(handle)->InitAudioEasingInfo(init_volume, elapsed_time, info); + return convert_return_type_(ret); +} + +int esplusplayer_update_audio_easing_info( + esplusplayer_handle handle, + const esplusplayer_target_audio_easing_info* easing_info) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + if (easing_info == nullptr) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + + AudioEasingInfo info; + info.target_volume = easing_info->volume; + info.duration = easing_info->duration; + info.type = static_cast(easing_info->type); + + auto ret = cast_(handle)->UpdateAudioEasingInfo(info); + return convert_return_type_(ret); +} + +int esplusplayer_get_audio_easing_info( + esplusplayer_handle handle, uint32_t* current_volume, + uint32_t* elapsed_time, + esplusplayer_target_audio_easing_info* easing_info) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + if (is_null_(current_volume) || is_null_(elapsed_time) || + is_null_(easing_info)) + return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + + AudioEasingInfo info; + auto ret = + cast_(handle)->GetAudioEasingInfo(current_volume, elapsed_time, &info); + easing_info->volume = info.target_volume; + easing_info->duration = info.duration; + easing_info->type = static_cast(info.type); + + return convert_return_type_(ret); +} + +int esplusplayer_start_audio_easing(esplusplayer_handle handle) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + auto ret = cast_(handle)->StartAudioEasing(); + return convert_return_type_(ret); +} + +int esplusplayer_stop_audio_easing(esplusplayer_handle handle) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + auto ret = cast_(handle)->StopAudioEasing(); + return convert_return_type_(ret); +} + +int get_size_of_esplusplayer_app_info(void) { + return sizeof(esplusplayer_app_info); +} + +int get_size_of_esplusplayer_es_packet(void) { + return sizeof(esplusplayer_es_packet); +} + +int get_size_of_esplusplayer_es_tz_packet(void) { + return sizeof(esplusplayer_es_tz_packet); +} + +int get_size_of_esplusplayer_audio_stream_info(void) { + return sizeof(esplusplayer_audio_stream_info); +} + +int get_size_of_esplusplayer_video_stream_info(void) { + return sizeof(esplusplayer_video_stream_info); +} + +int get_size_of_esplusplayer_drm_info(void) { + return sizeof(esplusplayer_drm_info); +} +int esplusplayer_set_catch_up_speed(esplusplayer_handle handle, + esplusplayer_catch_up_speed level) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + if (level < ESPLUSPLAYER_CATCH_UP_SPEED_NONE || + level > ESPLUSPLAYER_CATCH_UP_SPEED_FAST) { + return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + } + auto ret = cast_(handle)->SetCatchUpSpeed(static_cast(level)); + return convert_return_type_(ret); +} + +int esplusplayer_get_video_latency_status(esplusplayer_handle handle, + esplusplayer_latency_status* status) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle) || is_null_(status)) + return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + + LatencyStatus current_status = LatencyStatus::kLow; + auto ret = cast_(handle)->GetVideoLatencyStatus(¤t_status); + *status = static_cast(current_status); + + return convert_return_type_(ret); +} + +int esplusplayer_get_audio_latency_status(esplusplayer_handle handle, + esplusplayer_latency_status* status) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle) || is_null_(status)) + return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + + LatencyStatus current_status = LatencyStatus::kLow; + auto ret = cast_(handle)->GetAudioLatencyStatus(¤t_status); + *status = static_cast(current_status); + + return convert_return_type_(ret); +} + +int esplusplayer_set_video_mid_latency_threshold(esplusplayer_handle handle, + const unsigned int threshold) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + + auto ret = cast_(handle)->SetVideoMidLatencyThreshold(threshold); + return convert_return_type_(ret); +} + +int esplusplayer_set_audio_mid_latency_threshold(esplusplayer_handle handle, + const unsigned int threshold) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + + auto ret = cast_(handle)->SetAudioMidLatencyThreshold(threshold); + return convert_return_type_(ret); +} + +int esplusplayer_set_video_high_latency_threshold( + esplusplayer_handle handle, const unsigned int threshold, + esplusplayer_video_high_latency_cb video_high_latency_cb, void* userdata) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + + esplusplayer_set_video_high_latency_cb(handle, video_high_latency_cb, + userdata); + + auto ret = cast_(handle)->SetVideoHighLatencyThreshold(threshold); + return convert_return_type_(ret); +} + +int esplusplayer_set_audio_high_latency_threshold( + esplusplayer_handle handle, const unsigned int threshold, + esplusplayer_audio_high_latency_cb audio_high_latency_cb, void* userdata) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + + esplusplayer_set_audio_high_latency_cb(handle, audio_high_latency_cb, + userdata); + + auto ret = cast_(handle)->SetAudioHighLatencyThreshold(threshold); + return convert_return_type_(ret); +} + +int esplusplayer_get_virtual_rsc_id(esplusplayer_handle handle, + const esplusplayer_rsc_type type, + int* virtual_id) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle) || is_null_(virtual_id)) + return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + + auto ret = + cast_(handle)->GetVirtualRscId(static_cast(type), virtual_id); + return convert_return_type_(ret); +} + +int esplusplayer_set_advanced_picture_quality_type( + esplusplayer_handle handle, + esplusplayer_advanced_picture_quality_type type) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + + auto ret = cast_(handle)->SetAdvancedPictureQualityType( + static_cast(type)); + return convert_return_type_(ret); +} + +int esplusplayer_set_resource_allocate_policy( + esplusplayer_handle handle, esplusplayer_rsc_alloc_policy policy) { + LOG_ENTER_P(cast_(handle)) + if (is_null_(handle)) return ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER; + LOG_INFO("policy: %d", static_cast(policy)); + + auto ret = cast_(handle)->SetResourceAllocatePolicy( + static_cast(policy)); + return convert_return_type_(ret); +} diff --git a/src/mixer/CMakeLists.txt b/src/mixer/CMakeLists.txt new file mode 100755 index 0000000..85c8dee --- /dev/null +++ b/src/mixer/CMakeLists.txt @@ -0,0 +1,60 @@ +PROJECT(mixer) + +SET(fw_name "${PROJECT_NAME}") +SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib) +SET(${fw_name}_LDFLAGS) + +SET(${fw_name}_CXXFLAGS "-Wall -Werror -std=c++11 -fPIC -Wl,-z,relro -fstack-protector") + +SET(dependents "dlog boost gstreamer-1.0 libtbm graphics-control") + +INCLUDE(FindPkgConfig) + +IF(CMAKE_SYSTEM_PROCESSOR STREQUAL armv7l) +pkg_check_modules(${fw_name} REQUIRED ${dependents}) +ELSE(CMAKE_SYSTEM_PROCESSOR STREQUAL armv7l) +pkg_check_modules(${fw_name} REQUIRED ${dependents}) +ENDIF(CMAKE_SYSTEM_PROCESSOR STREQUAL armv7l) + +FOREACH(flag ${${fw_name}_CFLAGS}) +SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${flag}") +ENDFOREACH(flag) + +FOREACH(flag ${${fw_name}_CXXFLAGS}) +SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_C_FLAGS} ${flag}") +ENDFOREACH(flag) + +GET_FILENAME_COMPONENT(PARENT_DIR ${PROJECT_SOURCE_DIR} DIRECTORY) +INCLUDE_DIRECTORIES( + ${PROJECT_SOURCE_DIR}/include_internal + ${PARENT_DIR}/plusplayer-core/include_internal +) + +SET(CC_SRCS + ${PROJECT_SOURCE_DIR}/src/mixer_capi.cpp + ${PROJECT_SOURCE_DIR}/src/mixer.cpp + ${PROJECT_SOURCE_DIR}/src/defaultmixer.cpp + ${PROJECT_SOURCE_DIR}/src/sys/tbminterface.cpp + ${PROJECT_SOURCE_DIR}/src/tizen/tizenaccessiblebufferobj.cpp + ${PROJECT_SOURCE_DIR}/src/tizen/tizenbufferkeyvideoframe.cpp + ${PROJECT_SOURCE_DIR}/src/tizen/tizendefaultphyaddraccessor.cpp + ${PROJECT_SOURCE_DIR}/src/tizen/tizenhwbufferobj.cpp + ${PROJECT_SOURCE_DIR}/src/tizen/tizenhwvideoframe.cpp + ${PROJECT_SOURCE_DIR}/src/tizen/tizenrenderableobj_factory.cpp + ${PROJECT_SOURCE_DIR}/src/tizen/tizensurfacevideoframe.cpp + ${PROJECT_SOURCE_DIR}/src/abs_videoframe.cpp + ${PROJECT_SOURCE_DIR}/src/mixedframe.cpp + ${PROJECT_SOURCE_DIR}/src/renderer.cpp + ${PROJECT_SOURCE_DIR}/src/videoplane.cpp +) + +ADD_LIBRARY(${fw_name} SHARED ${CC_SRCS}) + +SET_TARGET_PROPERTIES(${fw_name} PROPERTIES LINKER_LANGUAGE CXX) + +TARGET_LINK_LIBRARIES(${fw_name} ${CMAKE_THREAD_LIBS_INIT} ${${fw_name}_LDFLAGS}) + +INSTALL(TARGETS ${fw_name} DESTINATION ${LIB_INSTALL_DIR}) +INSTALL( + DIRECTORY ${INC_DIR}/ DESTINATION include/ +) diff --git a/src/mixer/include_internal/mixer/abs_videoframe.h b/src/mixer/include_internal/mixer/abs_videoframe.h new file mode 100755 index 0000000..1cedf89 --- /dev/null +++ b/src/mixer/include_internal/mixer/abs_videoframe.h @@ -0,0 +1,41 @@ +#ifndef __PLUSPLAYER_ABSTRACT_MIXER_VIDEO_FRAME_H__ +#define __PLUSPLAYER_ABSTRACT_MIXER_VIDEO_FRAME_H__ + +#include + +#include "mixer/interfaces/videoplanecollection.h" +#include "mixer/interfaces/videoplanemanipulable.h" + +namespace plusplayer { + +class AbstractVideoFrame : public VideoPlaneCollection { + public: + using VideoPlaneManipulablePtr = std::unique_ptr; + + public: + explicit AbstractVideoFrame() = default; + virtual ~AbstractVideoFrame() = default; + + public: + virtual const std::vector GetVideoPlaneManipInfo() + const override; + + bool IsValid() const; + bool SetCropArea(const CropArea& croparea); + const std::uint32_t GetWidth() const; + const std::uint32_t GetHeight() const; + + protected: + virtual bool IsValid_() const = 0; + virtual const std::uint32_t GetWidth_() const = 0; + virtual const std::uint32_t GetHeight_() const = 0; + + protected: + void RegisterVideoPlaneManipulablePtr_(VideoPlaneManipulablePtr vpmanip); + + private: + std::vector planes_; +}; +} // namespace plusplayer + +#endif \ No newline at end of file diff --git a/src/mixer/include_internal/mixer/defaultmixer.h b/src/mixer/include_internal/mixer/defaultmixer.h new file mode 100755 index 0000000..364091d --- /dev/null +++ b/src/mixer/include_internal/mixer/defaultmixer.h @@ -0,0 +1,146 @@ +// +// @ Copyright [2020] +// + +#ifndef __PLUSPLAYER_SRC_PLAYER_DEFAULTMIXER__H__ +#define __PLUSPLAYER_SRC_PLAYER_DEFAULTMIXER__H__ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "core/utils/plusplayer_log.h" +#include "mixer/interfaces/videoplanecollection.h" +#include "mixer/mixer.h" +#include "mixer/mixerticket.h" +#include "mixer/renderer.h" +#include "mixer/tizen/tizenbuffermgr.h" + +namespace plusplayer { + +class DefaultMixer : public Mixer { + public: + using RendererPtr = std::unique_ptr; + + public: + DefaultMixer(); + ~DefaultMixer(); + + bool Start() override; + bool Stop() override; + int GetMaximumAllowedNumberOfPlayer() override; + bool SetDisplay(const DisplayType type, void* obj) override; + bool SetDisplay(const DisplayType type, const uint32_t surface_id, + const int x, const int y, const int w, const int h) override; + bool SetDisplayMode(const DisplayMode& mode) override; + bool SetDisplayRoi(const Geometry& geometry) override; + bool DisableAudioFocusSetting() override; + bool SetAlternativeVideoScaler() override; + bool SetAudioFocus(const void* player_instance) override; + bool SetRscAllocMode(const RscAllocMode& mode) override; + bool Commit() override; + bool SetResolution(const ResolutionInfo& info) override; + bool RegisterListener(MixerEventListener* listener) override; + MixerTicket* CreateTicket(const void* player_instance) override; + + class Ticket : public MixerTicket { + public: + explicit Ticket(DefaultMixer* handler, const void* player_instance) + : handler_(handler), player_instance_(player_instance) { + assert(handler); + } + ~Ticket(); + bool GetAvailableResourceType(const ResourceCategory& category, + ResourceType* type) override; + bool Alloc(const ResourceCategory& category, + const ResourceType& type) override; + bool Render(const DecodedRawInfo& info) override; + bool Render(const DecodedVideoKeyTypeInfo& info) override; + bool RegisterListener(MixerTicketEventListener* listener) override; + bool Prepare() override; + bool IsAudioFocusHandler() override; + bool IsRscAllocHandler() override; + + // interface for defaultmixer + MixerTicketEventListener* GetListener(); + bool IsAudioFocused(); + void SetAudioFocus(bool active); + void GetDisplayInfo(DisplayInfo* info); + bool HasRenderedBefore(); + void UpdateDisplayInfo(const DisplayInfo& info); + void DeallocResource(); + void RecordRenderingTime(); + + private: + DefaultMixer* handler_ = nullptr; + const void* player_instance_ = nullptr; + MixerTicketEventListener* ticket_listener_ = nullptr; + bool is_audio_focus_ = false; + DisplayInfo each_display_info_; + bool has_rendered_ = false; + std::chrono::system_clock::time_point last_rendering_time_; + }; + + private: + struct Resource { + ResourceCategory category = ResourceCategory::kVideoDecoder; + ResourceType type = ResourceType::kHwMain; + const void* assignee = nullptr; + }; + + class MixerRendererEventListener : public RendererEventListener { + public: + explicit MixerRendererEventListener(TrackRendererHandle* trhandle_ptr); + virtual ~MixerRendererEventListener(); + virtual bool OnRenderingRelease(const BufferKeyType& key) override; + + private: + void* CreateGstBuffer_(const BufferKeyType& key) const; + void FillDecoderInputBuffer_(TrackRendererDecoderInputBuffer& buffer, + const BufferKeyType& key) const; + + private: + TrackRendererHandle* trhandle_ptr_; + }; + + private: + bool Detach_(const void* player_instance); + bool GetAvailableResourceType_(); + bool Render_(const void* player_instance, + const VideoPlaneCollection& vplanes); + bool RegisterListener_(); + void InitResourceList_(); + bool IsNeededToSkipRendering_(const DisplayInfo& display_info); + + using UserData = void*; + static void ResourceConflictCb_(UserData userdata); + static void ErrorCb_(const TrackRendererErrorType error_code, + UserData userdata); + + private: + std::map player_map_; + std::mutex mixer_lock_; + std::mutex ticket_lock_; + bool is_started_ = false; + MixerEventListener* listener_ = nullptr; + TrackRendererHandle trackrenderer_handle_ = nullptr; + const void* audio_focused_player_ = nullptr; + ResolutionInfo whole_resolution_; + std::list resource_list_; + bool enable_audio_focus_setting_ = true; + RscAllocMode resource_allocation_mode_ = RscAllocMode::kDefault; + bool use_sub_scaler_ = false; + RendererPtr renderer_; + MixerRendererEventListener renderer_listener_; + TizenBufferManager bufmgr_; +}; + +} // namespace plusplayer + +#endif // __PLUSPLAYER_SRC_PLAYER_DEFAULTMIXER__H__ \ No newline at end of file diff --git a/src/mixer/include_internal/mixer/interfaces/accessiblebuffer.h b/src/mixer/include_internal/mixer/interfaces/accessiblebuffer.h new file mode 100755 index 0000000..aa4a9b8 --- /dev/null +++ b/src/mixer/include_internal/mixer/interfaces/accessiblebuffer.h @@ -0,0 +1,19 @@ +#ifndef __PLUSPLAYER_MIXER_INTERFACES_ACCESSIBLE_BUFFER_H__ +#define __PLUSPLAYER_MIXER_INTERFACES_ACCESSIBLE_BUFFER_H__ + +#include + +#include "mixer/interfaces/bufferobject.h" +#include "mixer/interfaces/phyaddraccessor.h" + +namespace plusplayer { +struct AccessibleBuffer { + using PhyAddrAccessorPtr = std::unique_ptr; + + virtual ~AccessibleBuffer() = default; + virtual PhyAddrAccessorPtr GetReadableAddress() const = 0; + virtual PhyAddrAccessorPtr GetWritableAddress() const = 0; +}; +} // namespace plusplayer + +#endif // __PLUSPLAYER_MIXER_INTERFACES_ACCESSIBLE_BUFFER_H__ \ No newline at end of file diff --git a/src/mixer/include_internal/mixer/interfaces/bufferobject.h b/src/mixer/include_internal/mixer/interfaces/bufferobject.h new file mode 100755 index 0000000..a08fbda --- /dev/null +++ b/src/mixer/include_internal/mixer/interfaces/bufferobject.h @@ -0,0 +1,16 @@ +#ifndef __PLUSPLAYER_MIXER_INTERFACES_BUFFER_OBJECT_H__ +#define __PLUSPLAYER_MIXER_INTERFACES_BUFFER_OBJECT_H__ + +#include "mixer/types/buffertype.h" + +namespace plusplayer { +struct BufferObject { + virtual ~BufferObject() = default; + + virtual BufferHandleType GetBufferHandle() const = 0; + virtual BufferKeyType Export() const = 0; + virtual std::uint32_t GetSize() const = 0; +}; +} // namespace plusplayer + +#endif // __PLUSPLAYER_MIXER_INTERFACES_BUFFER_OBJECT_H__ \ No newline at end of file diff --git a/src/mixer/include_internal/mixer/interfaces/memoryallocator.h b/src/mixer/include_internal/mixer/interfaces/memoryallocator.h new file mode 100755 index 0000000..7446a60 --- /dev/null +++ b/src/mixer/include_internal/mixer/interfaces/memoryallocator.h @@ -0,0 +1,17 @@ +#ifndef __PLUSPLAYER_MIXER_INTERFACES_MEMORY_OPERATOR_H__ +#define __PLUSPLAYER_MIXER_INTERFACES_MEMORY_OPERATOR_H__ + +#include +#include + +#include "mixer/interfaces/bufferobject.h" +#include "mixer/types/buffertype.h" + +namespace plusplayer { + +struct MemoryAllocator { + virtual ~MemoryAllocator() = default; + virtual BufferObject* Allocate(const std::uint32_t& size) const = 0; +}; +} // namespace plusplayer +#endif // __PLUSPLAYER_MIXER_INTERFACES_MEMORY_OPERATOR_H__ \ No newline at end of file diff --git a/src/mixer/include_internal/mixer/interfaces/phyaddraccessor.h b/src/mixer/include_internal/mixer/interfaces/phyaddraccessor.h new file mode 100755 index 0000000..d50593b --- /dev/null +++ b/src/mixer/include_internal/mixer/interfaces/phyaddraccessor.h @@ -0,0 +1,13 @@ +#ifndef __PLUSPLAYER_MIXER_INTERFACES_PHYSICAL_ADDRESS_ACCESSOR_H__ +#define __PLUSPLAYER_MIXER_INTERFACES_PHYSICAL_ADDRESS_ACCESSOR_H__ + +#include "mixer/types/buffertype.h" + +namespace plusplayer { +struct PhysicalAddressAccessor { + virtual ~PhysicalAddressAccessor() = default; + virtual BufferPhysicalAddrType GetAddress() = 0; +}; +} // namespace plusplayer + +#endif // __PLUSPLAYER_MIXER_INTERFACES_PHYSICAL_ADDRESS_ACCESSOR_H__ \ No newline at end of file diff --git a/src/mixer/include_internal/mixer/interfaces/renderableobj_factory.h b/src/mixer/include_internal/mixer/interfaces/renderableobj_factory.h new file mode 100755 index 0000000..ec7ea9b --- /dev/null +++ b/src/mixer/include_internal/mixer/interfaces/renderableobj_factory.h @@ -0,0 +1,14 @@ +#ifndef __PLUSPLAYER_MIXER_INTERFACES_RENDERABLE_OBJECT_FACTORY_H__ +#define __PLUSPLAYER_MIXER_INTERFACES_RENDERABLE_OBJECT_FACTORY_H__ + +#include "mixer/interfaces/renderableobject.h" + +namespace plusplayer { +struct RenderableObjectFactory { + virtual ~RenderableObjectFactory() = default; + virtual RenderableObject* CreateRenderableObject( + const std::uint32_t width, const std::uint32_t height) const = 0; +}; +} // namespace plusplayer + +#endif // __PLUSPLAYER_MIXER_INTERFACES_RENDERABLE_OBJECT_FACTORY_H__ \ No newline at end of file diff --git a/src/mixer/include_internal/mixer/interfaces/renderableobject.h b/src/mixer/include_internal/mixer/interfaces/renderableobject.h new file mode 100755 index 0000000..ccdb598 --- /dev/null +++ b/src/mixer/include_internal/mixer/interfaces/renderableobject.h @@ -0,0 +1,33 @@ +#ifndef __PLUSPLAYER_MIXER_INTERFACES_RENDERABLE_OBJECT_H__ +#define __PLUSPLAYER_MIXER_INTERFACES_RENDERABLE_OBJECT_H__ + +#include +#include +#include + +#include "mixer/interfaces/videoplanemanipulator.h" +#include "mixer/types/buffertype.h" +#include "mixer/types/planecomponent.h" +#include "mixer/types/videoplanemanipinfo.h" +#include "plusplayer/types/display.h" + +namespace plusplayer { +struct RenderableObject { + using Ptr = std::unique_ptr; + virtual ~RenderableObject() = default; + virtual bool IsValid() const = 0; + virtual std::uint32_t GetWidth() const = 0; + virtual std::uint32_t GetHeight() const = 0; + virtual std::uint32_t GetSize() const = 0; + virtual bool Render(const VideoPlaneManipulator* const vpmanip, + const std::vector& planes, + const Geometry& geom) = 0; + virtual bool Fill(const VideoPlaneColorManipulator* const vpmanip, + const PlaneComponent& comp, const std::uint32_t& color, + const Geometry& geom) = 0; + virtual bool Export(BufferKeyType& key) const = 0; +}; +using RenderableObjectPtr = RenderableObject::Ptr; +} // namespace plusplayer + +#endif // __PLUSPLAYER_MIXER_INTERFACES_RENDERABLE_OBJECT_H__ \ No newline at end of file diff --git a/src/mixer/include_internal/mixer/interfaces/videoplanecollection.h b/src/mixer/include_internal/mixer/interfaces/videoplanecollection.h new file mode 100755 index 0000000..e0bb422 --- /dev/null +++ b/src/mixer/include_internal/mixer/interfaces/videoplanecollection.h @@ -0,0 +1,16 @@ +#ifndef __PLUSPLAYER_MIXER_INTERFACES_VIDEO_PLANE_COLLECTION_H__ +#define __PLUSPLAYER_MIXER_INTERFACES_VIDEO_PLANE_COLLECTION_H__ + +#include + +#include "mixer/types/videoplanemanipinfo.h" + +namespace plusplayer { +struct VideoPlaneCollection { + virtual ~VideoPlaneCollection() = default; + virtual const std::vector GetVideoPlaneManipInfo() + const = 0; +}; +} // namespace plusplayer + +#endif // __PLUSPLAYER_MIXER_INTERFACES_VIDEO_PLANE_COLLECTION_H__ \ No newline at end of file diff --git a/src/mixer/include_internal/mixer/interfaces/videoplanecolorfiller.h b/src/mixer/include_internal/mixer/interfaces/videoplanecolorfiller.h new file mode 100755 index 0000000..b35b610 --- /dev/null +++ b/src/mixer/include_internal/mixer/interfaces/videoplanecolorfiller.h @@ -0,0 +1,17 @@ +#ifndef __PLUSPLAYER_MIXER_INTERFACES_VIDEO_PLANE_COLOR_FILLER_H__ +#define __PLUSPLAYER_MIXER_INTERFACES_VIDEO_PLANE_COLOR_FILLER_H__ + +#include + +#include "mixer/interfaces/videoplanemanipulator.h" +#include "mixer/types/planecomponent.h" + +namespace plusplayer { +struct VideoPlaneColorFiller { + virtual ~VideoPlaneColorFiller() = default; + virtual const VideoPlaneColorManipulator* const GetColorFillManipulator() + const = 0; +}; +} // namespace plusplayer + +#endif // __PLUSPLAYER_MIXER_INTERFACES_VIDEO_PLANE_COLOR_FILLER_H__ \ No newline at end of file diff --git a/src/mixer/include_internal/mixer/interfaces/videoplanecopier.h b/src/mixer/include_internal/mixer/interfaces/videoplanecopier.h new file mode 100755 index 0000000..b27dead --- /dev/null +++ b/src/mixer/include_internal/mixer/interfaces/videoplanecopier.h @@ -0,0 +1,13 @@ +#ifndef __PLUSPLAYER_MIXER_INTERFACES_VIDEO_PLANE_COPIER_H__ +#define __PLUSPLAYER_MIXER_INTERFACES_VIDEO_PLANE_COPIER_H__ + +#include "mixer/interfaces/videoplanemanipulator.h" + +namespace plusplayer { +struct VideoPlaneCopier { + virtual ~VideoPlaneCopier() = default; + virtual const VideoPlaneManipulator* const GetCopyManipulator() const = 0; +}; +} // namespace plusplayer + +#endif // __PLUSPLAYER_MIXER_INTERFACES_VIDEO_PLANE_COPIER_H__ \ No newline at end of file diff --git a/src/mixer/include_internal/mixer/interfaces/videoplanemanipulable.h b/src/mixer/include_internal/mixer/interfaces/videoplanemanipulable.h new file mode 100755 index 0000000..6bb20f0 --- /dev/null +++ b/src/mixer/include_internal/mixer/interfaces/videoplanemanipulable.h @@ -0,0 +1,18 @@ +#ifndef __PLUSPLAYER_MIXER_INTERFACES_VIDEO_PLANE_MANIPULABLE_H__ +#define __PLUSPLAYER_MIXER_INTERFACES_VIDEO_PLANE_MANIPULABLE_H__ + +#include "mixer/types/videoplanemanipinfo.h" +#include "plusplayer/types/display.h" + +namespace plusplayer { + +struct VideoPlaneManipulable { + virtual ~VideoPlaneManipulable() = default; + virtual bool IsValid() const = 0; + virtual VideoPlaneManipulableInfo GetVideoPlaneManipulableInfo() const = 0; + virtual void SetCropArea(const CropArea& croparea) = 0; +}; + +} // namespace plusplayer + +#endif //__PLUSPLAYER_MIXER_INTERFACES_VIDEO_PLANE_MANIPULABLE_H__ diff --git a/src/mixer/include_internal/mixer/interfaces/videoplanemanipulator.h b/src/mixer/include_internal/mixer/interfaces/videoplanemanipulator.h new file mode 100755 index 0000000..3ddc760 --- /dev/null +++ b/src/mixer/include_internal/mixer/interfaces/videoplanemanipulator.h @@ -0,0 +1,22 @@ +#ifndef __PLUSPLAYER_MIXER_INTERFACES_VIDEO_PLANE_MANIPULATOR_H__ +#define __PLUSPLAYER_MIXER_INTERFACES_VIDEO_PLANE_MANIPULATOR_H__ + +#include + +#include "mixer/types/planecomponent.h" +#include "mixer/types/videoplanemanipinfo.h" + +namespace plusplayer { + +template +struct VideoPlaneManipulatorWithType { + virtual bool Do(const T&, const VideoPlaneManipulableInfo&) const = 0; +}; + +using VideoPlaneManipulator = + VideoPlaneManipulatorWithType; + +using VideoPlaneColorManipulator = VideoPlaneManipulatorWithType; +} // namespace plusplayer + +#endif // __PLUSPLAYER_MIXER_INTERFACES_VIDEO_PLANE_MANIPULATOR_H__ \ No newline at end of file diff --git a/src/mixer/include_internal/mixer/interfaces/videoplanescaler.h b/src/mixer/include_internal/mixer/interfaces/videoplanescaler.h new file mode 100755 index 0000000..c1aae57 --- /dev/null +++ b/src/mixer/include_internal/mixer/interfaces/videoplanescaler.h @@ -0,0 +1,13 @@ +#ifndef __PLUSPLAYER_MIXER_INTERFACES_VIDEO_PLANE_SCALER_H__ +#define __PLUSPLAYER_MIXER_INTERFACES_VIDEO_PLANE_SCALER_H__ + +#include "mixer/interfaces/videoplanemanipulator.h" + +namespace plusplayer { +struct VideoPlaneScaler { + virtual ~VideoPlaneScaler() = default; + virtual const VideoPlaneManipulator* const GetScaleManipulator() const = 0; +}; +} // namespace plusplayer + +#endif // __PLUSPLAYER_MIXER_INTERFACES_VIDEO_PLANE_SCALER_H__ \ No newline at end of file diff --git a/src/mixer/include_internal/mixer/mixedframe.h b/src/mixer/include_internal/mixer/mixedframe.h new file mode 100755 index 0000000..a2e6c3c --- /dev/null +++ b/src/mixer/include_internal/mixer/mixedframe.h @@ -0,0 +1,63 @@ +#ifndef __PLUSPLAYER_MIXER_MIXED_FRAME_H__ +#define __PLUSPLAYER_MIXER_MIXED_FRAME_H__ + +#include +#include + +#include "mixer/interfaces/memoryallocator.h" +#include "mixer/interfaces/renderableobject.h" +#include "mixer/interfaces/videoplanecollection.h" +#include "mixer/interfaces/videoplanemanipulator.h" +#include "plusplayer/types/display.h" + +namespace plusplayer { +class MixedFrame : public VideoPlaneCollection, public RenderableObject { + public: + using Ptr = std::unique_ptr; + using BufferObjectPtr = std::unique_ptr; + + public: + static Ptr Create(const MemoryAllocator* const memop, + const std::uint32_t width, const std::uint32_t height); + + public: + explicit MixedFrame(const MemoryAllocator* const memop, + const std::uint32_t width, const std::uint32_t height); + virtual ~MixedFrame() = default; + + public: + virtual const std::vector GetVideoPlaneManipInfo() + const override; + + public: + virtual bool IsValid() const override; + virtual std::uint32_t GetWidth() const override; + virtual std::uint32_t GetHeight() const override; + virtual std::uint32_t GetSize() const override; + virtual bool Render(const VideoPlaneManipulator* const vpmanip, + const std::vector& planes, + const Geometry& geom) override; + virtual bool Fill(const VideoPlaneColorManipulator* const vpmanip, + const PlaneComponent& comp, const std::uint32_t& color, + const Geometry& geom) override; + virtual bool Export(BufferKeyType& key) const override; + + private: + std::uint32_t CalculateBufferSize_(const std::uint32_t width, + const std::uint32_t height); + VideoPlaneManipulableInfo GetMixedFrameVideoPlaneManipulableInfo_( + const PlaneComponent component, const Geometry& geom); + VideoPlaneManipulableInfo GetYComponentVMInfo_(BufferHandleType handle) const; + VideoPlaneManipulableInfo GetUVComponentVMInfo_( + BufferHandleType handle) const; + + private: + const std::uint32_t width_ = 0; + const std::uint32_t height_ = 0; + mutable std::uint32_t allocated_size_ = 0; + BufferObjectPtr buffer_ = nullptr; +}; +using MixedFramePtr = MixedFrame::Ptr; +} // namespace plusplayer + +#endif // __PLUSPLAYER_MIXER_MIXED_FRAME_H__ \ No newline at end of file diff --git a/src/mixer/include_internal/mixer/renderer.h b/src/mixer/include_internal/mixer/renderer.h new file mode 100755 index 0000000..cc326b1 --- /dev/null +++ b/src/mixer/include_internal/mixer/renderer.h @@ -0,0 +1,94 @@ +#ifndef __PLUSPLAYER_MIXER_RENDERER_H__ +#define __PLUSPLAYER_MIXER_RENDERER_H__ + +#include +#include +#include +#include +#include +#include + +#include "mixer/interfaces/renderableobj_factory.h" +#include "mixer/interfaces/videoplanecollection.h" +#include "mixer/interfaces/videoplanecolorfiller.h" +#include "mixer/interfaces/videoplanescaler.h" +#include "mixer/mixer.h" +#include "mixer/types/buffertype.h" +#include "mixer/types/videoplanemoveinfo.h" + +namespace plusplayer { + +struct RendererEventListener { + virtual bool OnRenderingRelease(const BufferKeyType&) = 0; +}; + +class Renderer { + private: + using JitterType = std::chrono::duration; + + public: + explicit Renderer(const RenderableObjectFactory& mf_factory, + const Mixer::ResolutionInfo& rinfo, + RendererEventListener* listener); + virtual ~Renderer(); + + public: + bool IsValid() const; + bool Start(); + bool Stop(); + bool ChangeResolution(const RenderableObjectFactory& mf_factory, + const std::uint32_t& width, + const std::uint32_t& height); + bool ChangeRenderingSpeed(const std::uint32_t framerate_num, + const std::uint32_t framerate_den); + + bool Render(const VideoPlaneScaler* const scaler, + const VideoPlaneCollection* const planes, const Geometry& geom); + bool Mute(const VideoPlaneColorFiller* const filler, const Geometry& geom); + bool Move(const VideoPlaneColorFiller* const filler, + const std::vector& moving_planes); + + protected: + virtual bool ChangeResolutionInternal_( + const RenderableObjectFactory& mf_factory, const std::uint32_t& width, + const std::uint32_t& height); + virtual bool RenderInternal_( + const VideoPlaneManipulator* const vpmanip, + const std::vector& planes, + const Geometry& geom); + virtual bool MuteInternal_(const VideoPlaneColorManipulator* const filler, + const Geometry& geom); + virtual bool MoveInternal_( + const VideoPlaneColorManipulator* const filler, + const std::vector& moving_planes); + virtual bool OnRenderingBefore_(const RenderableObject* const frame); + virtual bool IsValid_() const; + + std::uint32_t GetNextRenderingTimeWithJitter_(const JitterType& jitter) const; + const Mixer::ResolutionInfo& GetResolutionInfo_() const; + RenderableObjectPtr& GetMixedFrame_(); + void AcquireRenderingLock_(); + void ReleaseRenderingLock_(); + + private: + bool RaiseOnRenderingReleaseEvent_(const BufferKeyType& key); + void RenderingWorker_(); + + protected: + static Geometry MakeGeometry_(const std::uint32_t& width, + const std::uint32_t& height); + static bool IsSameGeometry_(const Geometry& g1, const Geometry& g2); + + private: + Mixer::ResolutionInfo resolution_info_; + RendererEventListener* listener_ = nullptr; + RenderableObjectPtr frame_ = nullptr; + + bool rendering_flag_ = false; + std::mutex rendering_mtx_; + std::condition_variable rendering_cv_; + std::thread rendering_worker_; +}; +} // namespace plusplayer + +#endif // __PLUSPLAYER_MIXER_RENDERER_H__ \ No newline at end of file diff --git a/src/mixer/include_internal/mixer/rendererwithdoublebuf.h b/src/mixer/include_internal/mixer/rendererwithdoublebuf.h new file mode 100755 index 0000000..b6597ff --- /dev/null +++ b/src/mixer/include_internal/mixer/rendererwithdoublebuf.h @@ -0,0 +1,151 @@ +#ifndef __PLUSPLAYER_MIXER_RENDERER_WITH_DOUBLE_BUFFERING_H__ +#define __PLUSPLAYER_MIXER_RENDERER_WITH_DOUBLE_BUFFERING_H__ + +#include +#include + +#include "core/utils/plusplayer_log.h" +#include "mixer/interfaces/videoplanecopier.h" +#include "mixer/interfaces/videoplanescaler.h" +#include "mixer/mixedframe.h" +#include "mixer/renderer.h" + +namespace plusplayer { + +class RendererWithDoubleBuffer : public Renderer { + public: + explicit RendererWithDoubleBuffer(const RenderableObjectFactory& mf_factory, + const VideoPlaneCopier* const vpcopier, + const VideoPlaneScaler* const vpscaler, + const Mixer::ResolutionInfo& rinfo, + RendererEventListener* listener) + : Renderer(mf_factory, rinfo, listener), + frame_(MixedFramePtr(dynamic_cast( + mf_factory.CreateRenderableObject(rinfo.width, rinfo.height)))), + vpcopier_(vpcopier), + vpscaler_(vpscaler) {} + virtual ~RendererWithDoubleBuffer() = default; + + protected: + virtual bool OnRenderingBefore_( + const RenderableObject* const frame) override { + // auto before = std::chrono::system_clock::now(); + std::unique_lock lk(rendering_mtx_); + if (IsValid() == false) return false; + // LOG_INFO("[PERF] OnRenderingBefore_ Lock [%llu]ms", + // std::chrono::duration_cast( + // std::chrono::system_clock::now() - before) + // .count()); + const auto& rinfo = GetResolutionInfo_(); + auto ret = GetMixedFrame_()->Render( + vpcopier_->GetCopyManipulator(), frame_->GetVideoPlaneManipInfo(), + MakeGeometry_(rinfo.width, rinfo.height)); + // LOG_INFO("[PERF] Copy [%llu]ms", + // std::chrono::duration_cast( + // std::chrono::system_clock::now() - before) + // .count()); + return ret; + } + + virtual bool ChangeResolutionInternal_( + const RenderableObjectFactory& mf_factory, const std::uint32_t& width, + const std::uint32_t& height) override { + { + std::unique_lock lk(rendering_mtx_); + frame_.reset(nullptr); + frame_ = MixedFramePtr(dynamic_cast( + mf_factory.CreateRenderableObject(width, height))); + } + return Renderer::ChangeResolutionInternal_(mf_factory, width, height); + } + + virtual bool RenderInternal_( + const VideoPlaneManipulator* const scaler, + const std::vector& planes, + const Geometry& geom) override { + if (scaler == nullptr) return false; + auto before = std::chrono::system_clock::now(); + std::unique_lock lk(rendering_mtx_); + if (IsValid() == false) return false; + auto ret = frame_->Render(scaler, planes, geom); + LOG_INFO("[PERF] Scale [%llu]ms", + std::chrono::duration_cast( + std::chrono::system_clock::now() - before) + .count()); + return ret; + } + + virtual bool MuteInternal_(const VideoPlaneColorManipulator* const filler, + const Geometry& geom) override { + if (filler == nullptr) return false; + std::unique_lock lk(rendering_mtx_); + if (IsValid() == false) return false; + bool ret = true; + LOG_INFO("MUTE REGION (%d, %d, %d x %d)", geom.x, geom.y, geom.w, geom.h); + ret &= frame_->Fill(filler, PlaneComponent::kYComponent, 0x00, geom); + ret &= frame_->Fill(filler, PlaneComponent::kUVComponent, 0x8080, geom); + return ret; + } + + virtual bool MoveInternal_( + const VideoPlaneColorManipulator* const filler, + const std::vector& moving_planes) override { + if (filler == nullptr) return false; + + AcquireRenderingLock_(); + BOOST_SCOPE_EXIT(this_) { this_->ReleaseRenderingLock_(); } + BOOST_SCOPE_EXIT_END + + std::unique_lock lk(rendering_mtx_); + if (IsValid() == false) return false; + + auto* mixedframe = dynamic_cast(GetMixedFrame_().get()); + if (mixedframe == nullptr) return false; + + const auto planes = mixedframe->GetVideoPlaneManipInfo(); + if (planes.size() != 2) return false; + + const auto& rinfo = GetResolutionInfo_(); + + auto fullgeom = MakeGeometry_(rinfo.width, rinfo.height); + bool ret = true; + ret &= frame_->Fill(filler, PlaneComponent::kYComponent, 0x00, fullgeom); + ret &= frame_->Fill(filler, PlaneComponent::kUVComponent, 0x8080, fullgeom); + auto* scaler = vpscaler_->GetScaleManipulator(); + for (const auto& move : moving_planes) { + VideoPlaneManipulableInfo y_manipinfo = planes[0]; + VideoPlaneManipulableInfo uv_manipinfo = planes[1]; + y_manipinfo.rect = move.cur; + uv_manipinfo.rect = move.cur; + uv_manipinfo.rect.x /= 2; + uv_manipinfo.rect.y /= 2; + uv_manipinfo.rect.w /= 2; + uv_manipinfo.rect.h /= 2; + uv_manipinfo.rect.y += rinfo.height; + LOG_INFO("MOVE (%d, %d, %d x %d) -> (%d, %d, %d x %d)", move.cur.x, + move.cur.y, move.cur.w, move.cur.h, move.target.x, move.target.y, + move.target.w, move.target.h); + ret &= frame_->Render(scaler, {y_manipinfo, uv_manipinfo}, move.target); + } + return true; + }; + + virtual bool IsValid_() const override { + if (frame_ == nullptr) return false; + if (frame_->IsValid() == false) return false; + if (vpcopier_ == nullptr || vpscaler_ == nullptr) return false; + if (vpcopier_->GetCopyManipulator() == nullptr) return false; + if (vpscaler_->GetScaleManipulator() == nullptr) return false; + return true; + } + + private: + MixedFramePtr frame_ = nullptr; + const VideoPlaneCopier* const vpcopier_ = nullptr; + const VideoPlaneScaler* const vpscaler_ = nullptr; + + std::mutex rendering_mtx_; +}; +} // namespace plusplayer + +#endif // __PLUSPLAYER_MIXER_RENDERER_WITH_DOUBLE_BUFFERING_H__ \ No newline at end of file diff --git a/src/mixer/include_internal/mixer/sys/tbminterface.h b/src/mixer/include_internal/mixer/sys/tbminterface.h new file mode 100755 index 0000000..2273a63 --- /dev/null +++ b/src/mixer/include_internal/mixer/sys/tbminterface.h @@ -0,0 +1,29 @@ +#ifndef __PLUSPLAYER_MIXER_SYS_TBM_INTERFACE_H__ +#define __PLUSPLAYER_MIXER_SYS_TBM_INTERFACE_H__ + +#include + +#include "mixer/types/buffertype.h" + +namespace plusplayer { +namespace tizen { +class TBMInterface { + public: + static BufferDefaultType BoRef(BufferDefaultType bo); + static void BoUnRef(BufferDefaultType bo); + static int BoSize(BufferDefaultType bo); + static BufferUnionHandleType BoGetHandle(BufferDefaultType bo, int device); + static BufferUnionHandleType BoMap(BufferDefaultType bo, int device, + int option); + static void BoUnmap(BufferDefaultType bo); + static BufferKeyType BoExport(BufferDefaultType bo); + static BufferDefaultType BoAlloc(tbm_bufmgr bufmgr, int size, int flag); + static BufferDefaultType BoImport(tbm_bufmgr bufmgr, BufferKeyType key); + static int GAScale(tbm_bufmgr bufmgr, GraphicsGAScaleInfo* info); + static int GACopy(tbm_bufmgr bufmgr, GraphicsGABltRopInfo* info); + static int GAFill(tbm_bufmgr bufmgr, GraphicsGAFillRectInfo* info); +}; +} // namespace tizen +} // namespace plusplayer + +#endif //__PLUSPLAYER_MIXER_SYS_TBM_INTERFACE_H__ \ No newline at end of file diff --git a/src/mixer/include_internal/mixer/tizen/tizenaccessiblebufferobj.h b/src/mixer/include_internal/mixer/tizen/tizenaccessiblebufferobj.h new file mode 100755 index 0000000..84d9d5d --- /dev/null +++ b/src/mixer/include_internal/mixer/tizen/tizenaccessiblebufferobj.h @@ -0,0 +1,19 @@ +#ifndef __PLUSPLAYER_MIXER_TIZEN_TIZEN_ACCESSIBLE_BUFFER_OBJECT_H__ +#define __PLUSPLAYER_MIXER_TIZEN_TIZEN_ACCESSIBLE_BUFFER_OBJECT_H__ + +#include "mixer/interfaces/accessiblebuffer.h" +#include "mixer/tizen/tizenbufferobj.h" +#include "mixer/tizen/tizenbufferphyaddraccessor.h" + +namespace plusplayer { +class TizenAccessibleBufferObject : public TizenBufferObject, + public AccessibleBuffer { + public: + explicit TizenAccessibleBufferObject(BufferDefaultType buffer); + virtual ~TizenAccessibleBufferObject() = default; + virtual PhyAddrAccessorPtr GetReadableAddress() const override; + virtual PhyAddrAccessorPtr GetWritableAddress() const override; +}; +} // namespace plusplayer + +#endif // __PLUSPLAYER_MIXER_TIZEN_TIZEN_ACCESSIBLE_BUFFER_OBJECT_H__ \ No newline at end of file diff --git a/src/mixer/include_internal/mixer/tizen/tizenbufferkeyvideoframe.h b/src/mixer/include_internal/mixer/tizen/tizenbufferkeyvideoframe.h new file mode 100755 index 0000000..f9f5f56 --- /dev/null +++ b/src/mixer/include_internal/mixer/tizen/tizenbufferkeyvideoframe.h @@ -0,0 +1,36 @@ +#ifndef __PLUSPLAYER_MIXER_TIZEN_BUFFER_KEY_VIDEO_SURFACE_H__ +#define __PLUSPLAYER_MIXER_TIZEN_BUFFER_KEY_VIDEO_SURFACE_H__ + +#include + +#include "mixer/abs_videoframe.h" +#include "mixer/interfaces/bufferobject.h" +#include "mixer/tizen/tizenbuffermgr.h" +#include "mixer/videoplane.h" + +namespace plusplayer { + +class TizenBufferKeyVideoFrame : public AbstractVideoFrame { + public: + using BufferObjectPtr = std::shared_ptr; + + public: + explicit TizenBufferKeyVideoFrame(const TizenBufferManager* const bufmgr, + const BufferKeyType& key, + const std::uint32_t& width, + const std::uint32_t& height); + virtual ~TizenBufferKeyVideoFrame() = default; + + protected: + virtual bool IsValid_() const override; + virtual const std::uint32_t GetWidth_() const override; + virtual const std::uint32_t GetHeight_() const override; + + private: + const BufferKeyType key_; + const std::uint32_t width_; + const std::uint32_t height_; +}; +} // namespace plusplayer + +#endif // __PLUSPLAYER_MIXER_TIZEN_BUFFER_KEY_VIDEO_SURFACE_H__ \ No newline at end of file diff --git a/src/mixer/include_internal/mixer/tizen/tizenbuffermgr.h b/src/mixer/include_internal/mixer/tizen/tizenbuffermgr.h new file mode 100755 index 0000000..855112c --- /dev/null +++ b/src/mixer/include_internal/mixer/tizen/tizenbuffermgr.h @@ -0,0 +1,289 @@ +#ifndef __PLUSPLAYER_MIXER_TIZEN_BUFFER_MANAGER_H__ +#define __PLUSPLAYER_MIXER_TIZEN_BUFFER_MANAGER_H__ + +#include + +#include "mixer/interfaces/memoryallocator.h" +#include "mixer/interfaces/videoplanecolorfiller.h" +#include "mixer/interfaces/videoplanecopier.h" +#include "mixer/interfaces/videoplanemanipulator.h" +#include "mixer/interfaces/videoplanescaler.h" +#include "mixer/sys/tbminterface.h" +#include "mixer/tizen/tizenaccessiblebufferobj.h" +#include "mixer/types/videoplanemanipinfo.h" + +namespace { +struct GlobalInstance { + tbm_bufmgr bufmgr; + GlobalInstance() { bufmgr = tbm_bufmgr_init(-1); } + ~GlobalInstance() { + if (bufmgr) tbm_bufmgr_deinit(bufmgr); + } +}; +static const GlobalInstance kGlobalInstance; +} // namespace + +namespace plusplayer { +template +class BufferManagerWithType : public MemoryAllocator, + public VideoPlaneScaler, + public VideoPlaneCopier, + public VideoPlaneColorFiller { + public: + class Scaler : public VideoPlaneManipulator { + public: + explicit Scaler(const BufferManagerWithType* const bufmgr); + virtual ~Scaler() = default; + virtual bool Do(const VideoPlaneManipulableInfo& src, + const VideoPlaneManipulableInfo& dest) const override; + + private: + const BufferManagerWithType* const bufmgr_; + }; + class Copier : public VideoPlaneManipulator { + public: + explicit Copier(const BufferManagerWithType* const bufmgr); + virtual ~Copier() = default; + virtual bool Do(const VideoPlaneManipulableInfo& src, + const VideoPlaneManipulableInfo& dest) const override; + + private: + const BufferManagerWithType* const bufmgr_; + }; + class ColorFiller : public VideoPlaneColorManipulator { + public: + explicit ColorFiller(const BufferManagerWithType* const bufmgr); + virtual ~ColorFiller() = default; + virtual bool Do(const std::uint32_t& color, + const VideoPlaneManipulableInfo& dest) const override; + + private: + const BufferManagerWithType* const bufmgr_; + }; + + public: + explicit BufferManagerWithType(); + virtual ~BufferManagerWithType() = default; + + public: + virtual BufferObject* Allocate(const std::uint32_t& size) const override; + virtual const VideoPlaneManipulator* const GetScaleManipulator() + const override; + virtual const VideoPlaneManipulator* const GetCopyManipulator() + const override; + virtual const VideoPlaneColorManipulator* const GetColorFillManipulator() + const override; + bool IsValid() const; + BufferObject* Import(BufferHandleType handle) const; + + protected: + bool Copy_(const VideoPlaneManipulableInfo& src, + const VideoPlaneManipulableInfo& dst) const; + bool Scale_(const VideoPlaneManipulableInfo& src, + const VideoPlaneManipulableInfo& dst) const; + bool Fill_(const std::uint32_t color, + const VideoPlaneManipulableInfo& dst) const; + + private: + tbm_bufmgr bufmgr_; + Scaler scaler_; + Copier copier_; + ColorFiller color_filler_; +}; + +using TizenBufferManager = BufferManagerWithType; + +/****************************************************************************** + * Body + */ +template +BufferManagerWithType::BufferManagerWithType() + : scaler_(this), copier_(this), color_filler_(this) { + bufmgr_ = ::kGlobalInstance.bufmgr; +} + +template +BufferObject* BufferManagerWithType::Allocate( + const std::uint32_t& size) const { + if (size == 0) return nullptr; + if (IsValid() == false) return nullptr; + auto buffer = TBM::BoAlloc(bufmgr_, size, TBM_BO_SCANOUT | (1 << 17)); + if (buffer == nullptr) return nullptr; + auto bufferobj = new TizenAccessibleBufferObject(buffer); + TBM::BoUnRef(buffer); + return bufferobj; +} + +template +const VideoPlaneManipulator* const +BufferManagerWithType::GetScaleManipulator() const { + return &scaler_; +} + +template +const VideoPlaneManipulator* const +BufferManagerWithType::GetCopyManipulator() const { + return &copier_; +} + +template +const VideoPlaneColorManipulator* const +BufferManagerWithType::GetColorFillManipulator() const { + return &color_filler_; +} + +template +bool BufferManagerWithType::IsValid() const { + if (bufmgr_ == nullptr) return false; + return true; +} + +template +BufferObject* BufferManagerWithType::Import( + BufferHandleType handle) const { + if (IsValid() == false) return nullptr; + auto buffer = TBM::BoImport(bufmgr_, handle); + if (buffer == nullptr) return nullptr; + auto bufferobj = new TizenBufferObject(buffer); + TBM::BoUnRef(buffer); + return bufferobj; +} + +template +bool BufferManagerWithType::Copy_( + const VideoPlaneManipulableInfo& src, + const VideoPlaneManipulableInfo& dst) const { + if (src.component != dst.component) return false; + if (IsValid() == false) return false; + GraphicsGABltRopInfo info; + std::memset(&info, 0, sizeof(GraphicsGABltRopInfo)); + info.ga_mode = GRAPHICS_GA_BITBLT_MODE_NORMAL; + info.rop_mode = GRAPHICS_GA_ROP_COPY; + info.ga_op_type = GRAPHICS_GA_COPY; + info.pre_alphamode = 0; + info.ca_value = 0; + info.color_format = + (src.component == PlaneComponent::kYComponent ? GRAPHICS_GA_FORMAT_8BPP + : GRAPHICS_GA_FORMAT_16BPP); + + info.src_handle = src.handle; + info.src_hbytesize = src.linesize; + info.src_rect.x = src.rect.x; + info.src_rect.y = src.rect.y; + info.src_rect.w = src.rect.w; + info.src_rect.h = src.rect.h; + + info.dst_handle = dst.handle; + info.dst_hbytesize = dst.linesize; + info.dst_rect.x = dst.rect.x; + info.dst_rect.y = dst.rect.y; + info.dst_rect.w = dst.rect.w; + info.dst_rect.h = dst.rect.h; + + return TBM::GACopy(bufmgr_, &info) > 0; +} + +template +bool BufferManagerWithType::Scale_( + const VideoPlaneManipulableInfo& src, + const VideoPlaneManipulableInfo& dst) const { + if (src.component != dst.component) return false; + if (IsValid() == false) return false; + GraphicsGAScaleInfo info; + std::memset(&info, 0, sizeof(GraphicsGAScaleInfo)); + info.ga_mode = GRAPHICS_GA_SCALE_MODE; + info.rop_mode = GRAPHICS_GA_ROP_COPY; + info.ga_op_type = GRAPHICS_GA_SCALE; + info.pre_alphamode = 0; + info.ca_value = 0; + info.rop_on_off = 0; + info.color_format = + (src.component == PlaneComponent::kYComponent ? GRAPHICS_GA_FORMAT_8BPP + : GRAPHICS_GA_FORMAT_16BPP); + + info.src_handle = src.handle; + info.src_hbytesize = src.linesize; + info.src_rect.x = src.rect.x; + info.src_rect.y = src.rect.y; + info.src_rect.w = src.rect.w; + info.src_rect.h = src.rect.h; + + info.dst_handle = dst.handle; + info.dst_hbytesize = dst.linesize; + info.dst_rect.x = dst.rect.x; + info.dst_rect.y = dst.rect.y; + info.dst_rect.w = dst.rect.w; + info.dst_rect.h = dst.rect.h; + + return TBM::GAScale(bufmgr_, &info) > 0; +} + +template +bool BufferManagerWithType::Fill_( + const std::uint32_t color, const VideoPlaneManipulableInfo& dst) const { + if (IsValid() == false) return false; + GraphicsGAFillRectInfo info; + std::memset(&info, 0, sizeof(GraphicsGAFillRectInfo)); + info.color_format = + (dst.component == PlaneComponent::kYComponent ? GRAPHICS_GA_FORMAT_8BPP + : GRAPHICS_GA_FORMAT_16BPP); + info.ga_op_type = GRAPHICS_GA_SOLID_FILL; + info.color = color; + + info.handle = dst.handle; + info.hbytesize = dst.linesize; + info.rect.x = dst.rect.x; + info.rect.y = dst.rect.y; + info.rect.w = dst.rect.w; + info.rect.h = dst.rect.h; + + return TBM::GAFill(bufmgr_, &info) > 0; +} + +/****************************************************************************** + * Body for scaler + */ +template +BufferManagerWithType::Scaler::Scaler( + const BufferManagerWithType* const bufmgr) + : bufmgr_(bufmgr) {} + +template +bool BufferManagerWithType::Scaler::Do( + const VideoPlaneManipulableInfo& src, + const VideoPlaneManipulableInfo& dest) const { + return bufmgr_->Scale_(src, dest); +} + +/****************************************************************************** + * Body for copier + */ +template +BufferManagerWithType::Copier::Copier( + const BufferManagerWithType* const bufmgr) + : bufmgr_(bufmgr) {} + +template +bool BufferManagerWithType::Copier::Do( + const VideoPlaneManipulableInfo& src, + const VideoPlaneManipulableInfo& dest) const { + return bufmgr_->Copy_(src, dest); +} + +/****************************************************************************** + * Body for color filler + */ +template +BufferManagerWithType::ColorFiller::ColorFiller( + const BufferManagerWithType* const bufmgr) + : bufmgr_(bufmgr) {} + +template +bool BufferManagerWithType::ColorFiller::Do( + const std::uint32_t& color, const VideoPlaneManipulableInfo& dest) const { + return bufmgr_->Fill_(color, dest); +} + +} // namespace plusplayer + +#endif // __PLUSPLAYER_MIXER_TIZEN_BUFFER_MANAGER_H__ \ No newline at end of file diff --git a/src/mixer/include_internal/mixer/tizen/tizenbufferobj.h b/src/mixer/include_internal/mixer/tizen/tizenbufferobj.h new file mode 100755 index 0000000..dcd1d3d --- /dev/null +++ b/src/mixer/include_internal/mixer/tizen/tizenbufferobj.h @@ -0,0 +1,72 @@ +#ifndef __PLUSPLAYER_MIXER_TIZEN_TIZEN_BUFFER_OBJECT_H__ +#define __PLUSPLAYER_MIXER_TIZEN_TIZEN_BUFFER_OBJECT_H__ + +#include "mixer/interfaces/bufferobject.h" +#include "mixer/sys/tbminterface.h" + +namespace plusplayer { +template +class BufferObjectWithType : public BufferObject { + public: + explicit BufferObjectWithType(BufferDefaultType buffer); + virtual ~BufferObjectWithType(); + bool IsValid() const; + virtual BufferHandleType GetBufferHandle() const override; + virtual BufferKeyType Export() const override; + virtual std::uint32_t GetSize() const override; + + protected: + const BufferDefaultType& GetBuffer_() const; + + private: + BufferDefaultType buffer_ = nullptr; +}; + +using TizenBufferObject = BufferObjectWithType; + +/****************************************************************************** + * Body + */ +template +BufferObjectWithType::BufferObjectWithType(BufferDefaultType buffer) { + if (buffer == nullptr) return; + buffer_ = TBM::BoRef(buffer); +} + +template +BufferObjectWithType::~BufferObjectWithType() { + if (buffer_) TBM::BoUnRef(buffer_); +} + +template +bool BufferObjectWithType::IsValid() const { + if (buffer_ == nullptr) return false; + return true; +} + +template +BufferHandleType BufferObjectWithType::GetBufferHandle() const { + if (IsValid() == false) return kInvalidBufferHandle; + auto handle = TBM::BoGetHandle(buffer_, TBM_DEVICE_2D); + return handle.u32; +} + +template +BufferKeyType BufferObjectWithType::Export() const { + if (IsValid() == false) return kInvalidBufferKey; + return TBM::BoExport(buffer_); +} + +template +std::uint32_t BufferObjectWithType::GetSize() const { + if (IsValid() == false) return kInvalidBufferSize; + return TBM::BoSize(buffer_); +} + +template +const BufferDefaultType& BufferObjectWithType::GetBuffer_() const { + return buffer_; +} +} // namespace plusplayer + +#endif // __PLUSPLAYER_MIXER_TIZEN_TIZEN_BUFFER_OBJECT_H__ \ No newline at end of file diff --git a/src/mixer/include_internal/mixer/tizen/tizenbufferphyaddraccessor.h b/src/mixer/include_internal/mixer/tizen/tizenbufferphyaddraccessor.h new file mode 100755 index 0000000..e16ecfa --- /dev/null +++ b/src/mixer/include_internal/mixer/tizen/tizenbufferphyaddraccessor.h @@ -0,0 +1,69 @@ +#ifndef __PLUSPLAYER_MIXER_TIZEN_BUFFER_PHYSICAL_ADDRESS_ACCESOR_H__ +#define __PLUSPLAYER_MIXER_TIZEN_BUFFER_PHYSICAL_ADDRESS_ACCESOR_H__ + +#include + +#include "mixer/interfaces/phyaddraccessor.h" + +namespace plusplayer { +class AbstractTizenBufferPhyAddrAccessor : public PhysicalAddressAccessor { + public: + explicit AbstractTizenBufferPhyAddrAccessor(BufferDefaultType buffer) { + if (buffer == nullptr) return; + buffer_ = tbm_bo_ref(buffer); + } + + virtual ~AbstractTizenBufferPhyAddrAccessor() { + if (buffer_ == nullptr) return; + if (handle_ != nullptr) tbm_bo_unmap(buffer_); + tbm_bo_unref(buffer_); + } + + public: + virtual BufferPhysicalAddrType GetAddress() override { + if (handle_ != nullptr) return handle_; + handle_ = GetAddress_(buffer_); + return handle_; + } + + protected: + virtual BufferPhysicalAddrType GetAddress_( + const BufferDefaultType& buffer) = 0; + + private: + BufferDefaultType buffer_; + BufferPhysicalAddrType handle_ = nullptr; +}; + +class TizenReadableBufferPhyAddrAccessor + : public AbstractTizenBufferPhyAddrAccessor { + public: + explicit TizenReadableBufferPhyAddrAccessor(const BufferDefaultType buffer) + : AbstractTizenBufferPhyAddrAccessor(buffer) {} + virtual ~TizenReadableBufferPhyAddrAccessor() = default; + + protected: + virtual BufferPhysicalAddrType GetAddress_( + const BufferDefaultType& buffer) override { + auto handle = tbm_bo_map(buffer, TBM_DEVICE_CPU, TBM_OPTION_READ); + return handle.ptr; + } +}; + +class TizenWritableBufferPhyAddrAccessor + : public AbstractTizenBufferPhyAddrAccessor { + public: + explicit TizenWritableBufferPhyAddrAccessor(BufferDefaultType buffer) + : AbstractTizenBufferPhyAddrAccessor(buffer) {} + virtual ~TizenWritableBufferPhyAddrAccessor() = default; + + protected: + virtual BufferPhysicalAddrType GetAddress_( + const BufferDefaultType& buffer) override { + auto handle = tbm_bo_map(buffer, TBM_DEVICE_CPU, TBM_OPTION_WRITE); + return handle.ptr; + } +}; +} // namespace plusplayer + +#endif // __PLUSPLAYER_MIXER_TIZEN_BUFFER_PHYSICAL_ADDRESS_ACCESOR_H__ \ No newline at end of file diff --git a/src/mixer/include_internal/mixer/tizen/tizendefaultphyaddraccessor.h b/src/mixer/include_internal/mixer/tizen/tizendefaultphyaddraccessor.h new file mode 100755 index 0000000..7668790 --- /dev/null +++ b/src/mixer/include_internal/mixer/tizen/tizendefaultphyaddraccessor.h @@ -0,0 +1,20 @@ +#ifndef __PLUSPLAYER_MIXER_TIZEN_DEFAULT_PHYSICAL_ADDRESS_ACCESOR_H__ +#define __PLUSPLAYER_MIXER_TIZEN_DEFAULT_PHYSICAL_ADDRESS_ACCESOR_H__ + +#include "mixer/interfaces/phyaddraccessor.h" + +namespace plusplayer { +class TizenDefaultPhyAddrAccessor : public PhysicalAddressAccessor { + public: + explicit TizenDefaultPhyAddrAccessor(std::uint32_t viraddr); + virtual ~TizenDefaultPhyAddrAccessor() = default; + + public: + virtual BufferPhysicalAddrType GetAddress() override; + + private: + std::uint32_t viraddr_; +}; +} // namespace plusplayer + +#endif // __PLUSPLAYER_MIXER_TIZEN_DEFAULT_PHYSICAL_ADDRESS_ACCESOR_H__ \ No newline at end of file diff --git a/src/mixer/include_internal/mixer/tizen/tizenhwbufferobj.h b/src/mixer/include_internal/mixer/tizen/tizenhwbufferobj.h new file mode 100755 index 0000000..9c325f7 --- /dev/null +++ b/src/mixer/include_internal/mixer/tizen/tizenhwbufferobj.h @@ -0,0 +1,27 @@ +#ifndef __PLUSPLAYER_MIXER_TIZEN_TIZEN_HW_BUFFER_OBJECT_H__ +#define __PLUSPLAYER_MIXER_TIZEN_TIZEN_HW_BUFFER_OBJECT_H__ + +#include "mixer/decodedvideoinfo.h" +#include "mixer/interfaces/bufferobject.h" + +namespace plusplayer { +class TizenHWBufferObject : public BufferObject { + public: + explicit TizenHWBufferObject(const std::uint32_t& width, + const std::uint32_t& height, + const DecodedRawPlaneInfo& info); + virtual ~TizenHWBufferObject() = default; + + bool IsValid() const; + virtual BufferHandleType GetBufferHandle() const override; + virtual BufferKeyType Export() const override; + virtual std::uint32_t GetSize() const override; + + private: + std::uint32_t width_; + std::uint32_t height_; + DecodedRawPlaneInfo info_; +}; +} // namespace plusplayer + +#endif // __PLUSPLAYER_MIXER_TIZEN_TIZEN_HW_BUFFER_OBJECT_H__ \ No newline at end of file diff --git a/src/mixer/include_internal/mixer/tizen/tizenhwvideoframe.h b/src/mixer/include_internal/mixer/tizen/tizenhwvideoframe.h new file mode 100755 index 0000000..4374269 --- /dev/null +++ b/src/mixer/include_internal/mixer/tizen/tizenhwvideoframe.h @@ -0,0 +1,32 @@ +#ifndef __PLUSPLAYER_MIXER_TIZEN_HW_VIDEO_FRAME_H__ +#define __PLUSPLAYER_MIXER_TIZEN_HW_VIDEO_FRAME_H__ + +#include + +#include "mixer/abs_videoframe.h" +#include "mixer/decodedvideoinfo.h" +#include "mixer/interfaces/bufferobject.h" + +namespace plusplayer { +class TizenHWVideoFrame : public AbstractVideoFrame { + public: + using BufferObjectPtr = std::unique_ptr; + + public: + explicit TizenHWVideoFrame(const DecodedRawInfo& info); + virtual ~TizenHWVideoFrame() = default; + + protected: + virtual bool IsValid_() const override; + virtual const std::uint32_t GetWidth_() const override; + virtual const std::uint32_t GetHeight_() const override; + + private: + void PrintDecodedRawInfo() const; + + private: + DecodedRawInfo info_; +}; +} // namespace plusplayer + +#endif //__PLUSPLAYER_MIXER_TIZEN_HW_VIDEO_FRAME_H__ \ No newline at end of file diff --git a/src/mixer/include_internal/mixer/tizen/tizenrenderableobj_factory.h b/src/mixer/include_internal/mixer/tizen/tizenrenderableobj_factory.h new file mode 100755 index 0000000..91401d0 --- /dev/null +++ b/src/mixer/include_internal/mixer/tizen/tizenrenderableobj_factory.h @@ -0,0 +1,25 @@ +#ifndef __PLUSPLAYER_MIXER_TIZEN_RENDERABLE_OBJECT_FACTORY_H__ +#define __PLUSPLAYER_MIXER_TIZEN_RENDERABLE_OBJECT_FACTORY_H__ + +#include + +#include "mixer/interfaces/memoryallocator.h" +#include "mixer/interfaces/renderableobj_factory.h" + +namespace plusplayer { +class TizenRenderableObjectFactory : public RenderableObjectFactory { + public: + explicit TizenRenderableObjectFactory( + const MemoryAllocator* const memallocator); + virtual ~TizenRenderableObjectFactory() = default; + + public: + virtual RenderableObject* CreateRenderableObject( + const std::uint32_t width, const std::uint32_t height) const override; + + private: + const MemoryAllocator* const memallocator_; +}; +} // namespace plusplayer + +#endif // __PLUSPLAYER_MIXER_TIZEN_RENDERABLE_OBJECT_FACTORY_H__ \ No newline at end of file diff --git a/src/mixer/include_internal/mixer/tizen/tizensurfacevideoframe.h b/src/mixer/include_internal/mixer/tizen/tizensurfacevideoframe.h new file mode 100755 index 0000000..eeac31c --- /dev/null +++ b/src/mixer/include_internal/mixer/tizen/tizensurfacevideoframe.h @@ -0,0 +1,34 @@ +#ifndef __PLUSPLAYER_MIXER_TIZEN_SURFACE_VIDEO_FRAME_H__ +#define __PLUSPLAYER_MIXER_TIZEN_SURFACE_VIDEO_FRAME_H__ + +#include + +#include "mixer/abs_videoframe.h" +#include "mixer/interfaces/bufferobject.h" +#include "plusplayer/decodedvideopacketex.h" + +namespace plusplayer { +class TizenSurfaceVideoFrame : public AbstractVideoFrame { + private: + enum ComponentIndex { kYIndex = 0, kUVIndex = 1 }; + + public: + using BufferObjectPtr = std::unique_ptr; + + public: + explicit TizenSurfaceVideoFrame(DecodedVideoPacketExPtr dvp); + virtual ~TizenSurfaceVideoFrame() = default; + + protected: + virtual bool IsValid_() const override; + virtual const std::uint32_t GetWidth_() const override; + virtual const std::uint32_t GetHeight_() const override; + + private: + DecodedVideoPacketExPtr dvp_; + std::uint32_t width_; + std::uint32_t height_; +}; +} // namespace plusplayer + +#endif //__PLUSPLAYER_MIXER_TIZEN_SURFACE_VIDEO_FRAME_H__ \ No newline at end of file diff --git a/src/mixer/include_internal/mixer/types/buffertype.h b/src/mixer/include_internal/mixer/types/buffertype.h new file mode 100755 index 0000000..d8cfc9e --- /dev/null +++ b/src/mixer/include_internal/mixer/types/buffertype.h @@ -0,0 +1,23 @@ +#ifndef __PLUSPLAYER_MIXER_TYPES_BUFFER_TYPE_H__ +#define __PLUSPLAYER_MIXER_TYPES_BUFFER_TYPE_H__ + +#include + +#include + +namespace plusplayer { + +using BufferDefaultType = tbm_bo; +using BufferUnionHandleType = tbm_bo_handle; +using BufferHandleType = decltype(BufferUnionHandleType::u32); +using BufferPhysicalAddrType = decltype(BufferUnionHandleType::ptr); +using BufferKeyType = tbm_key; + +const static BufferHandleType kInvalidBufferHandle = 0; +const static BufferPhysicalAddrType kInvalidBufferPhysicalAddr = nullptr; +const static BufferKeyType kInvalidBufferKey = 0; +const static BufferKeyType kInvalidBufferSize = 0; + +} // namespace plusplayer + +#endif // __PLUSPLAYER_MIXER_TYPES_BUFFER_TYPE_H__ \ No newline at end of file diff --git a/src/mixer/include_internal/mixer/types/planecomponent.h b/src/mixer/include_internal/mixer/types/planecomponent.h new file mode 100755 index 0000000..c16e8cc --- /dev/null +++ b/src/mixer/include_internal/mixer/types/planecomponent.h @@ -0,0 +1,13 @@ +#ifndef __PLUSPLAYER_MIXER_TYPES_PLANE_COMPONENT_H__ +#define __PLUSPLAYER_MIXER_TYPES_PLANE_COMPONENT_H__ + +namespace plusplayer { + +enum class PlaneComponent { + kYComponent, + kUVComponent, +}; + +} // namespace plusplayer + +#endif // __PLUSPLAYER_MIXER_TYPES_PLANE_COMPONENT_H__ \ No newline at end of file diff --git a/src/mixer/include_internal/mixer/types/videoplanemanipinfo.h b/src/mixer/include_internal/mixer/types/videoplanemanipinfo.h new file mode 100755 index 0000000..ce09752 --- /dev/null +++ b/src/mixer/include_internal/mixer/types/videoplanemanipinfo.h @@ -0,0 +1,21 @@ +#ifndef __PLUSPLAYER_MIXER_TYPES_VIDEO_PLANE_MAINIPULABLE_INFO_H__ +#define __PLUSPLAYER_MIXER_TYPES_VIDEO_PLANE_MAINIPULABLE_INFO_H__ + +#include + +#include "mixer/types/buffertype.h" +#include "mixer/types/planecomponent.h" +#include "plusplayer/types/display.h" + +namespace plusplayer { + +struct VideoPlaneManipulableInfo { + BufferHandleType handle; + PlaneComponent component; + std::uint32_t linesize; + Geometry rect; +}; + +} // namespace plusplayer + +#endif // __PLUSPLAYER_MIXER_TYPES_VIDEO_PLANE_MAINIPULABLE_INFO_H__ \ No newline at end of file diff --git a/src/mixer/include_internal/mixer/types/videoplanemoveinfo.h b/src/mixer/include_internal/mixer/types/videoplanemoveinfo.h new file mode 100755 index 0000000..9559f55 --- /dev/null +++ b/src/mixer/include_internal/mixer/types/videoplanemoveinfo.h @@ -0,0 +1,17 @@ +#ifndef __PLUSPLAYER_MIXER_TYPES_VIDEO_PLANE_MOVE_INFO_H__ +#define __PLUSPLAYER_MIXER_TYPES_VIDEO_PLANE_MOVE_INFO_H__ + +#include "plusplayer/types/display.h" + +namespace plusplayer { + +struct VideoPlaneMoveInfo { + explicit VideoPlaneMoveInfo(const Geometry& current_gemetry, const Geometry& target_gemetry) + : cur(current_gemetry), target(target_gemetry) {} + Geometry cur; + Geometry target; +}; + +} // namespace plusplayer + +#endif // __PLUSPLAYER_MIXER_TYPES_VIDEO_PLANE_MOVE_INFO_H__ \ No newline at end of file diff --git a/src/mixer/include_internal/mixer/videoplane.h b/src/mixer/include_internal/mixer/videoplane.h new file mode 100755 index 0000000..969f9b1 --- /dev/null +++ b/src/mixer/include_internal/mixer/videoplane.h @@ -0,0 +1,116 @@ +#ifndef __PLUSPLAYER_MIXER_VIDEO_PLANE_H__ +#define __PLUSPLAYER_MIXER_VIDEO_PLANE_H__ + +#include +#include + +#include "mixer/interfaces/bufferobject.h" +#include "mixer/interfaces/videoplanemanipulable.h" +#include "mixer/types/planecomponent.h" +#include "plusplayer/types/display.h" + +namespace plusplayer { +class VideoPlane : public VideoPlaneManipulable { + public: + explicit VideoPlane(PlaneComponent component, const std::uint32_t& width, + const std::uint32_t& height); + virtual ~VideoPlane() = default; + + public: + virtual bool IsValid() const override; + virtual VideoPlaneManipulableInfo GetVideoPlaneManipulableInfo() + const override; + virtual void SetCropArea(const CropArea& croparea) override; + + protected: + virtual const BufferObject* const GetBufferObject_() const = 0; + virtual std::uint32_t GetLineSize_() const; + + private: + const PlaneComponent component_; + const std::uint32_t width_; + const std::uint32_t height_; + CropArea croparea_; +}; + +class YComponentVideoPlane : public VideoPlane { + public: + using BufferObjectPtr = std::unique_ptr; + + public: + explicit YComponentVideoPlane(BufferObjectPtr buffer, + const std::uint32_t& width, + const std::uint32_t& height); + virtual ~YComponentVideoPlane() = default; + + protected: + virtual const BufferObject* const GetBufferObject_() const override; + + private: + BufferObjectPtr buffer_; +}; + +class UVComponentVideoPlane : public VideoPlane { + public: + using BufferObjectPtr = std::unique_ptr; + + public: + explicit UVComponentVideoPlane(BufferObjectPtr buffer, + const std::uint32_t& width, + const std::uint32_t& height); + virtual ~UVComponentVideoPlane() = default; + + protected: + virtual const BufferObject* const GetBufferObject_() const override; + + private: + BufferObjectPtr buffer_; +}; + +class YComponentVideoPlaneWithSharedMemory : public VideoPlane { + public: + using BufferObjectPtr = std::shared_ptr; + using BufferObjectWeakPtr = std::weak_ptr; + + public: + explicit YComponentVideoPlaneWithSharedMemory(BufferObjectWeakPtr buffer, + const std::uint32_t& width, + const std::uint32_t& height); + virtual ~YComponentVideoPlaneWithSharedMemory() = default; + + protected: + virtual std::uint32_t GetLineSize_() const override; + virtual const BufferObject* const GetBufferObject_() const override; + + private: + BufferObjectPtr buffer_; + std::uint32_t width_; +}; + +class UVComponentVideoPlaneWithSharedMemory : public VideoPlane { + public: + using BufferObjectPtr = std::shared_ptr; + using BufferObjectWeakPtr = std::weak_ptr; + + public: + explicit UVComponentVideoPlaneWithSharedMemory(BufferObjectWeakPtr buffer, + const std::uint32_t& width, + const std::uint32_t& height); + virtual ~UVComponentVideoPlaneWithSharedMemory() = default; + + public: + virtual VideoPlaneManipulableInfo GetVideoPlaneManipulableInfo() + const override; + + protected: + virtual std::uint32_t GetLineSize_() const override; + virtual const BufferObject* const GetBufferObject_() const override; + + private: + BufferObjectPtr buffer_; + std::uint32_t width_; + std::uint32_t height_; +}; +} // namespace plusplayer + +#endif //__PLUSPLAYER_MIXER_VIDEO_PLANE_H__ \ No newline at end of file diff --git a/src/mixer/src/abs_videoframe.cpp b/src/mixer/src/abs_videoframe.cpp new file mode 100755 index 0000000..3868a4a --- /dev/null +++ b/src/mixer/src/abs_videoframe.cpp @@ -0,0 +1,46 @@ +#include "mixer/abs_videoframe.h" + +namespace plusplayer { + +const std::vector +AbstractVideoFrame::GetVideoPlaneManipInfo() const { + if (IsValid() == false) return {}; + std::vector vector; + for (const auto& plane : planes_) { + vector.emplace_back(plane->GetVideoPlaneManipulableInfo()); + } + return vector; +} + +bool AbstractVideoFrame::IsValid() const { + if (planes_.size() == 0) return false; + if (GetWidth_() == 0 || GetHeight_() == 0) return false; + if (IsValid_() == false) return false; + return true; +} + +bool AbstractVideoFrame::SetCropArea(const CropArea& croparea) { + if (IsValid() == false) return false; + for (auto& plane : planes_) { + plane->SetCropArea(croparea); + } + return true; +} + +const std::uint32_t AbstractVideoFrame::GetWidth() const { + if (IsValid() == false) return 0; + return GetWidth_(); +} + +const std::uint32_t AbstractVideoFrame::GetHeight() const { + if (IsValid() == false) return 0; + return GetHeight_(); +} + +void AbstractVideoFrame::RegisterVideoPlaneManipulablePtr_( + VideoPlaneManipulablePtr vpmanip) { + if (vpmanip == nullptr) return; + planes_.emplace_back(std::move(vpmanip)); +} + +} // namespace plusplayer \ No newline at end of file diff --git a/src/mixer/src/defaultmixer.cpp b/src/mixer/src/defaultmixer.cpp new file mode 100755 index 0000000..5fa76a7 --- /dev/null +++ b/src/mixer/src/defaultmixer.cpp @@ -0,0 +1,649 @@ +#include "mixer/defaultmixer.h" + +#include +#include + +#include +#include +#include +#include +#include + +#include "core/utils/plusplayer_log.h" +#include "mixer/rendererwithdoublebuf.h" +#include "mixer/tizen/tizenbufferkeyvideoframe.h" +#include "mixer/tizen/tizenhwvideoframe.h" +#include "mixer/tizen/tizenrenderableobj_factory.h" +#include "mixer/tizen/tizensurfacevideoframe.h" +#include "mixer/types/videoplanemoveinfo.h" + +namespace plusplayer { + +namespace internal { +TrackRendererDisplayMode ConvertToTrackRendererDisplayMode( + const DisplayMode& mode); + +} // namespace internal +DefaultMixer::DefaultMixer() : renderer_listener_(&trackrenderer_handle_) { + LOG_ENTER; + gst_init(NULL, NULL); + trackrenderer_create(&trackrenderer_handle_); + + TizenRenderableObjectFactory factory(&bufmgr_); + renderer_ = RendererPtr(new RendererWithDoubleBuffer( + factory, &bufmgr_, &bufmgr_, whole_resolution_, &renderer_listener_)); + // renderer_ = RendererPtr( + // new Renderer(factory, whole_resolution_, &renderer_listener_)); + InitResourceList_(); +} + +DefaultMixer::~DefaultMixer() { + LOG_ENTER; + trackrenderer_destroy(trackrenderer_handle_); + LOG_LEAVE; +} + +bool DefaultMixer::Start() { + LOG_ENTER; + std::lock_guard lk(mixer_lock_); + if (is_started_ == true) { + LOG_ERROR("mixer was already started"); + return true; + } + + if (renderer_->Start() == false) { + LOG_ERROR("renderer start failed"); + return false; + } + + TrackRendererTrack track; + memset(&track, 0, sizeof(TrackRendererTrack)); + track.type = kTrackRendererTrackTypeVideo; + track.index = 0; + track.active = 1; + track.mimetype = "video/x-raw"; + track.width = whole_resolution_.width; + track.height = whole_resolution_.height; + track.framerate_num = whole_resolution_.framerate_num; + track.framerate_den = whole_resolution_.framerate_den; + + if (trackrenderer_set_track(trackrenderer_handle_, &track, 1) != 0) { + LOG_ERROR("fail to set track"); + return false; + } + + constexpr std::uint32_t kLowLatencyMode = + 0x0111; // video disable avsync & videoquailty + trackrenderer_set_attribute(trackrenderer_handle_, "low-latency-mode", + kLowLatencyMode, nullptr); + if (use_sub_scaler_) { + constexpr std::uint32_t alternative_rsc = 1; + trackrenderer_set_attribute(trackrenderer_handle_, + "alternative-video-resource", alternative_rsc, + nullptr); + } + if (trackrenderer_prepare(trackrenderer_handle_) != 0) { + LOG_ERROR("fail to prepare"); + return false; + } + if (trackrenderer_start(trackrenderer_handle_) != 0) { + LOG_ERROR("fail to start"); + return false; + } + + is_started_ = true; + LOG_LEAVE; + return true; +} + +bool DefaultMixer::Stop() { + LOG_ENTER; + std::lock_guard lk(mixer_lock_); + if (is_started_ == false) { + LOG_ERROR("mixer was already stopped"); + return true; + } + + if (renderer_->Stop() == false) { + LOG_ERROR("renderer stop failed"); + } + + if (trackrenderer_stop(trackrenderer_handle_) != 0) { + LOG_ERROR("fail to stop"); + } + + is_started_ = false; + LOG_LEAVE; + return true; +} + +int DefaultMixer::GetMaximumAllowedNumberOfPlayer() { + LOG_ENTER; + // Fix me if we get policy and detail method (bc_hi.lee) + constexpr int kMaximumAllowedNumberOfPlayer = 3; + constexpr int kMaximumNumForNdecoder = 4; + if (resource_allocation_mode_ == RscAllocMode::kNdecoder) + return kMaximumNumForNdecoder; + return kMaximumAllowedNumberOfPlayer; +} + +bool DefaultMixer::SetDisplay(const DisplayType type, void* obj) { + LOG_ENTER; + TrackRendererDisplayType _type = kTrackRendererDisplayTypeNone; + switch (type) { + case DisplayType::kNone: { + _type = kTrackRendererDisplayTypeNone; + break; + } + case DisplayType::kOverlay: { + _type = kTrackRendererDisplayTypeOverlay; + break; + } + case DisplayType::kEvas: { + _type = kTrackRendererDisplayTypeEvas; + break; + } + default: + LOG_ERROR("unknown displaytype"); + return false; + } + if (!trackrenderer_set_display(trackrenderer_handle_, _type, obj)) { + return true; + } + return false; +} + +bool DefaultMixer::SetDisplay(const DisplayType type, const uint32_t surface_id, + const int x, const int y, const int w, + const int h) { + LOG_ENTER; + TrackRendererDisplayType _type = kTrackRendererDisplayTypeNone; + switch (type) { + case DisplayType::kNone: { + _type = kTrackRendererDisplayTypeNone; + break; + } + case DisplayType::kOverlay: { + _type = kTrackRendererDisplayTypeOverlay; + break; + } + case DisplayType::kEvas: { + _type = kTrackRendererDisplayTypeEvas; + break; + } + default: + LOG_ERROR("unknown displaytype"); + return false; + } + if (!trackrenderer_set_display_surface(trackrenderer_handle_, _type, + surface_id, x, y, w, h)) { + return true; + } + return false; +} + +bool DefaultMixer::SetDisplayMode(const DisplayMode& mode) { + LOG_ENTER; + TrackRendererDisplayMode _mode = + internal::ConvertToTrackRendererDisplayMode(mode); + if (!trackrenderer_set_display_mode(trackrenderer_handle_, _mode)) { + return true; + } + return false; +} + +bool DefaultMixer::SetDisplayRoi(const Geometry& geometry) { + LOG_ENTER; + TrackRendererGeometry _geometry; + memset(&_geometry, 0, sizeof(TrackRendererGeometry)); + _geometry.x = geometry.x; + _geometry.y = geometry.y; + _geometry.w = geometry.w; + _geometry.h = geometry.h; + if (!trackrenderer_set_display_roi(trackrenderer_handle_, &_geometry)) { + return true; + } + return false; +} + +bool DefaultMixer::SetRscAllocMode(const RscAllocMode& mode) { + LOG_ENTER; + std::lock_guard lock(ticket_lock_); + if (player_map_.size() != 0) { + LOG_ERROR( + "it have to be called before setting player's display type to mixer " + "type"); + return false; + } + resource_allocation_mode_ = mode; + return true; +} + +bool DefaultMixer::DisableAudioFocusSetting() { + LOG_ENTER; + std::lock_guard lock(ticket_lock_); + if (player_map_.size() != 0) { + LOG_ERROR( + "it have to be called before setting player's display type to mixer " + "type"); + return false; + } + enable_audio_focus_setting_ = false; + if (audio_focused_player_) audio_focused_player_ = nullptr; + return true; +} + +bool DefaultMixer::SetAlternativeVideoScaler() { + LOG_ENTER; + use_sub_scaler_ = true; + return true; +} + +bool DefaultMixer::SetAudioFocus(const void* player_instance) { + LOG_ENTER; + if (!player_instance) return false; + std::lock_guard lock(ticket_lock_); + + if (!enable_audio_focus_setting_) return false; + + if (player_map_.count(player_instance) == 0) { + audio_focused_player_ = player_instance; + LOG_INFO("audio focus [%p]", audio_focused_player_); + return true; + } + + LOG_INFO("Active Audio player[%p]", player_instance); + auto ticket = player_map_[player_instance]; + if (ticket->IsAudioFocused()) { + LOG_INFO("already focused user"); + return true; + } + + auto target = std::find_if( + player_map_.begin(), player_map_.end(), + [](const std::pair& item) noexcept -> bool { + return (item.second)->IsAudioFocused(); + }); + + if (target != player_map_.end()) { + if (target->second->GetListener()->OnAudioFocusChanged(false)) { + target->second->SetAudioFocus(false); + } else { + LOG_ERROR("fail to change audio focus"); + return false; + } + } else { + LOG_INFO("not found audio focused player"); + } + + if (ticket->GetListener()) { + if (ticket->GetListener()->OnAudioFocusChanged(true)) { + ticket->SetAudioFocus(true); + return true; + } + } + LOG_ERROR("fail to change audio focus"); + return false; +} + +bool DefaultMixer::Commit() { + LOG_ENTER; + std::lock_guard lock(ticket_lock_); + std::vector move_list; + for (auto it = player_map_.begin(); it != player_map_.end(); ++it) { + auto listener = it->second->GetListener(); + if (!listener) continue; + DisplayInfo cur_info, new_info; + it->second->GetDisplayInfo(&cur_info); + if (listener->OnUpdateDisplayInfo(cur_info, &new_info)) { + it->second->UpdateDisplayInfo(new_info); + if (it->second->HasRenderedBefore() == true) { + move_list.emplace_back(cur_info.geometry, new_info.geometry); + } + } else { + LOG_ERROR("fail to update display info"); + } + } + renderer_->Move(&bufmgr_, move_list); + return true; +} + +bool DefaultMixer::SetResolution(const ResolutionInfo& info) { + whole_resolution_ = info; + renderer_->ChangeResolution(TizenRenderableObjectFactory(&bufmgr_), + info.width, info.height); + renderer_->ChangeRenderingSpeed(info.framerate_num, info.framerate_den); + return true; +} + +bool DefaultMixer::RegisterListener(MixerEventListener* listener) { + LOG_ENTER; + listener_ = listener; + trackrenderer_set_resourceconflict_cb(trackrenderer_handle_, + ResourceConflictCb_, this); + trackrenderer_set_error_cb(trackrenderer_handle_, ErrorCb_, this); + return true; +} + +MixerTicket* DefaultMixer::CreateTicket(const void* player_instance) { + std::lock_guard lock(ticket_lock_); + LOG_ENTER; + auto ticket = new Ticket(this, player_instance); + if (audio_focused_player_ == player_instance) { + ticket->SetAudioFocus(true); + LOG_INFO(" player [%p]", player_instance); + } + player_map_.emplace(player_instance, ticket); + return ticket; +} + +bool DefaultMixer::Detach_(const void* player_instance) { + std::lock_guard lock(ticket_lock_); + LOG_ENTER; + auto* ticket = player_map_.at(player_instance); + + if (ticket == nullptr) return false; + if (ticket->HasRenderedBefore()) { + DisplayInfo display_info; + ticket->GetDisplayInfo(&display_info); + // if this ticket is display visible status, draw black screen + if (IsNeededToSkipRendering_(display_info) == false) { + LOG_INFO( + "player [%p] has been rendered before, so will mute on rendered " + "region", + player_instance); + renderer_->Mute(&bufmgr_, display_info.geometry); + } + } + ticket->DeallocResource(); + player_map_.erase(player_instance); + return true; +} + +bool DefaultMixer::Render_(const void* player_instance, + const VideoPlaneCollection& vplanes) { + DisplayInfo display_info; + { + std::lock_guard t_lk(ticket_lock_); + auto* ticket = player_map_.at(player_instance); + if (ticket == nullptr) return false; + ticket->GetDisplayInfo(&display_info); + ticket->RecordRenderingTime(); + } + if (IsNeededToSkipRendering_(display_info)) { + LOG_INFO("Skip Rendering for player [%p]", player_instance); + return true; + } + bool ret = renderer_->Render(&bufmgr_, &vplanes, display_info.geometry); + if (ret == false) { + LOG_INFO("Rendering Failed for player [%p]", player_instance); + } + return ret; +} + +bool DefaultMixer::IsNeededToSkipRendering_(const DisplayInfo& display_info) { + if (display_info.visible_status == VisibleStatus::kHide) return true; + if (display_info.geometry.x == 0 && display_info.geometry.y == 0 && + display_info.geometry.w == 1 && display_info.geometry.h == 1) + return true; + return false; +} + +void DefaultMixer::ResourceConflictCb_(UserData userdata) { + auto mixer = static_cast(userdata); + if (!mixer) return; + mixer->listener_->OnResourceConflicted(); +} +void DefaultMixer::ErrorCb_(const TrackRendererErrorType error_code, + UserData userdata) { + auto mixer = static_cast(userdata); + if (!mixer) return; + mixer->listener_->OnError(); +} + +bool DefaultMixer::Ticket::Render(const DecodedRawInfo& info) { + LOG_INFO("player [%p] render frame [%d x %d]", player_instance_, info.width, + info.height); + TizenHWVideoFrame frame(info); + frame.SetCropArea(each_display_info_.croparea); + bool ret = handler_->Render_(player_instance_, frame); + if (ret == false) { + LOG_INFO("player [%p] rendering error", player_instance_); + } else { + has_rendered_ = true; + } + return ret; +} + +bool DefaultMixer::Ticket::Render(const DecodedVideoKeyTypeInfo& info) { + LOG_INFO("player [%p] render frame [%d x %d]", player_instance_, info.width, + info.height); + TizenBufferKeyVideoFrame frame(&handler_->bufmgr_, info.key, info.width, + info.height); + frame.SetCropArea(each_display_info_.croparea); + bool ret = handler_->Render_(player_instance_, frame); + if (ret == false) { + LOG_INFO("player [%p] rendering error", player_instance_); + } else { + has_rendered_ = true; + } + return ret; +} + +void DefaultMixer::InitResourceList_() { + // Must be improved (bc_hi.lee) + for (int type = static_cast(ResourceType::kHwMain); + type < static_cast(ResourceType::kNdecoder); type++) { + Resource resource; + resource.category = ResourceCategory::kVideoDecoder; + resource.type = static_cast(type); + resource_list_.push_back(std::move(resource)); + } +} + +bool DefaultMixer::Ticket::GetAvailableResourceType( + const ResourceCategory& category, ResourceType* type) { + LOG_ENTER; + std::lock_guard lock(handler_->ticket_lock_); + if (handler_->resource_allocation_mode_ == RscAllocMode::kDisable) { + LOG_ERROR("mixer is no loger involved in resource alloc"); + return false; + } + + if (handler_->resource_allocation_mode_ == RscAllocMode::kNdecoder) { + *type = ResourceType::kNdecoder; + return true; + } + + for (const auto& item : handler_->resource_list_) { + if (!item.assignee) { + *type = item.type; + LOG_INFO("Resource type [%d]", static_cast(*type)); + return true; + } + } + LOG_ERROR("no available resource type"); + return false; +} + +bool DefaultMixer::Ticket::IsAudioFocusHandler() { + std::lock_guard lock(handler_->ticket_lock_); + return handler_->enable_audio_focus_setting_; +} + +bool DefaultMixer::Ticket::IsRscAllocHandler() { + std::lock_guard lock(handler_->ticket_lock_); + return handler_->resource_allocation_mode_ != RscAllocMode::kDisable; +} + +bool DefaultMixer::Ticket::Alloc(const ResourceCategory& category, + const ResourceType& type) { + std::lock_guard lock(handler_->ticket_lock_); + if (handler_->resource_allocation_mode_ == RscAllocMode::kDisable) { + LOG_ERROR("mixer is no loger involved in resource alloc"); + return false; + } + + if (type == ResourceType::kNdecoder) { + LOG_INFO("Request N decoder"); + return true; + } + + auto compare = [category, type](Resource item) -> bool { + if (item.category == category && item.type == type && !item.assignee) { + return true; + } + return false; + }; + auto target = std::find_if(handler_->resource_list_.begin(), + handler_->resource_list_.end(), compare); + if (target == handler_->resource_list_.end()) { + LOG_ERROR("Resource alloc fail"); + return false; + } + LOG_INFO("assignee %p", player_instance_); + target->assignee = player_instance_; + LOG_LEAVE; + return true; +} + +MixerTicketEventListener* DefaultMixer::Ticket::GetListener() { + return ticket_listener_; +} + +bool DefaultMixer::Ticket::IsAudioFocused() { return is_audio_focus_; } + +void DefaultMixer::Ticket::SetAudioFocus(bool active) { + is_audio_focus_ = active; +} + +void DefaultMixer::Ticket::GetDisplayInfo(DisplayInfo* info) { + if (info == nullptr) return; + *info = each_display_info_; +} + +bool DefaultMixer::Ticket::HasRenderedBefore() { return has_rendered_; } + +void DefaultMixer::Ticket::UpdateDisplayInfo(const DisplayInfo& info) { + LOG_INFO( + "updated display info : geom[%d, %d, %d x %d] crop[%.3lf, %.3lf, %.3lf x " + "%.3lf]", + info.geometry.x, info.geometry.y, info.geometry.w, info.geometry.h, + info.croparea.scale_x, info.croparea.scale_y, info.croparea.scale_w, + info.croparea.scale_h); + each_display_info_ = info; +} + +void DefaultMixer::Ticket::DeallocResource() { + auto compare = [this](Resource item) -> bool { + if (item.assignee == player_instance_) { + return true; + } + return false; + }; + + auto target = std::find_if(handler_->resource_list_.begin(), + handler_->resource_list_.end(), compare); + if (target == handler_->resource_list_.end()) { + LOG_INFO("not found assignee"); + return; + } + target->assignee = nullptr; +} + +void DefaultMixer::Ticket::RecordRenderingTime() { + auto now = std::chrono::system_clock::now(); + LOG_INFO("[PERF] p[%p] render_interval[%llu]ms", player_instance_, + std::chrono::duration_cast( + now - last_rendering_time_) + .count()); + last_rendering_time_ = now; +} + +bool DefaultMixer::Ticket::RegisterListener( + MixerTicketEventListener* listener) { + LOG_ENTER; + ticket_listener_ = listener; + return true; +} + +bool DefaultMixer::Ticket::Prepare() { + LOG_ENTER; + DisplayInfo new_info; + ticket_listener_->OnUpdateDisplayInfo(each_display_info_, &new_info); + UpdateDisplayInfo(new_info); + if (!handler_->enable_audio_focus_setting_) return true; + LOG_INFO("audio focused [%d]", is_audio_focus_); + ticket_listener_->OnAudioFocusChanged(is_audio_focus_); + return true; +} + +DefaultMixer::Ticket::~Ticket() { + handler_->Detach_(player_instance_); + LOG_LEAVE; +} + +DefaultMixer::MixerRendererEventListener::MixerRendererEventListener( + TrackRendererHandle* trhandle_ptr) + : trhandle_ptr_(trhandle_ptr) {} + +DefaultMixer::MixerRendererEventListener::~MixerRendererEventListener() {} + +void* DefaultMixer::MixerRendererEventListener::CreateGstBuffer_( + const BufferKeyType& key) const { + if (key == 0) return nullptr; + auto* gstbuffer = gst_buffer_new(); + auto* gststructure = + gst_structure_new("tbm_bo", "tbm_bo_key", G_TYPE_UINT, key, nullptr); + gst_mini_object_set_qdata(GST_MINI_OBJECT(gstbuffer), + g_quark_from_static_string("tbm_bo"), gststructure, + (GDestroyNotify)gst_structure_free); + return static_cast(gstbuffer); +} + +void DefaultMixer::MixerRendererEventListener::FillDecoderInputBuffer_( + TrackRendererDecoderInputBuffer& buffer, const BufferKeyType& key) const { + buffer.type = kTrackRendererTrackTypeVideo; + buffer.index = 0; + buffer.buffer = CreateGstBuffer_(key); +} + +bool DefaultMixer::MixerRendererEventListener::OnRenderingRelease( + const BufferKeyType& key) { + if (trhandle_ptr_ == nullptr || *trhandle_ptr_ == nullptr) return false; + TrackRendererDecoderInputBuffer buffer; + FillDecoderInputBuffer_(buffer, key); + TrackRendererSubmitStatus status; + int ret = trackrenderer_submit_packet2(*trhandle_ptr_, &buffer, &status); + if (ret != 0 && buffer.buffer != nullptr) { + gst_buffer_unref(GST_BUFFER(buffer.buffer)); + } + return ret == 0; +} + +namespace internal { +TrackRendererDisplayMode ConvertToTrackRendererDisplayMode( + const DisplayMode& mode) { + switch (mode) { + case DisplayMode::kLetterBox: + return kTrackRendererDisplayModeLetterBox; + case DisplayMode::kOriginSize: + return kTrackRendererDisplayModeOriginSize; + case DisplayMode::kFullScreen: + return kTrackRendererDisplayModeFullScreen; + case DisplayMode::kCroppedFull: + return kTrackRendererDisplayModeCroppedFull; + case DisplayMode::kOriginOrLetter: + return kTrackRendererDisplayModeOriginOrLetter; + case DisplayMode::kDstRoi: + return kTrackRendererDisplayModeDstRoi; + case DisplayMode::kAutoAspectRatio: + return kTrackRendererDisplayModeAutoAspectRatio; + default: + return kTrackRendererDisplayModeDisplayMax; + } +} + +} // namespace internal + +} // namespace plusplayer diff --git a/src/mixer/src/mixedframe.cpp b/src/mixer/src/mixedframe.cpp new file mode 100755 index 0000000..e4e2328 --- /dev/null +++ b/src/mixer/src/mixedframe.cpp @@ -0,0 +1,143 @@ +#include "mixer/mixedframe.h" + +#include + +#include "core/utils/plusplayer_log.h" +#include "mixer/interfaces/accessiblebuffer.h" + +namespace plusplayer { +MixedFramePtr MixedFrame::Create(const MemoryAllocator* const memop, + const std::uint32_t width, + const std::uint32_t height) { + return Ptr(new MixedFrame(memop, width, height)); +} + +MixedFrame::MixedFrame(const MemoryAllocator* const memop, + const std::uint32_t width, const std::uint32_t height) + : width_(width), height_(height) { + if (memop == nullptr) return; + const auto size = CalculateBufferSize_(width_, height_); + if (size == 0) return; + buffer_ = BufferObjectPtr(memop->Allocate(size)); + if (buffer_ == nullptr) return; + allocated_size_ = buffer_->GetSize(); + if (allocated_size_ == 0) return; + + if (allocated_size_ != size) { + LOG_WARN("size mismatched request [%u] allocated [%u]", size, + allocated_size_); + } + + if (auto* accessible_buffer = + dynamic_cast(buffer_.get())) { + auto ptr = accessible_buffer->GetWritableAddress(); + if (ptr == nullptr) return; + const auto y_size = width_ * height_; + const auto uv_size = y_size >> 1; + { + std::memset(static_cast(ptr->GetAddress()), (char)0x00, y_size); + std::memset(static_cast(ptr->GetAddress()) + y_size, (char)0x80, + uv_size); + } + LOG_DEBUG("MixedFrame UV memset done"); + } +} + +const std::vector +MixedFrame::GetVideoPlaneManipInfo() const { + if (IsValid() == false) return {}; + auto handle = buffer_->GetBufferHandle(); + return {GetYComponentVMInfo_(handle), GetUVComponentVMInfo_(handle)}; +} + +bool MixedFrame::IsValid() const { + if (width_ == 0 || height_ == 0) return false; + if (allocated_size_ == 0) return false; + if (buffer_ == nullptr) return false; + return true; +} + +std::uint32_t MixedFrame::GetWidth() const { return width_; } + +std::uint32_t MixedFrame::GetHeight() const { return height_; } + +std::uint32_t MixedFrame::GetSize() const { + if (IsValid() == false) return 0; + return allocated_size_; +} + +bool MixedFrame::Render(const VideoPlaneManipulator* const vpmanip, + const std::vector& planes, + const Geometry& geom) { + if (vpmanip == nullptr) return false; + if (IsValid() == false) return false; + bool ret = true; + for (const auto& video_manipinfo : planes) { + ret &= vpmanip->Do(video_manipinfo, GetMixedFrameVideoPlaneManipulableInfo_( + video_manipinfo.component, geom)); + } + return ret; +} + +bool MixedFrame::Fill(const VideoPlaneColorManipulator* const vpmanip, + const PlaneComponent& comp, const std::uint32_t& color, + const Geometry& geom) { + VideoPlaneManipulableInfo info = + GetMixedFrameVideoPlaneManipulableInfo_(comp, geom); + return vpmanip->Do(color, info); +} + +bool MixedFrame::Export(BufferKeyType& key) const { + if (IsValid() == false) return false; + key = buffer_->Export(); + return true; +} + +std::uint32_t MixedFrame::CalculateBufferSize_(const std::uint32_t width, + const std::uint32_t height) { + return (width * height * 3) >> 1; +} + +VideoPlaneManipulableInfo MixedFrame::GetMixedFrameVideoPlaneManipulableInfo_( + const PlaneComponent component, const Geometry& geom) { + VideoPlaneManipulableInfo ret; + ret.handle = buffer_->GetBufferHandle(); + ret.component = component; + ret.linesize = GetWidth(); + if (component == PlaneComponent::kYComponent) { + ret.rect = geom; + } else { + ret.rect.x = geom.x / 2; + ret.rect.y = geom.y / 2 + GetHeight(); + ret.rect.w = geom.w / 2; + ret.rect.h = geom.h / 2; + } + return ret; +} + +VideoPlaneManipulableInfo MixedFrame::GetYComponentVMInfo_( + BufferHandleType handle) const { + VideoPlaneManipulableInfo info; + info.handle = handle; + info.linesize = GetWidth(); + info.rect.x = 0; + info.rect.y = 0; + info.rect.w = GetWidth(); + info.rect.h = GetHeight(); + info.component = PlaneComponent::kYComponent; + return info; +} + +VideoPlaneManipulableInfo MixedFrame::GetUVComponentVMInfo_( + BufferHandleType handle) const { + VideoPlaneManipulableInfo info; + info.handle = handle; + info.linesize = GetWidth(); + info.rect.x = 0; + info.rect.y = GetHeight(); + info.rect.w = GetWidth() >> 1; + info.rect.h = GetHeight() >> 1; + info.component = PlaneComponent::kUVComponent; + return info; +} +} // namespace plusplayer \ No newline at end of file diff --git a/src/mixer/src/mixer.cpp b/src/mixer/src/mixer.cpp new file mode 100755 index 0000000..b67e3bc --- /dev/null +++ b/src/mixer/src/mixer.cpp @@ -0,0 +1,14 @@ +#include "mixer/mixer.h" + +#include "core/utils/plusplayer_log.h" +#include "mixer/defaultmixer.h" + +namespace plusplayer { + +std::unique_ptr Mixer::Create() { + auto instance = std::unique_ptr(new DefaultMixer); + LOG_INFO("Create Mixer [%p]", instance.get()); + return instance; +} + +} // namespace plusplayer diff --git a/src/mixer/src/mixer_capi.cpp b/src/mixer/src/mixer_capi.cpp new file mode 100755 index 0000000..8cfb74c --- /dev/null +++ b/src/mixer/src/mixer_capi.cpp @@ -0,0 +1,206 @@ +#include "mixer_capi/mixer_capi.h" + +#include "core/utils/plusplayer_log.h" +#include "mixer/mixer.h" +#include "mixer/mixer_eventlistener.h" +#include "plusplayer/types/display.h" + +using plusplayer::Mixer; + +struct MixerPriv; + +class listener_bridge : public plusplayer::MixerEventListener { + public: + listener_bridge() { LOG_ENTER } + ~listener_bridge() { LOG_ENTER } + + virtual void OnResourceConflicted() { + LOG_ENTER + if (this->resource_conflicted_cb_) + this->resource_conflicted_cb_(resource_conflicted_cb_userdata_); + } + + private: + mixer_resource_conflicted_cb resource_conflicted_cb_ = nullptr; + void* resource_conflicted_cb_userdata_ = nullptr; + + friend int mixer_set_resource_conflicted_cb( + mixer_handle pp, mixer_resource_conflicted_cb resource_conflicted_cb, + void* userdata); +}; + +struct MixerPriv { + std::unique_ptr mixer; + std::unique_ptr listener{new listener_bridge()}; + + friend MixerPriv* MixerPrivCreate(); + friend void MixerPrivDestroy(MixerPriv*& instance); + + private: + MixerPriv() {} + ~MixerPriv() {} +}; + +MixerPriv* MixerPrivCreate() { + MixerPriv* instance = new MixerPriv(); + instance->mixer = Mixer::Create(); + instance->mixer->RegisterListener(instance->listener.get()); + return instance; +} + +void MixerPrivDestroy(MixerPriv*& instance) { + if (instance) delete instance; + instance = nullptr; +} + +inline bool is_null_(void* object) { return object == nullptr; } + +inline Mixer* cast_(mixer_handle mixer) { + auto priv = static_cast(mixer); + return priv->mixer.get(); +} + +inline listener_bridge* listener_cast_(mixer_handle pp) { + auto priv = static_cast(pp); + return priv->listener.get(); +} + +inline int convert_return_type_(bool ret) { + return ret ? MIXER_ERROR_TYPE_NONE : MIXER_ERROR_TYPE_INVALID_OPERATION; +} + +mixer_handle mixer_create() { + LOG_ENTER + mixer_handle mixer = static_cast(MixerPrivCreate()); + return mixer; +} + +int mixer_destroy(mixer_handle handle) { + LOG_ENTER + if (is_null_(handle)) return MIXER_ERROR_TYPE_INVALID_PARAMETER; + + auto priv = static_cast(handle); + MixerPrivDestroy(priv); + + return MIXER_ERROR_TYPE_NONE; +} + +int mixer_start(mixer_handle handle) { + LOG_ENTER + if (is_null_(handle)) return MIXER_ERROR_TYPE_INVALID_PARAMETER; + + return convert_return_type_(cast_(handle)->Start()); +} + +int mixer_stop(mixer_handle handle) { + LOG_ENTER + if (is_null_(handle)) return MIXER_ERROR_TYPE_INVALID_PARAMETER; + + return convert_return_type_(cast_(handle)->Stop()); +} + +int mixer_get_max_allowed_number_of_player(mixer_handle handle) { + LOG_ENTER + if (is_null_(handle)) return MIXER_ERROR_TYPE_INVALID_PARAMETER; + + return cast_(handle)->GetMaximumAllowedNumberOfPlayer(); +} + +int mixer_set_display(mixer_handle handle, mixer_display_type type, + void* window) { + LOG_ENTER + if (is_null_(handle)) return MIXER_ERROR_TYPE_INVALID_PARAMETER; + + return convert_return_type_(cast_(handle)->SetDisplay( + static_cast(type), window)); +} + +int mixer_set_display_mode(mixer_handle handle, mixer_display_mode mode) { + if (is_null_(handle)) return MIXER_ERROR_TYPE_INVALID_PARAMETER; + LOG_INFO("display mode : %d", static_cast(mode)); + return convert_return_type_(cast_(handle)->SetDisplayMode( + static_cast(mode))); +} + +int mixer_set_display_roi(mixer_handle handle, const int x, const int y, + const int width, const int height) { + LOG_ENTER + if (is_null_(handle)) return MIXER_ERROR_TYPE_INVALID_PARAMETER; + LOG_INFO("x : %d, y: %d, width : %d, height : %d", x, y, width, height); + plusplayer::Geometry roi; + roi.x = x; + roi.y = y; + roi.w = width; + roi.h = height; + return convert_return_type_(cast_(handle)->SetDisplayRoi(roi)); +} + +int mixer_set_rsc_alloc_mode(mixer_handle handle, + const mixer_rsc_alloc_mode mode) { + LOG_ENTER + if (is_null_(handle)) return MIXER_ERROR_TYPE_INVALID_PARAMETER; + + return convert_return_type_(cast_(handle)->SetRscAllocMode( + static_cast(mode))); +} + +int mixer_disable_audio_focus_setting(mixer_handle handle) { + LOG_ENTER + if (is_null_(handle)) return MIXER_ERROR_TYPE_INVALID_PARAMETER; + + return convert_return_type_(cast_(handle)->DisableAudioFocusSetting()); +} + +int mixer_set_alternative_video_scaler(mixer_handle handle) { + LOG_ENTER + if (is_null_(handle)) return MIXER_ERROR_TYPE_INVALID_PARAMETER; + + return convert_return_type_(cast_(handle)->SetAlternativeVideoScaler()); +} + +int mixer_set_audio_focus(mixer_handle handle, const void* player_instance) { + LOG_ENTER + if (is_null_(handle)) return MIXER_ERROR_TYPE_INVALID_PARAMETER; + + return convert_return_type_(cast_(handle)->SetAudioFocus(player_instance)); +} + +int mixer_commit(mixer_handle handle) { + LOG_ENTER + if (is_null_(handle)) return MIXER_ERROR_TYPE_INVALID_PARAMETER; + + return convert_return_type_(cast_(handle)->Commit()); +} + +int mixer_set_resolution(mixer_handle handle, const int width, const int height, + const int framerate_num, const int framerate_den) { + LOG_ENTER + if (is_null_(handle)) return MIXER_ERROR_TYPE_INVALID_PARAMETER; + plusplayer::Mixer::ResolutionInfo info; + info.width = width; + info.height = height; + info.framerate_num = framerate_num; + info.framerate_den = framerate_den; + return convert_return_type_(cast_(handle)->SetResolution(info)); +} + +mixer_ticket_h mixer_create_ticket(mixer_handle handle, const void* player_instance) { + LOG_ENTER + if (is_null_(handle)) return nullptr; + + return cast_(handle)->CreateTicket(player_instance); +} + +int mixer_set_resource_conflicted_cb( + mixer_handle handle, mixer_resource_conflicted_cb resource_conflicted_cb, + void* userdata) { + LOG_ENTER + listener_bridge* listener = nullptr; + if (is_null_(handle) || is_null_(listener = listener_cast_(handle))) { + LOG_ERROR("Mixer or Listener object is nil."); + return MIXER_ERROR_TYPE_INVALID_PARAMETER; + } + listener->resource_conflicted_cb_ = resource_conflicted_cb; + listener->resource_conflicted_cb_userdata_ = userdata; + return convert_return_type_(true); +} diff --git a/src/mixer/src/renderer.cpp b/src/mixer/src/renderer.cpp new file mode 100755 index 0000000..af48db1 --- /dev/null +++ b/src/mixer/src/renderer.cpp @@ -0,0 +1,252 @@ +#include "mixer/renderer.h" + +#include "core/utils/plusplayer_log.h" + +namespace plusplayer { +using std::chrono::duration_cast; +using std::chrono::milliseconds; +using std::chrono::system_clock; + +Renderer::Renderer(const RenderableObjectFactory& mf_factory, + const Mixer::ResolutionInfo& rinfo, + RendererEventListener* listener) + : resolution_info_(rinfo), + listener_(listener), + frame_(RenderableObjectPtr(mf_factory.CreateRenderableObject( + resolution_info_.width, resolution_info_.height))) {} + +Renderer::~Renderer() { Stop(); } + +bool Renderer::IsValid() const { + if (frame_ == nullptr) return false; + if (frame_->IsValid() == false) return false; + return IsValid_(); +} + +bool Renderer::Start() { + std::unique_lock lk(rendering_mtx_); + if (IsValid() == false) return false; + if (rendering_worker_.joinable()) return false; + rendering_flag_ = true; + rendering_worker_ = std::thread(&Renderer::RenderingWorker_, this); + return true; +} + +bool Renderer::Stop() { + { + std::unique_lock lk(rendering_mtx_); + if (rendering_worker_.joinable() == false) return false; + rendering_flag_ = false; + rendering_cv_.notify_all(); + } + rendering_worker_.join(); + return true; +} + +bool Renderer::ChangeResolution(const RenderableObjectFactory& mf_factory, + const std::uint32_t& width, + const std::uint32_t& height) { + if (width == 0 || height == 0) return false; + if (width == static_cast(resolution_info_.width) && + height == static_cast(resolution_info_.height)) + return false; + return ChangeResolutionInternal_(mf_factory, width, height); +} + +bool Renderer::ChangeResolutionInternal_( + const RenderableObjectFactory& mf_factory, const std::uint32_t& width, + const std::uint32_t& height) { + std::unique_lock lk(rendering_mtx_); + resolution_info_.width = width; + resolution_info_.height = height; + frame_.reset(nullptr); + frame_ = RenderableObjectPtr(mf_factory.CreateRenderableObject( + resolution_info_.width, resolution_info_.height)); + return IsValid(); +} + +bool Renderer::ChangeRenderingSpeed(const std::uint32_t framerate_num, + const std::uint32_t framerate_den) { + if (framerate_num == 0 || framerate_den == 0) return false; + if (framerate_num == + static_cast(resolution_info_.framerate_num) && + framerate_den == + static_cast(resolution_info_.framerate_den)) + return false; + std::unique_lock lk(rendering_mtx_); + if (IsValid() == false) return false; + resolution_info_.framerate_num = framerate_num; + resolution_info_.framerate_den = framerate_den; + return true; +} + +bool Renderer::Render(const VideoPlaneScaler* const scaler, + const VideoPlaneCollection* const planes, + const Geometry& geom) { + if (scaler == nullptr) return false; + if (planes == nullptr) return false; + return RenderInternal_(scaler->GetScaleManipulator(), + planes->GetVideoPlaneManipInfo(), geom); +} + +bool Renderer::RenderInternal_( + const VideoPlaneManipulator* const scaler, + const std::vector& planes, + const Geometry& geom) { + if (scaler == nullptr) return false; + const auto before = system_clock::now(); + std::unique_lock lk(rendering_mtx_); + if (IsValid() == false) return false; + LOG_INFO("[PERF] RenderInternal_ Lock [%llu]ms", + std::chrono::duration_cast( + system_clock::now() - before) + .count()); + const auto ret = frame_->Render(scaler, planes, geom); + LOG_INFO("[PERF] Scale [%llu]ms", + std::chrono::duration_cast( + system_clock::now() - before) + .count()); + return ret; +} + +bool Renderer::Mute(const VideoPlaneColorFiller* const filler, + const Geometry& geom) { + if (filler == nullptr) return false; + return MuteInternal_(filler->GetColorFillManipulator(), geom); +} + +bool Renderer::MuteInternal_(const VideoPlaneColorManipulator* const filler, + const Geometry& geom) { + if (filler == nullptr) return false; + std::unique_lock lk(rendering_mtx_); + if (IsValid() == false) return false; + bool ret = true; + ret &= frame_->Fill(filler, PlaneComponent::kYComponent, 0x00, geom); + ret &= frame_->Fill(filler, PlaneComponent::kUVComponent, 0x8080, geom); + return ret; +} + +bool Renderer::Move(const VideoPlaneColorFiller* const filler, + const std::vector& moving_planes) { + if (filler == nullptr) return false; + if (moving_planes.size() == 0) return false; + return MoveInternal_(filler->GetColorFillManipulator(), moving_planes); +} + +bool Renderer::MoveInternal_( + const VideoPlaneColorManipulator* const filler, + const std::vector& moving_planes) { + if (filler == nullptr) return false; + std::unique_lock lk(rendering_mtx_); + if (IsValid() == false) return false; + bool ret = true; + for (const auto& move : moving_planes) { + if (IsSameGeometry_(move.cur, move.target)) continue; + ret &= frame_->Fill(filler, PlaneComponent::kYComponent, 0x00, move.cur); + ret &= frame_->Fill(filler, PlaneComponent::kUVComponent, 0x8080, move.cur); + } + return true; +}; + +bool Renderer::OnRenderingBefore_(const RenderableObject* const frame) { + return true; +} + +bool Renderer::IsValid_() const { return true; } + +bool Renderer::RaiseOnRenderingReleaseEvent_(const BufferKeyType& key) { + if (listener_ == nullptr) return false; + return listener_->OnRenderingRelease(key); +} + +std::uint32_t Renderer::GetNextRenderingTimeWithJitter_( + const JitterType& jitter) const { + const static std::int64_t kOneSecondInMs = 1000; // ms + auto next = kOneSecondInMs / + (resolution_info_.framerate_num / resolution_info_.framerate_den); + auto jitter_in_ms = jitter.count(); + LOG_DEBUG("[PERF] jitter : [%lld]ms / next : [%lld]ms", jitter_in_ms, next); + jitter_in_ms %= next; + next -= jitter_in_ms; + return next < 0 ? 0 : static_cast(next); +} + +void Renderer::RenderingWorker_() { + LOG_DEBUG("Start Rendering"); + JitterType jitter(0); + while (1) { + LOG_DEBUG("= Start New Frame ==========================================="); + auto before_1 = system_clock::now(); + std::unique_lock lk(rendering_mtx_); + if (rendering_flag_ == false) break; + if (OnRenderingBefore_(frame_.get()) == false) { + LOG_WARN("OnRenderingBefore_ Failed"); + continue; + } + + LOG_DEBUG( + "[PERF] before_1 ~ now : [%lld]ms", + duration_cast(system_clock::now() - before_1).count()); + jitter += duration_cast(system_clock::now() - before_1); + + std::uint64_t next_in_ms = GetNextRenderingTimeWithJitter_(jitter); + + jitter = JitterType::zero(); + auto before_2 = system_clock::now(); + LOG_DEBUG("[PULSE] it will awake after [%llu]ms", next_in_ms); + if (next_in_ms > 0) { + rendering_cv_.wait_for(lk, milliseconds(next_in_ms), + [this] { return rendering_flag_ == false; }); + // LOG_DEBUG( + // "[PERF] before_2 ~ after awake : [%lld]ms", + // duration_cast(system_clock::now() - + // before_2).count()); + if (rendering_flag_ == false) break; + } + if (IsValid() == false) continue; + + BufferKeyType key; + if (frame_->Export(key) == false) { + LOG_ERROR("MixedFrame Export Failed"); + continue; + } + + RaiseOnRenderingReleaseEvent_(key); + jitter = duration_cast(system_clock::now() - before_2 - + milliseconds(next_in_ms)); + // LOG_DEBUG( + // "[PERF] before_2 ~ now : [%lld]ms / next_in_ms : [%llu]ms", + // duration_cast(system_clock::now() - before_2).count(), + // next_in_ms); + } + std::unique_lock lk(rendering_mtx_); + rendering_flag_ = false; + LOG_DEBUG("Rendering Stopped"); +} + +const Mixer::ResolutionInfo& Renderer::GetResolutionInfo_() const { + return resolution_info_; +} + +RenderableObjectPtr& Renderer::GetMixedFrame_() { return frame_; } + +void Renderer::AcquireRenderingLock_() { rendering_mtx_.lock(); } + +void Renderer::ReleaseRenderingLock_() { rendering_mtx_.unlock(); } + +Geometry Renderer::MakeGeometry_(const std::uint32_t& width, + const std::uint32_t& height) { + Geometry geom; + geom.w = width; + geom.h = height; + return geom; +} + +bool Renderer::IsSameGeometry_(const Geometry& g1, const Geometry& g2) { + if (g1.x != g2.x) return false; + if (g1.y != g2.y) return false; + if (g1.w != g2.w) return false; + if (g1.h != g2.h) return false; + return true; +} +} // namespace plusplayer \ No newline at end of file diff --git a/src/mixer/src/sys/tbminterface.cpp b/src/mixer/src/sys/tbminterface.cpp new file mode 100755 index 0000000..094a7f3 --- /dev/null +++ b/src/mixer/src/sys/tbminterface.cpp @@ -0,0 +1,50 @@ +#include "mixer/sys/tbminterface.h" + +#include +#include + +namespace plusplayer { +namespace tizen { +BufferDefaultType TBMInterface::BoRef(BufferDefaultType bo) { + return tbm_bo_ref(bo); +} +void TBMInterface::BoUnRef(BufferDefaultType bo) { tbm_bo_unref(bo); } + +int TBMInterface::BoSize(BufferDefaultType bo) { return tbm_bo_size(bo); } + +BufferUnionHandleType TBMInterface::BoGetHandle(BufferDefaultType bo, + int device) { + return tbm_bo_get_handle(bo, device); +} + +BufferUnionHandleType TBMInterface::BoMap(BufferDefaultType bo, int device, + int option) { + return tbm_bo_map(bo, device, option); +} + +void TBMInterface::BoUnmap(BufferDefaultType bo) { tbm_bo_unmap(bo); } + +BufferKeyType TBMInterface::BoExport(BufferDefaultType bo) { + return tbm_bo_export(bo); +} + +BufferDefaultType TBMInterface::BoAlloc(tbm_bufmgr bufmgr, int size, int flag) { + return tbm_bo_alloc(bufmgr, size, flag); +} +BufferDefaultType TBMInterface::BoImport(tbm_bufmgr bufmgr, BufferKeyType key) { + return tbm_bo_import(bufmgr, key); +} + +int TBMInterface::GAScale(tbm_bufmgr bufmgr, GraphicsGAScaleInfo* info) { + return Gfx_GA_Scale(bufmgr, info); +} + +int TBMInterface::GACopy(tbm_bufmgr bufmgr, GraphicsGABltRopInfo* info) { + return Gfx_GA_BltRop(bufmgr, info); +} + +int TBMInterface::GAFill(tbm_bufmgr bufmgr, GraphicsGAFillRectInfo* info) { + return Gfx_GA_FillRect(bufmgr, info); +} +} // namespace tizen +} // namespace plusplayer \ No newline at end of file diff --git a/src/mixer/src/tizen/tizenaccessiblebufferobj.cpp b/src/mixer/src/tizen/tizenaccessiblebufferobj.cpp new file mode 100755 index 0000000..49cab1f --- /dev/null +++ b/src/mixer/src/tizen/tizenaccessiblebufferobj.cpp @@ -0,0 +1,23 @@ +#include "mixer/tizen/tizenaccessiblebufferobj.h" + +namespace plusplayer { + +TizenAccessibleBufferObject::TizenAccessibleBufferObject( + BufferDefaultType buffer) + : TizenBufferObject(buffer) {} + +TizenAccessibleBufferObject::PhyAddrAccessorPtr +TizenAccessibleBufferObject::GetReadableAddress() const { + if (IsValid() == false) return nullptr; + return PhyAddrAccessorPtr( + new TizenReadableBufferPhyAddrAccessor(GetBuffer_())); +} + +TizenAccessibleBufferObject::PhyAddrAccessorPtr +TizenAccessibleBufferObject::GetWritableAddress() const { + if (IsValid() == false) return nullptr; + return PhyAddrAccessorPtr( + new TizenWritableBufferPhyAddrAccessor(GetBuffer_())); +} + +} // namespace plusplayer \ No newline at end of file diff --git a/src/mixer/src/tizen/tizenbufferkeyvideoframe.cpp b/src/mixer/src/tizen/tizenbufferkeyvideoframe.cpp new file mode 100755 index 0000000..9d10b5d --- /dev/null +++ b/src/mixer/src/tizen/tizenbufferkeyvideoframe.cpp @@ -0,0 +1,34 @@ +#include "mixer/tizen/tizenbufferkeyvideoframe.h" + +namespace plusplayer { + +TizenBufferKeyVideoFrame::TizenBufferKeyVideoFrame( + const TizenBufferManager* const bufmgr, const BufferKeyType& key, + const std::uint32_t& width, const std::uint32_t& height) + : key_(key), width_(width), height_(height) { + if (bufmgr == nullptr) return; + if (key_ == 0) return; + if (width_ == 0 || height_ == 0) return; + + auto buffer = BufferObjectPtr(bufmgr->Import(key_)); + if (buffer == nullptr) return; + RegisterVideoPlaneManipulablePtr_(VideoPlaneManipulablePtr( + new YComponentVideoPlaneWithSharedMemory(buffer, width_, height_))); + RegisterVideoPlaneManipulablePtr_(VideoPlaneManipulablePtr( + new UVComponentVideoPlaneWithSharedMemory(buffer, width_, height_))); +} + +bool TizenBufferKeyVideoFrame::IsValid_() const { + if (key_ == 0) return false; + return true; +} + +const std::uint32_t TizenBufferKeyVideoFrame::GetWidth_() const { + return width_; +} + +const std::uint32_t TizenBufferKeyVideoFrame::GetHeight_() const { + return height_; +} + +} // namespace plusplayer \ No newline at end of file diff --git a/src/mixer/src/tizen/tizendefaultphyaddraccessor.cpp b/src/mixer/src/tizen/tizendefaultphyaddraccessor.cpp new file mode 100755 index 0000000..681c8ae --- /dev/null +++ b/src/mixer/src/tizen/tizendefaultphyaddraccessor.cpp @@ -0,0 +1,12 @@ +#include "mixer/tizen/tizendefaultphyaddraccessor.h" + +namespace plusplayer { + +TizenDefaultPhyAddrAccessor::TizenDefaultPhyAddrAccessor(std::uint32_t viraddr) + : viraddr_(viraddr) {} + +BufferPhysicalAddrType TizenDefaultPhyAddrAccessor::GetAddress() { + return reinterpret_cast(viraddr_); +} + +} // namespace plusplayer \ No newline at end of file diff --git a/src/mixer/src/tizen/tizenhwbufferobj.cpp b/src/mixer/src/tizen/tizenhwbufferobj.cpp new file mode 100755 index 0000000..2ca0a4d --- /dev/null +++ b/src/mixer/src/tizen/tizenhwbufferobj.cpp @@ -0,0 +1,28 @@ +#include "mixer/tizen/tizenhwbufferobj.h" + +// #include "mixer/tizen/tizendefaultphyaddraccessor.h" + +namespace plusplayer { +TizenHWBufferObject::TizenHWBufferObject(const std::uint32_t& width, + const std::uint32_t& height, + const DecodedRawPlaneInfo& info) + : width_(width), height_(height), info_(info) {} + +bool TizenHWBufferObject::IsValid() const { + if (info_.phyaddr == 0) return false; + if (width_ == 0 || height_ == 0 || info_.linesize == 0) return false; + return true; +} + +BufferHandleType TizenHWBufferObject::GetBufferHandle() const { + if (IsValid() == false) return kInvalidBufferHandle; + return static_cast(info_.phyaddr); +} + +BufferKeyType TizenHWBufferObject::Export() const { return kInvalidBufferKey; } + +std::uint32_t TizenHWBufferObject::GetSize() const { + return height_ * info_.linesize; +} + +} // namespace plusplayer diff --git a/src/mixer/src/tizen/tizenhwvideoframe.cpp b/src/mixer/src/tizen/tizenhwvideoframe.cpp new file mode 100755 index 0000000..136d12e --- /dev/null +++ b/src/mixer/src/tizen/tizenhwvideoframe.cpp @@ -0,0 +1,44 @@ +#include "mixer/tizen/tizenhwvideoframe.h" + +#include "core/utils/plusplayer_log.h" +#include "mixer/tizen/tizenhwbufferobj.h" +#include "mixer/videoplane.h" + +namespace plusplayer { + +TizenHWVideoFrame::TizenHWVideoFrame(const DecodedRawInfo& info) : info_(info) { + if (IsValid_() == false) return; + + RegisterVideoPlaneManipulablePtr_(VideoPlaneManipulablePtr( + new YComponentVideoPlane(BufferObjectPtr(new TizenHWBufferObject( + info_.width, info_.height, info_.y_info)), + info_.width, info_.height))); + RegisterVideoPlaneManipulablePtr_( + VideoPlaneManipulablePtr(new UVComponentVideoPlane( + BufferObjectPtr(new TizenHWBufferObject( + info_.width / 2, info_.height / 2, info_.uv_info)), + info_.width, info_.height))); +} + +bool TizenHWVideoFrame::IsValid_() const { + if (info_.y_info.phyaddr == 0) return false; + if (info_.y_info.linesize == 0) return false; + if (info_.uv_info.phyaddr == 0) return false; + if (info_.uv_info.linesize == 0) return false; + return true; +} + +const std::uint32_t TizenHWVideoFrame::GetWidth_() const { return info_.width; } + +const std::uint32_t TizenHWVideoFrame::GetHeight_() const { + return info_.height; +} + +void TizenHWVideoFrame::PrintDecodedRawInfo() const { + LOG_DEBUG("WxH [%ux%u] Y [%u %u %u] / UV [%u %u %u]", info_.width, + info_.height, info_.y_info.phyaddr, info_.y_info.viraddr, + info_.y_info.linesize, info_.uv_info.phyaddr, info_.uv_info.viraddr, + info_.uv_info.linesize); +} + +} // namespace plusplayer \ No newline at end of file diff --git a/src/mixer/src/tizen/tizenrenderableobj_factory.cpp b/src/mixer/src/tizen/tizenrenderableobj_factory.cpp new file mode 100755 index 0000000..e678026 --- /dev/null +++ b/src/mixer/src/tizen/tizenrenderableobj_factory.cpp @@ -0,0 +1,16 @@ +#include "mixer/tizen/tizenrenderableobj_factory.h" + +#include "mixer/mixedframe.h" + +namespace plusplayer { + +TizenRenderableObjectFactory::TizenRenderableObjectFactory( + const MemoryAllocator* const memallocator) + : memallocator_(memallocator) {} + +RenderableObject* TizenRenderableObjectFactory::CreateRenderableObject( + const std::uint32_t width, const std::uint32_t height) const { + return new MixedFrame(memallocator_, width, height); +} + +} // namespace plusplayer \ No newline at end of file diff --git a/src/mixer/src/tizen/tizensurfacevideoframe.cpp b/src/mixer/src/tizen/tizensurfacevideoframe.cpp new file mode 100755 index 0000000..ed6030c --- /dev/null +++ b/src/mixer/src/tizen/tizensurfacevideoframe.cpp @@ -0,0 +1,43 @@ +#include "mixer/tizen/tizensurfacevideoframe.h" + +#include +#include + +#include "mixer/tizen/tizenbufferobj.h" +#include "mixer/videoplane.h" + +namespace plusplayer { + +TizenSurfaceVideoFrame::TizenSurfaceVideoFrame(DecodedVideoPacketExPtr dvp) + : dvp_(std::move(dvp)) { + auto surface = dvp_->GetTbmSurface(); + if (surface == nullptr) return; + + width_ = tbm_surface_get_width(surface); + height_ = tbm_surface_get_height(surface); + if (width_ == 0 || height_ == 0) return; + + auto y_bo = tbm_surface_internal_get_bo(surface, kYIndex); + auto uv_bo = tbm_surface_internal_get_bo(surface, kUVIndex); + if (y_bo == nullptr || uv_bo == nullptr) return; + + RegisterVideoPlaneManipulablePtr_( + VideoPlaneManipulablePtr(new YComponentVideoPlane( + BufferObjectPtr(new TizenBufferObject(y_bo)), width_, height_))); + RegisterVideoPlaneManipulablePtr_( + VideoPlaneManipulablePtr(new UVComponentVideoPlane( + BufferObjectPtr(new TizenBufferObject(uv_bo)), width_, height_))); +} + +bool TizenSurfaceVideoFrame::IsValid_() const { + if (dvp_ == nullptr) return false; + return true; +} + +const std::uint32_t TizenSurfaceVideoFrame::GetWidth_() const { return width_; } + +const std::uint32_t TizenSurfaceVideoFrame::GetHeight_() const { + return height_; +} + +} // namespace plusplayer \ No newline at end of file diff --git a/src/mixer/src/videoplane.cpp b/src/mixer/src/videoplane.cpp new file mode 100755 index 0000000..64ccbc8 --- /dev/null +++ b/src/mixer/src/videoplane.cpp @@ -0,0 +1,114 @@ +#include "mixer/videoplane.h" + +#include "core/utils/plusplayer_log.h" + +namespace plusplayer { + +/****************************************************************************** + * VideoPlane + */ +VideoPlane::VideoPlane(PlaneComponent component, const std::uint32_t& width, + const std::uint32_t& height) + : component_(component), width_(width), height_(height) {} + +bool VideoPlane::IsValid() const { + if (GetBufferObject_() == nullptr) return false; + if (width_ == 0 || height_ == 0 || GetLineSize_() == 0) return false; + return true; +} + +VideoPlaneManipulableInfo VideoPlane::GetVideoPlaneManipulableInfo() const { + VideoPlaneManipulableInfo info; + info.component = component_; + info.handle = GetBufferObject_()->GetBufferHandle(); + info.linesize = GetLineSize_(); + info.rect.x = width_ * croparea_.scale_x; + info.rect.y = height_ * croparea_.scale_y; + info.rect.w = width_ * croparea_.scale_w; + info.rect.h = height_ * croparea_.scale_h; + return info; +} + +void VideoPlane::SetCropArea(const CropArea& croparea) { croparea_ = croparea; } + +std::uint32_t VideoPlane::GetLineSize_() const { + return GetBufferObject_()->GetSize() / height_; +} + +/****************************************************************************** + * YComponentVideoPlane + */ + +YComponentVideoPlane::YComponentVideoPlane(BufferObjectPtr buffer, + const std::uint32_t& width, + const std::uint32_t& height) + : VideoPlane(PlaneComponent::kYComponent, width, height), + buffer_(std::move(buffer)) {} + +const BufferObject* const YComponentVideoPlane::GetBufferObject_() const { + return buffer_.get(); +} + +/****************************************************************************** + * UVComponentVideoPlane + */ + +UVComponentVideoPlane::UVComponentVideoPlane(BufferObjectPtr buffer, + const std::uint32_t& width, + const std::uint32_t& height) + : VideoPlane(PlaneComponent::kUVComponent, width / 2, height / 2), + buffer_(std::move(buffer)) {} + +const BufferObject* const UVComponentVideoPlane::GetBufferObject_() const { + return buffer_.get(); +} + +/****************************************************************************** + * YComponentVideoPlaneWithSharedMemory + */ + +YComponentVideoPlaneWithSharedMemory::YComponentVideoPlaneWithSharedMemory( + BufferObjectWeakPtr buffer, const std::uint32_t& width, + const std::uint32_t& height) + : VideoPlane(PlaneComponent::kYComponent, width, height), + buffer_(buffer), + width_(width) {} + +std::uint32_t YComponentVideoPlaneWithSharedMemory::GetLineSize_() const { + return width_; +} + +const BufferObject* const +YComponentVideoPlaneWithSharedMemory::GetBufferObject_() const { + return buffer_.get(); +} + +/****************************************************************************** + * UVComponentVideoPlaneWithSharedMemory + */ + +UVComponentVideoPlaneWithSharedMemory::UVComponentVideoPlaneWithSharedMemory( + BufferObjectWeakPtr buffer, const std::uint32_t& width, + const std::uint32_t& height) + : VideoPlane(PlaneComponent::kUVComponent, width / 2, height / 2), + buffer_(buffer), + width_(width), + height_(height) {} + +VideoPlaneManipulableInfo +UVComponentVideoPlaneWithSharedMemory::GetVideoPlaneManipulableInfo() const { + auto info = VideoPlane::GetVideoPlaneManipulableInfo(); + info.rect.y += height_; + return info; +} + +std::uint32_t UVComponentVideoPlaneWithSharedMemory::GetLineSize_() const { + return width_; +} + +const BufferObject* const +UVComponentVideoPlaneWithSharedMemory::GetBufferObject_() const { + return buffer_.get(); +} + +} // namespace plusplayer \ No newline at end of file diff --git a/src/plusplayer-core/Build/appendix.mk b/src/plusplayer-core/Build/appendix.mk new file mode 100755 index 0000000..2e06c34 --- /dev/null +++ b/src/plusplayer-core/Build/appendix.mk @@ -0,0 +1 @@ +# Appendix diff --git a/src/plusplayer-core/Build/basedef.mk b/src/plusplayer-core/Build/basedef.mk new file mode 100755 index 0000000..a7a0869 --- /dev/null +++ b/src/plusplayer-core/Build/basedef.mk @@ -0,0 +1,34 @@ +# Add inputs and outputs from these tool invocations to the build variables + + +OS_NAME := $(shell $(UNAME)) + + +#ifeq ($(origin BUILD_CONFIG), undefined) +BUILD_CONFIG ?= Debug +#endif + +#ifeq ($(origin ARCH), undefined) +ARCH ?= i386 +#endif + +#ifeq ($(origin PROJPATH), undefined) +PROJPATH ?= . +#endif + + +#ifeq ($(origin PROJ_PATH), undefined) +PROJ_PATH ?= $(PROJPATH) +#endif + +#ifeq ($(strip $(OUTPUT_DIR)),) +#OUTPUT_DIR ?= $(PROJ_PATH)/$(BUILD_CONFIG) +#endif + +#ifeq ($(strip $(BUILD_ARCH)),) +BUILD_ARCH ?= $(ARCH) +#endif + +#ifeq ($(strip $(ENVENTOR_PATH)),) +ENVENTOR_PATH ?= $(SDK_TOOLPATH)/enventor +#endif diff --git a/src/plusplayer-core/Build/build_c.mk b/src/plusplayer-core/Build/build_c.mk new file mode 100755 index 0000000..4ef9699 --- /dev/null +++ b/src/plusplayer-core/Build/build_c.mk @@ -0,0 +1,113 @@ +# C/C++ build script + + +_FUNC_EXT2O = $(patsubst %.$(3),$(1)/%.o,$(2)) +_FUNC_C2O = $(call _FUNC_EXT2O,$(1),$(2),c) +_FUNC_CPP2O = $(call _FUNC_EXT2O,$(1),$(2),cpp) + + +# parameter : +# $(1) - C/C++ soruce file +# $(2) - output path +# $(3) - .ext +# $(4) - unique id +CONVERT_ESC_EXT_TO_O = $(addprefix $(2)/,$(notdir $(patsubst %.$(3),%-$(4).o,$(1)))) + +#CONVERT_ESC_C_TO_O = $(call CONVERT_ESC_EXT_TO_O,$(1),$(2),c) +#CONVERT_ESC_CPP_TO_O = $(call CONVERT_ESC_EXT_TO_O,$(1),$(2),cpp) + + +# parameter : +# $(1) - encoded one C/C++ soruce file +# $(2) - output path +# $(3) - ext title (C/C++) +# $(4) - ext (c/cpp) +# $(5) - compiler ($(CC)/$(CXX)) +# $(6) - build opt +# $(7) - build opt file +# output : +# $(8) - output files list +define C_BUILD_PROC_RAW +$(call CONVERT_ESC_EXT_TO_O,$(1),$(2),$(4),$(8)) : $(call DECODE_4MAKE,$(1)) $(7) + @echo ' Building file: $$<' + @echo ' Invoking: $(3) Compiler' + $$(call MAKEDIRS,$$(@D)) + $(5) -c "$$<" -o "$$@" $(6) -Wp,@$(7) + @echo ' Finished building: $$<' +$(9) += $(call CONVERT_ESC_EXT_TO_O,$(1),$(2),$(4),$(8)) +endef + + +# parameter : +# $(1) - output paths +# $(2) - src paths +# $(3) - inc paths +# $(4) - inc files +# $(5) - Defs +# $(6) - UnDefs +# $(7) - compiler opt +# $(8) - compiler opt file +# $(9) - ext title (C/C++) +# $(10) - ext (c/cpp) +# $(11) - compiler ($(CC)/$(CXX)) +# output : +# $(12) - OBJS +# return : +# none +define C_PROC_RAW + +_OUTPUT_DIR := $$(strip $(1))# +_SRCS := $(2)# +_INCS := $(3)# +_INC_FILES := $(4)# +_DEFS := $(5)# +_UNDEFS := $(6)# + +_OPT := $(7) +_OPT_FILE := $(8) + +_EXT_TITLE := $(9) +_EXT := $(10) +_COMPILER := $(11) + +#_OUTPUT_FILES := $(12) + +_ENC_SRCS := $$(call ENCODE_4MAKE,$$(_SRCS)) +_ENC_SRCS := $$(filter %.$$(_EXT),$$(_ENC_SRCS)) + +ifneq ($$(strip $$(_SRCS)),) + +_NORMAL_SRCS := $$(filter-out %*.$$(_EXT),$$(_ENC_SRCS)) +_WIDLCARD_SRCS := $$(filter %*.$$(_EXT),$$(_ENC_SRCS)) + +_ALL_SRCS := $$(call DECODE_4MAKE,$$(_NORMAL_SRCS)) \ + $$(foreach var,$$(_WIDLCARD_SRCS),$$(call FIND_FILES_4MAKE,$$(call DECODE_4MAKE,$$(var)))) + +ifneq ($$(strip $$(_ALL_SRCS)),) + +_ENC_SRCS := $$(call ENCODE_4MAKE,$$(_ALL_SRCS)) + +_CDEFS := $$(CDEFS) +_CDEFS += $$(addprefix -D,$$(_DEFS)) +_CDEFS += $$(addprefix -U,$$(_UNDEFS)) + +_ENC_C_INCS := $$(call ENCODE_4MAKE,$$(_INCS)) +_ENC_C_INCS := $$(addprefix -I,$$(_ENC_C_INCS)) + +_ENC_INC_FILES := $$(call ENCODE_4MAKE,$$(_INC_FILES)) +_ENC_INC_FILES += $$(addprefix -include,$$(_ENC_INC_FILES)) + +_C_INCS := $$(call DECODE_4MAKE,$$(_ENC_C_INCS) $$(_ENC_C_INC_FILES)) + +_DEFS := $$(_CDEFS) $$(_C_INCS) -I"pch" $$(_OPT) + +_UNIQUE_ID = $$(firstword $$(shell echo $$(var) | $$(CKSUM))) + +$$(foreach var,$$(_ENC_SRCS),$$(eval $$(call C_BUILD_PROC_RAW,$$(var),$$(_OUTPUT_DIR),$$(_EXT_TITLE),$$(_EXT),$$(_COMPILER),$$(_DEFS),$$(_OPT_FILE),$$(_UNIQUE_ID),$(12)))) + +endif # (_(strip _(_ALL_SRCS)),) + +endif # (_(strip _(_SRCS)),) + + +endef diff --git a/src/plusplayer-core/Build/build_edc.mk b/src/plusplayer-core/Build/build_edc.mk new file mode 100755 index 0000000..27ee648 --- /dev/null +++ b/src/plusplayer-core/Build/build_edc.mk @@ -0,0 +1,81 @@ +# EDC build script + + +FUNC_EDC2EDJ = $(patsubst %.edc,$(2)/%.edj,$(1)) + +# parameter : +# $(1) - C/C++ soruce file +# $(2) - output path +CONVERT_ESC_EDC_TO_EDJ = $(call CONVERT_4MAKE_TO_OUT,$(call FUNC_EDC2EDJ,$(1),$(2))) + + +# parameter : +# $(1) - encoded one C/C++ soruce file +# $(2) - output path +# $(3) - build opt +# output : +# $(4) - output files list +define EDJ_BUILD_PROC_RAW +$(call CONVERT_ESC_EDC_TO_EDJ,$(1),$(2)) : $(call DECODE_4MAKE,$(1)) + @echo ' Building file: $$<' + @echo ' Invoking: EDC Resource Compiler' + $$(call MAKEDIRS,$$(@D)) + $$(EDJE_CC) $(3) "$$<" "$$@" + @echo ' Finished building: $$<' +$(4) += $(call CONVERT_ESC_EDC_TO_EDJ,$(1),$(2)) +endef + + +# parameter : +# $(1) - output paths +# $(2) - src paths +# $(3) - image inc paths +# $(4) - sound inc paths +# $(5) - font inc paths +# output : +# $(6) - OBJS +# return : +# none +define EDJ_PROC_RAW + +_OUTPUT_DIR := $$(strip $(1))# +_SRCS := $(2)# +_IMAGE_DIRS := $(3)# +_SOUND_DIRS := $(4)# +_FONT_DIRS := $(5)# + +ifneq ($$(strip $$(_SRCS)),) + +_ENC_SRCS := $$(call ENCODE_4MAKE,$$(_SRCS)) + +_NORMAL_SRCS := $$(filter-out %*.edc,$$(_ENC_SRCS)) +_WIDLCARD_SRCS := $$(filter %*.edc,$$(_ENC_SRCS)) + +_ALL_SRCS := $$(call DECODE_4MAKE,$$(_NORMAL_SRCS)) \ + $$(foreach var,$$(_WIDLCARD_SRCS),$$(call FIND_FILES_4MAKE,$$(call DECODE_4MAKE,$$(var)))) + +ifneq ($$(strip $$(_ALL_SRCS)),) + +_ENC_SRCS := $$(call ENCODE_4MAKE,$$(_ALL_SRCS)) + +_COMPILER_FLAGS := -id "$$(ENVENTOR_SHARED_RES_PATH)/images" +_COMPILER_FLAGS += -sd "$$(ENVENTOR_SHARED_RES_PATH)/sounds" +_COMPILER_FLAGS += -fd "$$(ENVENTOR_SHARED_RES_PATH)/fonts" + +ifneq ($$(strip $$(_IMAGE_DIRS)),) +_COMPILER_FLAGS += $$(addprefix -id ,$$(_IMAGE_DIRS)) +endif +ifneq ($$(strip $$(_SOUND_DIRS)),) +_COMPILER_FLAGS += $$(addprefix -sd ,$$(_SOUND_DIRS)) +endif +ifneq ($$(strip $$(_FONT_DIRS)),) +_COMPILER_FLAGS += $$(addprefix -fd ,$$(_FONT_DIRS)) +endif + +$$(foreach var,$$(_ENC_SRCS),$$(eval $$(call EDJ_BUILD_PROC_RAW,$$(var),$$(_OUTPUT_DIR),$$(_COMPILER_FLAGS),$(6)))) + +endif # (_(strip _(_ALL_SRCS)),) + +endif # (_(strip _(_SRCS)),) + +endef diff --git a/src/plusplayer-core/Build/build_po.mk b/src/plusplayer-core/Build/build_po.mk new file mode 100755 index 0000000..3453614 --- /dev/null +++ b/src/plusplayer-core/Build/build_po.mk @@ -0,0 +1,64 @@ +# PO build script + + +_FUNC_PO2MO = $(patsubst %.po,$(2)/res/locale/%/LC_MESSAGES/$(3).mo,$(notdir $(1))) + + +# parameter : +# $(1) - C/C++ soruce file +# $(2) - output path +# $(3) - app name +CONVERT_ESC_PO_TO_MO = $(call CONVERT_4MAKE_TO_OUT,$(call _FUNC_PO2MO,$(1),$(2),$(3))) + + +# parameter : +# $(1) - encoded one C/C++ soruce file +# $(2) - output path +# $(3) - app name +# output : +# $(4) - output files list +define MO_BUILD_PROC_RAW +$(call CONVERT_ESC_PO_TO_MO,$(1),$(2),$(3)) : $(call DECODE_4MAKE,$(1)) + @echo ' Building file: $$<' + @echo ' Invoking: msgfmt String Formatter' + $$(call MAKEDIRS,$$(@D)) + $$(MSGFMT) -o "$$@" "$$<" + @echo ' Finished building: $$<' +$(4) += $(call CONVERT_ESC_PO_TO_MO,$(1),$(2),$(3)) +endef + + +# parameter : +# $(1) - output dir +# $(2) - src paths +# $(3) - app name +# output : +# $(4) - OBJS + +define MO_PROC_RAW + +_OUTPUT_DIR := $(1) +_SRCS := $(2) +_APPNAME := $(3) + +ifneq ($$(strip $$(_SRCS)),) + +_ENC_SRCS := $$(call ENCODE_4MAKE,$$(_SRCS)) + +_NORMAL_SRCS := $$(filter-out %*.po,$$(_ENC_SRCS)) +_WIDLCARD_SRCS := $$(filter %*.po,$$(_ENC_SRCS)) + +_ALL_SRCS := $$(call DECODE_4MAKE,$$(_NORMAL_SRCS)) \ + $$(foreach var,$$(_WIDLCARD_SRCS),$$(call FIND_FILES_4MAKE,$$(call DECODE_4MAKE,$$(var)))) + +ifneq ($$(strip $$(_ALL_SRCS)),) + +_ENC_SRCS := $$(call ENCODE_4MAKE,$$(_ALL_SRCS)) + +$$(foreach var,$$(_ENC_SRCS),$$(eval $$(call MO_BUILD_PROC_RAW,$$(var),$$(_OUTPUT_DIR),$$(_APPNAME),$(4)))) + +endif # (_(strip _(_ALL_SRCS)),) + +endif # (_(strip _(_SRCS)),) + +endef diff --git a/src/plusplayer-core/Build/flags.mk b/src/plusplayer-core/Build/flags.mk new file mode 100755 index 0000000..5c0a812 --- /dev/null +++ b/src/plusplayer-core/Build/flags.mk @@ -0,0 +1,23 @@ +ifeq ($(strip $(BUILD_CONFIG)),Debug) +DEBUG_OP = -g2 +CPP_DEBUG_OP = -g2 + +OPTIMIZATION_OP = -O1 +CPP_OPTIMIZATION_OP = -O1 +else +DEBUG_OP = -g0 +CPP_DEBUG_OP = -g0 + +OPTIMIZATION_OP = -O2 +CPP_OPTIMIZATION_OP = -O2 +endif + +COMPILE_FLAGS = $(CFLAGS) $(DEBUG_OP) $(OPTIMIZATION_OP) + +CPP_COMPILE_FLAGS = $(CXXFLAGS) $(CPP_DEBUG_OP) $(CPP_OPTIMIZATION_OP) -Wall -Werror -std=c++11 -w -c -fmessage-length=0 -DPLUPLAYER_DOWNLOADABLE_APP_TVPLUS -fPIC + +LINK_FLAGS = $(CFLAGS) -shared -Wl,-z,relro + +AR_FLAGS = + +EDC_COMPILE_FLAGS = diff --git a/src/plusplayer-core/Build/funcs.mk b/src/plusplayer-core/Build/funcs.mk new file mode 100755 index 0000000..35939a5 --- /dev/null +++ b/src/plusplayer-core/Build/funcs.mk @@ -0,0 +1,50 @@ + +BSLASH := \\# +NULL_CHAR := # +SPACE := \ # +COLON := :# +DOTDOT := ..# +SPACE_ESC := &sp;# +COLON_ESC := &co;# +SPACE_OUT := ~sp~# +COLON_OUT := ~co~# +DOTDOT_OUT := ~dtdt~# + +BSLASH2SLASH = $(subst $(BSLASH),/,$(1)) + +REMOVE_TAIL = $(patsubst %/,%,$(1)) + +#LOWER_CASE = $(shell echo translit($(1),[A-Z],[a-z])|$(M4)) +LOWER_CASE = $(shell echo $(1)|$(TR) [A-Z] [a-z]) + +#ifneq ($(findstring Windows,$(OS)),) +# ... +#endif + +FIND_FILES = $(shell $(FIND) $(1)/$(2) | $(SED) 's/^$(subst /,$(BSLASH)/,$(1))$(BSLASH)///') +FIND_FILES_ESC = $(shell $(FIND) $(1)/$(2) | $(SED) 's/^$(subst /,$(BSLASH)/,$(1))$(BSLASH)///' -e 's/:/$(BSLASH)&co;/g' -e 's/$(BSLASH) /$(BSLASH)&sp;/g') +FIND_FILES_4MAKE = $(shell $(FIND) $(1)/$(2) | $(SED) 's/^$(subst /,$(BSLASH)/,$(1))$(BSLASH)///') + +FIND_FILES_ABS = $(shell $(FIND) $(1)) +FIND_FILES_ABS_4MAKE = $(shell $(FIND) $(1) -e 's/$(BSLASH) /$(BSLASH)&sp;/g') +FIND_FILES_ABS_ESC = $(shell $(FIND) $(1) -e 's/:/$(BSLASH)&co;/g' -e 's/$(BSLASH) /$(BSLASH)&sp;/g') + +FIND_FILES_4MAKE = $(shell $(FIND) $(1) | $(SED) 's/ /\\\ /g') + +#ENCODE_ESC = $(shell echo $(1) | $(SED) -e 's/:/$(BSLASH)&co;/g' -e 's/$(BSLASH) /$(BSLASH)&sp;/g') +#DECODE_ESC = $(shell echo $(1) | $(SED) -e 's/$(BSLASH)&co;/:/g' -e 's/$(BSLASH)&sp;/$(BSLASH) / g') +ENCODE_ESC = $(subst $(SPACE),$(SPACE_ESC),$(subst $(COLON),$(COLON_ESC),$(1))) +DECODE_ESC = $(subst $(COLON_ESC),$(COLON),$(subst $(SPACE_ESC),$(SPACE),$(1))) +ENCODE_4MAKE = $(subst $(SPACE),$(SPACE_ESC),$(1)) +DECODE_4MAKE = $(subst $(SPACE_ESC),$(SPACE),$(1)) + +CONVERT_TO_OUT = $(subst $(DOTDOT),$(DOTDOT_OUT),$(subst $(COLON),$(COLON_OUT),$(subst $(SPACE),$(SPACE_OUT),$(1)))) +CONVERT_ESC_TO_OUT = $(subst $(DOTDOT),$(DOTDOT_OUT),$(subst $(COLON_ESC),$(COLON_OUT),$(subst $(SPACE_ESC),$(SPACE_OUT),$(1)))) +CONVERT_4MAKE_TO_OUT = $(subst $(DOTDOT),$(DOTDOT_OUT),$(subst $(COLON),$(COLON_OUT),$(subst $(SPACE_ESC),$(SPACE_OUT),$(1)))) + +PROC_NO_EXIST = $(if $(wildcard $(1)),,$(call $(2),$(1))) +define MAKEDIRS0 + @echo ' Building directory: $(1)' + @$(MKDIR) $(MKDIR_OP) $(subst $(BSLASH),/,$(1)) +endef +MAKEDIRS = $(call PROC_NO_EXIST,$(1),MAKEDIRS0) diff --git a/src/plusplayer-core/Build/makefile b/src/plusplayer-core/Build/makefile new file mode 100755 index 0000000..95638bb --- /dev/null +++ b/src/plusplayer-core/Build/makefile @@ -0,0 +1,34 @@ +# +# Usege : make -f /Build/makefile -C +# + +BUILD_SCRIPT_VERSION := 1.1.0 + +.PHONY : app_version app_build app_clean build_version + + +all : app_build + +clean : app_clean + +version : build_version + +#PROJ_ROOT = . +#BUILD_ROOT := $(PROJ_PATH)/Build# + +ifeq ($(MAKE_NAME),mingw32-make) +ifneq ($(SHELL),) +OPTIONS += --eval="SHELL=$(SHELL)" +endif +endif + +app_build : + @echo $(MAKE) -f "$(BUILD_ROOT)/makefile.mk" + @$(MAKE_BIN) -f "$(BUILD_ROOT)/makefile.mk" -C "$(PROJ_PATH)" $(OPTIONS) + +app_clean : + @$(MAKE) -f "$(BUILD_ROOT)/makefile.mk" -C "$(PROJ_PATH)" $(OPTIONS) clean + +build_version : + @echo makefile : $(BUILD_SCRIPT_VERSION) + @$(MAKE) -f "$(BUILD_ROOT)/makefile.mk" -C "$(PROJ_PATH)" $(OPTIONS) version diff --git a/src/plusplayer-core/Build/makefile.mk b/src/plusplayer-core/Build/makefile.mk new file mode 100755 index 0000000..3a4ad19 --- /dev/null +++ b/src/plusplayer-core/Build/makefile.mk @@ -0,0 +1,206 @@ +# +# Usege : make -f /Build/makefile -C +# + +BUILD_SCRIPT_VERSION := 1.2.3 + +.PHONY : app_version app_clean build_version + + +all : app_build + +clean : app_clean + +version : build_version + +_BLANK :=# +_SPACE := $(_BLANK) $(_BLANK)# +_SPACE_4MAKE := \$(_SPACE)# + +NULL_CHAR :=# +SPACE := $(NULL_CHAR) $(NULL_CHAR)# + +PROJ_ROOT := . +_PROJ_ROOT_4MAKE := $(subst $(_SPACE),$(_SPACE_4MAKE),$(PROJ_ROOT))# +PROJ_ROOT=$(_PROJ_ROOT_4MAKE) +_BUILD_ROOT_4MAKE := $(subst $(_SPACE),$(_SPACE_4MAKE),$(BUILD_ROOT))# +BUILD_ROOT=$(_BUILD_ROOT_4MAKE) + +include $(BUILD_ROOT)/basedef.mk + +include $(PROJ_ROOT)/project_def.prop +-include $(PROJ_ROOT)/build_def.prop + +include $(BUILD_ROOT)/funcs.mk + +-include $(BUILD_ROOT)/tooldef.mk +-include $(BUILD_ROOT)/flags.mk +-include $(BUILD_ROOT)/platform.mk + + +APPTYPE := $(type) + +OUTPUT_DIR := $(PROJ_ROOT)/$(BUILD_CONFIG) +OBJ_OUTPUT := $(OUTPUT_DIR)/objs + +LOWER_APPNAME := $(call LOWER_CASE,$(APPNAME)) +APPID2 := $(subst $(basename $(APPID)).,,$(APPID)) + +ifeq ($(strip $(APPTYPE)),app) +APPFILE := $(OUTPUT_DIR)/$(LOWER_APPNAME) +endif +ifeq ($(strip $(APPTYPE)),staticLib) +APPFILE := $(OUTPUT_DIR)/lib$(LOWER_APPNAME).a +endif +ifeq ($(strip $(APPTYPE)),sharedLib) +APPFILE := $(OUTPUT_DIR)/lib$(LOWER_APPNAME).so +endif + +ifneq ($(strip $(PLATFORM_INCS)),) +PLATFORM_INCS_FILE := $(OBJ_OUTPUT)/platform_incs_file.inc +endif + +include $(BUILD_ROOT)/build_c.mk + + +ifeq ($(strip $(APPTYPE)),app) +EXT_OP := -fPIE +endif +ifeq ($(strip $(APPTYPE)),staticLib) +EXT_OP := -fPIE +endif +ifeq ($(strip $(APPTYPE)),sharedLib) +EXT_OP := -fPIC +endif + +C_OPT := $(COMPILE_FLAGS) $(TC_COMPILER_MISC) $(RS_COMPILER_MISC) $(EXT_OP) --sysroot="$(SYSROOT)" -Werror-implicit-function-declaration $(M_OPT) $(USER_C_OPTS) +CPP_OPT := $(CPP_COMPILE_FLAGS) $(TC_COMPILER_MISC) $(RS_COMPILER_MISC) $(EXT_OP) --sysroot="$(SYSROOT)" -Werror-implicit-function-declaration $(M_OPT) $(USER_CPP_OPTS) +C_OPT_FILE := $(PLATFORM_INCS_FILE) + +OBJS := # + +# Global C/C++ +ifeq ($(strip $(USER_ROOT)),) +USER_ROOT := $(PROJ_ROOT) +endif +$(eval $(call C_PROC_RAW,$(OBJ_OUTPUT),$(USER_SRCS),$(USER_INC_DIRS),$(USER_INC_FILES),$(USER_DEFS),$(USER_UNDEFS),$(C_OPT),$(C_OPT_FILE),C,c,$(CC),OBJS)) +$(foreach ext,cpp cxx cc c++ C,$(eval $(call C_PROC_RAW,$(OBJ_OUTPUT),$(USER_SRCS),$(USER_INC_DIRS),$(USER_CPP_INC_FILES),$(USER_CPP_DEFS),$(USER_CPP_UNDEFS),$(CPP_OPT),$(C_OPT_FILE),C++,$(ext),$(CXX),OBJS))) + +# Individual C/C++ +ifneq ($(strip $(USER_EXT_C_KEYS)),) +$(foreach var,$(USER_EXT_C_KEYS),$(eval $(call C_PROC_RAW,$(OBJ_OUTPUT),$(USER_EXT_$(var)_SRCS),$(USER_EXT_$(var)_INC_DIRS),$(USER_EXT_$(var)_INC_FILES),$(USER_EXT_$(var)_DEFS),$(USER_EXT_$(var)_UNDEFS),$(C_OPT),$(C_OPT_FILE),C,c,$(CC),OBJS))) +$(foreach ext,cpp cxx cc c++ C,$(foreach var,$(USER_EXT_C_KEYS),$(eval $(call C_PROC_RAW,$(OBJ_OUTPUT),$(USER_EXT_$(var)_SRCS),$(USER_EXT_$(var)_INC_DIRS),$(USER_EXT_$(var)_CPP_INC_FILES),$(USER_EXT_$(var)_CPP_DEFS),$(USER_EXT_$(var)_CPP_UNDEFS),$(CPP_OPT),$(C_OPT_FILE),C++,$(ext),$(CXX),OBJS)))) +endif + + +ifneq ($(strip $(USER_LIB_DIRS)),) +_ENC_USER_LIB_DIRS := $(call ENCODE_4MAKE,$(USER_LIB_DIRS)) +_ENC_USER_LIB_DIRS := $(addprefix -L,$(_ENC_USER_LIB_DIRS)) +LIBPATHS := $(call DECODE_4MAKE,$(_ENC_USER_LIB_DIRS)) +endif + +LIBS += $(addprefix -l,$(USER_LIBS)) + +UOBJS += $(USER_OBJS) + +M_OPT = -MMD -MP -MF"$(@:%.o=%.d)" + +DEPS := $(OBJS:.o=.d) + +ifneq ($(strip $(DEPS)),) +-include $(PROJ_ROOT)/Build/$(DEPS) +endif + + +ifeq ($(strip $(APPTYPE)),app) +$(APPFILE) : $(OBJS) $(UOBJS) + @echo ' Building target: $@' + @echo ' Invoking: C/C++ Linker' + $(call MAKEDIRS,$(@D)) +# $(CXX) -o $(APPFILE) $(OBJS) $(UOBJS) $(LIBPATHS) -Xlinker --as-needed $(LIBS) $(LINK_FLAGS) $(TC_LINKER_MISC) $(RS_LINKER_MISC) -pie -lpthread --sysroot="$(SYSROOT)" -Xlinker --version-script="$(PROJ_ROOT)/.exportMap" $(RS_LIB_PATHS) $(RS_LIBRARIES) -Xlinker -rpath='$$ORIGIN/../lib' -Werror-implicit-function-declaration $(USER_LINK_OPTS) + $(CXX) -o $(APPFILE) $(OBJS) $(UOBJS) $(LIBPATHS) -Xlinker $(LIBS) $(LINK_FLAGS) $(TC_LINKER_MISC) $(RS_LINKER_MISC) -pie -lpthread --sysroot="$(SYSROOT)" -Xlinker --version-script="$(PROJ_ROOT)/.exportMap" $(RS_LIB_PATHS) $(RS_LIBRARIES) -Xlinker -rpath='$$ORIGIN/../lib' -Werror-implicit-function-declaration $(USER_LINK_OPTS) + @echo ' Finished building target: $@' +endif +ifeq ($(strip $(APPTYPE)),staticLib) +$(APPFILE) : $(OBJS) $(UOBJS) + @echo ' Building target: $@' + @echo ' Invoking: Archive utility' + $(call MAKEDIRS,$(@D)) + $(AR) -r $(APPFILE) $(OBJS) $(UOBJS) $(AR_FLAGS) $(USER_LINK_OPTS) + @echo ' Finished building target: $@' +endif +ifeq ($(strip $(APPTYPE)),sharedLib) +$(APPFILE) : $(OBJS) $(UOBJS) + @echo ' Building target: $@' + @echo ' Invoking: C/C++ Linker' + $(call MAKEDIRS,$(@D)) + $(CXX) -o $(APPFILE) $(OBJS) $(UOBJS) $(LIBPATHS) -Xlinker --as-needed $(LIBS) $(LINK_FLAGS) $(TC_LINKER_MISC) $(RS_LINKER_MISC) -shared -lpthread --sysroot="$(SYSROOT)" $(RS_LIB_PATHS) $(RS_LIBRARIES) $(USER_LINK_OPTS) + @echo ' Finished building target: $@' +endif + + +$(OBJ_OUTPUT) : + $(call MAKEDIRS,$@) + +$(OUTPUT_DIR) : + $(call MAKEDIRS,$@) + + +#ifneq ($(strip $(PLATFORM_INCS)),) +#$(PLATFORM_INCS_FILE) : $(OBJ_OUTPUT) +# @echo ' Building inc file: $@' +#ifneq ($(findstring Windows,$(OS)),) +#ifneq ($(findstring 3.82,$(MAKE_VERSION)),) +# $(file > $@,$(PLATFORM_INCS)) +#else +# @echo $(PLATFORM_INCS) > $@ +#endif +#else +# @echo '$(PLATFORM_INCS)' > $@ +#endif +#endif + + +include $(BUILD_ROOT)/build_edc.mk + +#ifeq ($(strip $(ENVENTOR_SHARED_RES_PATH)),) +ENVENTOR_SHARED_RES_PATH ?= $(ENVENTOR_PATH)/share/enventor +#endif + +EDJ_FILES := + +# Global EDCs +ifneq ($(strip $(USER_EDCS)),) +$(eval $(call EDJ_PROC_RAW,$(OUTPUT_DIR),$(USER_EDCS),$(USER_EDCS_IMAGE_DIRS),$(USER_EDCS_SOUND_DIRS),$(USER_EDCS_FONT_DIRS),EDJ_FILES)) +endif + +# Individual EDCs +ifneq ($(strip $(USER_EXT_EDC_KEYS)),) +$(foreach var,$(USER_EXT_EDC_KEYS),$(eval $(call EDJ_PROC_RAW,$(OUTPUT_DIR),$(USER_EXT_$(var)_EDCS),$(USER_EXT_$(var)_EDCS_IMAGE_DIRS),$(USER_EXT_$(var)_EDCS_SOUND_DIRS),$(USER_EXT_$(var)_EDCS_FONT_DIRS),EDJ_FILES))) +endif + + +include $(BUILD_ROOT)/build_po.mk + +MO_FILES := + +# Global POs +ifneq ($(strip $(USER_POS)),) +$(eval $(call MO_PROC_RAW,$(OUTPUT_DIR),$(USER_POS),$(APPID2),MO_FILES)) +endif + + +secondary-outputs : $(EDJ_FILES) $(MO_FILES) + +-include appendix.mk + +app_build : $(OUTPUT_DIR) $(APPFILE) secondary-outputs + @echo ========= done ========= + + +app_clean : + rm -f $(APPFILE) + rm -rf $(OUTPUT_DIR) + +build_version : + @echo makefile.mk : $(BUILD_SCRIPT_VERSION) diff --git a/src/plusplayer-core/Build/platform.mk b/src/plusplayer-core/Build/platform.mk new file mode 100755 index 0000000..31248a9 --- /dev/null +++ b/src/plusplayer-core/Build/platform.mk @@ -0,0 +1,18 @@ +# Add inputs and outputs from these tool invocations to the build variables + +SYSROOT = $(SBI_SYSROOT) + +#USR_INCS := $(addprefix -I "$(SYSROOT),$(PLATFORM_INCS_EX)) +USR_INCS1 := $(addsuffix ",$(PLATFORM_INCS_EX)) +USR_INCS := $(addprefix -I "$(SYSROOT),$(USR_INCS1)) + +ifeq ($(strip $(PLATFORM_LIB_PATHS)),) +RS_LIB_PATHS := "$(SYSROOT)/usr/lib" +else +RS_LIB_PATHS1 := $(addsuffix ",$(PLATFORM_LIB_PATHS)) +RS_LIB_PATHS := $(addprefix -L "$(SYSROOT),$(RS_LIB_PATHS1)) +endif + +RS_LIBRARIES := $(addprefix -l,$(RS_LIBRARIES_EX)) + +PLATFORM_INCS = $(USR_INCS) -I "$(SDK_PATH)/library" diff --git a/src/plusplayer-core/Build/tooldef.mk b/src/plusplayer-core/Build/tooldef.mk new file mode 100755 index 0000000..7016cd6 --- /dev/null +++ b/src/plusplayer-core/Build/tooldef.mk @@ -0,0 +1,70 @@ +# Add inputs and outputs from these tool invocations to the build variables + +ifneq ($(strip $(SHELL_BIN)),) +SHELL = $(SHELL_BIN) +else +SHELL = sh +endif + +ifneq ($(strip $(MKDIR_BIN)),) +MKDIR = $(MKDIR_BIN) +MKDIR_OP = -p +else +MKDIR = mkdir +MKDIR_OP = -p +endif + +ifneq ($(strip $(UNAME_BIN)),) +UNAME = $(UNAME_BIN) +else +UNAME = uname +endif + +ifneq ($(strip $(M4_BIN)),) +M4 = $(M4_BIN) +else +M4 = m4 +endif + +ifneq ($(strip $(TR_BIN)),) +TR = $(TR_BIN) +else +TR = tr +endif + +ifneq ($(strip $(FIND_BIN)),) +FIND = $(FIND_BIN) +else +FIND = find +endif + +ifneq ($(strip $(SED_BIN)),) +SED = $(SED_BIN) +else +SED = sed +endif + +ifneq ($(strip $(GREP_BIN)),) +GREP = $(GREP_BIN) +else +GREP = grep +endif + +ifneq ($(strip $(EDJE_CC_BIN)),) +EDJE_CC = $(EDJE_CC_BIN) +else +EDJE_CC = edje_cc +endif + +ifneq ($(strip $(MSGFMT_BIN)),) +MSGFMT = $(MSGFMT_BIN) +else +MSGFMT = msgfmt +endif + +ifneq ($(strip $(CKSUM_BIN)),) +CKSUM = $(CKSUM_BIN) +else +CKSUM = cksum +endif + diff --git a/src/plusplayer-core/CMakeLists.txt b/src/plusplayer-core/CMakeLists.txt new file mode 100755 index 0000000..95c0912 --- /dev/null +++ b/src/plusplayer-core/CMakeLists.txt @@ -0,0 +1,71 @@ +PROJECT(plusplayer-core) + +SET(fw_name "espplayer-core") +SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib) +SET(${fw_name}_LDFLAGS) + +SET(ADD_LIBS + "gstvideo-1.0" + "gstapp-1.0" + "trackrenderer" +) + +SET(${fw_name}_CXXFLAGS "-Wall -Werror -std=c++11 -fPIC -Wl,-z,relro -fstack-protector -DEFL_BETA_API_SUPPORT") + +SET(dependents "gstreamer-1.0 dlog gstreamer-ffsubtitle-1.0" + "boost" + "context-aware-api" + "libtzplatform-config" + "drmdecrypt" + "logger") + +INCLUDE(FindPkgConfig) + +IF(CMAKE_SYSTEM_PROCESSOR STREQUAL armv7l) +pkg_check_modules(${fw_name} REQUIRED ${dependents}) +ELSE(CMAKE_SYSTEM_PROCESSOR STREQUAL armv7l) +pkg_check_modules(${fw_name} REQUIRED ${dependents}) +ENDIF(CMAKE_SYSTEM_PROCESSOR STREQUAL armv7l) + +FOREACH(flag ${${fw_name}_CFLAGS}) +SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${flag}") +ENDFOREACH(flag) + +FOREACH(flag ${${fw_name}_CXXFLAGS}) +SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_C_FLAGS} ${flag}") +ENDFOREACH(flag) + +GET_FILENAME_COMPONENT(PARENT_DIR ${PROJECT_SOURCE_DIR} DIRECTORY) +INCLUDE_DIRECTORIES( + ${PROJECT_SOURCE_DIR}/include_internal +) + +SET(CC_SRCS + ${PROJECT_SOURCE_DIR}/src/decoderinputbuffer.cpp + ${PROJECT_SOURCE_DIR}/src/gstobject_guard.cpp + ${PROJECT_SOURCE_DIR}/src/gstsignal_holder.cpp + ${PROJECT_SOURCE_DIR}/src/track_util.cpp + ${PROJECT_SOURCE_DIR}/src/gst_utils.cpp + ${PROJECT_SOURCE_DIR}/src/error.cpp + ${PROJECT_SOURCE_DIR}/src/serializer.cpp + ${PROJECT_SOURCE_DIR}/src/subtitle_attr_parser.cpp + ${PROJECT_SOURCE_DIR}/src/plusplayer_cfg.cpp + ${PROJECT_SOURCE_DIR}/src/trackrendereradapter.cpp + ${PROJECT_SOURCE_DIR}/src/trackrendereradapter_utils.cpp + ${PROJECT_SOURCE_DIR}/src/kpi.cpp + ${PROJECT_SOURCE_DIR}/src/decodedvideopacketex.cpp + ${PROJECT_SOURCE_DIR}/src/videoframetypestrategy.cpp + ${PROJECT_SOURCE_DIR}/src/base64.cpp + ${PROJECT_SOURCE_DIR}/src/caf_logger.cpp +) + +ADD_LIBRARY(${fw_name} SHARED ${CC_SRCS}) + +SET_TARGET_PROPERTIES(${fw_name} PROPERTIES LINKER_LANGUAGE CXX) + +TARGET_LINK_LIBRARIES(${fw_name} ${CMAKE_THREAD_LIBS_INIT} ${${fw_name}_LDFLAGS} ${ADD_LIBS}) + +INSTALL(TARGETS ${fw_name} DESTINATION ${LIB_INSTALL_DIR}) +INSTALL( + DIRECTORY ${INC_DIR}/ DESTINATION include/ +) diff --git a/src/plusplayer-core/build_def.prop b/src/plusplayer-core/build_def.prop new file mode 100755 index 0000000..d164d23 --- /dev/null +++ b/src/plusplayer-core/build_def.prop @@ -0,0 +1,6 @@ + +# Add pre/post build process +PREBUILD_DESC = +PREBUILD_COMMAND = +POSTBUILD_DESC = +POSTBUILD_COMMAND = \ No newline at end of file diff --git a/src/plusplayer-core/include_internal/core/decodedvideorawmodepacket.h b/src/plusplayer-core/include_internal/core/decodedvideorawmodepacket.h new file mode 100755 index 0000000..bb01d13 --- /dev/null +++ b/src/plusplayer-core/include_internal/core/decodedvideorawmodepacket.h @@ -0,0 +1,40 @@ +// +// @ Copyright [2020] +// + +#ifndef __PLUSPLAYER_SRC_CORE_DECODED_RAW_MODE_PACKET_H__ +#define __PLUSPLAYER_SRC_CORE_DECODED_RAW_MODE_PACKET_H__ + +#include + +namespace plusplayer { + +enum class DecodedVideoRawModePacketType { kPhysicalAddress, kTizenBuffer }; + +struct DecodedVideoRawModePacketRawData { + int y_phyaddr = 0; + int y_viraddr = 0; + int y_linesize = 0; + int uv_phyaddr = 0; + int uv_viraddr = 0; + int uv_linesize = 0; +}; +struct DecodedVideoRawModePacketTBMData { + tbm_key key; +}; + +struct DecodedVideoRawModePacket { + DecodedVideoRawModePacketType type = + DecodedVideoRawModePacketType::kPhysicalAddress; + uint64_t pts = 0; + uint32_t width = 0; + uint32_t height = 0; + union Data { + DecodedVideoRawModePacketRawData raw; + DecodedVideoRawModePacketTBMData tbm; + } data = {.tbm = {0}}; +}; + +} // namespace plusplayer + +#endif // __PLUSPLAYER_SRC_CORE_DECODED_RAW_MODE_PACKET_H__ \ No newline at end of file diff --git a/src/plusplayer-core/include_internal/core/decoderinputbuffer.h b/src/plusplayer-core/include_internal/core/decoderinputbuffer.h new file mode 100755 index 0000000..261857c --- /dev/null +++ b/src/plusplayer-core/include_internal/core/decoderinputbuffer.h @@ -0,0 +1,139 @@ +// +// @ Copyright [2017] +// + +#ifndef __PLUSPLAYER_SRC_CORE_DECODERINPUTBUFFER_H__ +#define __PLUSPLAYER_SRC_CORE_DECODERINPUTBUFFER_H__ + +#include +#include +#include +#include + +#include "gst/gst.h" +// temporary until drmdecrypt platform interfaces are added into rootstrap +#ifndef PLUPLAYER_DOWNLOADABLE_APP_TVPLUS +#include +#endif + +#include "plusplayer/track.h" + +namespace plusplayer { + +class DecoderInputBuffer : private boost::noncopyable { + public: + using Ptr = std::unique_ptr; + + static Ptr Create(const TrackType type = kTrackTypeMax, + const int index = kInvalidTrackIndex, + GstBuffer* buffer = nullptr) { + return Ptr(new DecoderInputBuffer(buffer, type, index)); + } + + DecoderInputBuffer() = delete; + + ~DecoderInputBuffer() { + while (std::atomic_flag_test_and_set_explicit(&buffer_lock_, + std::memory_order_acquire)) + ; // spin until the lock is acquired + if (buffer_) { + ReleaseTZHandle_(buffer_); + gst_buffer_unref(buffer_); + } + std::atomic_flag_clear_explicit(&buffer_lock_, std::memory_order_release); + } + + const TrackType GetType() const { return type_; } + const int GetIndex() const { return index_; } + + const uint64_t GetDuration() const { return duration_; } + + const uint32_t GetSize() const { return buffer_size_; } + + const uint8_t* GetRawData() const { return raw_data_; } + + const bool IsEos() const { return is_eos_; } + + GstBuffer* Release() { + while (std::atomic_flag_test_and_set_explicit(&buffer_lock_, + std::memory_order_acquire)) + ; // spin until the lock is acquired + GstBuffer* tmp = buffer_; + buffer_ = nullptr; + std::atomic_flag_clear_explicit(&buffer_lock_, std::memory_order_release); + return tmp; + } + + const GstBuffer* Get() const { return buffer_; } + + private: + explicit DecoderInputBuffer(GstBuffer* buffer, const TrackType type, + const int index) + : type_(type), index_(index) { + if (buffer) { + buffer_ = gst_buffer_ref(buffer); + duration_ = GST_TIME_AS_MSECONDS(GST_BUFFER_DURATION(buffer_)); + if (type == kTrackTypeSubtitle) { + GstMapInfo info; + gst_buffer_map(buffer_, &info, GST_MAP_READ); + raw_data_ = info.data; + buffer_size_ = info.size; + gst_buffer_unmap(buffer_, &info); + } + } else { + is_eos_ = true; + } + } + + void ReleaseTZHandle_(GstBuffer* buffer) { +#ifndef PLUPLAYER_DOWNLOADABLE_APP_TVPLUS + GstStructure* tzqdata = GST_STRUCTURE(gst_mini_object_get_qdata( + GST_MINI_OBJECT(buffer), + g_quark_from_static_string("GstTzHandleData"))); + + if (tzqdata) { + gboolean ret = FALSE; + guint packet_handle = 0; + guint packet_size = 0; + handle_and_size_s ret_Handle; + memset(&ret_Handle, 0, sizeof(ret_Handle)); + + ret = gst_structure_get_uint(tzqdata, "packet_handle", &packet_handle); + if (FALSE == ret) { + return; + } + + ret = gst_structure_get_uint(tzqdata, "packet_size", &packet_size); + if (FALSE == ret) { + return; + } + + ret_Handle.handle = packet_handle; + ret_Handle.size = packet_size; + release_handle(&ret_Handle); + } +#endif + } + + private: + std::atomic_flag buffer_lock_ = ATOMIC_FLAG_INIT; + const TrackType type_ = kTrackTypeMax; + const int index_ = kInvalidTrackIndex; + bool is_eos_ = false; + GstBuffer* buffer_ = nullptr; + uint32_t buffer_size_ = 0; + uint64_t duration_ = 0; + const uint8_t* raw_data_ = nullptr; +}; + +using DecoderInputBufferPtr = DecoderInputBuffer::Ptr; + +namespace decoderinputbuffer_util { + +bool FlushQueue(std::queue& queue); + +} // namespace decoderinputbuffer_util + +} // namespace plusplayer + +#endif // __PLUSPLAYER_SRC_CORE_DECODERINPUTBUFFER_H__ diff --git a/src/plusplayer-core/include_internal/core/decoderinputbuffer_listener.h b/src/plusplayer-core/include_internal/core/decoderinputbuffer_listener.h new file mode 100755 index 0000000..8363291 --- /dev/null +++ b/src/plusplayer-core/include_internal/core/decoderinputbuffer_listener.h @@ -0,0 +1,25 @@ +// +// @ Copyright [2017] +// + +#ifndef __PLUSPLAYER_SRC_TRACKSOURCE_DECODERINPUTBUFFER_LISTENER_H__ +#define __PLUSPLAYER_SRC_TRACKSOURCE_DECODERINPUTBUFFER_LISTENER_H__ + +#include + +#include "core/decoderinputbuffer.h" + +namespace plusplayer { + +class DecoderInputBufferListener : private boost::noncopyable { + public: + virtual ~DecoderInputBufferListener() {} + virtual void OnRecv(DecoderInputBufferPtr data) {} + + protected: + DecoderInputBufferListener() {} +}; + +} // namespace plusplayer + +#endif // __PLUSPLAYER_SRC_TRACKSOURCE_DECODERINPUTBUFFER_LISTENER_H__ diff --git a/src/plusplayer-core/include_internal/core/error.h b/src/plusplayer-core/include_internal/core/error.h new file mode 100755 index 0000000..057ece0 --- /dev/null +++ b/src/plusplayer-core/include_internal/core/error.h @@ -0,0 +1,18 @@ +// +// @ Copyright [2017] +// + +#ifndef __PLUSPLAYER_SRC_CORE_ERROR_H__ +#define __PLUSPLAYER_SRC_CORE_ERROR_H__ + +#include "gst/gst.h" + +#include "plusplayer/types/error.h" + +namespace plusplayer { + +ErrorType HandleGstError(const GError* error); + +} // namespace plusplayer + +#endif // __PLUSPLAYER_SRC_CORE_ERROR_H__ diff --git a/src/plusplayer-core/include_internal/core/gst_utils.h b/src/plusplayer-core/include_internal/core/gst_utils.h new file mode 100755 index 0000000..e2adbdd --- /dev/null +++ b/src/plusplayer-core/include_internal/core/gst_utils.h @@ -0,0 +1,26 @@ +// +// @ Copyright [2017] +// + +#ifndef __PLUSPLAYER_SRC_CORE_GST_UTILS_H__ +#define __PLUSPLAYER_SRC_CORE_GST_UTILS_H__ + +#include "gst/gst.h" +#include "json/json.h" + +namespace plusplayer { + +namespace gst_util { + +void GstInit(); +void GstInit(const Json::Value& root); +void ShowStateChangedMsg(GstMessage* msg, void* id = nullptr); +void SetGstStateToNull(GstElement* pipeline, void* id = nullptr); +const gchar* GetElementName(const GstMessage* msg); +const gchar* GetKlass(const GstMessage* msg); + +} // namespace gst_util + +} // namespace plusplayer + +#endif // __PLUSPLAYER_SRC_CORE_GST_UTILS_H__ \ No newline at end of file diff --git a/src/plusplayer-core/include_internal/core/gstobject_guard.h b/src/plusplayer-core/include_internal/core/gstobject_guard.h new file mode 100755 index 0000000..ad8be9e --- /dev/null +++ b/src/plusplayer-core/include_internal/core/gstobject_guard.h @@ -0,0 +1,66 @@ +// +// @ Copyright [2017] +// + +#ifndef __PLUSPLAYER_SRC_CORE_GSTOBJECT_GUARD_H__ +#define __PLUSPLAYER_SRC_CORE_GSTOBJECT_GUARD_H__ + +#include +#include +#include "gst/gst.h" + +namespace plusplayer { + +// +// +// bool CreatePipeline() { +// // Old Way +// // GstCaps* caps = gst_pad_get_currnet_caps(); +// // ... do somthing ... +// // gst_caps_unref(caps); +// +// // New way. +// auto caps_unique_ptr = utils::make_guard(gst_pad_get_current_caps()); +// GstCaps* caps = caps_unique_ptr.get(); +// // .. do somthing .. +// // you don't need to call "gst_caps_unref()" +// return true; +// } + +namespace gstguard { + +template +using GstGuardPtr = std::unique_ptr>; +// +// Register CustomDeleter Here (start) +// +void CustomDeleter(GstCaps* obj); +void CustomDeleter(GstObject* obj); +void CustomDeleter(GstPad* obj); +void CustomDeleter(GstBuffer* obj); +void CustomDeleter(GstElement* obj); +void CustomDeleter(GstQuery* obj); +void CustomDeleter(GstEvent* obj); +void CustomDeleter(GstMessage* obj); +void CustomDeleter(GstPluginFeature* obj); +void CustomDeleter(GstElementFactory* obj); +void CustomDeleter(GstBus* obj); +void CustomDeleter(gchar* obj); +void CustomDeleter(GError* obj); +void CustomDeleter(GstStructure* obj); +void CustomDeleter(GValue* obj); +void CustomDeleter(GstIterator* obj); +void CustomDeleter(GBytes* obj); +// +// Register CustomDeleter Here (end) +// +template +GstGuardPtr make_guard(T* obj) { + return GstGuardPtr(obj, [](T* _obj) { CustomDeleter(_obj); }); +} + +} // namespace gstguard + +} // namespace plusplayer + +#endif // __PLUSPLAYER_SRC_CORE_GSTOBJECT_GUARD_H__ diff --git a/src/plusplayer-core/include_internal/core/gstsignal_holder.h b/src/plusplayer-core/include_internal/core/gstsignal_holder.h new file mode 100755 index 0000000..5e76daf --- /dev/null +++ b/src/plusplayer-core/include_internal/core/gstsignal_holder.h @@ -0,0 +1,42 @@ +// +// @ Copyright [2017] +// + +#ifndef __PLUSPLAYER_SRC_CORE_GSTSIGNAL_HOLDER_H__ +#define __PLUSPLAYER_SRC_CORE_GSTSIGNAL_HOLDER_H__ + +#include +#include +#include +#include + +#include "glib-object.h" +#include "gst/gst.h" + +namespace plusplayer { + +#define GST_SIGNAL_CONNECT(x_holder, x_object, x_signal, x_callback, x_arg) \ + do { \ + x_holder->Add(G_OBJECT(x_object), x_signal, G_CALLBACK(x_callback), \ + (gpointer)x_arg); \ + } while (0); + +class GstSignalHolder : private boost::noncopyable { + public: + GstSignalHolder(); + ~GstSignalHolder(); + void Add(GObject* obj, const char* signal_name, GCallback handler, + gpointer data); + void Delete(GObject* obj); // If the obj is BIN, delete not only its signal + // but also its children's. + void DeleteAll(); + + private: + std::mutex item_lock_; + class GstSignalItem; + std::multimap> signal_list_; +}; + +} // namespace plusplayer + +#endif // __PLUSPLAYER_SRC_CORE_GSTSIGNAL_HOLDER_H__ diff --git a/src/plusplayer-core/include_internal/core/kpi.h b/src/plusplayer-core/include_internal/core/kpi.h new file mode 100755 index 0000000..83e8977 --- /dev/null +++ b/src/plusplayer-core/include_internal/core/kpi.h @@ -0,0 +1,58 @@ +// +// @ Copyright [2017] +// + +#ifndef __PLUSPLAYER_SRC_CORE_KPI_H__ +#define __PLUSPLAYER_SRC_CORE_KPI_H__ + +#include + +#include "plusplayer/drm.h" +#include "plusplayer/types/source.h" + +namespace plusplayer { + +namespace kpi { + +struct CodecLoggerKeys { + SourceType src_type = SourceType::kNone; + drm::Type drm_type = drm::Type::kNone; + std::string container_type; + int v_decoder_type = 0; /**< (0:DEFAULT, 1:HW, 2:SW, 3:DISABLE) */ + std::string v_codec; + unsigned int v_tag = 0; + int width = 0; + int height = 0; + int a_decoder_type = 0; /**< (0:DEFAULT, 1:HW, 2:SW, 3:DISABLE) */ + std::string a_codec; + unsigned int a_tag = 0; + std::string app_id; +}; + +struct EsCodecLoggerKeys { + std::string app_id; + bool is_clean = true; /**< (false:EME, true:MSE) */ + int width = 0; + int height = 0; + std::string v_codec; + int v_codec_version; + std::string a_codec; +}; + + +class CodecLogger { + public: + CodecLogger() {}; + ~CodecLogger() {}; + + bool SendKpi(bool event_case, const CodecLoggerKeys& keys); + bool SendKpi(bool event_case, const EsCodecLoggerKeys& keys); + private: + bool SendKpi_(bool event_case, const std::stringstream& message); +}; + +} // namespace kpi + +} // namespace plusplayer + +#endif // __PLUSPLAYER_SRC_CORE_KPI_H__ \ No newline at end of file diff --git a/src/plusplayer-core/include_internal/core/serializer.h b/src/plusplayer-core/include_internal/core/serializer.h new file mode 100755 index 0000000..d38a5a8 --- /dev/null +++ b/src/plusplayer-core/include_internal/core/serializer.h @@ -0,0 +1,58 @@ +// +// @ Copyright [2018] +// + +#ifndef __PLUSPLAYER_SRC_CORE_SERIALIZER_H__ +#define __PLUSPLAYER_SRC_CORE_SERIALIZER_H__ + +#include +#include +#include +#include + +namespace plusplayer { +class Serializer { + public: + using Byte = unsigned char; + using Offset = unsigned int; + + public: + explicit Serializer() : size_(0) {} + Serializer(const Serializer &from) = delete; + Serializer(Serializer &&from) = delete; + virtual ~Serializer() {} + + public: + template + Offset Put(const T data) { + static_assert( + !std::is_pointer::value || !std::is_same::value, + "this type can't be serialized"); + Offset offset = size_; + constexpr size_t size = sizeof(T); + const Byte *data_bytes = reinterpret_cast(&data); + Put_(data_bytes, size); + return offset; + } + Offset Put(const std::vector &data); + Offset Put(const std::string &data); + Offset Put(const Byte *data, size_t size); + size_t Serialize(Byte *serialized); + const size_t GetSize(); + + template + static void Put(Byte *bytes, const T value) { + constexpr size_t size = sizeof(T); + std::memcpy(bytes, reinterpret_cast(&value), size); + } + + private: + void Put_(const Byte *data_bytes, const size_t size); + + private: + std::basic_stringbuf buf_; + size_t size_; +}; +} // namespace plusplayer + +#endif // __PLUSPLAYER_SRC_CORE_SERIALIZER_H__ diff --git a/src/plusplayer-core/include_internal/core/subtitle_attr_parser.h b/src/plusplayer-core/include_internal/core/subtitle_attr_parser.h new file mode 100755 index 0000000..564c21a --- /dev/null +++ b/src/plusplayer-core/include_internal/core/subtitle_attr_parser.h @@ -0,0 +1,28 @@ +// +// @ Copyright [2017] +// + +#ifndef __PLUSPLAYER_SRC_CORE_SUBTITLE_ATTR_PARSER_H__ +#define __PLUSPLAYER_SRC_CORE_SUBTITLE_ATTR_PARSER_H__ + +#include + +#include "gst/gst.h" + +#include "plusplayer/track.h" + +namespace plusplayer { +class SubtitleAttrParser : private boost::noncopyable { + public: + explicit SubtitleAttrParser(GstBuffer* buf) : gstbuf_(buf) {} + SubtitleAttrListPtr Parse(); + ~SubtitleAttrParser() { + if(gstbuf_) + gst_buffer_unref(gstbuf_); + } + private: + GstBuffer* gstbuf_ = nullptr; +}; +} // namespace plusplayer + +#endif //__PLUSPLAYER_SRC_CORE_SUBTITLE_ATTR_PARSER_H__ \ No newline at end of file diff --git a/src/plusplayer-core/include_internal/core/track_util.h b/src/plusplayer-core/include_internal/core/track_util.h new file mode 100755 index 0000000..eaeb457 --- /dev/null +++ b/src/plusplayer-core/include_internal/core/track_util.h @@ -0,0 +1,35 @@ +// +// @ Copyright [2017] +// + +#ifndef __PLUSPLAYER_SRC_CORE_TRACK_UTIL_H__ +#define __PLUSPLAYER_SRC_CORE_TRACK_UTIL_H__ + +#include + +#include "gst/gst.h" + +#include "plusplayer/track.h" + +namespace plusplayer { + +namespace track_util { + +bool GetActiveTrack(const std::vector& track_list, TrackType type, + Track* track); +bool GetActiveTrackList(const std::vector& tracklist, + std::vector& active_track); + +void ShowTrackInfo(const std::vector& trackinfo); +void ShowTrackInfo(const Track& track); +uint64_t GetPositionWithinBoundary(const uint64_t duration, + const uint64_t position, + const uint64_t threshold); +bool IsValidCodecDataSize(int size); +void FillCodecDataIntoTrack(const GValue* codec_data, + plusplayer::Track* track); +} // namespace track_util + +} // namespace plusplayer + +#endif // __PLUSPLAYER_SRC_CORE_TRACK_UTIL_H__ diff --git a/src/plusplayer-core/include_internal/core/trackrendereradapter.h b/src/plusplayer-core/include_internal/core/trackrendereradapter.h new file mode 100755 index 0000000..f5d2101 --- /dev/null +++ b/src/plusplayer-core/include_internal/core/trackrendereradapter.h @@ -0,0 +1,277 @@ +// +// @ Copyright [2017] +// + +#ifndef __PLUSPLAYER_SRC_PLAYER_TRACKRENDERERADAPTER_H__ +#define __PLUSPLAYER_SRC_PLAYER_TRACKRENDERERADAPTER_H__ + +#include +#include + +#include +#include +#include + +#include "core/decodedvideorawmodepacket.h" +#include "core/decoderinputbuffer.h" +#include "core/videoframetypestrategy.h" +#include "plusplayer/appinfo.h" +#include "plusplayer/audioeasinginfo.h" +#include "plusplayer/drm.h" +#include "plusplayer/track.h" +#include "plusplayer/types/buffer.h" +#include "plusplayer/types/display.h" +#include "plusplayer/types/error.h" +#include "plusplayer/types/event.h" +#include "plusplayer/types/latency.h" +#include "plusplayer/types/picturequality.h" +#include "plusplayer/types/resource.h" +#include "plusplayer/types/stream.h" + +namespace plusplayer { + +class TrackRendererAdapter { + public: + enum RetVal { kSuccess = 0, kFailed = -1 }; + enum class SubmitStatus { + kNotPrepared, // not prepared to get data + kHold, // valid data, hold this packet + kFull, // buffer already full + kSuccess, // submit succeeded + kDrop, // invalid data , drop this packet + kFailed, + }; + + enum class Attribute { + /*attributes for gst plugin property*/ + kVideoQueueMaxByte, // std::uint64_t + kAudioQueueMaxByte, // std::uint64_t + kVideoQueueCurrentLevelByte, // std::uint64_t + kAudioQueueCurrentLevelByte, // std::uint64_t + kVideoMinByteThreshold, // std::uint32_t + kAudioMinByteThreshold, // std::uint32_t + kVideoQueueMaxTime, // std::uint64_t + kAudioQueueMaxTime, // std::uint64_t + kVideoQueueCurrentLevelTime, // std::uint64_t + kAudioQueueCurrentLevelTime, // std::uint64_t + kVideoMinTimeThreshold, // std::uint32_t + kAudioMinTimeThreshold, // std::uint32_t + kVideoSupportRotation, // std::unit32_t + kVideoRenderTimeOffset, // std::int64_t + kAudioRenderTimeOffset, // std::int64_t + + /*attributes for trackrenderer configures*/ + + kAccurateSeekMode, // std::uint32_t + kLowLatencyMode, // std::uint32_t + kVideoFramePeekMode, // std::uint32_t + kUnlimitedMaxBufferMode, // std::uint32_t + kVideoPreDisplayMode, // std::uint32_t + kStartRenderingTime, // std::uint64_t + kFmmMode, // std::uint32_t + kAlternativeVideoResource, // std::uint32_t + kVideoDecodingMode, // std::uint32_t + kLateVideoFrameDropMode, // std::uint32_t + + }; + + // TODO(js4716.chun):CHECK POINTS + // - duplicated TrackRenderer::EventListener + class EventListener { + public: + virtual ~EventListener() {} + virtual void OnError(const ErrorType& err_code) {} + virtual void OnErrorMsg(const ErrorType& error_code, char* error_msg) {} + virtual void OnResourceConflicted() {} + virtual void OnSeekDone() {} + virtual void OnEos() {} + virtual void OnEvent(const EventType& event, const EventMsg& msg_data) {} + virtual void OnSubtitleData(const DecoderInputBufferPtr& buf, + const SubtitleType& type) {} +#ifndef TRACKRENDERER_FEATURE_DEPRECATE_SUBTITLE_CB + virtual void OnSubtitleData(const char* data, const int size, + const SubtitleType& type, + const uint64_t duration, + SubtitleAttrListPtr attr_list) {} +#endif + virtual void OnClosedCaptionData(const char* data, const int size) {} + virtual void OnDrmInitData(int* drmhandle, unsigned int len, + unsigned char* psshdata, TrackType type) {} + virtual void OnBufferStatus(const TrackType& type, + const BufferStatus& status) {} + virtual void OnSeekData(const TrackType& type, const uint64_t offset) {} + virtual void OnMediaPacketGetTbmBufPtr(void** tbm_ptr, + bool is_scale_change) {} + virtual void OnMediaPacketVideoDecoded(const DecodedVideoPacket& packet) {} + virtual void OnMediaPacketVideoRawDecoded( + const DecodedVideoRawModePacket& packet) {} + virtual void OnFlushDone() {} + virtual void OnFirstDecodingDone() {} + virtual void OnVideoDecoderUnderrun() {} + virtual void OnVideoLatencyStatus(const LatencyStatus& latency_status) {} + virtual void OnAudioLatencyStatus(const LatencyStatus& latency_status) {} + virtual void OnVideoHighLatency() {} + virtual void OnAudioHighLatency() {} + virtual void OnMultiviewStartVideo() {} + virtual void OnMultiviewStopVideo() {} + }; + + public: + using Ptr = std::unique_ptr; + static Ptr Create(); + + ~TrackRendererAdapter(); + + bool Start(); + bool Stop(); + bool Prepare(); + bool Pause(); + bool Resume(); + bool SetTrack(const std::vector& trackinfo); + void SetIniProperty(const std::map& Properties); + bool Seek(uint64_t time_millisecond, double playback_rate); + bool Seek(uint64_t time_millisecond, double playback_rate, bool audio_mute); + bool SetPlaybackRate(double playback_rate, bool audio_mute); + bool GetPlayingTime(uint64_t* curtime_in_msec); + bool GetDroppedFrames(void* counts); + bool GetDroppedFramesForCatchup(TrackType type, void* counts); + bool Deactivate(TrackType type); + bool Activate(TrackType type, const Track& track); + bool SubmitPacket(const DecoderInputBufferPtr& data); + bool SubmitPacket(const DecoderInputBufferPtr& data, SubmitStatus* status); + bool SubmitPacket2(const DecoderInputBufferPtr& data, SubmitStatus* status); + void SetDrm(const drm::Property& drm_property); + void DrmLicenseAcquiredDone(TrackType type); + bool SetDisplayMode(const DisplayMode& mode); + bool SetDisplayRotate(const DisplayRotation& rotate); + bool GetDisplayRotate(DisplayRotation* rotate); + bool SetDisplay(const DisplayType& type, uint32_t surface_id, long x, long y, + long w, long h); + bool SetDisplay(const DisplayType& type, void* obj); + bool SetDisplay(const DisplayType& type, void* ecore_wl2_window, int x, int y, + int w, int h); + bool SetDisplaySubsurface(const DisplayType& type, void* ecore_wl2_subsurface, + int x, int y, int w, int h); + bool SetDisplayRoi(const Geometry& roi); + bool SetVideoRoi(const CropArea& area); + bool ResizeRenderRect(const RenderRect& rect); + bool SetDisplayVisible(bool is_visible); + void GetDisplay(DisplayType* type, Geometry* area); + void GetDisplayMode(DisplayMode* mode); + void SetAppId(const std::string& app_id); + void SetAppInfo(const PlayerAppInfo& app_info); + bool SetAudioMute(bool is_mute); + bool SetVolume(const int& volume); + bool GetVolume(int* volume); + bool SetCatchUpSpeed(const CatchUpSpeed& level); + bool GetVideoLatencyStatus(LatencyStatus* status); + bool GetAudioLatencyStatus(LatencyStatus* status); + + void RegisterListener(EventListener* listener); + void RegisterListenerForEsplayer(EventListener* listener); + void SetVideoStillMode(const StillMode& type); + void SetAttribute(const Attribute& attr, const boost::any& value); + TrackRendererState GetState(); + bool SetMatroskaColorInfo(const std::string& color_info); + void SetVideoFrameBufferType(VideoFrameTypeStrategyPtr strategy); + bool SetVideoFrameBufferScaleResolution(const uint32_t& target_width, + const uint32_t& target_height); + bool SetDecodedVideoFrameRate(const Rational& request_framerate); + bool Flush(const StreamType& type); + bool Flush(const TrackType& type); + void GetAttribute(const Attribute& attr, boost::any* value); + bool RenderVideoFrame(); + bool SetAiFilter(void* aifilter); + bool SetVideoMidLatencyThreshold(const unsigned int threshold); + bool SetAudioMidLatencyThreshold(const unsigned int threshold); + bool SetVideoHighLatencyThreshold(const unsigned int threshold); + bool SetAudioHighLatencyThreshold(const unsigned int threshold); + bool InitAudioEasingInfo(const uint32_t init_volume, + const uint32_t init_elapsed_time, + const AudioEasingInfo& easing_info); + bool UpdateAudioEasingInfo(const AudioEasingInfo& easing_info); + bool GetAudioEasingInfo(uint32_t* current_volume, uint32_t* elapsed_time, + AudioEasingInfo* easing_info); + bool StartAudioEasing(); + bool StopAudioEasing(); + bool GetVirtualRscId(const RscType type, int* virtual_id); + bool SetAdvancedPictureQualityType(const AdvPictureQualityType type); + bool SetResourceAllocatePolicy(const RscAllocPolicy policy); + bool SetVideoParDar(uint64_t time_millisecond, uint32_t par_num, + uint32_t par_den, uint32_t dar_num, uint32_t dar_den); + + private: + TrackRendererAdapter(); + using UserData = void*; + static void ErrorCb_(const TrackRendererErrorType error_code, + UserData userdata); + static void ErrorMsgCb_(const TrackRendererErrorType error_code, + char* error_msg, UserData userdata); + static void ResourceConflictCb_(UserData userdata); + + static void SeekDoneCb_(UserData userdata); + + static void FlushDoneCb_(UserData userdata); + + static void EosCb_(UserData userdata); + + static void EventCb_(const TrackRendererEventType event_type, + const TrackrendererEventMsg msg_data, UserData userdata); + + static void FirstDecodingDoneCb_(UserData userdata); + + static void SubtitleRawDataCb_(TrackRendererDecoderInputBuffer* buf, + const TrackRendererSubtitleType type, + UserData userdata); + +#ifndef TRACKRENDERER_FEATURE_DEPRECATE_SUBTITLE_CB + static void SubtitleDataCb_(const char* data, const int size, + const TrackRendererSubtitleType type, + const uint64_t duration, + TrackRendererSubtitleAttr* attr_list, + int attr_list_size, UserData userdata); +#endif + + static void ClosedCaptionDataCb_(const char* data, const int size, + UserData userdata); + + static void DrmInitDataCb_(int* drmhandle, unsigned int len, + unsigned char* psshdata, + TrackRendererTrackType type, UserData userdata); + + static void BufferStatusCb_(const TrackRendererTrackType type, + const TrackRendererBufferStatus status, + UserData userdata); + + static void SeekDataCb_(const TrackRendererTrackType type, + const uint64_t offset, UserData userdata); + + static void MediaPacketGetTbmBufPtrCb_(void** ptr, bool is_scale_change, + UserData userdata); + + static void MediaPacketVideoDecodedCb_( + const TrackRendererDecodedVideoPacket* packet, UserData userdata); + + static void MediaPacketVideoRawDecodedCb_( + const TrackRendererDecodedVideoRawModePacket* packet, + TrackRendererDecodedVideoType type, UserData userdata); + + static void VideoDecoderUnderrunCb_(UserData userdata); + static void VideoLatencyStatusCb_( + const TrackRendererLatencyStatus latency_status, UserData userdata); + static void AudioLatencyStatusCb_( + const TrackRendererLatencyStatus latency_status, UserData userdata); + static void VideoHighLatencyCb_(UserData userdata); + static void AudioHighLatencyCb_(UserData userdata); + static void MultiviewStartVideoCb_(UserData userdata); + static void MultiviewStopVideoCb_(UserData userdata); + + private: + using TrackRendererHandle = void*; + TrackRendererHandle handle_ = nullptr; + EventListener* eventlistener_{nullptr}; +}; // class TrackRendererAdapter + +} // namespace plusplayer + +#endif // __PLUSPLAYER_SRC_PLAYER_TRACKRENDERERADAPTER_H__ diff --git a/src/plusplayer-core/include_internal/core/trackrendereradapter_utils.h b/src/plusplayer-core/include_internal/core/trackrendereradapter_utils.h new file mode 100755 index 0000000..7c5ae43 --- /dev/null +++ b/src/plusplayer-core/include_internal/core/trackrendereradapter_utils.h @@ -0,0 +1,105 @@ +// +// @ Copyright [2017] +// + +#ifndef __PLUSPLAYER_PLAYER_TRACKRENDERERADAPTER_UTILS_H__ +#define __PLUSPLAYER_PLAYER_TRACKRENDERERADAPTER_UTILS_H__ + +#include + +#include "plusplayer/appinfo.h" +#include "plusplayer/audioeasinginfo.h" +#include "plusplayer/drm.h" +#include "plusplayer/track.h" +#include "plusplayer/types/buffer.h" +#include "plusplayer/types/display.h" +#include "plusplayer/types/error.h" +#include "plusplayer/types/latency.h" +#include "plusplayer/types/picturequality.h" +#include "plusplayer/types/resource.h" +#include "plusplayer/types/stream.h" +#include "trackrenderer_capi/buffer.h" +#include "trackrenderer_capi/decoderinputbuffer.h" +#include "trackrenderer_capi/display.h" +#include "trackrenderer_capi/drm.h" +#include "trackrenderer_capi/error.h" +#include "trackrenderer_capi/latency.h" +#include "trackrenderer_capi/track.h" +#include "trackrenderer_capi/trackrenderer_capi.h" + +namespace plusplayer { + +namespace adapter_utils { + +void InitTrack(TrackRendererTrack* track); +void MakeGeometry(Geometry* roi, const TrackRendererGeometry& geometry); +void MakeTrackRendererDrmProperty( + TrackRendererDrmProperty* trackrenderer_drm_property, + const drm::Property& drm_property); +void MakeTrackRendererGeometry(TrackRendererGeometry* geometry, + const Geometry& roi); +void MakeTrackRendererCropArea(TrackRendererCropArea* crop, + const CropArea& area); +void MakeTrackRendererRenderRect(TrackRendererRenderRect* output, + const RenderRect& input); +void MakeTrackRendererTrack(TrackRendererTrack* track, const Track& trackinfo); +void MakeTrackRendererAppInfo(TrackRendererAppInfo* app_attr, + const PlayerAppInfo& app_info); +void MakeTrackRendererAudioEasingInfo(TrackRendererAudioEasingInfo* easing_attr, + const AudioEasingInfo& easing_info); +void MakeAudioEasingInfo(AudioEasingInfo* easing_info, + const TrackRendererAudioEasingInfo& easing_attr); +void MakeTrackRendererRational(TrackRendererRational* rational_attr, + const Rational& rational_info); +DisplayMode ConvertToDisplayMode(TrackRendererDisplayMode typevalue); +DisplayType ConvertToDisplayType(const TrackRendererDisplayType typevalue); +ErrorType ConvertToErrorType(const TrackRendererErrorType type); +#ifndef TRACKRENDERER_FEATURE_DEPRECATE_SUBTITLE_CB +SubtitleAttrType ConvertToSubtitleAttrType( + const TrackRendererSubtitleAttrType& type); +#endif +SubtitleType ConvertToSubtitleType(const TrackRendererSubtitleType& type); +TrackType ConvertToTrackType(const TrackRendererTrackType typevalue); +DecodedVideoPacket ConvertToDecodedVideoPacket( + const TrackRendererDecodedVideoPacket* packet); +TrackRendererDecodedVideoFrameBufferType ConvertToVideoFrameBufferType( + const DecodedVideoFrameBufferType& type); +TrackRendererDisplayMode ConvertToTrackRendererDisplayMode( + const DisplayMode& mode); +TrackRendererDisplayRotate ConvertToTrackRendererDisplayRotate( + const DisplayRotation& rotate); +DisplayRotation ConvertToDisplayRotation( + const TrackRendererDisplayRotate rotate_value); +TrackRendererDisplayType ConvertToTrackRendererDisplayType( + const DisplayType& type); +TrackRendererDrmType ConvertToTrackRendererDrmType(const drm::Type& drm_type); +TrackRendererStillMode ConvertToTrackRendererStillMode( + const StillMode& still_mode); +TrackRendererTrackType ConvertToTrackRendererTrackType(const TrackType& type); +TrackRendererTrackType ConvertToTrackRendererTrackTypeFromStreamType( + const StreamType& type); +TrackRendererCatchUpSpeed ConvertToTrackRendererCatchUpSpeed( + const CatchUpSpeed& level); +LatencyStatus ConvertToLatencyStatus(const TrackRendererLatencyStatus& status); + +#ifndef TRACKRENDERER_FEATURE_DEPRECATE_SUBTITLE_CB +boost::any SetSubtitleAttrValue(const TrackRendererSubtitleAttr& value); +#endif + +BufferStatus ConvertToBufferStatus(const TrackRendererBufferStatus& status); +AudioEasingType ConvertToAudioEasingType( + const TrackRendererAudioEasingType& type); +TrackRendererAudioEasingType ConvertToTrackRendererAudioEasingType( + const AudioEasingType& type); +bool ConvertToTrackRendererRscType(const RscType& typevalue, + TrackRendererRscType* type); +bool ConvertToTrackRendererAdvPictureQualityType( + const AdvPictureQualityType& typevalue, + TrackRendererAdvPictureQualityType* type); +bool ConvertToTrackRendererRscAllocPolicy(const RscAllocPolicy& policyvalue, + TrackRendererRscAllocPolicy* policy); +} // namespace adapter_utils + +} // namespace plusplayer + +#endif // __PLUSPLAYER_PLAYER_TRACKRENDERERADAPTER_UTILS_H__ diff --git a/src/plusplayer-core/include_internal/core/utils/base64.h b/src/plusplayer-core/include_internal/core/utils/base64.h new file mode 100755 index 0000000..ca76b63 --- /dev/null +++ b/src/plusplayer-core/include_internal/core/utils/base64.h @@ -0,0 +1,22 @@ +// +// @ Copyright [2020] +// + +#ifndef __PLUSPLAYER_SRC_BASE64_H__ +#define __PLUSPLAYER_SRC_BASE64_H__ + +#include +#include +#include +#include "core/utils/plusplayer_log.h" + +namespace plusplayer { +namespace base64 { +std::string Base64Encode(const char *str, const int size); + +std::string Base64Decode(const std::string str); +} // namespace base64 + +} // namespace plusplayer + +#endif // __PLUSPLAYER_SRC_BASE64_H__ diff --git a/src/plusplayer-core/include_internal/core/utils/caf_logger.h b/src/plusplayer-core/include_internal/core/utils/caf_logger.h new file mode 100755 index 0000000..33a8218 --- /dev/null +++ b/src/plusplayer-core/include_internal/core/utils/caf_logger.h @@ -0,0 +1,91 @@ +#ifndef _AVPLAY_CAF_LOGGER_H__ +#define _AVPLAY_CAF_LOGGER_H__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace plusplayer { + + enum class CafEventType { + kNone = 0, + kStart, + kEnd, + kBitrate, + kBuffering, + kResolution, + kStreamReady, + kIdle, + kReady, + kPlaying, + kPaused, + kEventMax + }; + + typedef struct _CafEventData { + CafEventType event_type; + std::string event_data; + }CafEventData; + + class ContextAware { + public: + ContextAware() {} + ~ContextAware() {} + bool InitService(); + void Write(std::string json_data); + bool FiniService(); + }; + + class CafLogger { + private: + static CafLogger *instance_; + static std::shared_ptr context_aware_; + + std::mutex object_lock_; + bool connected_to_dbus_; + bool msg_thread_stopped_; + std::queue msg_queue_; + std::mutex msg_task_mutex_; + std::condition_variable msg_task_cv_; + std::future msg_handler_task_; + std::string app_id_; + int unique_number; + std::queue using_instance_; + + CafLogger(); + std::string GetEventStrName_(CafEventType event_type); + std::string GetEventValueStrName_(CafEventType event_type); + std::string GetStateValueStrName_(CafEventType event_type); + void SendData_(CafEventData event); + void MsgTask_(); + void StartMsgThread_(); + void StopMsgThread_(); + bool PushMessageToQueue_(CafEventType event_type, std::string data); + bool isConnected_(); + bool Connect_(); + bool Disconnect_(); + void setAppId_(std::string app_id); + void setUniqueNumber_(int uniqueNumber); + int getUniqueNumber_(); + + public: + static bool Initialize(); + static bool LogMessage(CafEventType event_type, std::string data); + static void StartLoggingThread(); + static void StopLoggingThread(); + static void SetAppId(std::string app_id); + static void SetUniqueNumber(); + static std::string GetUniqueNumber(); + static void SetContextAware(std::shared_ptr&& context_aware); + ~CafLogger(); + }; + +} //plusplayer + +#endif //_AVPLAY_CAF_LOGGER_H__ diff --git a/src/plusplayer-core/include_internal/core/utils/performance_checker.h b/src/plusplayer-core/include_internal/core/utils/performance_checker.h new file mode 100755 index 0000000..6308999 --- /dev/null +++ b/src/plusplayer-core/include_internal/core/utils/performance_checker.h @@ -0,0 +1,48 @@ +// +// @ Copyright [2017] +// + +#ifndef __PLUSPLAYER_SRC_UTILS_PERFORMANCE_CHECKER_H__ +#define __PLUSPLAYER_SRC_UTILS_PERFORMANCE_CHECKER_H__ + +#include +#include + +#include "core/utils/plusplayer_log.h" + +// Hw clock +#ifndef PR_TASK_PERF_USER_TRACE +#define PR_TASK_PERF_USER_TRACE 666 +#endif + +namespace plusplayer { + +namespace performance_checker { + +inline clock_t Start() { return clock(); } + +inline void End(const clock_t start, + const char* msg = nullptr) { + LOG_DEBUG("[PERF][%s] ELAPSED[%f]SECS", ((msg != nullptr) ? msg : "&"), + static_cast(clock() - start) / CLOCKS_PER_SEC); +} + +constexpr int kBufSize = 256; +inline void PerfUsrTrace(const char* arg = nullptr) { + char buf[kBufSize] {0,}; + const char* prefix_str = "[PERF][PLUSPLAYER]"; + bool use_arg {false}; + if(arg) { + if(strlen(arg) < (kBufSize - strlen(prefix_str))) { + use_arg = true; + } + } + snprintf(buf, kBufSize, "%s %s",prefix_str, (use_arg? arg : "")); + prctl(PR_TASK_PERF_USER_TRACE, buf, strlen(buf)); +} + +} // namespace performance_checker + +} // namespace plusplayer + +#endif // __PLUSPLAYER_SRC_UTILS_PERFORMANCE_CHECKER_H__ diff --git a/src/plusplayer-core/include_internal/core/utils/plusplayer_cfg.h b/src/plusplayer-core/include_internal/core/utils/plusplayer_cfg.h new file mode 100755 index 0000000..c520886 --- /dev/null +++ b/src/plusplayer-core/include_internal/core/utils/plusplayer_cfg.h @@ -0,0 +1,17 @@ +// +// @ Copyright [2017] +// + +#ifndef __PLUSPLAYER_SRC_CORE_UTIL_PLUSPLAYER_CFG__ +#define __PLUSPLAYER_SRC_CORE_UTIL_PLUSPLAYER_CFG__ + +namespace plusplayer { + +namespace plusplayer_cfg { + +const char* GetIniPath(); + +} // namespace plusplayer_cfg + +} // namespace plusplayer +#endif // __PLUSPLAYER_SRC_CORE_UTIL_PLUSPLAYER_CFG__ \ No newline at end of file diff --git a/src/plusplayer-core/include_internal/core/utils/plusplayer_log.h b/src/plusplayer-core/include_internal/core/utils/plusplayer_log.h new file mode 100755 index 0000000..b8e2318 --- /dev/null +++ b/src/plusplayer-core/include_internal/core/utils/plusplayer_log.h @@ -0,0 +1,110 @@ +// +// @ Copyright [2017] +// + +#ifndef __PLUSPLAYER_SRC_UTILS_PLUSPLAYER_LOG_H__ +#define __PLUSPLAYER_SRC_UTILS_PLUSPLAYER_LOG_H__ + +#include +#include + +#undef LOG_TAG +#define LOG_TAG "PLUSPLAYER" + +#ifndef __MODULE__ +#define __MODULE__ \ + (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) +#endif + +#define LOG_DEBUG(fmt, arg...) \ + ({ \ + do { \ + LOGE(fmt, ##arg); \ + } while (0); \ + }) + +#define LOG_DEBUG_P(id, fmt, arg...) \ + ({ \ + do { \ + LOGE("[%p] > " #fmt, id, ##arg); \ + } while (0); \ + }) + +#define LOG_INFO(fmt, arg...) \ + ({ \ + do { \ + LOGE(fmt, ##arg); \ + } while (0); \ + }) + +#define LOG_INFO_P(id, fmt, arg...) \ + ({ \ + do { \ + LOGE("[%p] > " #fmt, id, ##arg); \ + } while (0); \ + }) + +#define LOG_WARN(fmt, arg...) \ + ({ \ + do { \ + LOGE(fmt, ##arg); \ + } while (0); \ + }) + +#define LOG_WARN_P(id, fmt, arg...) \ + ({ \ + do { \ + LOGE("[ %p] > " #fmt, id, ##arg); \ + } while (0); \ + }) + +#define LOG_ERROR(fmt, arg...) \ + ({ \ + do { \ + LOGE(fmt, ##arg); \ + } while (0); \ + }) + +#define LOG_ERROR_P(id, fmt, arg...) \ + ({ \ + do { \ + LOGE("[ %p] > " #fmt, id, ##arg); \ + } while (0); \ + }) + +#define LOG_FATAL(fmt, arg...) \ + ({ \ + do { \ + LOGE(fmt, ##arg); \ + } while (0); \ + }) + +#define LOG_ENTER \ + { \ + do { \ + LOGE("ENTER"); \ + } while (0); \ + } + +#define LOG_LEAVE \ + { \ + do { \ + LOGE("LEAVE"); \ + } while (0); \ + } + +#define LOG_ENTER_P(p) \ + { \ + do { \ + LOGE("[%p] > ENTER", p); \ + } while (0); \ + } + +#define LOG_LEAVE_P(p) \ + { \ + do { \ + LOGE("[%p] > LEAVE", p); \ + } while (0); \ + } + +#endif // __PLUSPLAYER_SRC_UTILS_PLUSPLAYER_LOG_H__ diff --git a/src/plusplayer-core/include_internal/core/utils/scope_exit.h b/src/plusplayer-core/include_internal/core/utils/scope_exit.h new file mode 100755 index 0000000..a6295bc --- /dev/null +++ b/src/plusplayer-core/include_internal/core/utils/scope_exit.h @@ -0,0 +1,36 @@ +// +// @ Copyright [2017] +// + +#ifndef __PLUSPLAYER_SRC_UTILS_SCOPE_EXIT_H__ +#define __PLUSPLAYER_SRC_UTILS_SCOPE_EXIT_H__ + +namespace plusplayer { + +namespace utils { + +template +class AtScopeExit { + public: + explicit AtScopeExit(const T& func) : func_(func) {} + AtScopeExit(const AtScopeExit&) = delete; + AtScopeExit(AtScopeExit&& o) : func_(o.func_) { o.call_ = false; } + ~AtScopeExit() { + if (call_) func_(); + } + + private: + const T& func_; + bool call_ = true; +}; + +template +AtScopeExit ScopeExit(const T& func) { + return AtScopeExit(func); +} + +} // namespace utils + +} // namespace plusplayer + +#endif // __PLUSPLAYER_SRC_UTILS_SCOPE_EXIT_H__ diff --git a/src/plusplayer-core/include_internal/core/videoframetypestrategy.h b/src/plusplayer-core/include_internal/core/videoframetypestrategy.h new file mode 100755 index 0000000..e8018fd --- /dev/null +++ b/src/plusplayer-core/include_internal/core/videoframetypestrategy.h @@ -0,0 +1,40 @@ +// +// @ Copyright [2020] +// + +#ifndef __PLUSPLAYER_SRC_CORE_VIDEO_FRAME_TYPE_STRATEGY_H__ +#define __PLUSPLAYER_SRC_CORE_VIDEO_FRAME_TYPE_STRATEGY_H__ + +#include + +#include "plusplayer/types/buffer.h" + +namespace plusplayer { +struct VideoFrameTypeStrategy { + using TrackRendererHandle = void*; + virtual ~VideoFrameTypeStrategy() = default; + virtual void SetType(TrackRendererHandle handle) = 0; +}; + +using VideoFrameTypeStrategyPtr = std::unique_ptr; + +class DefaultVideoFrameTypeStrategy : public virtual VideoFrameTypeStrategy { + public: + explicit DefaultVideoFrameTypeStrategy( + const DecodedVideoFrameBufferType type); + virtual ~DefaultVideoFrameTypeStrategy() = default; + virtual void SetType(TrackRendererHandle handle) override; + + private: + const DecodedVideoFrameBufferType type_; +}; + +class RawVideoFrameTypeStrategy : public virtual VideoFrameTypeStrategy { + public: + explicit RawVideoFrameTypeStrategy() = default; + virtual ~RawVideoFrameTypeStrategy() = default; + virtual void SetType(TrackRendererHandle handle); +}; +} // namespace plusplayer + +#endif // __PLUSPLAYER_SRC_CORE_VIDEO_FRAME_TYPE_STRATEGY_H__ \ No newline at end of file diff --git a/src/plusplayer-core/project_def.prop b/src/plusplayer-core/project_def.prop new file mode 100755 index 0000000..95f2f9c --- /dev/null +++ b/src/plusplayer-core/project_def.prop @@ -0,0 +1,58 @@ + +# Project Name +APPNAME = plusplayercore_tvplus + +# Project Type +type = sharedLib + +# Project Profile +profile = tv-samsung-5.0 + +# C/CPP Sources +USER_SRCS = src/decoderinputbuffer.cpp src/error.cpp src/gstobject_guard.cpp src/gstsignal_holder.cpp src/gst_utils.cpp src/plusplayer_cfg.cpp src/serializer.cpp src/subtitle_attr_parser.cpp src/trackrendereradapter.cpp src/trackrendereradapter_utils.cpp src/track_util.cpp + +# EDC Sources +USER_EDCS = + +# PO Sources +USER_POS = + +# User Defines +USER_DEFS = +USER_CPP_DEFS = TIZEN_DEPRECATION DEPRECATION_WARNING + +# User Undefines +USER_UNDEFS = +USER_CPP_UNDEFS = + +# User Libraries +USER_LIBS = gstsubtitle_tvplus + +# User Objects +USER_OBJS = + +# User Includes +## C Compiler +USER_C_INC_DIRS = +USER_INC_FILES = +## C++ Compiler +USER_CPP_INC_DIRS = include_internal ../../include ../../../gst-plugins-subtitleparser/subtitle/include_internal +USER_CPP_INC_FILES = + +USER_INC_DIRS = $(USER_C_INC_DIRS) $(USER_CPP_INC_DIRS) + +# User Library Path +USER_LIB_DIRS = ../../../gst-plugins-subtitleparser/subtitle/${BUILD_CONFIG} + +# EDC Resource Path +USER_EDCS_IMAGE_DIRS = ${OUTPUT_DIR} +USER_EDCS_SOUND_DIRS = ${OUTPUT_DIR} +USER_EDCS_FONT_DIRS = ${OUTPUT_DIR} + +# EDC Flags +USER_EXT_EDC_KEYS = + +# Resource Filter +USER_RES_INCLUDE = +USER_RES_EXCLUDE = + diff --git a/src/plusplayer-core/src/base64.cpp b/src/plusplayer-core/src/base64.cpp new file mode 100755 index 0000000..083b8c3 --- /dev/null +++ b/src/plusplayer-core/src/base64.cpp @@ -0,0 +1,181 @@ +// +// @ Copyright [2020] +// + +#ifndef __PLUSPLAYER_SRC_BASE64_H__ +#define __PLUSPLAYER_SRC_BASE64_H__ + +#include +#include +#include +#include "core/kpi.h" +#include "core/utils/plusplayer_log.h" + +namespace plusplayer { + namespace base64 { +std::string Base64Encode(const char *str, const int size) { + LOG_ENTER + static const std::string sBase64Table = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + static const char cFillChar = '='; + std::string sResult; + + // Allocate memory for the converted string + sResult.reserve(size * 8 / 6 + 1); + + for (int nPos = 0; nPos < size; nPos++) { + char cCode = (str[nPos] >> 2) & 0x3F; + // Encode the first 6 bits + sResult.append(1, sBase64Table[cCode]); + // Encode the remaining 2 bits with the next 4 bits (if present) + cCode = (str[nPos] << 4) & 0x3F; + + if (++nPos < size) { + cCode |= (str[nPos] >> 4) & 0x0F; + } + + sResult.append(1, sBase64Table[cCode]); + + if (nPos < size) { + cCode = (str[nPos] << 2) & 0x3F; + + if (++nPos < size) { + cCode |= (str[nPos] >> 6) & 0x03; + } + + sResult.append(1, sBase64Table[cCode]); + } else { + ++nPos; + sResult.append(1, cFillChar); + } + + if (nPos < size) { + cCode = str[nPos] & 0x3F; + sResult.append(1, sBase64Table[cCode]); + } else { + sResult.append(1, cFillChar); + } + } + + LOG_INFO("Base64Encode output str %s", sResult.c_str()); + LOG_LEAVE + + return sResult; +} + +std::string Base64Decode(const std::string str) { + // LOG_ENTER + std::string destr; + const int strLen = str.size(); + + static const char arr[] = {// insert from 'A' to 'Z' + 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, + 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, + // insert from 'a' to 'z' + 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, + 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, + 118, 119, 120, 121, 122, + // insert from '0' to '9' + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, + // insert '+' and '/' + 43, 47}; + const int size = static_cast(sizeof(arr) / sizeof(char)); + static std::map base64EncodeMap; + + if (base64EncodeMap.size() == 0) + for (int i = 0; i < size; i++) + base64EncodeMap.insert(std::pair(arr[i], i)); + + LOG_INFO("Base64decode inconming str len %d ", strLen); + + if (strLen % 4 != 0) { + // input string size must be multiple of 4 + LOG_INFO("Quit base64decode for strlen error"); + return destr; + } + + if (strLen > 0) { + int pos = 0; + std::string subStr; + int encodePrev[4] = {0, 0, 0, 0}; + int encodeEnd[3] = {0, 0, 0}; + + while (pos < strLen) { + subStr = str.substr(pos, 4); + int i = 0; + + for (const auto &c : subStr) encodePrev[i++] = base64EncodeMap[c]; + + encodeEnd[0] = (encodePrev[0] << 2) | (encodePrev[1] >> 4); + encodeEnd[1] = (encodePrev[1] << 4) | (encodePrev[2] >> 2); + encodeEnd[2] = (encodePrev[2] << 6) | encodePrev[3]; + destr.push_back(static_cast(encodeEnd[0])); + destr.push_back(static_cast(encodeEnd[1])); + destr.push_back(static_cast(encodeEnd[2])); + pos += 4; + } + } + + // base64 data contain "=" case + int count = 0; + + for (int i = str.size() - 1; i >= 0; --i) { + if (str[i] == '=') + count++; + else + break; + } + + LOG_INFO("[*] num of [=] ( %d )", count); + + if (count > 2) { + LOG_INFO("[ERROR] num of = should less than two( %d )", count); + destr.clear(); + } else if (count > 0) { + destr.erase(destr.size() - count, count); + } + + LOG_INFO("Base64Decode output str %d", destr.size()); + LOG_LEAVE + return destr; +} + } + +} // namespace plusplayer + +// base64 rfs ut +// int main() +// { +// std::cout <<"test"< RFC_test;//RFC 4648 tc +// RFC_test.insert(std::pair("","")); +// RFC_test.insert(std::pair("f","Zg==")); +// RFC_test.insert(std::pair("fo","Zm8=")); +// RFC_test.insert(std::pair("foo","Zm9v")); +// RFC_test.insert(std::pair("foob","Zm9vYg==")); +// RFC_test.insert(std::pair("fooba","Zm9vYmE=")); +// RFC_test.insert(std::pair("foobar","Zm9vYmFy")); +// std::map::iterator iter; + +// for (iter = RFC_test.begin(); iter != RFC_test.end(); iter++) +// { +// std::string encodedstr = +// Base64Encode(iter->first.c_str(),iter->first.length()); +// if(0!=encodedstr.compare(iter->second)) +// { +// std::cout << "error encode test tc [" + iter->first +"] user result["+ +// encodedstr + "] refer["+iter->second+"]"; +// } +// } +// for (iter = RFC_test.begin(); iter != RFC_test.end(); iter++) +// { +// std::string decodedstr = Base64Decode(iter->second ); +// if(0!=decodedstr.compare(iter->first)) +// { +// std::cout << "error decode test tc [" + iter->second +"] user result["+ +// decodedstr + "] refer["+iter->first+"]"; +// } +// } + +// } +#endif // __PLUSPLAYER_SRC_BASE64_H__ diff --git a/src/plusplayer-core/src/caf_logger.cpp b/src/plusplayer-core/src/caf_logger.cpp new file mode 100755 index 0000000..62c3195 --- /dev/null +++ b/src/plusplayer-core/src/caf_logger.cpp @@ -0,0 +1,303 @@ +#define _TAG "plusplayer" +#include "core/utils/caf_logger.h" +#include "core/utils/plusplayer_log.h" +#include "context-aware-api.h" +#include "ContextData.h" +#include "json/json.h" +#define SUBJECT_RAW_STREAMING_EVENT "I/Raw/Streaming/Event" + +namespace plusplayer { + +CafLogger* CafLogger::instance_ = NULL; +std::shared_ptr CafLogger::context_aware_ = NULL; + +std::string EventStrName[] = {"None","Start", "End", "BitRate", "Buffering", "Resolution"}; + +bool ContextAware::InitService() { + return ContextAware_InitService(); +} + +void ContextAware::Write(std::string json_data) { + return ContextAware_WriteRawStreamingEvent(json_data.c_str()); +} + +bool ContextAware::FiniService() { + return ContextAware_FiniService(); +} + +CafLogger::CafLogger() { + LOG_ENTER; + connected_to_dbus_ = false; + std::lock_guard guard(object_lock_); + + if(context_aware_ == NULL) { + context_aware_ = std::make_shared(); + } + + if(context_aware_ != NULL) + connected_to_dbus_ = context_aware_->InitService(); + msg_thread_stopped_ = true; + app_id_ = "Unknown"; + unique_number = -1; + if(connected_to_dbus_) + LOG_INFO("CAF initialized successfully."); + else + LOG_ERROR("CAF initialization FAILED."); + + LOG_LEAVE; +} + +CafLogger::~CafLogger() { + LOG_ENTER; + std::lock_guard guard(object_lock_); + StopMsgThread_(); + if(connected_to_dbus_ == true && context_aware_ != NULL) + { + connected_to_dbus_ = context_aware_->FiniService(); + } + + if(!connected_to_dbus_) + LOG_INFO("CAF finished successfully."); + LOG_LEAVE; +} + +std::string CafLogger::GetEventStrName_(CafEventType event_type) { + if(event_type > CafEventType::kNone && event_type < CafEventType::kEventMax) + return EventStrName[(int)event_type]; + return ""; +} + +std::string CafLogger::GetEventValueStrName_(CafEventType event_type) { + switch(event_type) { + case CafEventType::kBitrate: return "BitRateValue"; + case CafEventType::kResolution: return "ResolutionValue"; + case CafEventType::kBuffering: return "BufferingValue"; + default: + return ""; + } +} + +std::string CafLogger::GetStateValueStrName_(CafEventType event_type) { + switch(event_type) { + case CafEventType::kStreamReady: return "StreamReady"; + case CafEventType::kIdle: return "Idle"; + case CafEventType::kReady: return "Ready"; + case CafEventType::kPlaying: return "Playing"; + case CafEventType::kPaused: return "Paused"; + default: + return ""; + } +} + +void CafLogger::SendData_(CafEventData event) { + LOG_ENTER; + ContextData cafData; + cafData.SetValue("Appid",app_id_); + switch(event.event_type) { + case CafEventType::kStart: cafData.SetValue("Event", "Start");break; + case CafEventType::kEnd: cafData.SetValue("Event", "End");break; + case CafEventType::kBuffering: /* FALL THROUGH */ + //case CafEventType::kResolution: /* FALL THROUGH */ + case CafEventType::kBitrate: { + std::string event_name = GetEventStrName_(event.event_type); + cafData.SetValue("Event", event_name); + std::string event_value_name = GetEventValueStrName_(event.event_type); + cafData.SetValue(event_value_name.c_str(), event.event_data); + }break; + case CafEventType::kStreamReady: /* FALL THROUGH */ + case CafEventType::kIdle: /* FALL THROUGH */ + case CafEventType::kReady: /* FALL THROUGH */ + case CafEventType::kPlaying: /* FALL THROUGH */ + case CafEventType::kPaused: { + cafData.SetValue("UniqueId", event.event_data); + std::string state_value_name = GetStateValueStrName_(event.event_type); + cafData.SetValue("PlayerState", state_value_name.c_str()); + }break; + default: + return; + } + LOG_ERROR("all eventdata message [%s]", cafData.MakeStr()); + if(context_aware_ != NULL) + context_aware_->Write(cafData.MakeStr()); + LOG_LEAVE; +} + +void CafLogger::MsgTask_() { +LOG_ENTER; +std::unique_lock msg_mutex(msg_task_mutex_); + do{ + if(msg_queue_.empty()) + msg_task_cv_.wait(msg_mutex); + if(!msg_queue_.empty()) { + CafEventData eventData = msg_queue_.front(); + SendData_(eventData); + msg_queue_.pop(); + } + } while(!msg_thread_stopped_); + LOG_LEAVE; +} + +void CafLogger::StartMsgThread_() { + LOG_ENTER; + std::lock_guard guard(object_lock_); + if(msg_thread_stopped_) { + using_instance_.push(unique_number); + msg_thread_stopped_ = false; + msg_handler_task_ = std::async(std::launch::async, &CafLogger::MsgTask_, this); + } + LOG_LEAVE; +} + +void CafLogger::StopMsgThread_() { + LOG_ENTER; + + if(msg_thread_stopped_) return; + + using_instance_.pop(); + + if(msg_handler_task_.valid() && using_instance_.empty()) { + std::unique_lock msg_mutex(msg_task_mutex_); + msg_thread_stopped_ = true; + msg_task_cv_.notify_one(); + msg_mutex.unlock(); + msg_handler_task_.wait(); + } + LOG_LEAVE; +} + +bool CafLogger::PushMessageToQueue_(CafEventType event_type, std::string data) { + LOG_ENTER; + bool ret = false; + std::lock_guard guard(object_lock_); + if(msg_handler_task_.valid()) { + CafEventData event; + event.event_type = event_type; + event.event_data = data; + std::unique_lock msg_mutex(msg_task_mutex_); + msg_queue_.push(event); + msg_mutex.unlock(); + msg_task_cv_.notify_one(); + ret = true; + } + LOG_LEAVE; + return ret; +} + +bool CafLogger::Connect_() { + LOG_ENTER; + std::lock_guard guard(object_lock_); + if(!connected_to_dbus_ && context_aware_ != NULL) + connected_to_dbus_ = context_aware_->InitService(); + LOG_LEAVE; + return connected_to_dbus_; +} + +bool CafLogger::Disconnect_() { + LOG_ENTER; + std::lock_guard guard(object_lock_); + /*first stop message thread, then disconnect. */ + + StopMsgThread_(); + LOG_INFO("Disconnecting to DBus."); + if(connected_to_dbus_ && context_aware_ != NULL) + connected_to_dbus_ = context_aware_->FiniService(); + if(!connected_to_dbus_) + LOG_INFO("Disconnecting to DBus FAILED."); + connected_to_dbus_ = false; + LOG_LEAVE; + return true; +} + +bool CafLogger::isConnected_() { + return connected_to_dbus_; +} + +void CafLogger::setAppId_(std::string app_id) { + app_id_ = app_id; +} + +void CafLogger::setUniqueNumber_(int uniqueNumber) { + unique_number = uniqueNumber; +} + +int CafLogger::getUniqueNumber_() { + return unique_number; +} +/******** STATIC FUNCTIONS ****************/ + +bool CafLogger::Initialize() { + LOG_ENTER; + + if(instance_ == NULL) { + instance_ = new CafLogger(); + } + LOG_LEAVE; + return instance_->isConnected_(); +} + +bool CafLogger::LogMessage(CafEventType event_type, std::string data) { + LOG_ENTER; + bool ret = false; +#ifndef SDK_DISABLED_FEATURE + if(instance_ != NULL && instance_->isConnected_()) { + ret = instance_->PushMessageToQueue_(event_type, data); + } +#endif + LOG_LEAVE; + return ret; +} + +void CafLogger::StartLoggingThread() { + LOG_ENTER; + if(instance_ != NULL) + instance_->StartMsgThread_(); + LOG_LEAVE; +} + +void CafLogger::StopLoggingThread() { + LOG_ENTER; + if(instance_ != NULL) + instance_->StopMsgThread_(); + LOG_LEAVE; +} + +void CafLogger::SetAppId(std::string app_id) { + LOG_ENTER; + if(instance_ != NULL) + instance_->setAppId_(app_id); + LOG_LEAVE; +} + +void CafLogger::SetUniqueNumber() +{ + LOG_ENTER; + int id = -1; + if(instance_ != NULL) + { + id = instance_->getUniqueNumber_(); + instance_->setUniqueNumber_(++id); + } + LOG_LEAVE; +} +std::string CafLogger::GetUniqueNumber() +{ + LOG_ENTER; + int id = -1; + std::string uniqueNumber; + if(instance_ != NULL) + { + id = instance_->getUniqueNumber_(); + uniqueNumber = std::to_string(getpid()) + "_" + std::to_string(id); + } + LOG_LEAVE; + return uniqueNumber; +} + +void CafLogger::SetContextAware(std::shared_ptr&& context_aware) +{ + LOG_ENTER; + context_aware_ = context_aware; + LOG_LEAVE; +} + +} //plusplayer \ No newline at end of file diff --git a/src/plusplayer-core/src/decodedvideopacketex.cpp b/src/plusplayer-core/src/decodedvideopacketex.cpp new file mode 100755 index 0000000..68b0f5a --- /dev/null +++ b/src/plusplayer-core/src/decodedvideopacketex.cpp @@ -0,0 +1,21 @@ +// +// @ Copyright [2020] +// + +#include "plusplayer/decodedvideopacketex.h" + +namespace plusplayer { + + DecodedVideoPacketExPtr DecodedVideoPacketEx::Create(const uint64_t pts, + const uint64_t duration, tbm_surface_h surface_data, + const void* scaler_index) { + return Ptr(new DecodedVideoPacketEx(pts, duration, surface_data, scaler_index)); + } + + DecodedVideoPacketEx::~DecodedVideoPacketEx() { + if (surface_data_) { + tbm_surface_destroy(surface_data_); + surface_data_ = nullptr; + } + } +} // namespace plusplayer diff --git a/src/plusplayer-core/src/decoderinputbuffer.cpp b/src/plusplayer-core/src/decoderinputbuffer.cpp new file mode 100755 index 0000000..1087462 --- /dev/null +++ b/src/plusplayer-core/src/decoderinputbuffer.cpp @@ -0,0 +1,20 @@ +// +// @ Copyright [2017] +// + +#include "core/decoderinputbuffer.h" + +namespace plusplayer { + +namespace decoderinputbuffer_util { + +bool FlushQueue(std::queue& queue) { + while (!queue.empty()) { + queue.pop(); + } + return true; +} + +} // namespace decoderinputbuffer_util + +} // namespace plusplayer diff --git a/src/plusplayer-core/src/error.cpp b/src/plusplayer-core/src/error.cpp new file mode 100755 index 0000000..2f8fa14 --- /dev/null +++ b/src/plusplayer-core/src/error.cpp @@ -0,0 +1,159 @@ +// +// @ Copyright [2017] +// + +#include "core/error.h" + +#include "core/gstobject_guard.h" +#include "core/utils/plusplayer_log.h" + +namespace plusplayer { + +namespace internal { + +ErrorType ConvertGstCoreError(const int error_code); +ErrorType ConvertGstLibraryError(const int error_code); +ErrorType ConvertGstResourceError(const int error_code); +ErrorType ConvertGstStreamError(const GError* error); + +} // namespace internal + +ErrorType HandleGstError(const GError* error) { + ErrorType ret = ErrorType::kNone; + + if (error == nullptr) return ret; + LOG_ERROR("Entered HandleGstError error->code[%d] ", error->code); + if (error->domain == GST_CORE_ERROR) { + ret = internal::ConvertGstCoreError(error->code); + } else if (error->domain == GST_LIBRARY_ERROR) { + ret = internal::ConvertGstLibraryError(error->code); + } else if (error->domain == GST_RESOURCE_ERROR) { + ret = internal::ConvertGstResourceError(error->code); + } else if (error->domain == GST_STREAM_ERROR) { + ret = internal::ConvertGstStreamError(error); + } else { + LOG_INFO("This error domain is not defined.\n"); + // we treat system error as an internal error + ret = ErrorType::kInvalidOperation; + } + return ret; +} + +namespace internal { + +ErrorType ConvertGstCoreError(const int error_code) { + ErrorType type = ErrorType::kNone; + switch (error_code) { + case GST_CORE_ERROR_MISSING_PLUGIN: + return ErrorType::kNotSupportedFormat; + case GST_CORE_ERROR_STATE_CHANGE: + case GST_CORE_ERROR_SEEK: + case GST_CORE_ERROR_NOT_IMPLEMENTED: + case GST_CORE_ERROR_FAILED: + case GST_CORE_ERROR_TOO_LAZY: + case GST_CORE_ERROR_PAD: + case GST_CORE_ERROR_THREAD: + case GST_CORE_ERROR_NEGOTIATION: + case GST_CORE_ERROR_EVENT: + case GST_CORE_ERROR_CAPS: + case GST_CORE_ERROR_TAG: + case GST_CORE_ERROR_CLOCK: + case GST_CORE_ERROR_DISABLED: + default: + type = ErrorType::kInvalidOperation; + break; + } + return type; +} + +ErrorType ConvertGstLibraryError(const int error_code) { + ErrorType type = ErrorType::kNone; + switch (error_code) { + case GST_LIBRARY_ERROR_FAILED: + case GST_LIBRARY_ERROR_TOO_LAZY: + case GST_LIBRARY_ERROR_INIT: + case GST_LIBRARY_ERROR_SHUTDOWN: + case GST_LIBRARY_ERROR_SETTINGS: + case GST_LIBRARY_ERROR_ENCODE: + default: + type = ErrorType::kInvalidOperation; + break; + } + + return type; +} + +ErrorType ConvertGstResourceError(const int error_code) { + ErrorType type = ErrorType::kNone; + switch (error_code) { + case GST_RESOURCE_ERROR_NO_SPACE_LEFT: + type = ErrorType::kFileNoSpaceOnDevice; + break; + case GST_RESOURCE_ERROR_NOT_FOUND: + case GST_RESOURCE_ERROR_OPEN_READ: + case GST_RESOURCE_ERROR_BUSY: + type = ErrorType::kConnectionFailed; + break; + case GST_RESOURCE_ERROR_READ: + type = ErrorType::kInvalidOperation; + break; + case GST_RESOURCE_ERROR_WRITE: + case GST_RESOURCE_ERROR_FAILED: + case GST_RESOURCE_ERROR_SEEK: + case GST_RESOURCE_ERROR_TOO_LAZY: + case GST_RESOURCE_ERROR_OPEN_WRITE: + case GST_RESOURCE_ERROR_OPEN_READ_WRITE: + case GST_RESOURCE_ERROR_CLOSE: + case GST_RESOURCE_ERROR_SYNC: + case GST_RESOURCE_ERROR_SETTINGS: + default: + type = ErrorType::kInvalidOperation; + break; + } + return type; +} + +ErrorType ConvertGstStreamError(const GError* error) { + ErrorType type = ErrorType::kNone; + if (!error) return ErrorType::kInvalidParameter; + + switch (error->code) { + case GST_STREAM_ERROR_DECODE: + return ErrorType::kInvalidOperation; + case GST_STREAM_ERROR_CODEC_NOT_FOUND: + case GST_STREAM_ERROR_TYPE_NOT_FOUND: + case GST_STREAM_ERROR_WRONG_TYPE: + return ErrorType::kNotSupportedFile; + case GST_STREAM_ERROR_FAILED: + return ErrorType::kNotSupportedFormat; + case GST_STREAM_ERROR_DECRYPT: + return ErrorType::kDrmInfo; + case GST_STREAM_ERROR_DECRYPT_NOKEY: { + LOG_INFO("decryption error, reason : [%s]\n", error->message); + if (strstr(error->message, "rights expired")) { + return ErrorType::kDrmExpired; + } else if (strstr(error->message, "no rights")) { + return ErrorType::kDrmNoLicense; + } else if (strstr(error->message, "has future rights")) { + return ErrorType::kDrmFutureUse; + } else if (strstr(error->message, "opl violation")) { + return ErrorType::kDrmNotPermitted; + } + return ErrorType::kDrmInfo; + } + case GST_STREAM_ERROR_NOT_IMPLEMENTED: + case GST_STREAM_ERROR_TOO_LAZY: + case GST_STREAM_ERROR_ENCODE: + case GST_STREAM_ERROR_DEMUX: + case GST_STREAM_ERROR_MUX: + case GST_STREAM_ERROR_FORMAT: + default: + type = ErrorType::kInvalidOperation; + break; + } + return type; +} + +} // namespace internal + +} // namespace plusplayer diff --git a/src/plusplayer-core/src/gst_utils.cpp b/src/plusplayer-core/src/gst_utils.cpp new file mode 100755 index 0000000..6111a46 --- /dev/null +++ b/src/plusplayer-core/src/gst_utils.cpp @@ -0,0 +1,83 @@ +// +// @ Copyright [2017] +// +#include "core/gst_utils.h" + +#include +#include + +#include "core/utils/plusplayer_log.h" + +namespace plusplayer { + +namespace gst_util { + +void ShowStateChangedMsg(GstMessage* msg, void* id) { + GstState old_state = GST_STATE_VOID_PENDING; + GstState new_state = GST_STATE_VOID_PENDING; + GstState pending_state = GST_STATE_VOID_PENDING; + gst_message_parse_state_changed(msg, &old_state, &new_state, &pending_state); + LOG_INFO_P(id, "thread[%p] msg[%p], old[%8s], new[%8s], pending[%8s] src[%s]", + g_thread_self(), msg, gst_element_state_get_name(old_state), + gst_element_state_get_name(new_state), + gst_element_state_get_name(pending_state), + GST_MESSAGE_SRC_NAME(msg)); +} + +void SetGstStateToNull(GstElement* pipeline, void* id) { + if (!pipeline) return; + GstStateChangeReturn ret; + ret = gst_element_set_state(pipeline, GST_STATE_NULL); + if (ret == GST_STATE_CHANGE_FAILURE) { + LOG_ERROR_P(id, "Set State to NULL failed"); + assert(0 && "Set State to NULL failed"); + } +} + +const gchar* GetElementName(const GstMessage* msg) { + GstElement* src_element = GST_ELEMENT_CAST(msg->src); + if (!src_element) return nullptr; + + return GST_ELEMENT_NAME(src_element); +} + +const gchar* GetKlass(const GstMessage* msg) { + GstElement* src_element = GST_ELEMENT_CAST(msg->src); + if (!src_element) return nullptr; + + GstElementFactory* factory = gst_element_get_factory(src_element); + if (!factory) return nullptr; + + return gst_element_factory_get_klass(factory); +} + +void GstInit() { + gst_init(NULL,NULL); +} + +void GstInit(const Json::Value& root) { + int argc = 1; + char* argv[6]{ + nullptr, + }; + std::string gstparam1 = root.get("gstparam1", "").asString(); + argv[argc++] = const_cast(gstparam1.c_str()); + std::string gstparam2 = root.get("gstparam2", "").asString(); + argv[argc++] = const_cast(gstparam2.c_str()); + std::string gstparam3 = root.get("gstparam3", "").asString(); + argv[argc++] = const_cast(gstparam3.c_str()); + std::string gstparam4 = root.get("gstparam4", "").asString(); + argv[argc++] = const_cast(gstparam4.c_str()); + std::string gstparam5 = root.get("gstparam5", "").asString(); + argv[argc++] = const_cast(gstparam5.c_str()); + + for (int i = 1; i < argc; ++i) { + LOG_INFO(" %s", argv[i]); + } + char** pargv = argv; + gst_init(&argc, &pargv); +} + +} // namespace gst_util + +} // namespace plusplayer diff --git a/src/plusplayer-core/src/gstobject_guard.cpp b/src/plusplayer-core/src/gstobject_guard.cpp new file mode 100755 index 0000000..3f62e9d --- /dev/null +++ b/src/plusplayer-core/src/gstobject_guard.cpp @@ -0,0 +1,50 @@ +// +// @ Copyright [2017] +// +#include "glib.h" + +#include "core/gstobject_guard.h" + +#include "core/utils/plusplayer_log.h" + +namespace plusplayer { + +namespace gstguard { + +void CustomDeleter(GstCaps* obj) { gst_caps_unref(obj); } + +void CustomDeleter(GstObject* obj) { gst_object_unref(obj); } + +void CustomDeleter(GstPad* obj) { gst_object_unref(obj); } + +void CustomDeleter(GstBuffer* obj) { gst_buffer_unref(obj); } + +void CustomDeleter(GstElement* obj) { gst_object_unref(obj); } + +void CustomDeleter(GstQuery* obj) { gst_query_unref(obj); } + +void CustomDeleter(GstEvent* obj) { gst_event_unref(obj); } + +void CustomDeleter(GstMessage* obj) { gst_message_unref(obj); } + +void CustomDeleter(GstPluginFeature* obj) { gst_object_unref(obj); } + +void CustomDeleter(GstElementFactory* obj) { gst_object_unref(obj); } + +void CustomDeleter(GstBus* obj) { gst_object_unref(GST_OBJECT(obj)); } + +void CustomDeleter(gchar* obj) { g_free(obj); } + +void CustomDeleter(GError* obj) { g_error_free(obj); } + +void CustomDeleter(GstStructure* obj) { gst_structure_free(obj); } + +void CustomDeleter(GValue* obj) { g_value_unset(obj); } + +void CustomDeleter(GstIterator* obj) { gst_iterator_free(obj); } + +void CustomDeleter(GBytes* obj) { g_bytes_unref(obj); } + +} // namespace gstguard + +} // namespace plusplayer diff --git a/src/plusplayer-core/src/gstsignal_holder.cpp b/src/plusplayer-core/src/gstsignal_holder.cpp new file mode 100755 index 0000000..07c68d8 --- /dev/null +++ b/src/plusplayer-core/src/gstsignal_holder.cpp @@ -0,0 +1,89 @@ +// +// @ Copyright [2017] +// + +#include "core/gstsignal_holder.h" + +#include "core/utils/plusplayer_log.h" + +namespace plusplayer { + +class GstSignalHolder::GstSignalItem { + public: + GstSignalItem(GObject* obj, const char* signal_name, GCallback handler, + gpointer data) + : obj_(obj) { + gst_object_ref(obj_); + sig_ = g_signal_connect(obj_, signal_name, handler, data); + if (sig_ == 0) + LOG_INFO("g_signal_connect return error. object[ %s ]", + GST_OBJECT_NAME(obj_)); + } + ~GstSignalItem() { + if (g_signal_handler_is_connected(obj_, sig_)) + g_signal_handler_disconnect(obj_, sig_); + LOG_INFO("Disconnect signal [%lu]", sig_); + gst_object_unref(obj_); + } + + private: + GstSignalItem() = default; + GObject* obj_ = nullptr; + gulong sig_ = 0; +}; + +namespace internal { + +void DisconnectSignal(const GValue* item, gpointer user_data); + +} // namespace internal + +GstSignalHolder::GstSignalHolder() {} +GstSignalHolder::~GstSignalHolder() { + std::lock_guard guard(item_lock_); + signal_list_.clear(); +} + +void GstSignalHolder::Add(GObject* obj, const char* signal_name, + GCallback handler, gpointer data) { + std::lock_guard guard(item_lock_); + std::unique_ptr item( + new GstSignalItem(obj, signal_name, handler, data)); + signal_list_.insert(std::pair>( + obj, std::move(item))); +} + +void GstSignalHolder::Delete(GObject* obj) { + if (!obj || !GST_IS_ELEMENT(obj)) { + LOG_ERROR("object null or object is not gst element"); + return; + } + + std::lock_guard guard(item_lock_); + if (GST_IS_BIN(obj)) { + GstIterator* it = gst_bin_iterate_elements(GST_BIN_CAST(obj)); + gst_iterator_foreach(it, + (GstIteratorForeachFunction)internal::DisconnectSignal, + (gpointer) this); + gst_iterator_free(it); + } + signal_list_.erase(obj); +} + +void GstSignalHolder::DeleteAll() { + std::lock_guard guard(item_lock_); + LOG_INFO("num of signals[ %d ]", signal_list_.size()); + signal_list_.clear(); +} + +namespace internal { + +void DisconnectSignal(const GValue* item, gpointer user_data) { + GstSignalHolder* pthis = static_cast(user_data); + GstElement* element = GST_ELEMENT_CAST(g_value_get_object(item)); + pthis->Delete(G_OBJECT(element)); +} + +} // namespace internal + +} // namespace plusplayer diff --git a/src/plusplayer-core/src/kpi.cpp b/src/plusplayer-core/src/kpi.cpp new file mode 100755 index 0000000..0b310c7 --- /dev/null +++ b/src/plusplayer-core/src/kpi.cpp @@ -0,0 +1,199 @@ +// +// @ Copyright [2017] +// + +#include "core/kpi.h" + +#include + +#include "core/utils/plusplayer_log.h" + +/* for logger */ +#include "capi-system-info/system_info.h" +#include "logger/Logger2.h" + +using namespace KPILogFramework; + +namespace { + +const char* GetProductYear(void) { + static const char* year = nullptr; + if (year) return year; + + int value = -1; + int result = system_info_get_custom_int( + "com.samsung/featureconf/product.tv_year", &value); + if (result != SYSTEM_INFO_ERROR_NONE || value < 0) { + LOG_ERROR( + "can't get com.samsung/featureconf/product.tv_year, result:%d, " + "value:%d", + result, value); + return "20"; + } + + static char str_year[3] = {0, }; + int size = snprintf(str_year, 3, "%d", value); + if (size != 2) { + LOG_ERROR("size is not 2!! size:%d, year:%d", size, value); + return "20"; + } + + year = str_year; + return year; +} +} // unnamed namespace + +namespace plusplayer { + +namespace internal { +std::string GetSrcType(SourceType ptype) { + switch (ptype) { + case SourceType::kHttp: + return "HTTP"; + case SourceType::kHls: + return "HLS"; + case SourceType::kDash: + return "DASH"; + case SourceType::kFile: + return "FILE"; + case SourceType::kNone: + case SourceType::kBase: + case SourceType::kExternalSubtitle: + case SourceType::kMax: + default: + return "others"; + } +} + +std::string GetDrmType(drm::Type dtype) { + switch (dtype) { + case drm::Type::kNone: + return "NONE"; + case drm::Type::kPlayready: + return "PLAYREADY"; + case drm::Type::kMarlin: + return "MARLIN"; + case drm::Type::kVerimatrix: + return "VERIMATRIX"; + case drm::Type::kWidevineClassic: + return "WIDEVINE CLASSIC"; + case drm::Type::kSecuremedia: + return "SECUREMEDIA"; + case drm::Type::kSdrm: + return "SDRM"; + case drm::Type::kWidevineCdm: + return "WIDEVINE CDM"; + case drm::Type::kMax: + default: + return "others"; + } +} + +std::string GetDecoderType(int ctype) { //(0:DEFAULT, 1:HW, 2:SW, 3:DISABLE) + if (ctype == 0 || ctype == 1) { + return "HW"; + } else if (ctype == 2) { + return "SW"; + } else { + return "DISABLE"; + } +} +} // namespace internal + +namespace kpi { +bool CodecLogger::SendKpi(bool event_case, const CodecLoggerKeys& keys) { + LOG_ENTER; + + std::string ptype = internal::GetSrcType(keys.src_type); + std::string drm_type = internal::GetDrmType(keys.drm_type); + std::string v_decoder_type = internal::GetDecoderType(keys.v_decoder_type); + std::string a_decoder_type = internal::GetDecoderType(keys.a_decoder_type); + + // generate message + std::stringstream str; + str << "{"; + str << "ptype=" << ptype; + str << ";dtype=" << drm_type; + str << ";data_container=" << keys.container_type; + str << ";v_decoder_type=" << v_decoder_type; + str << ";v_codec=" << keys.v_codec; + str << ";v_tag=0x" << std::hex << keys.v_tag; + str << ";width=" << std::dec << keys.width; + str << ";height=" << std::dec << keys.height; + str << ";a_decoder_type=" << a_decoder_type; + str << ";a_codec=" << keys.a_codec; + str << ";a_tag=0x" << std::hex << keys.a_tag; + str << ";app_id=" << keys.app_id; + str << "}"; + + return SendKpi_(event_case, str); +} + +bool CodecLogger::SendKpi(bool event_case, const EsCodecLoggerKeys& keys) { + LOG_ENTER; + + std::string ptype = keys.is_clean ? "MSE" : "EME"; + std::string drm_type = keys.is_clean ? "NONE" : "EME"; + std::string v_decoder_type("HW"); + std::string a_decoder_type("HW"); + std::string container_type("EsPlusplayer"); + unsigned int video_tag = 0; + unsigned int audio_tag = 0; + std::string video_codec = keys.v_codec + "-" + + std::to_string(keys.v_codec_version); + + // generate message + std::stringstream str; + str << "{"; + str << "ptype=" << ptype; + str << ";dtype=" << drm_type; + str << ";data_container=" << container_type; + str << ";v_decoder_type=" << v_decoder_type; + str << ";v_codec=" << keys.v_codec; + str << ";v_tag=0x" << std::hex << video_tag; + str << ";width=" << std::dec << keys.width; + str << ";height=" << std::dec << keys.height; + str << ";a_decoder_type=" << a_decoder_type; + str << ";a_codec=" << keys.a_codec; + str << ";a_tag=0x" << std::hex << audio_tag; + str << ";app_id=" << keys.app_id; + str << "}"; + + return SendKpi_(event_case, str); +} + +bool CodecLogger::SendKpi_(bool event_case, const std::stringstream& message) { + LOG_ENTER; + const char* year = ::GetProductYear(); + + // send message to KPI logger + std::stringstream service_name; + service_name << year << "_codec"; + std::string eventName; + std::string category; + if (event_case) { + eventName = "PLAYBACK"; + category = "EV001"; + } else { + eventName = "ERRPLAY"; + category = "EV002"; + } + + LOG_ERROR("[KPI] service_name: %s, desc_log: %s", + service_name.str().c_str(), message.str().c_str()); + + CLogger* pLogger = CLogger::GetInstance(); + LoggerErrorCode result = LOGGER_ERROR_NONE; + result = pLogger->AddEventLogSync(service_name.str().c_str(), eventName.c_str(), + category.c_str(), message.str().c_str()); + if (result != LOGGER_ERROR_NONE) { + LOG_ERROR("Failed to send KPI log for esplayer, result:%d", result); + return false; + } + + return true; +} + + +} // namespace kpi +} // namespace plusplayer diff --git a/src/plusplayer-core/src/plusplayer_cfg.cpp b/src/plusplayer-core/src/plusplayer_cfg.cpp new file mode 100755 index 0000000..bce0a47 --- /dev/null +++ b/src/plusplayer-core/src/plusplayer_cfg.cpp @@ -0,0 +1,24 @@ +// +// @ Copyright [2017] +// + +#include + +#ifndef PLUPLAYER_DOWNLOADABLE_APP_TVPLUS +#include "tzplatform_config.h" +#endif + +namespace plusplayer { + +namespace plusplayer_cfg { + +const char* GetIniPath() { + const char* path = + tzplatform_mkpath(TZ_SYS_RO_ETC, "multimedia/esplusplayer.ini"); + assert(path); + return path; +} + +} // namespace plusplayer_cfg + +} // namespace plusplayer \ No newline at end of file diff --git a/src/plusplayer-core/src/serializer.cpp b/src/plusplayer-core/src/serializer.cpp new file mode 100755 index 0000000..a31ab75 --- /dev/null +++ b/src/plusplayer-core/src/serializer.cpp @@ -0,0 +1,47 @@ +// +// @ Copyright [2018] +// + +#include "core/serializer.h" + +namespace plusplayer { +using Offset = Serializer::Offset; +using Byte = Serializer::Byte; + +Offset Serializer::Put(const std::vector &vector) { + Offset offset = size_; + if (vector.size() == 0) return offset; + const size_t size = vector.size(); + const Byte *data_bytes = reinterpret_cast(vector.data()); + Put_(data_bytes, size); + return offset; +} + +Offset Serializer::Put(const std::string &data) { + Offset offset = size_; + if (data.length() == 0) return offset; + const size_t size = data.length(); + const Byte *data_bytes = reinterpret_cast(data.c_str()); + Put_(data_bytes, size); + return offset; +} + +Offset Serializer::Put(const Byte *data, size_t size) { + Offset offset = size_; + if (size == 0) return offset; + Put_(data, size); + return offset; +} + +size_t Serializer::Serialize(Byte *serialized) { + buf_.sgetn(serialized, size_); + return size_; +} + +const size_t Serializer::GetSize() { return size_; } + +void Serializer::Put_(const Byte *data_bytes, const size_t size) { + buf_.sputn(data_bytes, size); + size_ += size; +} +} // namespace plusplayer diff --git a/src/plusplayer-core/src/subtitle_attr_parser.cpp b/src/plusplayer-core/src/subtitle_attr_parser.cpp new file mode 100755 index 0000000..8daa6f5 --- /dev/null +++ b/src/plusplayer-core/src/subtitle_attr_parser.cpp @@ -0,0 +1,556 @@ +// +// @ Copyright [2017] +// + +#include "core/subtitle_attr_parser.h" + +#include +#include + +#include "gst/ffsubtitle/gstsubattributes.h" + +#include "core/utils/plusplayer_log.h" + +namespace plusplayer { + +#ifdef PLUPLAYER_DOWNLOADABLE_APP_TVPLUS +#define GST_SUB_ATTRI_FUNC(name) gst_sub_attributes_##name +#define GST_SUB_ATTRI_ENUM(name) GST_SUB_ATTRIBUTES_##name +#define GstSubAttriScope GstSubAttributesScope +#define GstSubAttriType GstSubAttributesType +#else +#define GST_SUB_ATTRI_FUNC(name) gst_sub_attribute_##name +#define GST_SUB_ATTRI_ENUM(name) GST_SUB_ATTRI_##name +#define GstSubAttriScope GstSubAttributeScope +#define GstSubAttriType GstSubAttributeType +#endif + +namespace internal { +void AddSubtitleAttribute(std::list* list, + const SubtitleAttrType type, const boost::any& value, + const unsigned int start_pos, + const unsigned int stop_pos) { + list->emplace_back(type, start_pos, stop_pos, value, -1); +} + +bool ComparingStartTime(const SubtitleAttr& a, const SubtitleAttr& b) { + return (a.start_time < b.start_time); +} + +constexpr int kAttrInvalidIntVal = -1; +constexpr unsigned int kAttrInvalidUintVal = + std::numeric_limits::max(); +constexpr float kAttrInvalidFloatVal = 0.0; + +void ParseSubtitleRegionAttr(GstStructure* attribute, + std::list* attr_list) { + LOG_ENTER; + while (attribute) { + gfloat x_pos = kAttrInvalidIntVal, y_pos = kAttrInvalidFloatVal, + width = kAttrInvalidFloatVal, height = kAttrInvalidFloatVal; + attribute = GST_SUB_ATTRI_FUNC(region_parse)(attribute, &x_pos, &y_pos, + &width, &height); + LOG_DEBUG( + "parsed new region attribute: x(%f), y(%f), width(%f), " + "height(%f)", + x_pos, y_pos, width, height); + if (x_pos != kAttrInvalidFloatVal) { + boost::any value = x_pos; + internal::AddSubtitleAttribute(attr_list, kSubAttrRegionXPos, value, + kAttrInvalidUintVal, kAttrInvalidUintVal); + } + if (y_pos != kAttrInvalidFloatVal) { + boost::any value = y_pos; + internal::AddSubtitleAttribute(attr_list, kSubAttrRegionYPos, value, + kAttrInvalidUintVal, kAttrInvalidUintVal); + } + if (width != kAttrInvalidFloatVal) { + boost::any value = width; + internal::AddSubtitleAttribute(attr_list, kSubAttrRegionWidth, value, + kAttrInvalidUintVal, kAttrInvalidUintVal); + } + if (height != kAttrInvalidFloatVal) { + boost::any value = height; + internal::AddSubtitleAttribute(attr_list, kSubAttrRegionHeight, value, + kAttrInvalidUintVal, kAttrInvalidUintVal); + } + } +} + +void ParseSubtitleWindowAttr(GstStructure* attribute, + std::list* attr_list) { + LOG_ENTER; + while (attribute) { + gfloat x_padding = kAttrInvalidFloatVal, y_padding = kAttrInvalidFloatVal; + gint l_margin = kAttrInvalidIntVal, r_margin = kAttrInvalidIntVal, + t_margin = kAttrInvalidIntVal, b_margin = kAttrInvalidIntVal; + guint bg_color = kAttrInvalidUintVal; + gfloat opacity = kAttrInvalidFloatVal; + guint show_bg = kAttrInvalidUintVal; + attribute = GST_SUB_ATTRI_FUNC(window_parse)( + attribute, &x_padding, &y_padding, &l_margin, &r_margin, &t_margin, + &b_margin, &bg_color, &opacity, &show_bg); + LOG_DEBUG( + "parsed new window attribute: x_padding(%f), y_padding(%f), " + "l_margin(%d), r_margin(%d), t_margin(%d), b_margin(%d), " + "bg_color(%u), opacity(%f), show_bg(%u)", + x_padding, y_padding, l_margin, r_margin, t_margin, b_margin, bg_color, + opacity, show_bg); + if (x_padding != kAttrInvalidFloatVal) { + boost::any value = x_padding; + internal::AddSubtitleAttribute(attr_list, kSubAttrWindowXPadding, value, + kAttrInvalidUintVal, kAttrInvalidUintVal); + } + if (y_padding != kAttrInvalidFloatVal) { + boost::any value = y_padding; + internal::AddSubtitleAttribute(attr_list, kSubAttrWindowYPadding, value, + kAttrInvalidUintVal, kAttrInvalidUintVal); + } + if (l_margin != kAttrInvalidIntVal) { + boost::any value = l_margin; + internal::AddSubtitleAttribute(attr_list, kSubAttrWindowLeftMargin, value, + kAttrInvalidUintVal, kAttrInvalidUintVal); + } + if (r_margin != kAttrInvalidIntVal) { + boost::any value = r_margin; + internal::AddSubtitleAttribute(attr_list, kSubAttrWindowRightMargin, + value, kAttrInvalidUintVal, + kAttrInvalidUintVal); + } + if (t_margin != kAttrInvalidIntVal) { + boost::any value = t_margin; + internal::AddSubtitleAttribute(attr_list, kSubAttrWindowTopMargin, value, + kAttrInvalidUintVal, kAttrInvalidUintVal); + } + if (b_margin != kAttrInvalidIntVal) { + boost::any value = b_margin; + internal::AddSubtitleAttribute(attr_list, kSubAttrWindowBottomMargin, + value, kAttrInvalidUintVal, + kAttrInvalidUintVal); + } + if (bg_color != kAttrInvalidUintVal) { + boost::any value = bg_color; + internal::AddSubtitleAttribute(attr_list, kSubAttrWindowBgColor, value, + kAttrInvalidUintVal, kAttrInvalidUintVal); + } + if (opacity != kAttrInvalidFloatVal) { + boost::any value = opacity; + internal::AddSubtitleAttribute(attr_list, kSubAttrWindowOpacity, value, + kAttrInvalidUintVal, kAttrInvalidUintVal); + } + if (show_bg != kAttrInvalidUintVal) { + boost::any value = show_bg; + internal::AddSubtitleAttribute(attr_list, kSubAttrWindowShowBg, value, + kAttrInvalidUintVal, kAttrInvalidUintVal); + } + } +} + +void ParseSubtitleFontAttr(GstStructure* attribute, + std::list* attr_list) { + LOG_DEBUG("Now parse attribute font!"); + while (attribute) { + GstSubAttriScope scope; + guint start_index = kAttrInvalidUintVal, stop_index = kAttrInvalidUintVal; + gchar* family = nullptr; + gfloat size = kAttrInvalidFloatVal; + gint weight = kAttrInvalidIntVal, style = kAttrInvalidIntVal; + guint color = kAttrInvalidUintVal, bg_color = kAttrInvalidUintVal; + gfloat opacity = kAttrInvalidFloatVal, bg_opacity = kAttrInvalidFloatVal; + guint text_outline_color = kAttrInvalidUintVal, + text_outline_tn = kAttrInvalidUintVal; + gint text_outline_br = kAttrInvalidIntVal, v_align = kAttrInvalidIntVal, + h_align = kAttrInvalidIntVal; + attribute = GST_SUB_ATTRI_FUNC(font_parse)( + attribute, &scope, &start_index, &stop_index, &family, &size, &weight, + &style, &color, &bg_color, &opacity, &bg_opacity, &text_outline_color, + &text_outline_tn, &text_outline_br, &v_align, &h_align); + LOG_DEBUG( + "passed a font attribute: scope(%u), start_index(%u), " + "stop_index(%u), family(%s), size(%f)," + "weight(%d), style(%d), color(%u), bg_color(%u), opacity(%f), " + "bg_opacity(%f), text_outline_color(%u)," + "text_outline_tn(%u),text_outline_br(%d), v_align(%d), " + "h_align(%d)", + scope, start_index, stop_index, family, size, weight, style, color, + bg_color, opacity, bg_opacity, text_outline_color, text_outline_tn, + text_outline_br, v_align, h_align); + if (family != nullptr) { + boost::any value = std::string(family); + internal::AddSubtitleAttribute(attr_list, kSubAttrFontFamily, value, + start_index, stop_index); + } + if (size != kAttrInvalidFloatVal) { + boost::any value = size; + internal::AddSubtitleAttribute(attr_list, kSubAttrFontSize, value, + start_index, stop_index); + } + if (weight != kAttrInvalidIntVal) { + boost::any value = weight; + internal::AddSubtitleAttribute(attr_list, kSubAttrFontWeight, value, + start_index, stop_index); + } + if (style != kAttrInvalidIntVal) { + boost::any value = style; + internal::AddSubtitleAttribute(attr_list, kSubAttrFontStyle, value, + start_index, stop_index); + } + if (color != kAttrInvalidUintVal) { + boost::any value = color; + internal::AddSubtitleAttribute(attr_list, kSubAttrFontColor, value, + start_index, stop_index); + } + if (bg_color != kAttrInvalidUintVal) { + boost::any value = bg_color; + internal::AddSubtitleAttribute(attr_list, kSubAttrFontBgColor, value, + start_index, stop_index); + } + if (opacity != kAttrInvalidFloatVal) { + boost::any value = opacity; + internal::AddSubtitleAttribute(attr_list, kSubAttrFontOpacity, value, + start_index, stop_index); + } + if (bg_opacity != kAttrInvalidFloatVal) { + boost::any value = bg_opacity; + internal::AddSubtitleAttribute(attr_list, kSubAttrFontBgOpacity, value, + start_index, stop_index); + } + if (text_outline_color != kAttrInvalidUintVal) { + boost::any value = text_outline_color; + internal::AddSubtitleAttribute(attr_list, kSubAttrFontTextOutlineColor, + value, start_index, stop_index); + } + if (text_outline_tn != kAttrInvalidUintVal) { + boost::any value = text_outline_tn; + internal::AddSubtitleAttribute(attr_list, + kSubAttrFontTextOutlineThickness, value, + start_index, stop_index); + } + if (text_outline_br != kAttrInvalidIntVal) { + boost::any value = text_outline_br; + internal::AddSubtitleAttribute(attr_list, + kSubAttrFontTextOutlineBlurRadius, value, + start_index, stop_index); + } + if (v_align != kAttrInvalidIntVal) { + boost::any value = v_align; + internal::AddSubtitleAttribute(attr_list, kSubAttrFontVerticalAlign, + value, start_index, stop_index); + } + if (h_align != kAttrInvalidIntVal) { + boost::any value = h_align; + internal::AddSubtitleAttribute(attr_list, kSubAttrFontHorizontalAlign, + value, start_index, stop_index); + } + } +} +void ParseSubtitleFontSizeWeightStyleClolr(GstStructure* attribute, + std::list* attr_list) { + LOG_DEBUG("Now parse attribute font shortcut SIZE_WEIGHT_STYLE_COLOR!"); + while (attribute) { + GstSubAttriScope scope; + guint start_index = kAttrInvalidUintVal, stop_index = kAttrInvalidUintVal; + gfloat size = kAttrInvalidFloatVal; + gint weight = kAttrInvalidIntVal, style = kAttrInvalidIntVal; + guint color = kAttrInvalidUintVal; + attribute = GST_SUB_ATTRI_FUNC(font_sc_size_weight_style_color_parse)( + attribute, &scope, &start_index, &stop_index, &size, &weight, &style, + &color); + LOG_DEBUG( + "passed a font attribute: scope(%u), start_index(%u), " + "stop_index(%u), size(%f)," + "weight(%d), style(%d), color(%u)", + scope, start_index, stop_index, size, weight, style, color); + if (size != kAttrInvalidFloatVal) { + boost::any value = size; + internal::AddSubtitleAttribute(attr_list, kSubAttrFontSize, value, + start_index, stop_index); + } + if (weight != kAttrInvalidIntVal) { + boost::any value = weight; + internal::AddSubtitleAttribute(attr_list, kSubAttrFontWeight, value, + start_index, stop_index); + } + if (style != kAttrInvalidIntVal) { + boost::any value = style; + internal::AddSubtitleAttribute(attr_list, kSubAttrFontStyle, value, + start_index, stop_index); + } + if (color != kAttrInvalidUintVal) { + boost::any value = color; + internal::AddSubtitleAttribute(attr_list, kSubAttrFontColor, value, + start_index, stop_index); + } + } +} +void ParseSubtitleFontColorOpacities(GstStructure* attribute, + std::list* attr_list) { + LOG_DEBUG("Now parse attribute font shortcut SIZE_COLORS_AND_OPACITIES!"); + while (attribute) { + GstSubAttriScope scope; + guint start_index = kAttrInvalidUintVal, stop_index = kAttrInvalidUintVal, + color = kAttrInvalidUintVal, bg_color = kAttrInvalidUintVal; + gfloat opacity = kAttrInvalidFloatVal, bg_opacity = kAttrInvalidFloatVal; + guint text_outline_color = kAttrInvalidUintVal; + attribute = GST_SUB_ATTRI_FUNC(font_sc_colors_and_opacities_parse)( + attribute, &scope, &start_index, &stop_index, &color, &bg_color, + &opacity, &bg_opacity, &text_outline_color); + LOG_DEBUG( + "passed a font attribute: scope(%u), start_index(%u), " + "stop_index(%u)," + "color(%u), bg_color(%u), opacity(%f), bg_opacity(%f), " + "text_outline_color(%u)", + scope, start_index, stop_index, color, bg_color, opacity, bg_opacity, + text_outline_color); + if (color != kAttrInvalidUintVal) { + boost::any value = color; + internal::AddSubtitleAttribute(attr_list, kSubAttrFontColor, value, + start_index, stop_index); + } + if (bg_color != kAttrInvalidUintVal) { + boost::any value = bg_color; + internal::AddSubtitleAttribute(attr_list, kSubAttrFontBgColor, value, + start_index, stop_index); + } + if (opacity != kAttrInvalidFloatVal) { + boost::any value = opacity; + internal::AddSubtitleAttribute(attr_list, kSubAttrFontOpacity, value, + start_index, stop_index); + } + if (bg_opacity != kAttrInvalidFloatVal) { + boost::any value = bg_opacity; + internal::AddSubtitleAttribute(attr_list, kSubAttrFontBgOpacity, value, + start_index, stop_index); + } + if (text_outline_color != kAttrInvalidUintVal) { + boost::any value = text_outline_color; + internal::AddSubtitleAttribute(attr_list, kSubAttrFontTextOutlineColor, + value, start_index, stop_index); + } + } +} +void ParseSubtitleFontSize(GstStructure* attribute, + std::list* attr_list) { + LOG_DEBUG("Now parse attribute font shortcut SIZE!"); + while (attribute) { + guint start_index = kAttrInvalidUintVal, stop_index = kAttrInvalidUintVal; + gfloat size = kAttrInvalidFloatVal; + attribute = GST_SUB_ATTRI_FUNC(font_sc_size_parse)(attribute, &start_index, + &stop_index, &size); + LOG_DEBUG( + "passed a font attribute: start_index(%u), stop_index(%u), " + "size(%f)", + start_index, stop_index, size); + if (size != kAttrInvalidFloatVal) { + boost::any value = size; + internal::AddSubtitleAttribute(attr_list, kSubAttrFontSize, value, + start_index, stop_index); + } + } +} +void ParseSubtitleFontWeight(GstStructure* attribute, + std::list* attr_list) { + LOG_DEBUG("Now parse attribute font shortcut WEIGHT!"); + while (attribute) { + guint start_index = kAttrInvalidUintVal, stop_index = kAttrInvalidUintVal; + gint weight = kAttrInvalidIntVal; + attribute = GST_SUB_ATTRI_FUNC(font_sc_weight_parse)(attribute, &start_index, + &stop_index, &weight); + LOG_DEBUG( + "passed a font attribute: start_index(%u), stop_index(%u), " + "weight(%d)", + start_index, stop_index, weight); + if (weight != kAttrInvalidIntVal) { + boost::any value = weight; + internal::AddSubtitleAttribute(attr_list, kSubAttrFontWeight, value, + start_index, stop_index); + } + } +} +void ParseSubtitleFontStyle(GstStructure* attribute, + std::list* attr_list) { + LOG_DEBUG("Now parse attribute font shortcut STYLE!"); + while (attribute) { + guint start_index = kAttrInvalidUintVal, stop_index = kAttrInvalidUintVal; + gint style = kAttrInvalidIntVal; + attribute = GST_SUB_ATTRI_FUNC(font_sc_style_parse)(attribute, &start_index, + &stop_index, &style); + LOG_DEBUG( + "passed a font attribute: start_index(%u), stop_index(%u), " + "style(%d)", + start_index, stop_index, style); + if (style != kAttrInvalidIntVal) { + boost::any value = style; + internal::AddSubtitleAttribute(attr_list, kSubAttrFontStyle, value, + start_index, stop_index); + } + } +} +void ParseSubtitleFontColor(GstStructure* attribute, + std::list* attr_list) { + LOG_DEBUG("Now parse attribute font shortcut COLOR!"); + while (attribute) { + guint start_index = kAttrInvalidUintVal, stop_index = kAttrInvalidUintVal; + guint color = kAttrInvalidUintVal; + attribute = GST_SUB_ATTRI_FUNC(font_sc_color_parse)(attribute, &start_index, + &stop_index, &color); + LOG_DEBUG( + "passed a font attribute: start_index(%u), stop_index(%u), " + "color(%u)", + start_index, stop_index, color); + if (color != kAttrInvalidUintVal) { + boost::any value = color; + internal::AddSubtitleAttribute(attr_list, kSubAttrFontColor, value, + start_index, stop_index); + } + } +} +void ParseSubtitleRaw(GstStructure* attribute, + std::list* attr_list) { + LOG_DEBUG("Now parse attribute raw!"); + while (attribute) { + guint start_index = kAttrInvalidUintVal, stop_index = kAttrInvalidUintVal; + gchar* raw_subtitle = nullptr; + attribute = GST_SUB_ATTRI_FUNC(raw_data)(attribute, &raw_subtitle); + LOG_DEBUG("passed a raw attribute: raw_subtitle(%s)", raw_subtitle); + if (raw_subtitle != nullptr) { + boost::any value = std::string(raw_subtitle); + internal::AddSubtitleAttribute(attr_list, kSubAttrRawSubtitle, value, + start_index, stop_index); + } + } +} +void ParseSubtitleWebvttCue(GstStructure* attribute, + std::list* attr_list) { + LOG_DEBUG("Now parse attribute of webvtt cue!"); + while (attribute) { + guint start_index = kAttrInvalidUintVal, stop_index = kAttrInvalidUintVal; + gint line_num = kAttrInvalidIntVal, line_align = kAttrInvalidIntVal, + pos_align = kAttrInvalidIntVal, align = kAttrInvalidIntVal, + vertical = kAttrInvalidIntVal; + gfloat line = kAttrInvalidFloatVal, size = kAttrInvalidFloatVal, + position = kAttrInvalidFloatVal; + attribute = GST_SUB_ATTRI_FUNC(webvttcue_parse)( + attribute, &line, &line_num, &line_align, &align, &size, &position, + &pos_align, &vertical); + LOG_DEBUG( + "passed webvttcue attributes: line(%f), line_num(%d), line_align(%d), " + "align(%d), size(%f), position(%f), pos_align(%d), vertical(%d)", + line, line_num, line_align, align, size, position, pos_align, vertical); + if (line != kAttrInvalidFloatVal) { + boost::any value = line; + internal::AddSubtitleAttribute(attr_list, kSubAttrWebvttCueLine, value, + start_index, stop_index); + } + if (line_num != kAttrInvalidIntVal) { + boost::any value = line_num; + internal::AddSubtitleAttribute(attr_list, kSubAttrWebvttCueLineNum, value, + start_index, stop_index); + } + if (line_align != kAttrInvalidIntVal) { + boost::any value = line_align; + internal::AddSubtitleAttribute(attr_list, kSubAttrWebvttCueLineAlign, + value, start_index, stop_index); + } + if (align != kAttrInvalidIntVal) { + boost::any value = align; + internal::AddSubtitleAttribute(attr_list, kSubAttrWebvttCueAlign, value, + start_index, stop_index); + } + if (size != kAttrInvalidFloatVal) { + boost::any value = size; + internal::AddSubtitleAttribute(attr_list, kSubAttrWebvttCueSize, value, + start_index, stop_index); + } + if (position != kAttrInvalidFloatVal) { + boost::any value = position; + internal::AddSubtitleAttribute(attr_list, kSubAttrWebvttCuePosition, + value, start_index, stop_index); + } + if (pos_align != kAttrInvalidIntVal) { + boost::any value = pos_align; + internal::AddSubtitleAttribute(attr_list, kSubAttrWebvttCuePositionAlign, + value, start_index, stop_index); + } + if (vertical != kAttrInvalidIntVal) { + boost::any value = vertical; + internal::AddSubtitleAttribute(attr_list, kSubAttrWebvttCueVertical, + value, start_index, stop_index); + } + } +} + +} // namespace internal + +SubtitleAttrListPtr SubtitleAttrParser::Parse() { + SubtitleAttrListPtr attr_list{new SubtitleAttrList}; + for (int attr_type = GST_SUB_ATTRI_ENUM(TYPE_REGION); + attr_type < GST_SUB_ATTRI_ENUM(TYPE_UNKNOWN); ++attr_type) { + const gchar* type_name = + GST_SUB_ATTRI_FUNC(type_to_name)((GstSubAttriType)attr_type); + GQuark attri_quark = + GST_SUB_ATTRI_FUNC(type_to_quark)((GstSubAttriType)attr_type); + if (!attri_quark) { + LOG_DEBUG("We don't have quark of this attribute type(%s)!", type_name); + continue; + } + + GstStructure* attribute = GST_STRUCTURE_CAST( + gst_mini_object_get_qdata(GST_MINI_OBJECT(gstbuf_), attri_quark)); + + if (!attribute) continue; + LOG_DEBUG("[core] attribute type(%s) from gstbuffer!", type_name); + + switch (attr_type) { + case GST_SUB_ATTRI_ENUM(TYPE_REGION): + internal::ParseSubtitleRegionAttr(attribute, attr_list.get()); + break; + case GST_SUB_ATTRI_ENUM(TYPE_WINDOW): + internal::ParseSubtitleWindowAttr(attribute, attr_list.get()); + break; + case GST_SUB_ATTRI_ENUM(TYPE_FONT): + internal::ParseSubtitleFontAttr(attribute, attr_list.get()); + break; + case GST_SUB_ATTRI_ENUM(TYPE_FONT_SC_SIZE_WEIGHT_STYLE_COLOR): + internal::ParseSubtitleFontSizeWeightStyleClolr(attribute, + attr_list.get()); + break; + case GST_SUB_ATTRI_ENUM(TYPE_FONT_SC_COLORS_AND_OPACITIES): + internal::ParseSubtitleFontColorOpacities(attribute, attr_list.get()); + break; + case GST_SUB_ATTRI_ENUM(TYPE_FONT_SC_SIZE): + internal::ParseSubtitleFontSize(attribute, attr_list.get()); + break; + case GST_SUB_ATTRI_ENUM(TYPE_FONT_SC_WEIGHT): + internal::ParseSubtitleFontWeight(attribute, attr_list.get()); + break; + case GST_SUB_ATTRI_ENUM(TYPE_FONT_SC_STYLE): + internal::ParseSubtitleFontStyle(attribute, attr_list.get()); + break; + case GST_SUB_ATTRI_ENUM(TYPE_FONT_SC_COLOR): + internal::ParseSubtitleFontColor(attribute, attr_list.get()); + break; + case GST_SUB_ATTRI_ENUM(TYPE_RAW): + internal::ParseSubtitleRaw(attribute, attr_list.get()); + break; + case GST_SUB_ATTRI_ENUM(TYPE_WEBVTT_CUE): + internal::ParseSubtitleWebvttCue(attribute, attr_list.get()); + break; + default: + LOG_ERROR("UNKNOWN ATTR TYPE"); + return nullptr; + } + } + + (attr_list.get())->sort(internal::ComparingStartTime); + boost::any value = 0; + GstClockTime timestamp = GST_CLOCK_TIME_NONE, duration = GST_CLOCK_TIME_NONE; + duration = GST_TIME_AS_MSECONDS(GST_BUFFER_DURATION(gstbuf_)); + timestamp = GST_TIME_AS_MSECONDS(GST_BUFFER_TIMESTAMP(gstbuf_)); + LOG_DEBUG("pts[%llu] duration[%llu]", timestamp, duration); + internal::AddSubtitleAttribute(attr_list.get(), kSubAttrTimestamp, value, + timestamp, timestamp + duration); + + return attr_list; +} +} // namespace plusplayer diff --git a/src/plusplayer-core/src/track_util.cpp b/src/plusplayer-core/src/track_util.cpp new file mode 100755 index 0000000..df9ceec --- /dev/null +++ b/src/plusplayer-core/src/track_util.cpp @@ -0,0 +1,137 @@ +// +// @ Copyright [2017] +// + +#include "core/track_util.h" + +#include + +#include "core/utils/plusplayer_log.h" + +namespace plusplayer { + +namespace track_util { + +bool GetActiveTrack(const std::vector& track_list, const TrackType type, + Track* track) { + if (!track) return false; + auto find_target = [type](const Track& item)->bool { + if (item.type == type && item.active) { + return true; + } else { + return false; + } + }; + auto target = std::find_if(track_list.begin(), track_list.end(), find_target); + if (target == track_list.end()) return false; // There is no target. + *track = *target; + // LOG_INFO("tracktype : %d, index : %d", track->type, track->index); + return true; +} + +bool GetActiveTrackList(const std::vector& tracklist, + std::vector& active_track) { + unsigned int video = 0, audio = 0, text = 0; + for (const auto& track : tracklist) { + if (track.active == true) { + active_track.push_back(track); + if (track.type == kTrackTypeAudio) + audio++; + else if (track.type == kTrackTypeVideo) + video++; + else if (track.type == kTrackTypeSubtitle) + text++; + } + } + if (active_track.empty()) { + LOG_ERROR("no active track found"); + return false; + } + if (video > 1 || audio > 1 || text > 1) { + LOG_ERROR("actived tracks are too much: video(%d), audio(%d), text(%d)", + video, audio, text); + return false; + } + return true; +} + +void ShowTrackInfo(const std::vector& trackinfo) { + std::vector info = trackinfo; + LOG_INFO("### Track List ###"); + for (const Track& item : info) { + ShowTrackInfo(item); + } + LOG_INFO("### ~Track List ###"); +} + +void ShowTrackInfo(const Track& track) { + LOG_INFO("### TrackInfo ###"); + LOG_INFO("index : %d id : %d ,", track.index, track.id); + LOG_INFO("mimetype: %s", track.mimetype.c_str()); + LOG_INFO("streamtype: %s", track.streamtype.c_str()); + LOG_INFO("container_type: %s", track.container_type.c_str()); + LOG_INFO("tracktype : %d", track.type); + LOG_INFO("codec tag : %d", track.codec_tag); + LOG_INFO("width: %d height : %d", track.width, track.height); + LOG_INFO("maxwidth: %d maxheight : %d", track.maxwidth, track.maxheight); + LOG_INFO("framerate(num : %d den : %d)", track.framerate_num, + track.framerate_den); + LOG_INFO("framerate(codec_data : %p )", track.codec_data.get()); + LOG_INFO("framerate(codec_data_len : %d )", track.codec_data_len); + LOG_INFO( + "sample_rate %d sample_format : %d channel : %d version : %d layer : %d", + track.sample_rate, track.sample_format, track.channels, track.version, track.layer); + LOG_INFO( + "bits_per_sample %d block_align : %d bitrate : %d endianness : %d is_signed : %d", + track.bits_per_sample, track.block_align, track.bitrate, track.endianness, track.is_signed); + LOG_INFO("active %d subtitle_format : %s ", track.active, track.subtitle_format.c_str() ); + LOG_INFO("use_swdecoder : %d", track.use_swdecoder); + LOG_INFO("language_code: %s", track.language_code.c_str()); +} + +uint64_t GetPositionWithinBoundary(const uint64_t duration, + const uint64_t position, + const uint64_t threshold) { + LOG_DEBUG("duration[%llu] position[%llu] threshold[%llu]", duration, position, + threshold); + if (duration < threshold) return position; + uint64_t safe_pos = position; + uint64_t boundary = duration - threshold; + if (position > boundary) { + safe_pos = boundary; + } + return safe_pos; +} + +bool IsValidCodecDataSize(int size) { + constexpr int kMaxSize = 1024 * 1024; // 1MB + if (size > 0 && size < kMaxSize) return true; + return false; +} + +void FillCodecDataIntoTrack(const GValue* codec_data, + plusplayer::Track* track) { + GstBuffer* buffer = gst_value_get_buffer(codec_data); + GstMapInfo codec_data_info; + if (gst_buffer_map(buffer, &codec_data_info, GST_MAP_READ)) { + LOG_DEBUG("codec extra data [ %s ]", codec_data_info.data); + LOG_DEBUG("codec extra data size[ %d ]", codec_data_info.size); + if (IsValidCodecDataSize(codec_data_info.size)) { + std::shared_ptr data(new char[codec_data_info.size], + std::default_delete()); + memcpy(data.get(), codec_data_info.data, codec_data_info.size); + track->codec_data = data; + track->codec_data_len = codec_data_info.size; + } else { + LOG_WARN("Warning invalid codec extra data size [%d]", + codec_data_info.size); + } + gst_buffer_unmap(buffer, &codec_data_info); + } else { + LOG_DEBUG("Fail to gst_buffer_map for codec data"); + } +} + +} // namespace track_util + +} // namespace plusplayer diff --git a/src/plusplayer-core/src/trackrendereradapter.cpp b/src/plusplayer-core/src/trackrendereradapter.cpp new file mode 100755 index 0000000..95d87f6 --- /dev/null +++ b/src/plusplayer-core/src/trackrendereradapter.cpp @@ -0,0 +1,1113 @@ +// +// @ Copyright [2017] +// + +#include "core/trackrendereradapter.h" + +#include + +#include "core/trackrendereradapter_utils.h" +#include "core/utils/plusplayer_log.h" +#include "trackrenderer_capi/trackrenderer_internal.h" + +namespace plusplayer { + +TrackRendererAdapter::Ptr TrackRendererAdapter::Create() { + LOG_INFO("Trackrenderer adapter is created"); + return Ptr(new TrackRendererAdapter); +} + +TrackRendererAdapter::TrackRendererAdapter() { trackrenderer_create(&handle_); } + +TrackRendererAdapter::~TrackRendererAdapter() { + if (handle_) { + trackrenderer_destroy(handle_); + handle_ = nullptr; + } +} + +bool TrackRendererAdapter::Start() { + if (trackrenderer_start(handle_) == kFailed) { + return false; + } + return true; +} + +bool TrackRendererAdapter::Stop() { + if (trackrenderer_stop(handle_) == kFailed) { + return false; + } + return true; +} + +bool TrackRendererAdapter::Prepare() { + if (trackrenderer_prepare(handle_) == kFailed) { + return false; + } + return true; +} + +bool TrackRendererAdapter::Pause() { + if (trackrenderer_pause(handle_) == kFailed) { + return false; + } + return true; +} + +bool TrackRendererAdapter::Resume() { + if (trackrenderer_resume(handle_) == kFailed) { + return false; + } + return true; +} + +constexpr int kTrackRendererMaxStreamNumber = 3; +bool TrackRendererAdapter::SetTrack(const std::vector& trackinfo) { + int size = trackinfo.size(); + if (size <= 0 || size > kTrackRendererMaxStreamNumber) return false; + + TrackRendererTrack tracks[size]; + int index = 0; + for (const auto& track : trackinfo) { + adapter_utils::MakeTrackRendererTrack(&tracks[index], track); + index++; + } + + if (trackrenderer_set_track(handle_, tracks, size) == kFailed) { + return false; + } + return true; +} + +void TrackRendererAdapter::SetIniProperty( + const std::map& properties) { + const int size = properties.size(); + if (size <= 0) return; + TrackRendererIniProperty trackrenderer_iniproperty[size]; + int index = 0; + for (const auto& pair : properties) { + trackrenderer_iniproperty[index].key = pair.first.c_str(); + trackrenderer_iniproperty[index].value = pair.second; + index++; + } + trackrenderer_set_ini_property(handle_, trackrenderer_iniproperty, size); +} + +bool TrackRendererAdapter::Seek(uint64_t time_millisecond, + double playback_rate) { + if (trackrenderer_seek(handle_, time_millisecond, playback_rate) == kFailed) { + return false; + } + return true; +} + +bool TrackRendererAdapter::Seek(uint64_t time_millisecond, double playback_rate, + bool audio_mute) { + if (trackrenderer_seek2(handle_, time_millisecond, playback_rate, + audio_mute) == kFailed) { + return false; + } + return true; +} + +bool TrackRendererAdapter::SetPlaybackRate(double playback_rate, + bool audio_mute) { + if (trackrenderer_set_playback_rate(handle_, playback_rate, audio_mute) == + kFailed) { + return false; + } + return true; +} + +bool TrackRendererAdapter::GetPlayingTime(uint64_t* time_millisecond) { + if (trackrenderer_get_playing_time(handle_, time_millisecond) == kFailed) { + return false; + } + return true; +} + +bool TrackRendererAdapter::GetDroppedFrames(void* counts) { + if (trackrenderer_get_dropped_frames(handle_, counts) == kFailed) { + return false; + } + return true; +} + +bool TrackRendererAdapter::GetDroppedFramesForCatchup(TrackType type, + void* counts) { + if (trackrenderer_get_dropped_frames_for_catchup( + handle_, adapter_utils::ConvertToTrackRendererTrackType(type), + counts) == kFailed) { + return false; + } + return true; +} + +bool TrackRendererAdapter::Deactivate(TrackType type) { + if (trackrenderer_deactivate( + handle_, adapter_utils::ConvertToTrackRendererTrackType(type)) == + kFailed) { + return false; + } + return true; +} + +bool TrackRendererAdapter::Activate(TrackType type, const Track& trackinfo) { + TrackRendererTrack track; + adapter_utils::MakeTrackRendererTrack(&track, trackinfo); + + if (trackrenderer_activate( + handle_, adapter_utils::ConvertToTrackRendererTrackType(type), + &track) == kFailed) { + return false; + } + return true; +} + +bool TrackRendererAdapter::SubmitPacket(const DecoderInputBufferPtr& data) { +#ifdef TRACKRENDERER_GST_DEPENDENCY_REMOVAL + TrackRendererDecoderInputBuffer decoderinputbuffer{ + adapter_utils::ConvertToTrackRendererTrackType(data->GetType()), + data->GetIndex(), + static_cast(const_cast(data->Get()))}; +#else + TrackRendererDecoderInputBuffer decoderinputbuffer{ + adapter_utils::ConvertToTrackRendererTrackType(data->GetType()), + data->GetIndex(), const_cast(data->Get())}; +#endif + if (trackrenderer_submit_packet(handle_, &decoderinputbuffer, nullptr) == + kFailed) { + return false; + } + return true; +} + +namespace adapter_utils { + +TrackRendererAdapter::SubmitStatus ConvertToAdapterSubmitStatus( + TrackRendererSubmitStatus status) { + switch (status) { + case kTrackRendererSubmitStatusNotPrepared: { + return TrackRendererAdapter::SubmitStatus::kNotPrepared; + } + case kTrackRendererSubmitStatusHold: { + return TrackRendererAdapter::SubmitStatus::kHold; + } + case kTrackRendererSubmitStatusFull: { + return TrackRendererAdapter::SubmitStatus::kFull; + } + case kTrackRendererSubmitStatusSuccess: { + return TrackRendererAdapter::SubmitStatus::kSuccess; + } + case kTrackRendererSubmitStatusDrop: { + return TrackRendererAdapter::SubmitStatus::kDrop; + } + case kTrackRendererSubmitStatusFailed: { + return TrackRendererAdapter::SubmitStatus::kFailed; + } + default: + LOG_ERROR("unknown submitstatus"); + return TrackRendererAdapter::SubmitStatus::kFailed; + } +} + +} // namespace adapter_utils + +bool TrackRendererAdapter::SubmitPacket(const DecoderInputBufferPtr& data, + SubmitStatus* status) { +#ifdef TRACKRENDERER_GST_DEPENDENCY_REMOVAL + TrackRendererDecoderInputBuffer decoderinputbuffer{ + adapter_utils::ConvertToTrackRendererTrackType(data->GetType()), + data->GetIndex(), + static_cast(const_cast(data->Get()))}; +#else + TrackRendererDecoderInputBuffer decoderinputbuffer{ + adapter_utils::ConvertToTrackRendererTrackType(data->GetType()), + data->GetIndex(), const_cast(data->Get())}; +#endif + TrackRendererSubmitStatus submitstatus; + if (trackrenderer_submit_packet(handle_, &decoderinputbuffer, + &submitstatus) == kFailed) { + if (status) *status = TrackRendererAdapter::SubmitStatus::kFailed; + return false; + } + if (status) + *status = adapter_utils::ConvertToAdapterSubmitStatus(submitstatus); + return true; +} + +bool TrackRendererAdapter::SubmitPacket2(const DecoderInputBufferPtr& data, + SubmitStatus* status) { +#ifdef TRACKRENDERER_GST_DEPENDENCY_REMOVAL + TrackRendererDecoderInputBuffer decoderinputbuffer{ + adapter_utils::ConvertToTrackRendererTrackType(data->GetType()), + data->GetIndex(), + static_cast(const_cast(data->Get()))}; +#else + TrackRendererDecoderInputBuffer decoderinputbuffer{ + adapter_utils::ConvertToTrackRendererTrackType(data->GetType()), + data->GetIndex(), const_cast(data->Get())}; +#endif + TrackRendererSubmitStatus submitstatus; + if (trackrenderer_submit_packet2(handle_, &decoderinputbuffer, + &submitstatus) == kFailed) { + if (status) + *status = adapter_utils::ConvertToAdapterSubmitStatus(submitstatus); + return false; + } + + if (status) + *status = adapter_utils::ConvertToAdapterSubmitStatus(submitstatus); + + if (submitstatus == kTrackRendererSubmitStatusDrop) return true; + + data->Release(); + return true; +} + +void TrackRendererAdapter::SetDrm(const drm::Property& drm_property) { + TrackRendererDrmProperty trackrenderer_drm_property{kTrackRendererDrmTypeNone, + 0, 0, nullptr, nullptr}; + adapter_utils::MakeTrackRendererDrmProperty(&trackrenderer_drm_property, + drm_property); + trackrenderer_set_drm(handle_, &trackrenderer_drm_property); +} + +void TrackRendererAdapter::DrmLicenseAcquiredDone(TrackType type) { + trackrenderer_drm_license_acquired_done( + handle_, adapter_utils::ConvertToTrackRendererTrackType(type)); +} + +bool TrackRendererAdapter::SetDisplayMode(const DisplayMode& mode) { + if (trackrenderer_set_display_mode( + handle_, adapter_utils::ConvertToTrackRendererDisplayMode(mode)) == + kFailed) { + return false; + } + return true; +} + +bool TrackRendererAdapter::SetDisplay(const DisplayType& type, + uint32_t surface_id, long x, long y, + long w, long h) { + if (trackrenderer_set_display_surface( + handle_, adapter_utils::ConvertToTrackRendererDisplayType(type), + surface_id, x, y, w, h) == kFailed) { + return false; + } + return true; +} + +bool TrackRendererAdapter::SetDisplay(const DisplayType& type, void* obj) { + if (trackrenderer_set_display( + handle_, adapter_utils::ConvertToTrackRendererDisplayType(type), + obj) == kFailed) { + return false; + } + return true; +} + +bool TrackRendererAdapter::SetDisplay(const DisplayType& type, + void* ecore_wl2_window, int x, int y, + int w, int h) { + if (trackrenderer_set_display_ecore_wl2_window( + handle_, adapter_utils::ConvertToTrackRendererDisplayType(type), + ecore_wl2_window, x, y, w, h) == kFailed) { + return false; + } + return true; +} + +bool TrackRendererAdapter::SetDisplaySubsurface(const DisplayType& type, + void* ecore_wl2_subsurface, + int x, int y, int w, int h) { + if (trackrenderer_set_display_ecore_wl2_subsurface( + handle_, adapter_utils::ConvertToTrackRendererDisplayType(type), + ecore_wl2_subsurface, x, y, w, h) == kFailed) { + return false; + } + return true; +} + +void TrackRendererAdapter::GetDisplay(DisplayType* type, Geometry* area) { + TrackRendererGeometry geometry = {0, 0, 1920, 1080}; + TrackRendererDisplayType display_type = kTrackRendererDisplayTypeNone; + trackrenderer_get_display(handle_, &display_type, &geometry); + adapter_utils::MakeGeometry(area, geometry); + *type = adapter_utils::ConvertToDisplayType(display_type); +} + +bool TrackRendererAdapter::SetDisplayRoi(const Geometry& roi) { + TrackRendererGeometry geometry = {0, 0, 1920, 1080}; + adapter_utils::MakeTrackRendererGeometry(&geometry, roi); + if (trackrenderer_set_display_roi(handle_, &geometry) == kFailed) { + return false; + } + return true; +} + +bool TrackRendererAdapter::SetVideoRoi(const CropArea& area) { + TrackRendererCropArea croparea = {0.0, 0.0, 1.0, 1.0}; + adapter_utils::MakeTrackRendererCropArea(&croparea, area); + if (trackrenderer_set_video_roi(handle_, &croparea) == kFailed) { + return false; + } + return true; +} + +bool TrackRendererAdapter::ResizeRenderRect(const RenderRect& rect) { + TrackRendererRenderRect result = {0, 0, 1920, 1080}; + adapter_utils::MakeTrackRendererRenderRect(&result, rect); + if (trackrenderer_resize_render_rect(handle_, &result) == kFailed) { + return false; + } + return true; +} + +bool TrackRendererAdapter::SetDisplayRotate(const DisplayRotation& rotate) { + if (trackrenderer_set_display_rotate( + handle_, adapter_utils::ConvertToTrackRendererDisplayRotate( + rotate)) == kFailed) { + return false; + } + return true; +} + +bool TrackRendererAdapter::GetDisplayRotate(DisplayRotation* rotate) { + TrackRendererDisplayRotate display_rotate = kTrackRendererDisplayRotateNone; + if (trackrenderer_get_display_rotate(handle_, &display_rotate) == kFailed) { + return false; + } + *rotate = adapter_utils::ConvertToDisplayRotation(display_rotate); + return true; +} + +bool TrackRendererAdapter::SetDisplayVisible(bool is_visible) { + if (trackrenderer_set_display_visible(handle_, is_visible) == kFailed) { + return false; + } + return true; +} + +void TrackRendererAdapter::GetDisplayMode(DisplayMode* mode) { + // TODO: sy0207.ju + TrackRendererDisplayMode display_mode = kTrackRendererDisplayModeDisplayMax; + trackrenderer_get_display_mode(handle_, &display_mode); + *mode = adapter_utils::ConvertToDisplayMode(display_mode); +} + +bool TrackRendererAdapter::SetAudioMute(bool is_mute) { + if (trackrenderer_set_audio_mute(handle_, is_mute) == kFailed) { + return false; + } + return true; +} + +void TrackRendererAdapter::SetAppId(const std::string& app_id) { + trackrenderer_set_app_id(handle_, app_id.c_str()); +} + +void TrackRendererAdapter::SetAppInfo(const PlayerAppInfo& app_info) { + TrackRendererAppInfo info; + adapter_utils::MakeTrackRendererAppInfo(&info, app_info); + trackrenderer_set_app_info(handle_, &info); +} + +void TrackRendererAdapter::SetVideoStillMode(const StillMode& type) { + trackrenderer_set_video_still_mode( + handle_, adapter_utils::ConvertToTrackRendererStillMode(type)); +} + +bool TrackRendererAdapter::SetVolume(const int& volume) { + if (trackrenderer_set_volume(handle_, volume) == kFailed) { + return false; + } + return true; +} + +bool TrackRendererAdapter::GetVolume(int* volume) { + if (trackrenderer_get_volume(handle_, volume) == kFailed) { + return false; + } + return true; +} + +bool TrackRendererAdapter::Flush(const StreamType& type) { + if (trackrenderer_flush( + handle_, adapter_utils::ConvertToTrackRendererTrackTypeFromStreamType( + type)) == kFailed) { + return false; + } + return true; +} + +bool TrackRendererAdapter::Flush(const TrackType& type) { + if (trackrenderer_flush( + handle_, adapter_utils::ConvertToTrackRendererTrackType(type)) == + kFailed) { + return false; + } + return true; +} + +namespace adapter_utils { + +enum class ValueType { + kUnknown, + kInt32, + kUInt32, + kInt64, + kUInt64, + kMax, +}; +struct AttrInfo { + const ValueType value_type; + const std::string name; +}; +static const std::map + kPluginPropertyInfoTable = { + {TrackRendererAdapter::Attribute::kVideoQueueMaxByte, + {ValueType::kUInt64, "video-queue-max-byte"}}, + {TrackRendererAdapter::Attribute::kAudioQueueMaxByte, + {ValueType::kUInt64, "audio-queue-max-byte"}}, + {TrackRendererAdapter::Attribute::kVideoQueueCurrentLevelByte, + {ValueType::kUInt64, "video-current-level-byte"}}, + {TrackRendererAdapter::Attribute::kAudioQueueCurrentLevelByte, + {ValueType::kUInt64, "audio-current-level-byte"}}, + {TrackRendererAdapter::Attribute::kVideoMinByteThreshold, + {ValueType::kUInt32, "video-min-byte-percent"}}, + {TrackRendererAdapter::Attribute::kAudioMinByteThreshold, + {ValueType::kUInt32, "audio-min-byte-percent"}}, + {TrackRendererAdapter::Attribute::kVideoQueueMaxTime, + {ValueType::kUInt64, "video-queue-max-time"}}, + {TrackRendererAdapter::Attribute::kAudioQueueMaxTime, + {ValueType::kUInt64, "audio-queue-max-time"}}, + {TrackRendererAdapter::Attribute::kVideoQueueCurrentLevelTime, + {ValueType::kUInt64, "video-current-level-time"}}, + {TrackRendererAdapter::Attribute::kAudioQueueCurrentLevelTime, + {ValueType::kUInt64, "audio-current-level-time"}}, + {TrackRendererAdapter::Attribute::kVideoMinTimeThreshold, + {ValueType::kUInt32, "video-min-time-percent"}}, + {TrackRendererAdapter::Attribute::kAudioMinTimeThreshold, + {ValueType::kUInt32, "audio-min-time-percent"}}, + {TrackRendererAdapter::Attribute::kVideoSupportRotation, + {ValueType::kUInt32, "support-rotation"}}, + {TrackRendererAdapter::Attribute::kVideoRenderTimeOffset, + {ValueType::kInt64, "video-render-time-offset"}}, + {TrackRendererAdapter::Attribute::kAudioRenderTimeOffset, + {ValueType::kInt64, "audio-render-time-offset"}}}; + +static const std::map + kConfigInfoTable = { + {TrackRendererAdapter::Attribute::kAccurateSeekMode, + {ValueType::kUInt32, "accurate-seek-mode"}}, + {TrackRendererAdapter::Attribute::kLowLatencyMode, + {ValueType::kUInt32, "low-latency-mode"}}, + {TrackRendererAdapter::Attribute::kVideoFramePeekMode, + {ValueType::kUInt32, "video-frame-peek-mode"}}, + {TrackRendererAdapter::Attribute::kUnlimitedMaxBufferMode, + {ValueType::kUInt32, "unlimited-max-buffer-mode"}}, + {TrackRendererAdapter::Attribute::kVideoPreDisplayMode, + {ValueType::kUInt32, "video-pre-display-mode"}}, + {TrackRendererAdapter::Attribute::kStartRenderingTime, + {ValueType::kUInt64, "start-rendering-time"}}, + {TrackRendererAdapter::Attribute::kFmmMode, + {ValueType::kUInt32, "fmm-mode"}}, + {TrackRendererAdapter::Attribute::kAlternativeVideoResource, + {ValueType::kUInt32, "alternative-video-resource"}}, + {TrackRendererAdapter::Attribute::kVideoDecodingMode, + {ValueType::kUInt32, "video-decoding-mode"}}, + {TrackRendererAdapter::Attribute::kLateVideoFrameDropMode, + {ValueType::kUInt32, "late-video-frame-drop-mode"}}}; +} // namespace adapter_utils + +void TrackRendererAdapter::SetAttribute( + const TrackRendererAdapter::Attribute& attr, const boost::any& value) { + if (adapter_utils::kPluginPropertyInfoTable.count(attr) > 0) { + const adapter_utils::AttrInfo& info = + adapter_utils::kPluginPropertyInfoTable.at(attr); + LOG_DEBUG("attribute [%d] : %s", static_cast(attr), info.name.c_str()); + switch (info.value_type) { + case adapter_utils::ValueType::kInt32: { + std::int32_t _value = boost::any_cast(value); + trackrenderer_set_attribute(handle_, info.name.c_str(), _value, + nullptr); + } break; + case adapter_utils::ValueType::kUInt32: { + std::uint32_t _value = boost::any_cast(value); + trackrenderer_set_attribute(handle_, info.name.c_str(), _value, + nullptr); + } break; + case adapter_utils::ValueType::kInt64: { + std::int64_t _value = boost::any_cast(value); + trackrenderer_set_attribute(handle_, info.name.c_str(), _value, + nullptr); + } break; + case adapter_utils::ValueType::kUInt64: { + std::uint64_t _value = boost::any_cast(value); + trackrenderer_set_attribute(handle_, info.name.c_str(), _value, + nullptr); + } break; + default: + LOG_ERROR("unknown attribute ..."); + break; + } + return; + } else if (adapter_utils::kConfigInfoTable.count(attr) > 0) { + const adapter_utils::AttrInfo& info = + adapter_utils::kConfigInfoTable.at(attr); + switch (info.value_type) { + case adapter_utils::ValueType::kUInt32: { + std::uint32_t _value = boost::any_cast(value); + trackrenderer_set_attribute(handle_, info.name.c_str(), _value, + nullptr); + } break; + case adapter_utils::ValueType::kUInt64: { + std::uint64_t _value = boost::any_cast(value); + trackrenderer_set_attribute(handle_, info.name.c_str(), _value, + nullptr); + } break; + default: + LOG_ERROR("unknown attribute ..."); + break; + } + return; + } else { + LOG_ERROR("unknown attribute"); + return; + } +} + +void TrackRendererAdapter::GetAttribute( + const TrackRendererAdapter::Attribute& attr, boost::any* value) { + if (adapter_utils::kPluginPropertyInfoTable.count(attr) == 0) { + LOG_ERROR("unknown attribute"); + return; + } + const adapter_utils::AttrInfo& info = + adapter_utils::kPluginPropertyInfoTable.at(attr); + + switch (info.value_type) { + case adapter_utils::ValueType::kInt32: { + std::int32_t _value = 0; + trackrenderer_get_attribute(handle_, info.name.c_str(), &_value, nullptr); + *value = _value; + } break; + case adapter_utils::ValueType::kUInt32: { + std::uint32_t _value = 0; + trackrenderer_get_attribute(handle_, info.name.c_str(), &_value, nullptr); + *value = _value; + } break; + case adapter_utils::ValueType::kInt64: { + std::int64_t _value = 0; + trackrenderer_get_attribute(handle_, info.name.c_str(), &_value, nullptr); + *value = _value; + } break; + case adapter_utils::ValueType::kUInt64: { + std::uint64_t _value = 0; + trackrenderer_get_attribute(handle_, info.name.c_str(), &_value, nullptr); + *value = _value; + } break; + default: + LOG_ERROR("unknown attribute ..."); + break; + } + return; +} + +void TrackRendererAdapter::RegisterListener(EventListener* listener) { + eventlistener_ = listener; + trackrenderer_set_error_cb(handle_, ErrorCb_, (void*)this); + trackrenderer_set_error_msg_cb(handle_, ErrorMsgCb_, (void*)this); + trackrenderer_set_resourceconflict_cb(handle_, ResourceConflictCb_, + (void*)this); + trackrenderer_set_seekdone_cb(handle_, SeekDoneCb_, (void*)this); + trackrenderer_set_eos_cb(handle_, EosCb_, (void*)this); + trackrenderer_set_subtitle_rawdata_cb(handle_, SubtitleRawDataCb_, + (void*)this); + trackrenderer_set_closedcaption_cb(handle_, ClosedCaptionDataCb_, + (void*)this); + trackrenderer_set_drminitdata_cb(handle_, DrmInitDataCb_, (void*)this); + trackrenderer_set_media_packet_video_decoded_cb( + handle_, MediaPacketVideoDecodedCb_, (void*)this); + trackrenderer_set_media_packet_video_raw_decoded_cb( + handle_, MediaPacketVideoRawDecodedCb_, (void*)this); + trackrenderer_set_multiview_start_video_cb(handle_, MultiviewStartVideoCb_, + (void*)this); + trackrenderer_set_multiview_stop_video_cb(handle_, MultiviewStopVideoCb_, + (void*)this); +} + +void TrackRendererAdapter::RegisterListenerForEsplayer( + EventListener* listener) { + eventlistener_ = listener; + trackrenderer_set_error_cb(handle_, ErrorCb_, (void*)this); + trackrenderer_set_error_msg_cb(handle_, ErrorMsgCb_, (void*)this); + trackrenderer_set_resourceconflict_cb(handle_, ResourceConflictCb_, + (void*)this); + trackrenderer_set_seekdone_cb(handle_, SeekDoneCb_, (void*)this); + trackrenderer_set_flushdone_cb(handle_, FlushDoneCb_, (void*)this); + trackrenderer_set_eos_cb(handle_, EosCb_, (void*)this); + trackrenderer_set_event_cb(handle_, EventCb_, (void*)this); + trackrenderer_set_bufferstatus_cb(handle_, BufferStatusCb_, (void*)this); + trackrenderer_set_seekdata_cb(handle_, SeekDataCb_, (void*)this); + trackrenderer_set_closedcaption_cb(handle_, ClosedCaptionDataCb_, + (void*)this); + trackrenderer_set_media_packet_video_tbmptr_cb( + handle_, MediaPacketGetTbmBufPtrCb_, (void*)this); + trackrenderer_set_media_packet_video_decoded_cb( + handle_, MediaPacketVideoDecodedCb_, (void*)this); + trackrenderer_set_media_packet_video_raw_decoded_cb( + handle_, MediaPacketVideoRawDecodedCb_, (void*)this); + trackrenderer_set_first_decoding_done_cb(handle_, FirstDecodingDoneCb_, + (void*)this); + trackrenderer_set_video_decoder_underrun_cb(handle_, VideoDecoderUnderrunCb_, + (void*)this); + trackrenderer_set_video_latency_status_cb(handle_, VideoLatencyStatusCb_, + (void*)this); + trackrenderer_set_audio_latency_status_cb(handle_, AudioLatencyStatusCb_, + (void*)this); +} + +TrackRendererState TrackRendererAdapter::GetState() { + return trackrenderer_get_state(handle_); +} + +bool TrackRendererAdapter::SetMatroskaColorInfo(const std::string& color_info) { + if (trackrenderer_set_matroska_color_info(handle_, color_info.c_str()) == + kFailed) { + return false; + } + return true; +} + +void TrackRendererAdapter::SetVideoFrameBufferType( + VideoFrameTypeStrategyPtr strategy) { + strategy->SetType(handle_); +} + +bool TrackRendererAdapter::SetVideoFrameBufferScaleResolution( + const uint32_t& target_width, const uint32_t& target_height) { + if (trackrenderer_set_video_frame_buffer_scale_resolution( + handle_, target_width, target_height) == kFailed) { + return false; + } + return true; +} + +bool TrackRendererAdapter::SetDecodedVideoFrameRate( + const Rational& request_framerate) { + TrackRendererRational request_fps; + adapter_utils::MakeTrackRendererRational(&request_fps, request_framerate); + if (trackrenderer_set_decoded_video_frame_rate(handle_, request_fps) == + kFailed) { + return false; + } + return true; +} + +bool TrackRendererAdapter::RenderVideoFrame() { + if (trackrenderer_render_video_frame(handle_) == kFailed) { + return false; + } + return true; +} + +bool TrackRendererAdapter::SetAiFilter(void* aifilter) { + if (trackrenderer_set_aifilter(handle_, aifilter) == kFailed) { + return false; + } + return true; +} + +bool TrackRendererAdapter::SetCatchUpSpeed(const CatchUpSpeed& level) { + if (trackrenderer_set_catch_up_speed( + handle_, adapter_utils::ConvertToTrackRendererCatchUpSpeed(level)) == + kFailed) { + return false; + } + return true; +} + +bool TrackRendererAdapter::GetVideoLatencyStatus(LatencyStatus* status) { + TrackRendererLatencyStatus current_status = kTrackRendererLatencyStatusLow; + if (trackrenderer_get_video_latency_status(handle_, ¤t_status) == + kFailed) { + return false; + } + *status = adapter_utils::ConvertToLatencyStatus(current_status); + return true; +} + +bool TrackRendererAdapter::GetAudioLatencyStatus(LatencyStatus* status) { + TrackRendererLatencyStatus current_status = kTrackRendererLatencyStatusLow; + if (trackrenderer_get_audio_latency_status(handle_, ¤t_status) == + kFailed) { + return false; + } + *status = adapter_utils::ConvertToLatencyStatus(current_status); + return true; +} + +bool TrackRendererAdapter::SetVideoMidLatencyThreshold( + const unsigned int threshold) { + if (trackrenderer_set_video_mid_latency_threshold(handle_, threshold) == + kFailed) { + return false; + } + return true; +} + +bool TrackRendererAdapter::SetAudioMidLatencyThreshold( + const unsigned int threshold) { + if (trackrenderer_set_audio_mid_latency_threshold(handle_, threshold) == + kFailed) { + return false; + } + return true; +} + +bool TrackRendererAdapter::SetVideoHighLatencyThreshold( + const unsigned int threshold) { + trackrenderer_set_video_high_latency_cb(handle_, VideoHighLatencyCb_, + (void*)this); + + if (trackrenderer_set_video_high_latency_threshold(handle_, threshold) == + kFailed) { + return false; + } + return true; +} + +bool TrackRendererAdapter::SetAudioHighLatencyThreshold( + const unsigned int threshold) { + trackrenderer_set_audio_high_latency_cb(handle_, AudioHighLatencyCb_, + (void*)this); + + if (trackrenderer_set_audio_high_latency_threshold(handle_, threshold) == + kFailed) { + return false; + } + return true; +} + +bool TrackRendererAdapter::InitAudioEasingInfo( + const uint32_t init_volume, const uint32_t init_elapsed_time, + const AudioEasingInfo& easing_info) { + TrackRendererAudioEasingInfo info; + adapter_utils::MakeTrackRendererAudioEasingInfo(&info, easing_info); + if (trackrenderer_init_audio_easing_info( + handle_, init_volume, init_elapsed_time, &info) == kFailed) { + return false; + } + return true; +} + +bool TrackRendererAdapter::UpdateAudioEasingInfo( + const AudioEasingInfo& easing_info) { + TrackRendererAudioEasingInfo info; + adapter_utils::MakeTrackRendererAudioEasingInfo(&info, easing_info); + if (trackrenderer_update_audio_easing_info(handle_, &info) == kFailed) { + return false; + } + return true; +} + +bool TrackRendererAdapter::GetAudioEasingInfo(uint32_t* current_volume, + uint32_t* elapsed_time, + AudioEasingInfo* easing_info) { + TrackRendererAudioEasingInfo info; + if (trackrenderer_get_audio_easing_info(handle_, current_volume, elapsed_time, + &info) == kFailed) { + return false; + } + adapter_utils::MakeAudioEasingInfo(easing_info, info); + return true; +} + +bool TrackRendererAdapter::StartAudioEasing() { + if (trackrenderer_start_audio_easing(handle_) == kFailed) { + return false; + } + return true; +} + +bool TrackRendererAdapter::StopAudioEasing() { + if (trackrenderer_stop_audio_easing(handle_) == kFailed) { + return false; + } + return true; +} + +bool TrackRendererAdapter::GetVirtualRscId(const RscType type, + int* virtual_id) { + TrackRendererRscType converted_type = kTrackRendererRscTypeVideoRenderer; + if (!adapter_utils::ConvertToTrackRendererRscType(type, &converted_type)) + return false; + if (trackrenderer_get_virtual_rsc_id(handle_, converted_type, virtual_id) == + kFailed) { + return false; + } + return true; +} + +bool TrackRendererAdapter::SetAdvancedPictureQualityType( + const AdvPictureQualityType type) { + TrackRendererAdvPictureQualityType converted_type = + kTrackRendererAdvPictureQualityTypeVideoCall; + if (!adapter_utils::ConvertToTrackRendererAdvPictureQualityType( + type, &converted_type)) + return false; + if (trackrenderer_set_advanced_picture_quality_type( + handle_, converted_type) == kFailed) { + return false; + } + return true; +} + +bool TrackRendererAdapter::SetResourceAllocatePolicy( + const RscAllocPolicy policy) { + TrackRendererRscAllocPolicy converted_policy = + kTrackRendererRscAllocExclusive; + if (!adapter_utils::ConvertToTrackRendererRscAllocPolicy(policy, + &converted_policy)) + return false; + if (trackrenderer_set_resource_allocate_policy(handle_, converted_policy) == + kFailed) { + return false; + } + return true; +} + +bool TrackRendererAdapter::SetVideoParDar(uint64_t time_millisecond, + uint32_t par_num, uint32_t par_den, + uint32_t dar_num, uint32_t dar_den) { + if (trackrenderer_set_video_par_dar(handle_, time_millisecond, par_num, + par_den, dar_num, dar_den) == kFailed) { + return false; + } + return true; +} + +///////////////////////////////////////////// +/////////////// Callbacks /////////////////// +///////////////////////////////////////////// +void TrackRendererAdapter::ErrorCb_(const TrackRendererErrorType error_code, + UserData userdata) { + auto adapter = static_cast(userdata); + if (!adapter) return; + adapter->eventlistener_->OnError( + adapter_utils::ConvertToErrorType(error_code)); +} + +void TrackRendererAdapter::ErrorMsgCb_(const TrackRendererErrorType error_code, + char* error_msg, UserData userdata) { + auto adapter = static_cast(userdata); + if (!adapter) return; + adapter->eventlistener_->OnErrorMsg( + adapter_utils::ConvertToErrorType(error_code), error_msg); +} + +void TrackRendererAdapter::ResourceConflictCb_(UserData userdata) { + auto adapter = static_cast(userdata); + if (!adapter) return; + adapter->eventlistener_->OnResourceConflicted(); +} + +void TrackRendererAdapter::SeekDoneCb_(UserData userdata) { + auto adapter = static_cast(userdata); + if (!adapter) return; + adapter->eventlistener_->OnSeekDone(); +} + +void TrackRendererAdapter::FlushDoneCb_(UserData userdata) { + auto adapter = static_cast(userdata); + if (!adapter) return; + adapter->eventlistener_->OnFlushDone(); +} + +void TrackRendererAdapter::EosCb_(UserData userdata) { + auto adapter = static_cast(userdata); + if (!adapter) return; + adapter->eventlistener_->OnEos(); +} + +void TrackRendererAdapter::EventCb_(const TrackRendererEventType event_type, + const TrackrendererEventMsg msg_data, + UserData userdata) { + auto adapter = static_cast(userdata); + if (!adapter) return; + EventMsg event_msg; + event_msg.data = msg_data.data; + event_msg.len = msg_data.len; + adapter->eventlistener_->OnEvent(static_cast(event_type), + event_msg); +} + +void TrackRendererAdapter::FirstDecodingDoneCb_(UserData userdata) { + auto adapter = static_cast(userdata); + if (!adapter) return; + adapter->eventlistener_->OnFirstDecodingDone(); +} + +void TrackRendererAdapter::SubtitleRawDataCb_( + TrackRendererDecoderInputBuffer* buf, const TrackRendererSubtitleType type, + UserData userdata) { + auto adapter = static_cast(userdata); + if (!adapter || !buf) return; + +#ifdef TRACKRENDERER_GST_DEPENDENCY_REMOVAL + auto buffer = DecoderInputBuffer::Create( + adapter_utils::ConvertToTrackType(buf->type), buf->index, + static_cast(buf->buffer)); +#else + auto buffer = DecoderInputBuffer::Create( + adapter_utils::ConvertToTrackType(buf->type), buf->index, buf->buffer); +#endif + adapter->eventlistener_->OnSubtitleData( + std::move(buffer), adapter_utils::ConvertToSubtitleType(type)); +} + +#ifndef TRACKRENDERER_FEATURE_DEPRECATE_SUBTITLE_CB +void TrackRendererAdapter::SubtitleDataCb_(const char* data, const int size, + const TrackRendererSubtitleType type, + const uint64_t duration, + TrackRendererSubtitleAttr* attr_list, + int attr_list_size, + UserData userdata) { + auto adapter = static_cast(userdata); + if (!adapter) return; + + SubtitleAttrList list; + int index = 0; + for (index = 0; index < attr_list_size; index++) { + SubtitleAttr attr{ + adapter_utils::ConvertToSubtitleAttrType(attr_list[index].type), + attr_list[index].start_time, attr_list[index].stop_time, + adapter_utils::SetSubtitleAttrValue(attr_list[index]), + attr_list[index].extsub_index}; + list.push_back(std::move(attr)); + } + auto new_list = SubtitleAttrListPtr(new SubtitleAttrList(std::move(list))); + + adapter->eventlistener_->OnSubtitleData( + data, size, adapter_utils::ConvertToSubtitleType(type), duration, + std::move(new_list)); +} +#endif + +void TrackRendererAdapter::ClosedCaptionDataCb_(const char* data, + const int size, + UserData userdata) { + auto adapter = static_cast(userdata); + if (!adapter) return; + adapter->eventlistener_->OnClosedCaptionData(data, size); +} + +void TrackRendererAdapter::DrmInitDataCb_(int* drmhandle, unsigned int len, + unsigned char* psshdata, + TrackRendererTrackType type, + UserData userdata) { + auto adapter = static_cast(userdata); + if (!adapter) return; + adapter->eventlistener_->OnDrmInitData( + drmhandle, len, psshdata, adapter_utils::ConvertToTrackType(type)); +} + +void TrackRendererAdapter::BufferStatusCb_( + const TrackRendererTrackType type, const TrackRendererBufferStatus status, + UserData userdata) { + auto adapter = static_cast(userdata); + if (!adapter) return; + adapter->eventlistener_->OnBufferStatus( + adapter_utils::ConvertToTrackType(type), + adapter_utils::ConvertToBufferStatus(status)); +} + +void TrackRendererAdapter::SeekDataCb_(const TrackRendererTrackType type, + const uint64_t offset, + UserData userdata) { + auto adapter = static_cast(userdata); + if (!adapter) return; + adapter->eventlistener_->OnSeekData(adapter_utils::ConvertToTrackType(type), + offset); +} + +void TrackRendererAdapter::MediaPacketGetTbmBufPtrCb_(void** ptr, + bool is_scale_change, + UserData userdata) { + auto adapter = static_cast(userdata); + if (!adapter) return; + adapter->eventlistener_->OnMediaPacketGetTbmBufPtr(ptr, is_scale_change); +} + +void TrackRendererAdapter::MediaPacketVideoDecodedCb_( + const TrackRendererDecodedVideoPacket* packet, UserData userdata) { + auto adapter = static_cast(userdata); + if (!adapter) return; + DecodedVideoPacket _packet = + adapter_utils::ConvertToDecodedVideoPacket(packet); + adapter->eventlistener_->OnMediaPacketVideoDecoded(_packet); +} + +void TrackRendererAdapter::MediaPacketVideoRawDecodedCb_( + const TrackRendererDecodedVideoRawModePacket* packet, + TrackRendererDecodedVideoType type, UserData userdata) { + auto adapter = static_cast(userdata); + if (!adapter) return; + DecodedVideoRawModePacket _packet; + _packet.type = static_cast(type); + _packet.pts = packet->pts; + _packet.width = packet->width; + _packet.height = packet->height; + _packet.data = + *static_cast(packet->internal_data); + adapter->eventlistener_->OnMediaPacketVideoRawDecoded(_packet); +} + +void TrackRendererAdapter::VideoDecoderUnderrunCb_(UserData userdata) { + auto* adapter = static_cast(userdata); + if (!adapter) return; + adapter->eventlistener_->OnVideoDecoderUnderrun(); +} + +void TrackRendererAdapter::VideoLatencyStatusCb_( + const TrackRendererLatencyStatus video_latency_status, UserData userdata) { + auto* adapter = static_cast(userdata); + if (!adapter) return; + adapter->eventlistener_->OnVideoLatencyStatus( + adapter_utils::ConvertToLatencyStatus(video_latency_status)); +} + +void TrackRendererAdapter::AudioLatencyStatusCb_( + const TrackRendererLatencyStatus audio_latency_status, UserData userdata) { + auto* adapter = static_cast(userdata); + if (!adapter) return; + adapter->eventlistener_->OnAudioLatencyStatus( + adapter_utils::ConvertToLatencyStatus(audio_latency_status)); +} + +void TrackRendererAdapter::VideoHighLatencyCb_(UserData userdata) { + auto* adapter = static_cast(userdata); + if (!adapter) return; + adapter->eventlistener_->OnVideoHighLatency(); +} + +void TrackRendererAdapter::AudioHighLatencyCb_(UserData userdata) { + auto* adapter = static_cast(userdata); + if (!adapter) return; + adapter->eventlistener_->OnAudioHighLatency(); +} + +void TrackRendererAdapter::MultiviewStartVideoCb_(UserData userdata) { + auto adapter = static_cast(userdata); + if (!adapter) return; + adapter->eventlistener_->OnMultiviewStartVideo(); +} + +void TrackRendererAdapter::MultiviewStopVideoCb_(UserData userdata) { + auto adapter = static_cast(userdata); + if (!adapter) return; + adapter->eventlistener_->OnMultiviewStopVideo(); +} +} // namespace plusplayer diff --git a/src/plusplayer-core/src/trackrendereradapter_utils.cpp b/src/plusplayer-core/src/trackrendereradapter_utils.cpp new file mode 100755 index 0000000..4db154a --- /dev/null +++ b/src/plusplayer-core/src/trackrendereradapter_utils.cpp @@ -0,0 +1,875 @@ +// +// @ Copyright [2017] +// + +#include "core/trackrendereradapter_utils.h" + +#include + +#include "core/utils/plusplayer_log.h" + +namespace plusplayer { + +namespace adapter_utils { + +void InitTrack(TrackRendererTrack* track) { + track->index = kTrackRendererInvalidTrackIndex; // int index + track->id = 0; // int id + track->mimetype = nullptr; // const char* mimetype + track->streamtype = nullptr; // const char* streamtype + track->type = kTrackRendererTrackTypeMax; // int type + track->codec_data = nullptr; // char* codec_data + track->codec_data_len = 0; // int codec_data_len + track->width = 0; // int width + track->height = 0; // int height + track->maxwidth = 0; // int maxwidth + track->maxheight = 0; // int maxheight + track->framerate_num = 0; // int framerate_num + track->framerate_den = 0; // int framerate_den + track->sample_rate = 0; // int sample_rate + track->sample_format = 0; // int sample_format + track->channels = 0; // int channels + track->version = 0; // int version + track->layer = 0; // int layer + track->bits_per_sample = 0; // int bit_per_sample + track->block_align = 0; // int block_align + track->bitrate = 0; // int bitrate + track->endianness = 1234; // int endianness + track->is_signed = 0; // int is_signed + track->active = 0; // int active + track->use_swdecoder = 0; // int use_swdecoder + track->language_code = nullptr; // const char* language_code + track->subtitle_format = nullptr; // const char* subtitle_format +} + +void MakeGeometry(Geometry* roi, const TrackRendererGeometry& geometry) { + roi->x = geometry.x; + roi->y = geometry.y; + roi->w = geometry.w; + roi->h = geometry.h; +} + +void MakeTrackRendererDrmProperty( + TrackRendererDrmProperty* trackrenderer_drm_property, + const drm::Property& drm_property) { + trackrenderer_drm_property->type = + ConvertToTrackRendererDrmType(drm_property.type); + trackrenderer_drm_property->handle = static_cast(drm_property.handle); + trackrenderer_drm_property->external_decryption = + static_cast(drm_property.external_decryption); + trackrenderer_drm_property->license_acquired_cb = + static_cast(drm_property.license_acquired_cb); + trackrenderer_drm_property->license_acquired_userdata = + static_cast(drm_property.license_acquired_userdata); +} + +void MakeTrackRendererGeometry(TrackRendererGeometry* geometry, + const Geometry& roi) { + geometry->x = roi.x; + geometry->y = roi.y; + geometry->w = roi.w; + geometry->h = roi.h; +} + +void MakeTrackRendererCropArea(TrackRendererCropArea* crop, + const CropArea& area) { + crop->scale_x = area.scale_x; + crop->scale_y = area.scale_y; + crop->scale_w = area.scale_w; + crop->scale_h = area.scale_h; +} + +void MakeTrackRendererRenderRect(TrackRendererRenderRect* output, + const RenderRect& input) { + output->x = input.x; + output->y = input.y; + output->w = input.w; + output->h = input.h; +} + +void MakeTrackRendererTrack(TrackRendererTrack* track, const Track& trackinfo) { + InitTrack(track); + track->index = trackinfo.index; + track->id = trackinfo.id; + track->mimetype = trackinfo.mimetype.c_str(); + track->streamtype = trackinfo.streamtype.c_str(); + track->type = ConvertToTrackRendererTrackType(trackinfo.type); + track->codec_data = trackinfo.codec_data.get(); + track->codec_data_len = trackinfo.codec_data_len; + track->width = trackinfo.width; + track->height = trackinfo.height; + track->maxwidth = trackinfo.maxwidth; + track->maxheight = trackinfo.maxheight; + track->framerate_num = trackinfo.framerate_num; + track->framerate_den = trackinfo.framerate_den; + track->sample_rate = trackinfo.sample_rate; + track->sample_format = trackinfo.sample_format; + track->channels = trackinfo.channels; + track->version = trackinfo.version; + track->layer = trackinfo.layer; + track->bits_per_sample = trackinfo.bits_per_sample; + track->block_align = trackinfo.block_align; + track->bitrate = trackinfo.bitrate; + track->endianness = trackinfo.endianness; + track->is_signed = trackinfo.is_signed; + track->active = trackinfo.active; + track->use_swdecoder = trackinfo.use_swdecoder; + track->language_code = trackinfo.language_code.c_str(); + track->subtitle_format = trackinfo.subtitle_format.c_str(); +} + +void MakeTrackRendererAppInfo(TrackRendererAppInfo* app_attr, + const PlayerAppInfo& app_info) { + app_attr->id = const_cast(app_info.id.c_str()); + app_attr->version = const_cast(app_info.version.c_str()); + app_attr->type = const_cast(app_info.type.c_str()); +} + +void MakeAudioEasingInfo(AudioEasingInfo* easing_info, + const TrackRendererAudioEasingInfo& easing_attr) { + easing_info->target_volume = easing_attr.target_volume; + easing_info->duration = easing_attr.duration; + easing_info->type = ConvertToAudioEasingType(easing_attr.type); +} + +void MakeTrackRendererAudioEasingInfo(TrackRendererAudioEasingInfo* easing_attr, + const AudioEasingInfo& easing_info) { + easing_attr->target_volume = easing_info.target_volume; + easing_attr->duration = easing_info.duration; + easing_attr->type = ConvertToTrackRendererAudioEasingType(easing_info.type); +} + +void MakeTrackRendererRational(TrackRendererRational* rational_attr, + const Rational& rational_info) { + if (!rational_attr) return; + rational_attr->num = rational_info.num; + rational_attr->den = rational_info.den; +} + +DisplayMode ConvertToDisplayMode(TrackRendererDisplayMode typevalue) { + switch (typevalue) { + case kTrackRendererDisplayModeLetterBox: { + return DisplayMode::kLetterBox; + } + case kTrackRendererDisplayModeOriginSize: { + return DisplayMode::kOriginSize; + } + case kTrackRendererDisplayModeFullScreen: { + return DisplayMode::kFullScreen; + } + case kTrackRendererDisplayModeCroppedFull: { + return DisplayMode::kCroppedFull; + } + case kTrackRendererDisplayModeOriginOrLetter: { + return DisplayMode::kOriginOrLetter; + } + case kTrackRendererDisplayModeDstRoi: { + return DisplayMode::kDstRoi; + } + case kTrackRendererDisplayModeAutoAspectRatio: { + return DisplayMode::kAutoAspectRatio; + } + case kTrackRendererDisplayModeDisplayMax: { + return DisplayMode::kMax; + } + default: + LOG_ERROR("unknown DisplayMode"); + return DisplayMode::kFullScreen; + } +} +DisplayRotation ConvertToDisplayRotation( + const TrackRendererDisplayRotate rotate_value) { + switch (rotate_value) { + case kTrackRendererDisplayRotateNone: { + return DisplayRotation::kNone; + } + case kTrackRendererDisplayRotate90: { + return DisplayRotation::kRotate90; + } + case kTrackRendererDisplayRotate180: { + return DisplayRotation::kRotate180; + } + case kTrackRendererDisplayRotate270: { + return DisplayRotation::kRotate270; + } + default: + return DisplayRotation::kNone; + } +} + +DisplayType ConvertToDisplayType(const TrackRendererDisplayType typevalue) { + switch (typevalue) { + case kTrackRendererDisplayTypeNone: { + return DisplayType::kNone; + } + case kTrackRendererDisplayTypeOverlay: { + return DisplayType::kOverlay; + } + case kTrackRendererDisplayTypeEvas: { + return DisplayType::kEvas; + } + default: + LOG_ERROR("unknown DisplayType"); + return DisplayType::kNone; + } +} + +ErrorType ConvertToErrorType(const TrackRendererErrorType type) { + switch (type) { + case kTrackRendererErrorTypeErrorNone: { + return ErrorType::kNone; + } + case kTrackRendererErrorTypeOutOfMemory: { + return ErrorType::kOutOfMemory; + } + case kTrackRendererErrorTypeInvalidParameter: { + return ErrorType::kInvalidParameter; + } + case kTrackRendererErrorTypeNoSuchFile: { + return ErrorType::kNoSuchFile; + } + case kTrackRendererErrorTypeInvalidOperation: { + return ErrorType::kInvalidOperation; + } + case kTrackRendererErrorTypeFileNoSpaceOnDevice: { + return ErrorType::kFileNoSpaceOnDevice; + } + case kTrackRendererErrorTypeFeatureNotSupportedOnDevice: { + return ErrorType::kFeatureNotSupportedOnDevice; + } + case kTrackRendererErrorTypeSeekFailed: { + return ErrorType::kSeekFailed; + } + case kTrackRendererErrorTypeInvalidState: { + return ErrorType::kInvalidState; + } + case kTrackRendererErrorTypeNotSupportedFile: { + return ErrorType::kNotSupportedFile; + } + case kTrackRendererErrorTypeInvalidUri: { + return ErrorType::kInvalidUri; + } + case kTrackRendererErrorTypeSoundPolicy: { + return ErrorType::kSoundPolicy; + } + case kTrackRendererErrorTypeConnectionFailed: { + return ErrorType::kConnectionFailed; + } + case kTrackRendererErrorTypeVideoCaptureFailed: { + return ErrorType::kVideoCaptureFailed; + } + case kTrackRendererErrorTypeDrmExpired: { + return ErrorType::kDrmExpired; + } + case kTrackRendererErrorTypeDrmNoLicense: { + return ErrorType::kDrmNoLicense; + } + case kTrackRendererErrorTypeDrmFutureUse: { + return ErrorType::kDrmFutureUse; + } + case kTrackRendererErrorTypeDrmNotPermitted: { + return ErrorType::kDrmNotPermitted; + } + case kTrackRendererErrorTypeResourceLimit: { + return ErrorType::kResourceLimit; + } + case kTrackRendererErrorTypePermissionDenied: { + return ErrorType::kPermissionDenied; + } + case kTrackRendererErrorTypeServiceDisconnected: { + return ErrorType::kServiceDisconnected; + } + case kTrackRendererErrorTypeBufferSpace: { + return ErrorType::kBufferSpace; + } + case kTrackRendererErrorTypeNotSupportedAudioCodec: { + return ErrorType::kNotSupportedAudioCodec; + } + case kTrackRendererErrorTypeNotSupportedVideoCodec: { + return ErrorType::kNotSupportedVideoCodec; + } + case kTrackRendererErrorTypeNotSupportedSubtitle: { + return ErrorType::kNotSupportedSubtitle; + } + case kTrackRendererErrorTypeDrmInfo: { + return ErrorType::kDrmInfo; + } + case kTrackRendererErrorTypeNotSupportedFormat: { + return ErrorType::kNotSupportedFormat; + } + case kTrackRendererErrorTypeStreamingPlayer: { + return ErrorType::kStreamingPlayer; + } + case kTrackRendererErrorTypeDtcpFsk: { + return ErrorType::kDtcpFsk; + } + case kTrackRendererErrorTypePreLoadingTimeOut: { + return ErrorType::kPreLoadingTimeOut; + } + case kTrackRendererErrorTypeNetworkError: { + return ErrorType::kNetworkError; + } + case kTrackRendererErrorTypeChannelSurfingFailed: { + return ErrorType::kChannelSurfingFailed; + } + case kTrackRendererErrorTypeUnknown: { + return ErrorType::kUnknown; + } + default: + LOG_ERROR("unknown errortype"); + return ErrorType::kUnknown; + } +} + +#ifndef TRACKRENDERER_FEATURE_DEPRECATE_SUBTITLE_CB +SubtitleAttrType ConvertToSubtitleAttrType( + const TrackRendererSubtitleAttrType& type) { + switch (type) { + case kTrackRendererSubtitleAttrTypeRegionXPos: { + return SubtitleAttrType::kSubAttrRegionXPos; + } + case kTrackRendererSubtitleAttrTypeRegionYPos: { + return SubtitleAttrType::kSubAttrRegionYPos; + } + case kTrackRendererSubtitleAttrTypeRegionWidth: { + return SubtitleAttrType::kSubAttrRegionWidth; + } + case kTrackRendererSubtitleAttrTypeRegionHeight: { + return SubtitleAttrType::kSubAttrRegionHeight; + } + case kTrackRendererSubtitleAttrTypeWindowXPadding: { + return SubtitleAttrType::kSubAttrWindowXPadding; + } + case kTrackRendererSubtitleAttrTypeWindowYPadding: { + return SubtitleAttrType::kSubAttrWindowYPadding; + } + case kTrackRendererSubtitleAttrTypeWindowLeftMargin: { + return SubtitleAttrType::kSubAttrWindowLeftMargin; + } + case kTrackRendererSubtitleAttrTypeWindowRightMargin: { + return SubtitleAttrType::kSubAttrWindowRightMargin; + } + case kTrackRendererSubtitleAttrTypeWindowTopMargin: { + return SubtitleAttrType::kSubAttrWindowTopMargin; + } + case kTrackRendererSubtitleAttrTypeWindowBottomMargin: { + return SubtitleAttrType::kSubAttrWindowBottomMargin; + } + case kTrackRendererSubtitleAttrTypeWindowBgColor: { + return SubtitleAttrType::kSubAttrWindowBgColor; + } + case kTrackRendererSubtitleAttrTypeWindowOpacity: { + return SubtitleAttrType::kSubAttrWindowOpacity; + } + case kTrackRendererSubtitleAttrTypeWindowShowBg: { + return SubtitleAttrType::kSubAttrWindowShowBg; + } + case kTrackRendererSubtitleAttrTypeFontFamily: { + return SubtitleAttrType::kSubAttrFontFamily; + } + case kTrackRendererSubtitleAttrTypeFontSize: { + return SubtitleAttrType::kSubAttrFontSize; + } + case kTrackRendererSubtitleAttrTypeFontWeight: { + return SubtitleAttrType::kSubAttrFontWeight; + } + case kTrackRendererSubtitleAttrTypeFontStyle: { + return SubtitleAttrType::kSubAttrFontStyle; + } + case kTrackRendererSubtitleAttrTypeFontColor: { + return SubtitleAttrType::kSubAttrFontColor; + } + case kTrackRendererSubtitleAttrTypeFontBgColor: { + return SubtitleAttrType::kSubAttrFontBgColor; + } + case kTrackRendererSubtitleAttrTypeFontOpacity: { + return SubtitleAttrType::kSubAttrFontOpacity; + } + case kTrackRendererSubtitleAttrTypeFontBgOpacity: { + return SubtitleAttrType::kSubAttrFontBgOpacity; + } + case kTrackRendererSubtitleAttrTypeFontTextOutlineColor: { + return SubtitleAttrType::kSubAttrFontTextOutlineColor; + } + case kTrackRendererSubtitleAttrTypeFontTextOutlineThickness: { + return SubtitleAttrType::kSubAttrFontTextOutlineThickness; + } + case kTrackRendererSubtitleAttrTypeFontTextOutlineBlurRadius: { + return SubtitleAttrType::kSubAttrFontTextOutlineBlurRadius; + } + case kTrackRendererSubtitleAttrTypeFontVerticalAlign: { + return SubtitleAttrType::kSubAttrFontVerticalAlign; + } + case kTrackRendererSubtitleAttrTypeFontHorizontalAlign: { + return SubtitleAttrType::kSubAttrFontHorizontalAlign; + } + case kTrackRendererSubtitleAttrTypeRawSubtitle: { + return SubtitleAttrType::kSubAttrRawSubtitle; + } + case kTrackRendererSubtitleAttrTypeWebvttCueLine: { + return SubtitleAttrType::kSubAttrWebvttCueLine; + } + case kTrackRendererSubtitleAttrTypeWebvttCueLineNum: { + return SubtitleAttrType::kSubAttrWebvttCueLineNum; + } + case kTrackRendererSubtitleAttrTypeWebvttCueLineAlign: { + return SubtitleAttrType::kSubAttrWebvttCueLineAlign; + } + case kTrackRendererSubtitleAttrTypeWebvttCueAlign: { + return SubtitleAttrType::kSubAttrWebvttCueAlign; + } + case kTrackRendererSubtitleAttrTypeWebvttCueSize: { + return SubtitleAttrType::kSubAttrWebvttCueSize; + } + case kTrackRendererSubtitleAttrTypeWebvttCuePosition: { + return SubtitleAttrType::kSubAttrWebvttCuePosition; + } + case kTrackRendererSubtitleAttrTypeWebvttCuePositionAlign: { + return SubtitleAttrType::kSubAttrWebvttCuePositionAlign; + } + case kTrackRendererSubtitleAttrTypeWebvttCueVertical: { + return SubtitleAttrType::kSubAttrWebvttCueVertical; + } + case kTrackRendererSubtitleAttrTypeTimestamp: { + return SubtitleAttrType::kSubAttrTimestamp; + } + case kTrackRendererSubtitleAttrTypeExtsubIndex: { + return SubtitleAttrType::kSubAttrExtsubIndex; + } + case kTrackRendererSubtitleAttrTypeTypeNone: { + return SubtitleAttrType::kSubAttrTypeNone; + } + default: + LOG_ERROR("unknown subtitle attr tracktype"); + return SubtitleAttrType::kSubAttrTypeNone; + } +} +#endif + +SubtitleType ConvertToSubtitleType(const TrackRendererSubtitleType& type) { + switch (type) { + case kTrackRendererSubtitleTypeText: { + return SubtitleType::kText; + } + case kTrackRendererSubtitleTypePicture: { + return SubtitleType::kPicture; + } + case kTrackRendererSubtitleTypeInvalid: { + return SubtitleType::kInvalid; + } + default: + LOG_ERROR("unknown subtitletype"); + return SubtitleType::kInvalid; + } +} + +TrackType ConvertToTrackType(TrackRendererTrackType typevalue) { + switch (typevalue) { + case kTrackRendererTrackTypeAudio: { + return TrackType::kTrackTypeAudio; + } + case kTrackRendererTrackTypeVideo: { + return TrackType::kTrackTypeVideo; + } + case kTrackRendererTrackTypeSubtitle: { + return TrackType::kTrackTypeSubtitle; + } + case kTrackRendererTrackTypeMax: { + return TrackType::kTrackTypeMax; + } + default: + LOG_ERROR("unknown tracktype"); + return TrackType::kTrackTypeMax; + } +} + +DecodedVideoPacket ConvertToDecodedVideoPacket( + const TrackRendererDecodedVideoPacket* packet) { + DecodedVideoPacket _packet; + _packet.pts = packet->pts; + _packet.duration = packet->duration; + _packet.surface_data = static_cast(packet->surface_data); + _packet.scaler_index = packet->scaler_index; + return _packet; +} + +TrackRendererDecodedVideoFrameBufferType ConvertToVideoFrameBufferType( + const DecodedVideoFrameBufferType& type) { + switch (type) { + case DecodedVideoFrameBufferType::kCopy: { + return kTrackRendererDecodedVideoFrameBufferCopy; + } + case DecodedVideoFrameBufferType::kReference: { + return kTrackRendererDecodedVideoFrameBufferReference; + } + case DecodedVideoFrameBufferType::kScale: { + return kTrackRendererDecodedVideoFrameBufferScale; + } + case DecodedVideoFrameBufferType::kNone: { + return kTrackRendererDecodedVideoFrameBufferNone; + } + default: + LOG_ERROR("wrong buffer type"); + return kTrackRendererDecodedVideoFrameBufferNone; + } +} + +TrackRendererDisplayMode ConvertToTrackRendererDisplayMode( + const DisplayMode& mode) { + switch (mode) { + case DisplayMode::kLetterBox: { + return kTrackRendererDisplayModeLetterBox; + } + case DisplayMode::kOriginSize: { + return kTrackRendererDisplayModeOriginSize; + } + case DisplayMode::kFullScreen: { + return kTrackRendererDisplayModeFullScreen; + } + case DisplayMode::kCroppedFull: { + return kTrackRendererDisplayModeCroppedFull; + } + case DisplayMode::kOriginOrLetter: { + return kTrackRendererDisplayModeOriginOrLetter; + } + case DisplayMode::kDstRoi: { + return kTrackRendererDisplayModeDstRoi; + } + case DisplayMode::kAutoAspectRatio: { + return kTrackRendererDisplayModeAutoAspectRatio; + } + case DisplayMode::kMax: { + return kTrackRendererDisplayModeDisplayMax; + } + default: + LOG_ERROR("unknown displaymode"); + return kTrackRendererDisplayModeFullScreen; + } +} + +TrackRendererDisplayRotate ConvertToTrackRendererDisplayRotate( + const DisplayRotation& rotate) { + switch (rotate) { + case DisplayRotation::kNone: { + return kTrackRendererDisplayRotateNone; + } + case DisplayRotation::kRotate90: { + return kTrackRendererDisplayRotate90; + } + case DisplayRotation::kRotate180: { + return kTrackRendererDisplayRotate180; + } + case DisplayRotation::kRotate270: { + return kTrackRendererDisplayRotate270; + } + default: + LOG_ERROR("unknown displayrotate"); + return kTrackRendererDisplayRotateNone; + } +} + +TrackRendererDisplayType ConvertToTrackRendererDisplayType( + const DisplayType& type) { + switch (type) { + case DisplayType::kNone: { + return kTrackRendererDisplayTypeNone; + } + case DisplayType::kOverlay: { + return kTrackRendererDisplayTypeOverlay; + } + case DisplayType::kEvas: { + return kTrackRendererDisplayTypeEvas; + } + default: + LOG_ERROR("unknown displaytype"); + return kTrackRendererDisplayTypeNone; + } +} + +TrackRendererDrmType ConvertToTrackRendererDrmType(const drm::Type& drm_type) { + switch (drm_type) { + case drm::Type::kNone: { + return kTrackRendererDrmTypeNone; + } + case drm::Type::kPlayready: { + return kTrackRendererDrmTypePlayready; + } + case drm::Type::kMarlin: { + return kTrackRendererDrmTypeMarlin; + } + case drm::Type::kVerimatrix: { + return kTrackRendererDrmTypeVerimatrix; + } + case drm::Type::kWidevineClassic: { + return kTrackRendererDrmTypeWidevineClassic; + } + case drm::Type::kSecuremedia: { + return kTrackRendererDrmTypeSecuremedia; + } + case drm::Type::kSdrm: { + return kTrackRendererDrmTypeSdrm; + } + case drm::Type::kWidevineCdm: { + return kTrackRendererDrmTypeWidevineCdm; + } + case drm::Type::kMax: { + return kTrackRendererDrmTypeDrmMax; + } + default: + LOG_ERROR("unknown drmtype"); + return kTrackRendererDrmTypeNone; + } +} + +TrackRendererStillMode ConvertToTrackRendererStillMode( + const StillMode& still_mode) { + switch (still_mode) { + case StillMode::kNone: { + return kTrackRendererStillModeNone; + } + case StillMode::kOff: { + return kTrackRendererStillModeOff; + } + case StillMode::kOn: { + return kTrackRendererStillModeOn; + } + default: + LOG_ERROR("unknown stillmode"); + return kTrackRendererStillModeNone; + } +} + +TrackRendererTrackType ConvertToTrackRendererTrackType(const TrackType& type) { + switch (type) { + case TrackType::kTrackTypeAudio: { + return kTrackRendererTrackTypeAudio; + } + case TrackType::kTrackTypeVideo: { + return kTrackRendererTrackTypeVideo; + } + case TrackType::kTrackTypeSubtitle: { + return kTrackRendererTrackTypeSubtitle; + } + case TrackType::kTrackTypeMax: { + return kTrackRendererTrackTypeMax; + } + default: + LOG_ERROR("unknown tracktype"); + return kTrackRendererTrackTypeMax; + } +} + +TrackRendererTrackType ConvertToTrackRendererTrackTypeFromStreamType( + const StreamType& type) { + switch (type) { + case StreamType::kAudio: { + return kTrackRendererTrackTypeAudio; + } + case StreamType::kVideo: { + return kTrackRendererTrackTypeVideo; + } + case StreamType::kMax: { + return kTrackRendererTrackTypeMax; + } + default: + LOG_ERROR("unknown steamtype"); + return kTrackRendererTrackTypeMax; + } +} + +#ifndef TRACKRENDERER_FEATURE_DEPRECATE_SUBTITLE_CB +boost::any SetSubtitleAttrValue(const TrackRendererSubtitleAttr& attr) { + boost::any any_value; + switch (attr.type) { + case kTrackRendererSubtitleAttrTypeRegionXPos: // fall through + case kTrackRendererSubtitleAttrTypeRegionYPos: // fall through + case kTrackRendererSubtitleAttrTypeRegionWidth: // fall through + case kTrackRendererSubtitleAttrTypeRegionHeight: // fall through + case kTrackRendererSubtitleAttrTypeWindowXPadding: // fall through + case kTrackRendererSubtitleAttrTypeWindowYPadding: // fall through + case kTrackRendererSubtitleAttrTypeWindowOpacity: // fall through + case kTrackRendererSubtitleAttrTypeWindowShowBg: // fall through + case kTrackRendererSubtitleAttrTypeFontSize: // fall through + case kTrackRendererSubtitleAttrTypeFontOpacity: // fall through + case kTrackRendererSubtitleAttrTypeFontBgOpacity: // fall through + case kTrackRendererSubtitleAttrTypeWebvttCueLine: // fall through + case kTrackRendererSubtitleAttrTypeWebvttCueSize: // fall through + case kTrackRendererSubtitleAttrTypeWebvttCuePosition: // fall through + case kTrackRendererSubtitleAttrTypeWebvttCueVertical: { + if (attr.value.f) any_value = attr.value.f; + break; + } + case kTrackRendererSubtitleAttrTypeWindowLeftMargin: // fall through + case kTrackRendererSubtitleAttrTypeWindowRightMargin: // fall through + case kTrackRendererSubtitleAttrTypeWindowTopMargin: // fall through + case kTrackRendererSubtitleAttrTypeWindowBottomMargin: // fall through + case kTrackRendererSubtitleAttrTypeWindowBgColor: // fall through + case kTrackRendererSubtitleAttrTypeFontWeight: // fall through + case kTrackRendererSubtitleAttrTypeFontStyle: // fall through + case kTrackRendererSubtitleAttrTypeFontColor: // fall through + case kTrackRendererSubtitleAttrTypeFontBgColor: // fall through + case kTrackRendererSubtitleAttrTypeFontTextOutlineColor: // fall through + case kTrackRendererSubtitleAttrTypeFontTextOutlineThickness: // fall + // through + case kTrackRendererSubtitleAttrTypeFontTextOutlineBlurRadius: // fall + // through + case kTrackRendererSubtitleAttrTypeFontVerticalAlign: // fall through + case kTrackRendererSubtitleAttrTypeFontHorizontalAlign: // fall through + case kTrackRendererSubtitleAttrTypeWebvttCueLineNum: // fall through + case kTrackRendererSubtitleAttrTypeWebvttCueLineAlign: // fall through + case kTrackRendererSubtitleAttrTypeWebvttCueAlign: // fall through + case kTrackRendererSubtitleAttrTypeWebvttCuePositionAlign: { + if (attr.value.i32) any_value = attr.value.i32; + break; + } + case kTrackRendererSubtitleAttrTypeFontFamily: // fall through + case kTrackRendererSubtitleAttrTypeRawSubtitle: { + if (attr.value.str) any_value = std::string(attr.value.str); + break; + } + case kTrackRendererSubtitleAttrTypeTimestamp: + case kTrackRendererSubtitleAttrTypeExtsubIndex: + break; + default: + LOG_ERROR("Unknown subtitle attr type"); + } + return any_value; +} +#endif + +BufferStatus ConvertToBufferStatus(const TrackRendererBufferStatus& status) { + switch (status) { + case kTrackRendererBufferStatusUnderrun: { + return BufferStatus::kUnderrun; + } + case kTrackRendererBufferStatusOverrun: { + return BufferStatus::kOverrun; + } + } + LOG_ERROR("Unknown buffern status"); + return BufferStatus::kUnderrun; +} + +TrackRendererCatchUpSpeed ConvertToTrackRendererCatchUpSpeed( + const CatchUpSpeed& level) { + switch (level) { + case CatchUpSpeed::kNone: { + return kTrackRendererCatchUpSpeedNone; + } + case CatchUpSpeed::kSlow: { + return kTrackRendererCatchUpSpeedSlow; + } + case CatchUpSpeed::kNormal: { + return kTrackRendererCatchUpSpeedNormal; + } + case CatchUpSpeed::kFast: { + return kTrackRendererCatchUpSpeedFast; + } + } + LOG_ERROR("Unknown catch up speed"); + return kTrackRendererCatchUpSpeedNone; +} + +LatencyStatus ConvertToLatencyStatus(const TrackRendererLatencyStatus& status) { + switch (status) { + case kTrackRendererLatencyStatusLow: { + return LatencyStatus::kLow; + } + case kTrackRendererLatencyStatusMid: { + return LatencyStatus::kMid; + } + case kTrackRendererLatencyStatusHigh: { + return LatencyStatus::kHigh; + } + } + LOG_ERROR("Unknown status"); + return LatencyStatus::kLow; +} + +AudioEasingType ConvertToAudioEasingType( + const TrackRendererAudioEasingType& type) { + switch (type) { + case TrackRendererAudioEasingType::kTrackRendererAudioEasingLinear: { + return AudioEasingType::kAudioEasingLinear; + } + case TrackRendererAudioEasingType::kTrackRendererAudioEasingIncubic: { + return AudioEasingType::kAudioEasingIncubic; + } + case TrackRendererAudioEasingType::kTrackRendererAudioEasingOutcubic: { + return AudioEasingType::kAudioEasingOutcubic; + } + default: + LOG_ERROR("Unknown audio easing type"); + return AudioEasingType::kAudioEasingNone; + } +} + +TrackRendererAudioEasingType ConvertToTrackRendererAudioEasingType( + const AudioEasingType& type) { + switch (type) { + case AudioEasingType::kAudioEasingLinear: { + return TrackRendererAudioEasingType::kTrackRendererAudioEasingLinear; + } + case AudioEasingType::kAudioEasingIncubic: { + return TrackRendererAudioEasingType::kTrackRendererAudioEasingIncubic; + } + case AudioEasingType::kAudioEasingOutcubic: { + return TrackRendererAudioEasingType::kTrackRendererAudioEasingOutcubic; + } + default: + LOG_ERROR("Unknown audio easing type"); + return TrackRendererAudioEasingType::kTrackRendererAudioEasingNone; + } +} + +bool ConvertToTrackRendererRscType(const RscType& typevalue, + TrackRendererRscType* type) { + switch (typevalue) { + case RscType::kVideoRenderer: { + *type = kTrackRendererRscTypeVideoRenderer; + return true; + } + default: + LOG_ERROR("unknown resource type"); + return false; + } +} + +bool ConvertToTrackRendererAdvPictureQualityType( + const AdvPictureQualityType& typevalue, + TrackRendererAdvPictureQualityType* type) { + switch (typevalue) { + case AdvPictureQualityType::kVideoCall: { + *type = kTrackRendererAdvPictureQualityTypeVideoCall; + return true; + } + case AdvPictureQualityType::kUsbCamera: { + *type = kTrackRendererAdvPictureQualityTypeUsbCamera; + return true; + } + default: + LOG_ERROR("unknown resource type"); + return false; + } +} + +bool ConvertToTrackRendererRscAllocPolicy(const RscAllocPolicy& policyvalue, + TrackRendererRscAllocPolicy* policy) { + switch (policyvalue) { + case RscAllocPolicy::kRscAllocExclusive: { + *policy = kTrackRendererRscAllocExclusive; + return true; + } + case RscAllocPolicy::kRscAllocConditional: { + *policy = kTrackRendererRscAllocConditional; + return true; + } + default: + LOG_ERROR("unknown policy"); + return false; + } +} + +} // namespace adapter_utils + +} // namespace plusplayer diff --git a/src/plusplayer-core/src/videoframetypestrategy.cpp b/src/plusplayer-core/src/videoframetypestrategy.cpp new file mode 100755 index 0000000..f923ed8 --- /dev/null +++ b/src/plusplayer-core/src/videoframetypestrategy.cpp @@ -0,0 +1,22 @@ +#include "core/videoframetypestrategy.h" + +#include +#include + +#include "core/trackrendereradapter_utils.h" + +namespace plusplayer { +DefaultVideoFrameTypeStrategy::DefaultVideoFrameTypeStrategy( + const DecodedVideoFrameBufferType type) + : type_(type) {} + +void DefaultVideoFrameTypeStrategy::SetType(TrackRendererHandle handle) { + trackrenderer_set_video_frame_buffer_type( + handle, adapter_utils::ConvertToVideoFrameBufferType(type_)); +} + +void RawVideoFrameTypeStrategy::SetType(TrackRendererHandle handle) { + trackrenderer_set_video_frame_buffer_type_ext( + handle, kTrackRendererDecodedVideoFrameBufferExtRaw); +} +} // namespace plusplayer \ No newline at end of file diff --git a/tomato/tc/TCList.dat b/tomato/tc/TCList.dat new file mode 100755 index 0000000..568f446 --- /dev/null +++ b/tomato/tc/TCList.dat @@ -0,0 +1 @@ +unit_test/ut_esplusplayer_all.xml \ No newline at end of file diff --git a/tomato/tc/testfarm_script.xml b/tomato/tc/testfarm_script.xml new file mode 100755 index 0000000..517b656 --- /dev/null +++ b/tomato/tc/testfarm_script.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/tomato/tc/unit_test/ut_esplusplayer_all.xml b/tomato/tc/unit_test/ut_esplusplayer_all.xml new file mode 100755 index 0000000..afcb07f --- /dev/null +++ b/tomato/tc/unit_test/ut_esplusplayer_all.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/ut/CMakeLists.txt b/ut/CMakeLists.txt new file mode 100755 index 0000000..3bc02dc --- /dev/null +++ b/ut/CMakeLists.txt @@ -0,0 +1,100 @@ +SET(fw_name "${PROJECT_NAME}_ut") + +SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin) +SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib) + +SET(${fw_name}_CXXFLAGS "-Wall -Werror -std=c++11 -pthread -fPIE -Wl,-z,relro -fstack-protector -fno-delete-null-pointer-checks -DEFL_BETA_API_SUPPORT") + +SET(${fw_name}_LDFLAGS) + +IF(${PRODUCT_TYPE_AUDIO} STREQUAL "no") +SET(ADD_LIBS + "espplayer-core" + "trackrenderer" + "esplusplayer" + "mixer" + "gstvideo-1.0" +) +ELSE(${PRODUCT_TYPE_AUDIO} STREQUAL "no") +SET(ADD_LIBS + "espplayer-core" + "trackrenderer" + "esplusplayer" + "gstvideo-1.0" +) +ENDIF(${PRODUCT_TYPE_AUDIO} STREQUAL "no") + +SET(dependents "gstreamer-1.0 glib-2.0 gstreamer-plugins-base-1.0 gstreamer-app-1.0 dlog gtest_gmock" + "boost" + "tv-resource-information tv-resource-manager libresourced appcore-efl elementary ecore evas ecore-wl2" + "capi-media-player" + "video-capture libturbojpeg libjpeg opencv" + "audio-control" + ) + +INCLUDE(FindPkgConfig) +IF(CMAKE_SYSTEM_PROCESSOR STREQUAL armv7l) +pkg_check_modules(${fw_name} REQUIRED ${dependents}) +ELSE(CMAKE_SYSTEM_PROCESSOR STREQUAL armv7l) +pkg_check_modules(${fw_name} REQUIRED ${dependents}) +ENDIF(CMAKE_SYSTEM_PROCESSOR STREQUAL armv7l) + +FOREACH(flag ${${fw_name}_CFLAGS}) +SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} ${flag}") +ENDFOREACH(flag) +FOREACH(flag ${${fw_name}_CXXFLAGS}) +SET(EXTRA_CXXFLAGS "${EXTRA_CXXFLAGS} ${EXTRA_CFLAGS} ${flag}") +ENDFOREACH(flag) + +SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${EXTRA_CFLAGS}") +SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${EXTRA_CXXFLAGS}") + +INCLUDE_DIRECTORIES( + ${PROJECT_SOURCE_DIR}/ut/include + ${PROJECT_SOURCE_DIR}/src + ${PROJECT_SOURCE_DIR}/include + ${PROJECT_SOURCE_DIR} + ${PROJECT_SOURCE_DIR}/src/plusplayer-core/include_internal + ${PROJECT_SOURCE_DIR}/src/esplusplayer/include_internal + ${PROJECT_SOURCE_DIR}/src/mixer/include_internal +) + +FILE(GLOB UT_SRC + src/plusplayer/*.cpp + src/ut_main.cpp +) + +IF(${PRODUCT_TYPE_AUDIO} STREQUAL "no") +SET(UT_MIXER_SRC + src/mixer/constant.cpp + src/mixer/matcher.cpp + src/mixer/ut_mixer_capi.cpp + src/mixer/ut_mixer_espp_capi.cpp + src/mixer/ut_mixer.cpp + src/mixer/ut_mixerticket.cpp +# src/mixer/ut_mixerscenario.cpp + src/mixer/ut_espp_mixerscenario.cpp + src/mixer/ut_mixedframe.cpp + src/mixer/ut_renderer.cpp + src/mixer/ut_tizenbuffermgr.cpp + src/mixer/ut_tizenbufferobj.cpp + src/mixer/ut_videoplane.cpp +) +#ADD_EXECUTABLE(${fw_name} ${UT_SRC} ${UT_MIXER_SRC}) +#ELSE(${PRODUCT_TYPE_AUDIO} STREQUAL "no") +#ADD_EXECUTABLE(${fw_name} ${UT_SRC}) +ENDIF(${PRODUCT_TYPE_AUDIO} STREQUAL "no") +ADD_EXECUTABLE(${fw_name} ${UT_SRC}) +LINK_DIRECTORIES(${LIB_INSTALL_DIR}) + +TARGET_LINK_LIBRARIES(${fw_name} + ${CMAKE_THREAD_LIBS_INIT} + ${ADD_LIBS} + ${${fw_name}_LDFLAGS} + "-pie" +) + +INSTALL( + TARGETS ${fw_name} + DESTINATION bin +) diff --git a/ut/README.md b/ut/README.md new file mode 100755 index 0000000..0fa6c9e --- /dev/null +++ b/ut/README.md @@ -0,0 +1,127 @@ +**GTest guide** +=============== + For unit test for plusplayer + +--- +### Reference +- + +## Assertion +* ASSERT_* : 실패시 해당 테스트를 바로 종료
+* EXPECT_* : 실패하여도 테스트 계속 진행
+ +#### Basic Assertions ### + +These assertions do basic true/false condition testing. + +| **Fatal assertion** | **Nonfatal assertion** | **Verifies** | +|:--------------------|:-----------------------|:-------------| +| `ASSERT_TRUE(`_condition_`)`; | `EXPECT_TRUE(`_condition_`)`; | _condition_ is true | +| `ASSERT_FALSE(`_condition_`)`; | `EXPECT_FALSE(`_condition_`)`; | _condition_ is false | + +#### Binary Comparison ### + +This section describes assertions that compare two values. + +| **Fatal assertion** | **Nonfatal assertion** | **Verifies** | +|:--------------------|:-----------------------|:-------------| +|`ASSERT_EQ(`_val1_`, `_val2_`);`|`EXPECT_EQ(`_val1_`, `_val2_`);`| _val1_ `==` _val2_ | +|`ASSERT_NE(`_val1_`, `_val2_`);`|`EXPECT_NE(`_val1_`, `_val2_`);`| _val1_ `!=` _val2_ | +|`ASSERT_LT(`_val1_`, `_val2_`);`|`EXPECT_LT(`_val1_`, `_val2_`);`| _val1_ `<` _val2_ | +|`ASSERT_LE(`_val1_`, `_val2_`);`|`EXPECT_LE(`_val1_`, `_val2_`);`| _val1_ `<=` _val2_ | +|`ASSERT_GT(`_val1_`, `_val2_`);`|`EXPECT_GT(`_val1_`, `_val2_`);`| _val1_ `>` _val2_ | +|`ASSERT_GE(`_val1_`, `_val2_`);`|`EXPECT_GE(`_val1_`, `_val2_`);`| _val1_ `>=` _val2_ | + + +#### String Comparison ### + +The assertions in this group compare two **C strings**.
+If you want to compare two `string` objects, use `EXPECT_EQ`, `EXPECT_NE`, and etc instead. + +| **Fatal assertion** | **Nonfatal assertion** | **Verifies** | +|:--------------------|:-----------------------|:-------------| +| `ASSERT_STREQ(`_str1_`, `_str2_`);` | `EXPECT_STREQ(`_str1_`, `_str_2`);` | the two C strings have the same content | +| `ASSERT_STRNE(`_str1_`, `_str2_`);` | `EXPECT_STRNE(`_str1_`, `_str2_`);` | the two C strings have different content | +| `ASSERT_STRCASEEQ(`_str1_`, `_str2_`);`| `EXPECT_STRCASEEQ(`_str1_`, `_str2_`);` | the two C strings have the same content, ignoring case | +| `ASSERT_STRCASENE(`_str1_`, `_str2_`);`| `EXPECT_STRCASENE(`_str1_`, `_str2_`);` | the two C strings have different content, ignoring case | + +## Text Fixtures : Using the Same Data Configuration for Multiple Tests ## + +사용자가 유사한 data를 사용해서 하나 이상의 test를 작성한다면, test fixture를 사용할 수 있다. 이 test fixture를 사용한다는 것은 여러개의 다양한 test를 작성하는 과정에서 같은 object의 configuration을 재사용한다는 것을 의미한다. + +Fixture를 작성할 때에는 아래의 내용대로 수행하면 된다. + +1. ::testing::Test 로부터 class를 derive한다. Sub-class 에서 fixture member에 접근해야 하기 때문에 protected 혹은 public 으로 작성해야 한다. +2. Class 내부에서 사용자가 원하는대로 object들을 선언해 사용한다. +3. 필요하다면, 생성자나 SetUp() function을 작성해둔다. +4. 생성자나 SetUp() function을 정의해서 사용하고 있다면, 해당 function에서 사용했던 resource를 반환하기 위해 소멸자나 TearDown() function을 작성한다. +5. Subroutine 들을 작성한다. + +Fixture를 사용하기 위해서는 TEST() 대신에 TEST_F()를 사용해야만 한다. +TEST()에서는 첫번째 argument가 testcase의 이름이었지만 TEST_F()를 사용할 때는 첫번째 argument로 test fixture class의 이름을 사용해야만 한다. + +**Fixture class 기본 구현 Form** +* 관례에 따라 테스트할 클래스가 Foo라면 이름을 FooTest라고 하는게 좋다. +~~~ +class PlusPlayerTest : public ::testing::Test { +public: + PlusPlayerTest(std::string url) + : plusplayer_(nullptr), url_(url) + { + } + + void SetUp() override + { + plusplayer_ = new PlusPlayer(); + create(url_); + } + + void TearDown() override + { + destory(plusplayer_); + } + +private: + std::string url_; + PlusPlayer* plusplayer_; + +} +~~~ + +**실행 순서** +1. 모든 구글 테스트 플래그 상태를 저장한다. +2. 첫 번째 테스트에 대해 테스트 fixture 객체를 생성한다. +3. 만든 객체를 SetUp()에서 초기화한다. +4. 픽스처 객체에 대해 테스트를 실행한다. +5. TearDown()에서 해당 픽스처를 정리한다. +6. 해당 픽스처를 삭제한다. +7. 모든 구글 테스트 플래그 상태를 복원한다. +8. 모든 테스트를 마칠 때까지 다음 테스트에 대해 위 과정을 반복한다. + +--- + +## Arguments +reference + +1. test selection + * --gtest_list_tests
+ > 테스트할 항목을 보여준다. (테스트 실시 X) + * --gtest_also_run_disabled_tests
+ > DISABLED_ 로 막아둔 test case 를 일시적으로 실행 + * --gtest_filter
+ > 특정 case 들만 실행 가능
+ Ex) --gtest_filter="*.create*" : 모든 TC중에서 create 로 시작하는 모든 TC 실행.
+ +2. test Execution + * --gtest_repeat
+ > test 반복 가능. -1일 경우 무한히 반복
+ --gtest_break_on_failure와 함께 사용하면 실패할 경우 멈춤. + * --gtest_shuffle
+ > 무작위로 실행 가능 (test case 간 dependency 가 없어야 하기 때문이다)
+ gtest는 기본적으로 현재시간을 랜덤함수의 시드값으로 사용하나, --gtest_random_seed로 조절가능하다 + * --gtest_random_seed + > 1 ~ 99999까지의 값을 --gtest_shuffle에서 사용할 랜덤함수의 시드로 사용. + +--- +## Reference + * Gtest Primer(EN)
diff --git a/ut/cpplint.py b/ut/cpplint.py new file mode 100755 index 0000000..4b9d830 --- /dev/null +++ b/ut/cpplint.py @@ -0,0 +1,6123 @@ +#!/usr/bin/env python +# +# Copyright (c) 2009 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""Does google-lint on c++ files. + +The goal of this script is to identify places in the code that *may* +be in non-compliance with google style. It does not attempt to fix +up these problems -- the point is to educate. It does also not +attempt to find all problems, or to ensure that everything it does +find is legitimately a problem. + +In particular, we can get very confused by /* and // inside strings! +We do a small hack, which is to ignore //'s with "'s after them on the +same line, but it is far from perfect (in either direction). +""" + +import codecs +import copy +import getopt +import math # for log +import os +import re +import sre_compile +import string +import sys +import unicodedata + + +_USAGE = """ +Syntax: cpplint.py [--verbose=#] [--output=vs7] [--filter=-x,+y,...] + [--counting=total|toplevel|detailed] [--root=subdir] + [--linelength=digits] [--headers=x,y,...] + [file] ... + + The style guidelines this tries to follow are those in + https://google-styleguide.googlecode.com/svn/trunk/cppguide.xml + + Every problem is given a confidence score from 1-5, with 5 meaning we are + certain of the problem, and 1 meaning it could be a legitimate construct. + This will miss some errors, and is not a substitute for a code review. + + To suppress false-positive errors of a certain category, add a + 'NOLINT(category)' comment to the line. NOLINT or NOLINT(*) + suppresses errors of all categories on that line. + + The files passed in will be linted; at least one file must be provided. + Default linted extensions are .cc, .cpp, .cu, .cuh and .h. Change the + extensions with the --extensions flag. + + Flags: + + output=vs7 + By default, the output is formatted to ease emacs parsing. Visual Studio + compatible output (vs7) may also be used. Other formats are unsupported. + + verbose=# + Specify a number 0-5 to restrict errors to certain verbosity levels. + + filter=-x,+y,... + Specify a comma-separated list of category-filters to apply: only + error messages whose category names pass the filters will be printed. + (Category names are printed with the message and look like + "[whitespace/indent]".) Filters are evaluated left to right. + "-FOO" and "FOO" means "do not print categories that start with FOO". + "+FOO" means "do print categories that start with FOO". + + Examples: --filter=-whitespace,+whitespace/braces + --filter=whitespace,runtime/printf,+runtime/printf_format + --filter=-,+build/include_what_you_use + + To see a list of all the categories used in cpplint, pass no arg: + --filter= + + counting=total|toplevel|detailed + The total number of errors found is always printed. If + 'toplevel' is provided, then the count of errors in each of + the top-level categories like 'build' and 'whitespace' will + also be printed. If 'detailed' is provided, then a count + is provided for each category like 'build/class'. + + root=subdir + The root directory used for deriving header guard CPP variable. + By default, the header guard CPP variable is calculated as the relative + path to the directory that contains .git, .hg, or .svn. When this flag + is specified, the relative path is calculated from the specified + directory. If the specified directory does not exist, this flag is + ignored. + + Examples: + Assuming that src/.git exists, the header guard CPP variables for + src/chrome/browser/ui/browser.h are: + + No flag => CHROME_BROWSER_UI_BROWSER_H_ + --root=chrome => BROWSER_UI_BROWSER_H_ + --root=chrome/browser => UI_BROWSER_H_ + + linelength=digits + This is the allowed line length for the project. The default value is + 80 characters. + + Examples: + --linelength=120 + + extensions=extension,extension,... + The allowed file extensions that cpplint will check + + Examples: + --extensions=hpp,cpp + + headers=x,y,... + The header extensions that cpplint will treat as .h in checks. Values are + automatically added to --extensions list. + + Examples: + --headers=hpp,hxx + --headers=hpp + + cpplint.py supports per-directory configurations specified in CPPLINT.cfg + files. CPPLINT.cfg file can contain a number of key=value pairs. + Currently the following options are supported: + + set noparent + filter=+filter1,-filter2,... + exclude_files=regex + linelength=80 + root=subdir + headers=x,y,... + + "set noparent" option prevents cpplint from traversing directory tree + upwards looking for more .cfg files in parent directories. This option + is usually placed in the top-level project directory. + + The "filter" option is similar in function to --filter flag. It specifies + message filters in addition to the |_DEFAULT_FILTERS| and those specified + through --filter command-line flag. + + "exclude_files" allows to specify a regular expression to be matched against + a file name. If the expression matches, the file is skipped and not run + through liner. + + "linelength" allows to specify the allowed line length for the project. + + The "root" option is similar in function to the --root flag (see example + above). + + The "headers" option is similar in function to the --headers flag + (see example above). + + CPPLINT.cfg has an effect on files in the same directory and all + sub-directories, unless overridden by a nested configuration file. + + Example file: + filter=-build/include_order,+build/include_alpha + exclude_files=.*\.cc + + The above example disables build/include_order warning and enables + build/include_alpha as well as excludes all .cc from being + processed by linter, in the current directory (where the .cfg + file is located) and all sub-directories. +""" + +# We categorize each error message we print. Here are the categories. +# We want an explicit list so we can list them all in cpplint --filter=. +# If you add a new error message with a new category, add it to the list +# here! cpplint_unittest.py should tell you if you forget to do this. +_ERROR_CATEGORIES = [ + 'build/class', + 'build/c++11', + 'build/c++14', + 'build/c++tr1', + 'build/deprecated', + 'build/endif_comment', + 'build/explicit_make_pair', + 'build/forward_decl', + 'build/header_guard', + 'build/include', + 'build/include_alpha', + 'build/include_order', + 'build/include_what_you_use', + 'build/namespaces', + 'build/printf_format', + 'build/storage_class', + 'legal/copyright', + 'readability/alt_tokens', + 'readability/braces', + 'readability/casting', + 'readability/check', + 'readability/constructors', + 'readability/fn_size', + 'readability/inheritance', + 'readability/multiline_comment', + 'readability/multiline_string', + 'readability/namespace', + 'readability/nolint', + 'readability/nul', + 'readability/strings', + 'readability/todo', + 'readability/utf8', + 'runtime/arrays', + 'runtime/casting', + 'runtime/explicit', + 'runtime/int', + 'runtime/init', + 'runtime/invalid_increment', + 'runtime/member_string_references', + 'runtime/memset', + 'runtime/indentation_namespace', + 'runtime/operator', + 'runtime/printf', + 'runtime/printf_format', + 'runtime/references', + 'runtime/string', + 'runtime/threadsafe_fn', + 'runtime/vlog', + 'whitespace/blank_line', + 'whitespace/braces', + 'whitespace/comma', + 'whitespace/comments', + 'whitespace/empty_conditional_body', + 'whitespace/empty_if_body', + 'whitespace/empty_loop_body', + 'whitespace/end_of_line', + 'whitespace/ending_newline', + 'whitespace/forcolon', + 'whitespace/indent', + 'whitespace/line_length', + 'whitespace/newline', + 'whitespace/operators', + 'whitespace/parens', + 'whitespace/semicolon', + 'whitespace/tab', + 'whitespace/todo', + ] + +# These error categories are no longer enforced by cpplint, but for backwards- +# compatibility they may still appear in NOLINT comments. +_LEGACY_ERROR_CATEGORIES = [ + 'readability/streams', + 'readability/function', + ] + +# The default state of the category filter. This is overridden by the --filter= +# flag. By default all errors are on, so only add here categories that should be +# off by default (i.e., categories that must be enabled by the --filter= flags). +# All entries here should start with a '-' or '+', as in the --filter= flag. +_DEFAULT_FILTERS = ['-build/include_alpha'] + +# The default list of categories suppressed for C (not C++) files. +_DEFAULT_C_SUPPRESSED_CATEGORIES = [ + 'readability/casting', + ] + +# The default list of categories suppressed for Linux Kernel files. +_DEFAULT_KERNEL_SUPPRESSED_CATEGORIES = [ + 'whitespace/tab', + ] + +# We used to check for high-bit characters, but after much discussion we +# decided those were OK, as long as they were in UTF-8 and didn't represent +# hard-coded international strings, which belong in a separate i18n file. + +# C++ headers +_CPP_HEADERS = frozenset([ + # Legacy + 'algobase.h', + 'algo.h', + 'alloc.h', + 'builtinbuf.h', + 'bvector.h', + 'complex.h', + 'defalloc.h', + 'deque.h', + 'editbuf.h', + 'fstream.h', + 'function.h', + 'hash_map', + 'hash_map.h', + 'hash_set', + 'hash_set.h', + 'hashtable.h', + 'heap.h', + 'indstream.h', + 'iomanip.h', + 'iostream.h', + 'istream.h', + 'iterator.h', + 'list.h', + 'map.h', + 'multimap.h', + 'multiset.h', + 'ostream.h', + 'pair.h', + 'parsestream.h', + 'pfstream.h', + 'procbuf.h', + 'pthread_alloc', + 'pthread_alloc.h', + 'rope', + 'rope.h', + 'ropeimpl.h', + 'set.h', + 'slist', + 'slist.h', + 'stack.h', + 'stdiostream.h', + 'stl_alloc.h', + 'stl_relops.h', + 'streambuf.h', + 'stream.h', + 'strfile.h', + 'strstream.h', + 'tempbuf.h', + 'tree.h', + 'type_traits.h', + 'vector.h', + # 17.6.1.2 C++ library headers + 'algorithm', + 'array', + 'atomic', + 'bitset', + 'chrono', + 'codecvt', + 'complex', + 'condition_variable', + 'deque', + 'exception', + 'forward_list', + 'fstream', + 'functional', + 'future', + 'initializer_list', + 'iomanip', + 'ios', + 'iosfwd', + 'iostream', + 'istream', + 'iterator', + 'limits', + 'list', + 'locale', + 'map', + 'memory', + 'mutex', + 'new', + 'numeric', + 'ostream', + 'queue', + 'random', + 'ratio', + 'regex', + 'scoped_allocator', + 'set', + 'sstream', + 'stack', + 'stdexcept', + 'streambuf', + 'string', + 'strstream', + 'system_error', + 'thread', + 'tuple', + 'typeindex', + 'typeinfo', + 'type_traits', + 'unordered_map', + 'unordered_set', + 'utility', + 'valarray', + 'vector', + # 17.6.1.2 C++ headers for C library facilities + 'cassert', + 'ccomplex', + 'cctype', + 'cerrno', + 'cfenv', + 'cfloat', + 'cinttypes', + 'ciso646', + 'climits', + 'clocale', + 'cmath', + 'csetjmp', + 'csignal', + 'cstdalign', + 'cstdarg', + 'cstdbool', + 'cstddef', + 'cstdint', + 'cstdio', + 'cstdlib', + 'cstring', + 'ctgmath', + 'ctime', + 'cuchar', + 'cwchar', + 'cwctype', + ]) + +# Type names +_TYPES = re.compile( + r'^(?:' + # [dcl.type.simple] + r'(char(16_t|32_t)?)|wchar_t|' + r'bool|short|int|long|signed|unsigned|float|double|' + # [support.types] + r'(ptrdiff_t|size_t|max_align_t|nullptr_t)|' + # [cstdint.syn] + r'(u?int(_fast|_least)?(8|16|32|64)_t)|' + r'(u?int(max|ptr)_t)|' + r')$') + + +# These headers are excluded from [build/include] and [build/include_order] +# checks: +# - Anything not following google file name conventions (containing an +# uppercase character, such as Python.h or nsStringAPI.h, for example). +# - Lua headers. +_THIRD_PARTY_HEADERS_PATTERN = re.compile( + r'^(?:[^/]*[A-Z][^/]*\.h|lua\.h|lauxlib\.h|lualib\.h)$') + +# Pattern for matching FileInfo.BaseName() against test file name +_TEST_FILE_SUFFIX = r'(_test|_unittest|_regtest)$' + +# Pattern that matches only complete whitespace, possibly across multiple lines. +_EMPTY_CONDITIONAL_BODY_PATTERN = re.compile(r'^\s*$', re.DOTALL) + +# Assertion macros. These are defined in base/logging.h and +# testing/base/public/gunit.h. +_CHECK_MACROS = [ + 'DCHECK', 'CHECK', + 'EXPECT_TRUE', 'ASSERT_TRUE', + 'EXPECT_FALSE', 'ASSERT_FALSE', + ] + +# Replacement macros for CHECK/DCHECK/EXPECT_TRUE/EXPECT_FALSE +_CHECK_REPLACEMENT = dict([(m, {}) for m in _CHECK_MACROS]) + +for op, replacement in [('==', 'EQ'), ('!=', 'NE'), + ('>=', 'GE'), ('>', 'GT'), + ('<=', 'LE'), ('<', 'LT')]: + _CHECK_REPLACEMENT['DCHECK'][op] = 'DCHECK_%s' % replacement + _CHECK_REPLACEMENT['CHECK'][op] = 'CHECK_%s' % replacement + _CHECK_REPLACEMENT['EXPECT_TRUE'][op] = 'EXPECT_%s' % replacement + _CHECK_REPLACEMENT['ASSERT_TRUE'][op] = 'ASSERT_%s' % replacement + +for op, inv_replacement in [('==', 'NE'), ('!=', 'EQ'), + ('>=', 'LT'), ('>', 'LE'), + ('<=', 'GT'), ('<', 'GE')]: + _CHECK_REPLACEMENT['EXPECT_FALSE'][op] = 'EXPECT_%s' % inv_replacement + _CHECK_REPLACEMENT['ASSERT_FALSE'][op] = 'ASSERT_%s' % inv_replacement + +# Alternative tokens and their replacements. For full list, see section 2.5 +# Alternative tokens [lex.digraph] in the C++ standard. +# +# Digraphs (such as '%:') are not included here since it's a mess to +# match those on a word boundary. +_ALT_TOKEN_REPLACEMENT = { + 'and': '&&', + 'bitor': '|', + 'or': '||', + 'xor': '^', + 'compl': '~', + 'bitand': '&', + 'and_eq': '&=', + 'or_eq': '|=', + 'xor_eq': '^=', + 'not': '!', + 'not_eq': '!=' + } + +# Compile regular expression that matches all the above keywords. The "[ =()]" +# bit is meant to avoid matching these keywords outside of boolean expressions. +# +# False positives include C-style multi-line comments and multi-line strings +# but those have always been troublesome for cpplint. +_ALT_TOKEN_REPLACEMENT_PATTERN = re.compile( + r'[ =()](' + ('|'.join(_ALT_TOKEN_REPLACEMENT.keys())) + r')(?=[ (]|$)') + + +# These constants define types of headers for use with +# _IncludeState.CheckNextIncludeOrder(). +_C_SYS_HEADER = 1 +_CPP_SYS_HEADER = 2 +_LIKELY_MY_HEADER = 3 +_POSSIBLE_MY_HEADER = 4 +_OTHER_HEADER = 5 + +# These constants define the current inline assembly state +_NO_ASM = 0 # Outside of inline assembly block +_INSIDE_ASM = 1 # Inside inline assembly block +_END_ASM = 2 # Last line of inline assembly block +_BLOCK_ASM = 3 # The whole block is an inline assembly block + +# Match start of assembly blocks +_MATCH_ASM = re.compile(r'^\s*(?:asm|_asm|__asm|__asm__)' + r'(?:\s+(volatile|__volatile__))?' + r'\s*[{(]') + +# Match strings that indicate we're working on a C (not C++) file. +_SEARCH_C_FILE = re.compile(r'\b(?:LINT_C_FILE|' + r'vim?:\s*.*(\s*|:)filetype=c(\s*|:|$))') + +# Match string that indicates we're working on a Linux Kernel file. +_SEARCH_KERNEL_FILE = re.compile(r'\b(?:LINT_KERNEL_FILE)') + +_regexp_compile_cache = {} + +# {str, set(int)}: a map from error categories to sets of linenumbers +# on which those errors are expected and should be suppressed. +_error_suppressions = {} + +# The root directory used for deriving header guard CPP variable. +# This is set by --root flag. +_root = None + +# The allowed line length of files. +# This is set by --linelength flag. +_line_length = 80 + +# The allowed extensions for file names +# This is set by --extensions flag. +_valid_extensions = set(['cc', 'h', 'cpp', 'cu', 'cuh']) + +# Treat all headers starting with 'h' equally: .h, .hpp, .hxx etc. +# This is set by --headers flag. +_hpp_headers = set(['h']) + +# {str, bool}: a map from error categories to booleans which indicate if the +# category should be suppressed for every line. +_global_error_suppressions = {} + +def ProcessHppHeadersOption(val): + global _hpp_headers + try: + _hpp_headers = set(val.split(',')) + # Automatically append to extensions list so it does not have to be set 2 times + _valid_extensions.update(_hpp_headers) + except ValueError: + PrintUsage('Header extensions must be comma seperated list.') + +def IsHeaderExtension(file_extension): + return file_extension in _hpp_headers + +def ParseNolintSuppressions(filename, raw_line, linenum, error): + """Updates the global list of line error-suppressions. + + Parses any NOLINT comments on the current line, updating the global + error_suppressions store. Reports an error if the NOLINT comment + was malformed. + + Args: + filename: str, the name of the input file. + raw_line: str, the line of input text, with comments. + linenum: int, the number of the current line. + error: function, an error handler. + """ + matched = Search(r'\bNOLINT(NEXTLINE)?\b(\([^)]+\))?', raw_line) + if matched: + if matched.group(1): + suppressed_line = linenum + 1 + else: + suppressed_line = linenum + category = matched.group(2) + if category in (None, '(*)'): # => "suppress all" + _error_suppressions.setdefault(None, set()).add(suppressed_line) + else: + if category.startswith('(') and category.endswith(')'): + category = category[1:-1] + if category in _ERROR_CATEGORIES: + _error_suppressions.setdefault(category, set()).add(suppressed_line) + elif category not in _LEGACY_ERROR_CATEGORIES: + error(filename, linenum, 'readability/nolint', 5, + 'Unknown NOLINT error category: %s' % category) + + +def ProcessGlobalSuppresions(lines): + """Updates the list of global error suppressions. + + Parses any lint directives in the file that have global effect. + + Args: + lines: An array of strings, each representing a line of the file, with the + last element being empty if the file is terminated with a newline. + """ + for line in lines: + if _SEARCH_C_FILE.search(line): + for category in _DEFAULT_C_SUPPRESSED_CATEGORIES: + _global_error_suppressions[category] = True + if _SEARCH_KERNEL_FILE.search(line): + for category in _DEFAULT_KERNEL_SUPPRESSED_CATEGORIES: + _global_error_suppressions[category] = True + + +def ResetNolintSuppressions(): + """Resets the set of NOLINT suppressions to empty.""" + _error_suppressions.clear() + _global_error_suppressions.clear() + + +def IsErrorSuppressedByNolint(category, linenum): + """Returns true if the specified error category is suppressed on this line. + + Consults the global error_suppressions map populated by + ParseNolintSuppressions/ProcessGlobalSuppresions/ResetNolintSuppressions. + + Args: + category: str, the category of the error. + linenum: int, the current line number. + Returns: + bool, True iff the error should be suppressed due to a NOLINT comment or + global suppression. + """ + return (_global_error_suppressions.get(category, False) or + linenum in _error_suppressions.get(category, set()) or + linenum in _error_suppressions.get(None, set())) + + +def Match(pattern, s): + """Matches the string with the pattern, caching the compiled regexp.""" + # The regexp compilation caching is inlined in both Match and Search for + # performance reasons; factoring it out into a separate function turns out + # to be noticeably expensive. + if pattern not in _regexp_compile_cache: + _regexp_compile_cache[pattern] = sre_compile.compile(pattern) + return _regexp_compile_cache[pattern].match(s) + + +def ReplaceAll(pattern, rep, s): + """Replaces instances of pattern in a string with a replacement. + + The compiled regex is kept in a cache shared by Match and Search. + + Args: + pattern: regex pattern + rep: replacement text + s: search string + + Returns: + string with replacements made (or original string if no replacements) + """ + if pattern not in _regexp_compile_cache: + _regexp_compile_cache[pattern] = sre_compile.compile(pattern) + return _regexp_compile_cache[pattern].sub(rep, s) + + +def Search(pattern, s): + """Searches the string for the pattern, caching the compiled regexp.""" + if pattern not in _regexp_compile_cache: + _regexp_compile_cache[pattern] = sre_compile.compile(pattern) + return _regexp_compile_cache[pattern].search(s) + + +def _IsSourceExtension(s): + """File extension (excluding dot) matches a source file extension.""" + return s in ('c', 'cc', 'cpp', 'cxx') + + +class _IncludeState(object): + """Tracks line numbers for includes, and the order in which includes appear. + + include_list contains list of lists of (header, line number) pairs. + It's a lists of lists rather than just one flat list to make it + easier to update across preprocessor boundaries. + + Call CheckNextIncludeOrder() once for each header in the file, passing + in the type constants defined above. Calls in an illegal order will + raise an _IncludeError with an appropriate error message. + + """ + # self._section will move monotonically through this set. If it ever + # needs to move backwards, CheckNextIncludeOrder will raise an error. + _INITIAL_SECTION = 0 + _MY_H_SECTION = 1 + _C_SECTION = 2 + _CPP_SECTION = 3 + _OTHER_H_SECTION = 4 + + _TYPE_NAMES = { + _C_SYS_HEADER: 'C system header', + _CPP_SYS_HEADER: 'C++ system header', + _LIKELY_MY_HEADER: 'header this file implements', + _POSSIBLE_MY_HEADER: 'header this file may implement', + _OTHER_HEADER: 'other header', + } + _SECTION_NAMES = { + _INITIAL_SECTION: "... nothing. (This can't be an error.)", + _MY_H_SECTION: 'a header this file implements', + _C_SECTION: 'C system header', + _CPP_SECTION: 'C++ system header', + _OTHER_H_SECTION: 'other header', + } + + def __init__(self): + self.include_list = [[]] + self.ResetSection('') + + def FindHeader(self, header): + """Check if a header has already been included. + + Args: + header: header to check. + Returns: + Line number of previous occurrence, or -1 if the header has not + been seen before. + """ + for section_list in self.include_list: + for f in section_list: + if f[0] == header: + return f[1] + return -1 + + def ResetSection(self, directive): + """Reset section checking for preprocessor directive. + + Args: + directive: preprocessor directive (e.g. "if", "else"). + """ + # The name of the current section. + self._section = self._INITIAL_SECTION + # The path of last found header. + self._last_header = '' + + # Update list of includes. Note that we never pop from the + # include list. + if directive in ('if', 'ifdef', 'ifndef'): + self.include_list.append([]) + elif directive in ('else', 'elif'): + self.include_list[-1] = [] + + def SetLastHeader(self, header_path): + self._last_header = header_path + + def CanonicalizeAlphabeticalOrder(self, header_path): + """Returns a path canonicalized for alphabetical comparison. + + - replaces "-" with "_" so they both cmp the same. + - removes '-inl' since we don't require them to be after the main header. + - lowercase everything, just in case. + + Args: + header_path: Path to be canonicalized. + + Returns: + Canonicalized path. + """ + return header_path.replace('-inl.h', '.h').replace('-', '_').lower() + + def IsInAlphabeticalOrder(self, clean_lines, linenum, header_path): + """Check if a header is in alphabetical order with the previous header. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + header_path: Canonicalized header to be checked. + + Returns: + Returns true if the header is in alphabetical order. + """ + # If previous section is different from current section, _last_header will + # be reset to empty string, so it's always less than current header. + # + # If previous line was a blank line, assume that the headers are + # intentionally sorted the way they are. + if (self._last_header > header_path and + Match(r'^\s*#\s*include\b', clean_lines.elided[linenum - 1])): + return False + return True + + def CheckNextIncludeOrder(self, header_type): + """Returns a non-empty error message if the next header is out of order. + + This function also updates the internal state to be ready to check + the next include. + + Args: + header_type: One of the _XXX_HEADER constants defined above. + + Returns: + The empty string if the header is in the right order, or an + error message describing what's wrong. + + """ + error_message = ('Found %s after %s' % + (self._TYPE_NAMES[header_type], + self._SECTION_NAMES[self._section])) + + last_section = self._section + + if header_type == _C_SYS_HEADER: + if self._section <= self._C_SECTION: + self._section = self._C_SECTION + else: + self._last_header = '' + return error_message + elif header_type == _CPP_SYS_HEADER: + if self._section <= self._CPP_SECTION: + self._section = self._CPP_SECTION + else: + self._last_header = '' + return error_message + elif header_type == _LIKELY_MY_HEADER: + if self._section <= self._MY_H_SECTION: + self._section = self._MY_H_SECTION + else: + self._section = self._OTHER_H_SECTION + elif header_type == _POSSIBLE_MY_HEADER: + if self._section <= self._MY_H_SECTION: + self._section = self._MY_H_SECTION + else: + # This will always be the fallback because we're not sure + # enough that the header is associated with this file. + self._section = self._OTHER_H_SECTION + else: + assert header_type == _OTHER_HEADER + self._section = self._OTHER_H_SECTION + + if last_section != self._section: + self._last_header = '' + + return '' + + +class _CppLintState(object): + """Maintains module-wide state..""" + + def __init__(self): + self.verbose_level = 1 # global setting. + self.error_count = 0 # global count of reported errors + # filters to apply when emitting error messages + self.filters = _DEFAULT_FILTERS[:] + # backup of filter list. Used to restore the state after each file. + self._filters_backup = self.filters[:] + self.counting = 'total' # In what way are we counting errors? + self.errors_by_category = {} # string to int dict storing error counts + + # output format: + # "emacs" - format that emacs can parse (default) + # "vs7" - format that Microsoft Visual Studio 7 can parse + self.output_format = 'emacs' + + def SetOutputFormat(self, output_format): + """Sets the output format for errors.""" + self.output_format = output_format + + def SetVerboseLevel(self, level): + """Sets the module's verbosity, and returns the previous setting.""" + last_verbose_level = self.verbose_level + self.verbose_level = level + return last_verbose_level + + def SetCountingStyle(self, counting_style): + """Sets the module's counting options.""" + self.counting = counting_style + + def SetFilters(self, filters): + """Sets the error-message filters. + + These filters are applied when deciding whether to emit a given + error message. + + Args: + filters: A string of comma-separated filters (eg "+whitespace/indent"). + Each filter should start with + or -; else we die. + + Raises: + ValueError: The comma-separated filters did not all start with '+' or '-'. + E.g. "-,+whitespace,-whitespace/indent,whitespace/badfilter" + """ + # Default filters always have less priority than the flag ones. + self.filters = _DEFAULT_FILTERS[:] + self.AddFilters(filters) + + def AddFilters(self, filters): + """ Adds more filters to the existing list of error-message filters. """ + for filt in filters.split(','): + clean_filt = filt.strip() + if clean_filt: + self.filters.append(clean_filt) + for filt in self.filters: + if not (filt.startswith('+') or filt.startswith('-')): + raise ValueError('Every filter in --filters must start with + or -' + ' (%s does not)' % filt) + + def BackupFilters(self): + """ Saves the current filter list to backup storage.""" + self._filters_backup = self.filters[:] + + def RestoreFilters(self): + """ Restores filters previously backed up.""" + self.filters = self._filters_backup[:] + + def ResetErrorCounts(self): + """Sets the module's error statistic back to zero.""" + self.error_count = 0 + self.errors_by_category = {} + + def IncrementErrorCount(self, category): + """Bumps the module's error statistic.""" + self.error_count += 1 + if self.counting in ('toplevel', 'detailed'): + if self.counting != 'detailed': + category = category.split('/')[0] + if category not in self.errors_by_category: + self.errors_by_category[category] = 0 + self.errors_by_category[category] += 1 + + def PrintErrorCounts(self): + """Print a summary of errors by category, and the total.""" + for category, count in self.errors_by_category.iteritems(): + sys.stderr.write('Category \'%s\' errors found: %d\n' % + (category, count)) + sys.stdout.write('Total errors found: %d\n' % self.error_count) + +_cpplint_state = _CppLintState() + + +def _OutputFormat(): + """Gets the module's output format.""" + return _cpplint_state.output_format + + +def _SetOutputFormat(output_format): + """Sets the module's output format.""" + _cpplint_state.SetOutputFormat(output_format) + + +def _VerboseLevel(): + """Returns the module's verbosity setting.""" + return _cpplint_state.verbose_level + + +def _SetVerboseLevel(level): + """Sets the module's verbosity, and returns the previous setting.""" + return _cpplint_state.SetVerboseLevel(level) + + +def _SetCountingStyle(level): + """Sets the module's counting options.""" + _cpplint_state.SetCountingStyle(level) + + +def _Filters(): + """Returns the module's list of output filters, as a list.""" + return _cpplint_state.filters + + +def _SetFilters(filters): + """Sets the module's error-message filters. + + These filters are applied when deciding whether to emit a given + error message. + + Args: + filters: A string of comma-separated filters (eg "whitespace/indent"). + Each filter should start with + or -; else we die. + """ + _cpplint_state.SetFilters(filters) + +def _AddFilters(filters): + """Adds more filter overrides. + + Unlike _SetFilters, this function does not reset the current list of filters + available. + + Args: + filters: A string of comma-separated filters (eg "whitespace/indent"). + Each filter should start with + or -; else we die. + """ + _cpplint_state.AddFilters(filters) + +def _BackupFilters(): + """ Saves the current filter list to backup storage.""" + _cpplint_state.BackupFilters() + +def _RestoreFilters(): + """ Restores filters previously backed up.""" + _cpplint_state.RestoreFilters() + +class _FunctionState(object): + """Tracks current function name and the number of lines in its body.""" + + _NORMAL_TRIGGER = 250 # for --v=0, 500 for --v=1, etc. + _TEST_TRIGGER = 400 # about 50% more than _NORMAL_TRIGGER. + + def __init__(self): + self.in_a_function = False + self.lines_in_function = 0 + self.current_function = '' + + def Begin(self, function_name): + """Start analyzing function body. + + Args: + function_name: The name of the function being tracked. + """ + self.in_a_function = True + self.lines_in_function = 0 + self.current_function = function_name + + def Count(self): + """Count line in current function body.""" + if self.in_a_function: + self.lines_in_function += 1 + + def Check(self, error, filename, linenum): + """Report if too many lines in function body. + + Args: + error: The function to call with any errors found. + filename: The name of the current file. + linenum: The number of the line to check. + """ + if not self.in_a_function: + return + + if Match(r'T(EST|est)', self.current_function): + base_trigger = self._TEST_TRIGGER + else: + base_trigger = self._NORMAL_TRIGGER + trigger = base_trigger * 2**_VerboseLevel() + + if self.lines_in_function > trigger: + error_level = int(math.log(self.lines_in_function / base_trigger, 2)) + # 50 => 0, 100 => 1, 200 => 2, 400 => 3, 800 => 4, 1600 => 5, ... + if error_level > 5: + error_level = 5 + error(filename, linenum, 'readability/fn_size', error_level, + 'Small and focused functions are preferred:' + ' %s has %d non-comment lines' + ' (error triggered by exceeding %d lines).' % ( + self.current_function, self.lines_in_function, trigger)) + + def End(self): + """Stop analyzing function body.""" + self.in_a_function = False + + +class _IncludeError(Exception): + """Indicates a problem with the include order in a file.""" + pass + + +class FileInfo(object): + """Provides utility functions for filenames. + + FileInfo provides easy access to the components of a file's path + relative to the project root. + """ + + def __init__(self, filename): + self._filename = filename + + def FullName(self): + """Make Windows paths like Unix.""" + return os.path.abspath(self._filename).replace('\\', '/') + + def RepositoryName(self): + """FullName after removing the local path to the repository. + + If we have a real absolute path name here we can try to do something smart: + detecting the root of the checkout and truncating /path/to/checkout from + the name so that we get header guards that don't include things like + "C:\Documents and Settings\..." or "/home/username/..." in them and thus + people on different computers who have checked the source out to different + locations won't see bogus errors. + """ + fullname = self.FullName() + + if os.path.exists(fullname): + project_dir = os.path.dirname(fullname) + + if os.path.exists(os.path.join(project_dir, ".svn")): + # If there's a .svn file in the current directory, we recursively look + # up the directory tree for the top of the SVN checkout + root_dir = project_dir + one_up_dir = os.path.dirname(root_dir) + while os.path.exists(os.path.join(one_up_dir, ".svn")): + root_dir = os.path.dirname(root_dir) + one_up_dir = os.path.dirname(one_up_dir) + + prefix = os.path.commonprefix([root_dir, project_dir]) + return fullname[len(prefix) + 1:] + + # Not SVN <= 1.6? Try to find a git, hg, or svn top level directory by + # searching up from the current path. + root_dir = current_dir = os.path.dirname(fullname) + while current_dir != os.path.dirname(current_dir): + if (os.path.exists(os.path.join(current_dir, ".git")) or + os.path.exists(os.path.join(current_dir, ".hg")) or + os.path.exists(os.path.join(current_dir, ".svn"))): + root_dir = current_dir + current_dir = os.path.dirname(current_dir) + + if (os.path.exists(os.path.join(root_dir, ".git")) or + os.path.exists(os.path.join(root_dir, ".hg")) or + os.path.exists(os.path.join(root_dir, ".svn"))): + prefix = os.path.commonprefix([root_dir, project_dir]) + return fullname[len(prefix) + 1:] + + # Don't know what to do; header guard warnings may be wrong... + return fullname + + def Split(self): + """Splits the file into the directory, basename, and extension. + + For 'chrome/browser/browser.cc', Split() would + return ('chrome/browser', 'browser', '.cc') + + Returns: + A tuple of (directory, basename, extension). + """ + + googlename = self.RepositoryName() + project, rest = os.path.split(googlename) + return (project,) + os.path.splitext(rest) + + def BaseName(self): + """File base name - text after the final slash, before the final period.""" + return self.Split()[1] + + def Extension(self): + """File extension - text following the final period.""" + return self.Split()[2] + + def NoExtension(self): + """File has no source file extension.""" + return '/'.join(self.Split()[0:2]) + + def IsSource(self): + """File has a source file extension.""" + return _IsSourceExtension(self.Extension()[1:]) + + +def _ShouldPrintError(category, confidence, linenum): + """If confidence >= verbose, category passes filter and is not suppressed.""" + + # There are three ways we might decide not to print an error message: + # a "NOLINT(category)" comment appears in the source, + # the verbosity level isn't high enough, or the filters filter it out. + if IsErrorSuppressedByNolint(category, linenum): + return False + + if confidence < _cpplint_state.verbose_level: + return False + + is_filtered = False + for one_filter in _Filters(): + if one_filter.startswith('-'): + if category.startswith(one_filter[1:]): + is_filtered = True + elif one_filter.startswith('+'): + if category.startswith(one_filter[1:]): + is_filtered = False + else: + assert False # should have been checked for in SetFilter. + if is_filtered: + return False + + return True + + +def Error(filename, linenum, category, confidence, message): + """Logs the fact we've found a lint error. + + We log where the error was found, and also our confidence in the error, + that is, how certain we are this is a legitimate style regression, and + not a misidentification or a use that's sometimes justified. + + False positives can be suppressed by the use of + "cpplint(category)" comments on the offending line. These are + parsed into _error_suppressions. + + Args: + filename: The name of the file containing the error. + linenum: The number of the line containing the error. + category: A string used to describe the "category" this bug + falls under: "whitespace", say, or "runtime". Categories + may have a hierarchy separated by slashes: "whitespace/indent". + confidence: A number from 1-5 representing a confidence score for + the error, with 5 meaning that we are certain of the problem, + and 1 meaning that it could be a legitimate construct. + message: The error message. + """ + if _ShouldPrintError(category, confidence, linenum): + _cpplint_state.IncrementErrorCount(category) + if _cpplint_state.output_format == 'vs7': + sys.stderr.write('%s(%s): error cpplint: [%s] %s [%d]\n' % ( + filename, linenum, category, message, confidence)) + elif _cpplint_state.output_format == 'eclipse': + sys.stderr.write('%s:%s: warning: %s [%s] [%d]\n' % ( + filename, linenum, message, category, confidence)) + else: + sys.stderr.write('%s:%s: %s [%s] [%d]\n' % ( + filename, linenum, message, category, confidence)) + + +# Matches standard C++ escape sequences per 2.13.2.3 of the C++ standard. +_RE_PATTERN_CLEANSE_LINE_ESCAPES = re.compile( + r'\\([abfnrtv?"\\\']|\d+|x[0-9a-fA-F]+)') +# Match a single C style comment on the same line. +_RE_PATTERN_C_COMMENTS = r'/\*(?:[^*]|\*(?!/))*\*/' +# Matches multi-line C style comments. +# This RE is a little bit more complicated than one might expect, because we +# have to take care of space removals tools so we can handle comments inside +# statements better. +# The current rule is: We only clear spaces from both sides when we're at the +# end of the line. Otherwise, we try to remove spaces from the right side, +# if this doesn't work we try on left side but only if there's a non-character +# on the right. +_RE_PATTERN_CLEANSE_LINE_C_COMMENTS = re.compile( + r'(\s*' + _RE_PATTERN_C_COMMENTS + r'\s*$|' + + _RE_PATTERN_C_COMMENTS + r'\s+|' + + r'\s+' + _RE_PATTERN_C_COMMENTS + r'(?=\W)|' + + _RE_PATTERN_C_COMMENTS + r')') + + +def IsCppString(line): + """Does line terminate so, that the next symbol is in string constant. + + This function does not consider single-line nor multi-line comments. + + Args: + line: is a partial line of code starting from the 0..n. + + Returns: + True, if next character appended to 'line' is inside a + string constant. + """ + + line = line.replace(r'\\', 'XX') # after this, \\" does not match to \" + return ((line.count('"') - line.count(r'\"') - line.count("'\"'")) & 1) == 1 + + +def CleanseRawStrings(raw_lines): + """Removes C++11 raw strings from lines. + + Before: + static const char kData[] = R"( + multi-line string + )"; + + After: + static const char kData[] = "" + (replaced by blank line) + ""; + + Args: + raw_lines: list of raw lines. + + Returns: + list of lines with C++11 raw strings replaced by empty strings. + """ + + delimiter = None + lines_without_raw_strings = [] + for line in raw_lines: + if delimiter: + # Inside a raw string, look for the end + end = line.find(delimiter) + if end >= 0: + # Found the end of the string, match leading space for this + # line and resume copying the original lines, and also insert + # a "" on the last line. + leading_space = Match(r'^(\s*)\S', line) + line = leading_space.group(1) + '""' + line[end + len(delimiter):] + delimiter = None + else: + # Haven't found the end yet, append a blank line. + line = '""' + + # Look for beginning of a raw string, and replace them with + # empty strings. This is done in a loop to handle multiple raw + # strings on the same line. + while delimiter is None: + # Look for beginning of a raw string. + # See 2.14.15 [lex.string] for syntax. + # + # Once we have matched a raw string, we check the prefix of the + # line to make sure that the line is not part of a single line + # comment. It's done this way because we remove raw strings + # before removing comments as opposed to removing comments + # before removing raw strings. This is because there are some + # cpplint checks that requires the comments to be preserved, but + # we don't want to check comments that are inside raw strings. + matched = Match(r'^(.*?)\b(?:R|u8R|uR|UR|LR)"([^\s\\()]*)\((.*)$', line) + if (matched and + not Match(r'^([^\'"]|\'(\\.|[^\'])*\'|"(\\.|[^"])*")*//', + matched.group(1))): + delimiter = ')' + matched.group(2) + '"' + + end = matched.group(3).find(delimiter) + if end >= 0: + # Raw string ended on same line + line = (matched.group(1) + '""' + + matched.group(3)[end + len(delimiter):]) + delimiter = None + else: + # Start of a multi-line raw string + line = matched.group(1) + '""' + else: + break + + lines_without_raw_strings.append(line) + + # TODO(unknown): if delimiter is not None here, we might want to + # emit a warning for unterminated string. + return lines_without_raw_strings + + +def FindNextMultiLineCommentStart(lines, lineix): + """Find the beginning marker for a multiline comment.""" + while lineix < len(lines): + if lines[lineix].strip().startswith('/*'): + # Only return this marker if the comment goes beyond this line + if lines[lineix].strip().find('*/', 2) < 0: + return lineix + lineix += 1 + return len(lines) + + +def FindNextMultiLineCommentEnd(lines, lineix): + """We are inside a comment, find the end marker.""" + while lineix < len(lines): + if lines[lineix].strip().endswith('*/'): + return lineix + lineix += 1 + return len(lines) + + +def RemoveMultiLineCommentsFromRange(lines, begin, end): + """Clears a range of lines for multi-line comments.""" + # Having // dummy comments makes the lines non-empty, so we will not get + # unnecessary blank line warnings later in the code. + for i in range(begin, end): + lines[i] = '/**/' + + +def RemoveMultiLineComments(filename, lines, error): + """Removes multiline (c-style) comments from lines.""" + lineix = 0 + while lineix < len(lines): + lineix_begin = FindNextMultiLineCommentStart(lines, lineix) + if lineix_begin >= len(lines): + return + lineix_end = FindNextMultiLineCommentEnd(lines, lineix_begin) + if lineix_end >= len(lines): + error(filename, lineix_begin + 1, 'readability/multiline_comment', 5, + 'Could not find end of multi-line comment') + return + RemoveMultiLineCommentsFromRange(lines, lineix_begin, lineix_end + 1) + lineix = lineix_end + 1 + + +def CleanseComments(line): + """Removes //-comments and single-line C-style /* */ comments. + + Args: + line: A line of C++ source. + + Returns: + The line with single-line comments removed. + """ + commentpos = line.find('//') + if commentpos != -1 and not IsCppString(line[:commentpos]): + line = line[:commentpos].rstrip() + # get rid of /* ... */ + return _RE_PATTERN_CLEANSE_LINE_C_COMMENTS.sub('', line) + + +class CleansedLines(object): + """Holds 4 copies of all lines with different preprocessing applied to them. + + 1) elided member contains lines without strings and comments. + 2) lines member contains lines without comments. + 3) raw_lines member contains all the lines without processing. + 4) lines_without_raw_strings member is same as raw_lines, but with C++11 raw + strings removed. + All these members are of , and of the same length. + """ + + def __init__(self, lines): + self.elided = [] + self.lines = [] + self.raw_lines = lines + self.num_lines = len(lines) + self.lines_without_raw_strings = CleanseRawStrings(lines) + for linenum in range(len(self.lines_without_raw_strings)): + self.lines.append(CleanseComments( + self.lines_without_raw_strings[linenum])) + elided = self._CollapseStrings(self.lines_without_raw_strings[linenum]) + self.elided.append(CleanseComments(elided)) + + def NumLines(self): + """Returns the number of lines represented.""" + return self.num_lines + + @staticmethod + def _CollapseStrings(elided): + """Collapses strings and chars on a line to simple "" or '' blocks. + + We nix strings first so we're not fooled by text like '"http://"' + + Args: + elided: The line being processed. + + Returns: + The line with collapsed strings. + """ + if _RE_PATTERN_INCLUDE.match(elided): + return elided + + # Remove escaped characters first to make quote/single quote collapsing + # basic. Things that look like escaped characters shouldn't occur + # outside of strings and chars. + elided = _RE_PATTERN_CLEANSE_LINE_ESCAPES.sub('', elided) + + # Replace quoted strings and digit separators. Both single quotes + # and double quotes are processed in the same loop, otherwise + # nested quotes wouldn't work. + collapsed = '' + while True: + # Find the first quote character + match = Match(r'^([^\'"]*)([\'"])(.*)$', elided) + if not match: + collapsed += elided + break + head, quote, tail = match.groups() + + if quote == '"': + # Collapse double quoted strings + second_quote = tail.find('"') + if second_quote >= 0: + collapsed += head + '""' + elided = tail[second_quote + 1:] + else: + # Unmatched double quote, don't bother processing the rest + # of the line since this is probably a multiline string. + collapsed += elided + break + else: + # Found single quote, check nearby text to eliminate digit separators. + # + # There is no special handling for floating point here, because + # the integer/fractional/exponent parts would all be parsed + # correctly as long as there are digits on both sides of the + # separator. So we are fine as long as we don't see something + # like "0.'3" (gcc 4.9.0 will not allow this literal). + if Search(r'\b(?:0[bBxX]?|[1-9])[0-9a-fA-F]*$', head): + match_literal = Match(r'^((?:\'?[0-9a-zA-Z_])*)(.*)$', "'" + tail) + collapsed += head + match_literal.group(1).replace("'", '') + elided = match_literal.group(2) + else: + second_quote = tail.find('\'') + if second_quote >= 0: + collapsed += head + "''" + elided = tail[second_quote + 1:] + else: + # Unmatched single quote + collapsed += elided + break + + return collapsed + + +def FindEndOfExpressionInLine(line, startpos, stack): + """Find the position just after the end of current parenthesized expression. + + Args: + line: a CleansedLines line. + startpos: start searching at this position. + stack: nesting stack at startpos. + + Returns: + On finding matching end: (index just after matching end, None) + On finding an unclosed expression: (-1, None) + Otherwise: (-1, new stack at end of this line) + """ + for i in xrange(startpos, len(line)): + char = line[i] + if char in '([{': + # Found start of parenthesized expression, push to expression stack + stack.append(char) + elif char == '<': + # Found potential start of template argument list + if i > 0 and line[i - 1] == '<': + # Left shift operator + if stack and stack[-1] == '<': + stack.pop() + if not stack: + return (-1, None) + elif i > 0 and Search(r'\boperator\s*$', line[0:i]): + # operator<, don't add to stack + continue + else: + # Tentative start of template argument list + stack.append('<') + elif char in ')]}': + # Found end of parenthesized expression. + # + # If we are currently expecting a matching '>', the pending '<' + # must have been an operator. Remove them from expression stack. + while stack and stack[-1] == '<': + stack.pop() + if not stack: + return (-1, None) + if ((stack[-1] == '(' and char == ')') or + (stack[-1] == '[' and char == ']') or + (stack[-1] == '{' and char == '}')): + stack.pop() + if not stack: + return (i + 1, None) + else: + # Mismatched parentheses + return (-1, None) + elif char == '>': + # Found potential end of template argument list. + + # Ignore "->" and operator functions + if (i > 0 and + (line[i - 1] == '-' or Search(r'\boperator\s*$', line[0:i - 1]))): + continue + + # Pop the stack if there is a matching '<'. Otherwise, ignore + # this '>' since it must be an operator. + if stack: + if stack[-1] == '<': + stack.pop() + if not stack: + return (i + 1, None) + elif char == ';': + # Found something that look like end of statements. If we are currently + # expecting a '>', the matching '<' must have been an operator, since + # template argument list should not contain statements. + while stack and stack[-1] == '<': + stack.pop() + if not stack: + return (-1, None) + + # Did not find end of expression or unbalanced parentheses on this line + return (-1, stack) + + +def CloseExpression(clean_lines, linenum, pos): + """If input points to ( or { or [ or <, finds the position that closes it. + + If lines[linenum][pos] points to a '(' or '{' or '[' or '<', finds the + linenum/pos that correspond to the closing of the expression. + + TODO(unknown): cpplint spends a fair bit of time matching parentheses. + Ideally we would want to index all opening and closing parentheses once + and have CloseExpression be just a simple lookup, but due to preprocessor + tricks, this is not so easy. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + pos: A position on the line. + + Returns: + A tuple (line, linenum, pos) pointer *past* the closing brace, or + (line, len(lines), -1) if we never find a close. Note we ignore + strings and comments when matching; and the line we return is the + 'cleansed' line at linenum. + """ + + line = clean_lines.elided[linenum] + if (line[pos] not in '({[<') or Match(r'<[<=]', line[pos:]): + return (line, clean_lines.NumLines(), -1) + + # Check first line + (end_pos, stack) = FindEndOfExpressionInLine(line, pos, []) + if end_pos > -1: + return (line, linenum, end_pos) + + # Continue scanning forward + while stack and linenum < clean_lines.NumLines() - 1: + linenum += 1 + line = clean_lines.elided[linenum] + (end_pos, stack) = FindEndOfExpressionInLine(line, 0, stack) + if end_pos > -1: + return (line, linenum, end_pos) + + # Did not find end of expression before end of file, give up + return (line, clean_lines.NumLines(), -1) + + +def FindStartOfExpressionInLine(line, endpos, stack): + """Find position at the matching start of current expression. + + This is almost the reverse of FindEndOfExpressionInLine, but note + that the input position and returned position differs by 1. + + Args: + line: a CleansedLines line. + endpos: start searching at this position. + stack: nesting stack at endpos. + + Returns: + On finding matching start: (index at matching start, None) + On finding an unclosed expression: (-1, None) + Otherwise: (-1, new stack at beginning of this line) + """ + i = endpos + while i >= 0: + char = line[i] + if char in ')]}': + # Found end of expression, push to expression stack + stack.append(char) + elif char == '>': + # Found potential end of template argument list. + # + # Ignore it if it's a "->" or ">=" or "operator>" + if (i > 0 and + (line[i - 1] == '-' or + Match(r'\s>=\s', line[i - 1:]) or + Search(r'\boperator\s*$', line[0:i]))): + i -= 1 + else: + stack.append('>') + elif char == '<': + # Found potential start of template argument list + if i > 0 and line[i - 1] == '<': + # Left shift operator + i -= 1 + else: + # If there is a matching '>', we can pop the expression stack. + # Otherwise, ignore this '<' since it must be an operator. + if stack and stack[-1] == '>': + stack.pop() + if not stack: + return (i, None) + elif char in '([{': + # Found start of expression. + # + # If there are any unmatched '>' on the stack, they must be + # operators. Remove those. + while stack and stack[-1] == '>': + stack.pop() + if not stack: + return (-1, None) + if ((char == '(' and stack[-1] == ')') or + (char == '[' and stack[-1] == ']') or + (char == '{' and stack[-1] == '}')): + stack.pop() + if not stack: + return (i, None) + else: + # Mismatched parentheses + return (-1, None) + elif char == ';': + # Found something that look like end of statements. If we are currently + # expecting a '<', the matching '>' must have been an operator, since + # template argument list should not contain statements. + while stack and stack[-1] == '>': + stack.pop() + if not stack: + return (-1, None) + + i -= 1 + + return (-1, stack) + + +def ReverseCloseExpression(clean_lines, linenum, pos): + """If input points to ) or } or ] or >, finds the position that opens it. + + If lines[linenum][pos] points to a ')' or '}' or ']' or '>', finds the + linenum/pos that correspond to the opening of the expression. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + pos: A position on the line. + + Returns: + A tuple (line, linenum, pos) pointer *at* the opening brace, or + (line, 0, -1) if we never find the matching opening brace. Note + we ignore strings and comments when matching; and the line we + return is the 'cleansed' line at linenum. + """ + line = clean_lines.elided[linenum] + if line[pos] not in ')}]>': + return (line, 0, -1) + + # Check last line + (start_pos, stack) = FindStartOfExpressionInLine(line, pos, []) + if start_pos > -1: + return (line, linenum, start_pos) + + # Continue scanning backward + while stack and linenum > 0: + linenum -= 1 + line = clean_lines.elided[linenum] + (start_pos, stack) = FindStartOfExpressionInLine(line, len(line) - 1, stack) + if start_pos > -1: + return (line, linenum, start_pos) + + # Did not find start of expression before beginning of file, give up + return (line, 0, -1) + + +def CheckForCopyright(filename, lines, error): + """Logs an error if no Copyright message appears at the top of the file.""" + + # We'll say it should occur by line 10. Don't forget there's a + # dummy line at the front. + for line in xrange(1, min(len(lines), 11)): + if re.search(r'Copyright', lines[line], re.I): break + else: # means no copyright line was found + error(filename, 0, 'legal/copyright', 5, + 'No copyright message found. ' + 'You should have a line: "Copyright [year] "') + + +def GetIndentLevel(line): + """Return the number of leading spaces in line. + + Args: + line: A string to check. + + Returns: + An integer count of leading spaces, possibly zero. + """ + indent = Match(r'^( *)\S', line) + if indent: + return len(indent.group(1)) + else: + return 0 + + +def GetHeaderGuardCPPVariable(filename): + """Returns the CPP variable that should be used as a header guard. + + Args: + filename: The name of a C++ header file. + + Returns: + The CPP variable that should be used as a header guard in the + named file. + + """ + + # Restores original filename in case that cpplint is invoked from Emacs's + # flymake. + filename = re.sub(r'_flymake\.h$', '.h', filename) + filename = re.sub(r'/\.flymake/([^/]*)$', r'/\1', filename) + # Replace 'c++' with 'cpp'. + filename = filename.replace('C++', 'cpp').replace('c++', 'cpp') + + fileinfo = FileInfo(filename) + file_path_from_root = fileinfo.RepositoryName() + if _root: + suffix = os.sep + # On Windows using directory separator will leave us with + # "bogus escape error" unless we properly escape regex. + if suffix == '\\': + suffix += '\\' + file_path_from_root = re.sub('^' + _root + suffix, '', file_path_from_root) + return re.sub(r'[^a-zA-Z0-9]', '_', file_path_from_root).upper() + '_' + + +def CheckForHeaderGuard(filename, clean_lines, error): + """Checks that the file contains a header guard. + + Logs an error if no #ifndef header guard is present. For other + headers, checks that the full pathname is used. + + Args: + filename: The name of the C++ header file. + clean_lines: A CleansedLines instance containing the file. + error: The function to call with any errors found. + """ + + # Don't check for header guards if there are error suppression + # comments somewhere in this file. + # + # Because this is silencing a warning for a nonexistent line, we + # only support the very specific NOLINT(build/header_guard) syntax, + # and not the general NOLINT or NOLINT(*) syntax. + raw_lines = clean_lines.lines_without_raw_strings + for i in raw_lines: + if Search(r'//\s*NOLINT\(build/header_guard\)', i): + return + + cppvar = GetHeaderGuardCPPVariable(filename) + + ifndef = '' + ifndef_linenum = 0 + define = '' + endif = '' + endif_linenum = 0 + for linenum, line in enumerate(raw_lines): + linesplit = line.split() + if len(linesplit) >= 2: + # find the first occurrence of #ifndef and #define, save arg + if not ifndef and linesplit[0] == '#ifndef': + # set ifndef to the header guard presented on the #ifndef line. + ifndef = linesplit[1] + ifndef_linenum = linenum + if not define and linesplit[0] == '#define': + define = linesplit[1] + # find the last occurrence of #endif, save entire line + if line.startswith('#endif'): + endif = line + endif_linenum = linenum + + if not ifndef or not define or ifndef != define: + error(filename, 0, 'build/header_guard', 5, + 'No #ifndef header guard found, suggested CPP variable is: %s' % + cppvar) + return + + # The guard should be PATH_FILE_H_, but we also allow PATH_FILE_H__ + # for backward compatibility. + if ifndef != cppvar: + error_level = 0 + if ifndef != cppvar + '_': + error_level = 5 + + ParseNolintSuppressions(filename, raw_lines[ifndef_linenum], ifndef_linenum, + error) + error(filename, ifndef_linenum, 'build/header_guard', error_level, + '#ifndef header guard has wrong style, please use: %s' % cppvar) + + # Check for "//" comments on endif line. + ParseNolintSuppressions(filename, raw_lines[endif_linenum], endif_linenum, + error) + match = Match(r'#endif\s*//\s*' + cppvar + r'(_)?\b', endif) + if match: + if match.group(1) == '_': + # Issue low severity warning for deprecated double trailing underscore + error(filename, endif_linenum, 'build/header_guard', 0, + '#endif line should be "#endif // %s"' % cppvar) + return + + # Didn't find the corresponding "//" comment. If this file does not + # contain any "//" comments at all, it could be that the compiler + # only wants "/**/" comments, look for those instead. + no_single_line_comments = True + for i in xrange(1, len(raw_lines) - 1): + line = raw_lines[i] + if Match(r'^(?:(?:\'(?:\.|[^\'])*\')|(?:"(?:\.|[^"])*")|[^\'"])*//', line): + no_single_line_comments = False + break + + if no_single_line_comments: + match = Match(r'#endif\s*/\*\s*' + cppvar + r'(_)?\s*\*/', endif) + if match: + if match.group(1) == '_': + # Low severity warning for double trailing underscore + error(filename, endif_linenum, 'build/header_guard', 0, + '#endif line should be "#endif /* %s */"' % cppvar) + return + + # Didn't find anything + error(filename, endif_linenum, 'build/header_guard', 5, + '#endif line should be "#endif // %s"' % cppvar) + + +def CheckHeaderFileIncluded(filename, include_state, error): + """Logs an error if a .cc file does not include its header.""" + + # Do not check test files + fileinfo = FileInfo(filename) + if Search(_TEST_FILE_SUFFIX, fileinfo.BaseName()): + return + + headerfile = filename[0:len(filename) - len(fileinfo.Extension())] + '.h' + if not os.path.exists(headerfile): + return + headername = FileInfo(headerfile).RepositoryName() + first_include = 0 + for section_list in include_state.include_list: + for f in section_list: + if headername in f[0] or f[0] in headername: + return + if not first_include: + first_include = f[1] + + error(filename, first_include, 'build/include', 5, + '%s should include its header file %s' % (fileinfo.RepositoryName(), + headername)) + + +def CheckForBadCharacters(filename, lines, error): + """Logs an error for each line containing bad characters. + + Two kinds of bad characters: + + 1. Unicode replacement characters: These indicate that either the file + contained invalid UTF-8 (likely) or Unicode replacement characters (which + it shouldn't). Note that it's possible for this to throw off line + numbering if the invalid UTF-8 occurred adjacent to a newline. + + 2. NUL bytes. These are problematic for some tools. + + Args: + filename: The name of the current file. + lines: An array of strings, each representing a line of the file. + error: The function to call with any errors found. + """ + for linenum, line in enumerate(lines): + if u'\ufffd' in line: + error(filename, linenum, 'readability/utf8', 5, + 'Line contains invalid UTF-8 (or Unicode replacement character).') + if '\0' in line: + error(filename, linenum, 'readability/nul', 5, 'Line contains NUL byte.') + + +def CheckForNewlineAtEOF(filename, lines, error): + """Logs an error if there is no newline char at the end of the file. + + Args: + filename: The name of the current file. + lines: An array of strings, each representing a line of the file. + error: The function to call with any errors found. + """ + + # The array lines() was created by adding two newlines to the + # original file (go figure), then splitting on \n. + # To verify that the file ends in \n, we just have to make sure the + # last-but-two element of lines() exists and is empty. + if len(lines) < 3 or lines[-2]: + error(filename, len(lines) - 2, 'whitespace/ending_newline', 5, + 'Could not find a newline character at the end of the file.') + + +def CheckForMultilineCommentsAndStrings(filename, clean_lines, linenum, error): + """Logs an error if we see /* ... */ or "..." that extend past one line. + + /* ... */ comments are legit inside macros, for one line. + Otherwise, we prefer // comments, so it's ok to warn about the + other. Likewise, it's ok for strings to extend across multiple + lines, as long as a line continuation character (backslash) + terminates each line. Although not currently prohibited by the C++ + style guide, it's ugly and unnecessary. We don't do well with either + in this lint program, so we warn about both. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # Remove all \\ (escaped backslashes) from the line. They are OK, and the + # second (escaped) slash may trigger later \" detection erroneously. + line = line.replace('\\\\', '') + + if line.count('/*') > line.count('*/'): + error(filename, linenum, 'readability/multiline_comment', 5, + 'Complex multi-line /*...*/-style comment found. ' + 'Lint may give bogus warnings. ' + 'Consider replacing these with //-style comments, ' + 'with #if 0...#endif, ' + 'or with more clearly structured multi-line comments.') + + if (line.count('"') - line.count('\\"')) % 2: + error(filename, linenum, 'readability/multiline_string', 5, + 'Multi-line string ("...") found. This lint script doesn\'t ' + 'do well with such strings, and may give bogus warnings. ' + 'Use C++11 raw strings or concatenation instead.') + + +# (non-threadsafe name, thread-safe alternative, validation pattern) +# +# The validation pattern is used to eliminate false positives such as: +# _rand(); // false positive due to substring match. +# ->rand(); // some member function rand(). +# ACMRandom rand(seed); // some variable named rand. +# ISAACRandom rand(); // another variable named rand. +# +# Basically we require the return value of these functions to be used +# in some expression context on the same line by matching on some +# operator before the function name. This eliminates constructors and +# member function calls. +_UNSAFE_FUNC_PREFIX = r'(?:[-+*/=%^&|(<]\s*|>\s+)' +_THREADING_LIST = ( + ('asctime(', 'asctime_r(', _UNSAFE_FUNC_PREFIX + r'asctime\([^)]+\)'), + ('ctime(', 'ctime_r(', _UNSAFE_FUNC_PREFIX + r'ctime\([^)]+\)'), + ('getgrgid(', 'getgrgid_r(', _UNSAFE_FUNC_PREFIX + r'getgrgid\([^)]+\)'), + ('getgrnam(', 'getgrnam_r(', _UNSAFE_FUNC_PREFIX + r'getgrnam\([^)]+\)'), + ('getlogin(', 'getlogin_r(', _UNSAFE_FUNC_PREFIX + r'getlogin\(\)'), + ('getpwnam(', 'getpwnam_r(', _UNSAFE_FUNC_PREFIX + r'getpwnam\([^)]+\)'), + ('getpwuid(', 'getpwuid_r(', _UNSAFE_FUNC_PREFIX + r'getpwuid\([^)]+\)'), + ('gmtime(', 'gmtime_r(', _UNSAFE_FUNC_PREFIX + r'gmtime\([^)]+\)'), + ('localtime(', 'localtime_r(', _UNSAFE_FUNC_PREFIX + r'localtime\([^)]+\)'), + ('rand(', 'rand_r(', _UNSAFE_FUNC_PREFIX + r'rand\(\)'), + ('strtok(', 'strtok_r(', + _UNSAFE_FUNC_PREFIX + r'strtok\([^)]+\)'), + ('ttyname(', 'ttyname_r(', _UNSAFE_FUNC_PREFIX + r'ttyname\([^)]+\)'), + ) + + +def CheckPosixThreading(filename, clean_lines, linenum, error): + """Checks for calls to thread-unsafe functions. + + Much code has been originally written without consideration of + multi-threading. Also, engineers are relying on their old experience; + they have learned posix before threading extensions were added. These + tests guide the engineers to use thread-safe functions (when using + posix directly). + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + for single_thread_func, multithread_safe_func, pattern in _THREADING_LIST: + # Additional pattern matching check to confirm that this is the + # function we are looking for + if Search(pattern, line): + error(filename, linenum, 'runtime/threadsafe_fn', 2, + 'Consider using ' + multithread_safe_func + + '...) instead of ' + single_thread_func + + '...) for improved thread safety.') + + +def CheckVlogArguments(filename, clean_lines, linenum, error): + """Checks that VLOG() is only used for defining a logging level. + + For example, VLOG(2) is correct. VLOG(INFO), VLOG(WARNING), VLOG(ERROR), and + VLOG(FATAL) are not. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + if Search(r'\bVLOG\((INFO|ERROR|WARNING|DFATAL|FATAL)\)', line): + error(filename, linenum, 'runtime/vlog', 5, + 'VLOG() should be used with numeric verbosity level. ' + 'Use LOG() if you want symbolic severity levels.') + +# Matches invalid increment: *count++, which moves pointer instead of +# incrementing a value. +_RE_PATTERN_INVALID_INCREMENT = re.compile( + r'^\s*\*\w+(\+\+|--);') + + +def CheckInvalidIncrement(filename, clean_lines, linenum, error): + """Checks for invalid increment *count++. + + For example following function: + void increment_counter(int* count) { + *count++; + } + is invalid, because it effectively does count++, moving pointer, and should + be replaced with ++*count, (*count)++ or *count += 1. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + if _RE_PATTERN_INVALID_INCREMENT.match(line): + error(filename, linenum, 'runtime/invalid_increment', 5, + 'Changing pointer instead of value (or unused value of operator*).') + + +def IsMacroDefinition(clean_lines, linenum): + if Search(r'^#define', clean_lines[linenum]): + return True + + if linenum > 0 and Search(r'\\$', clean_lines[linenum - 1]): + return True + + return False + + +def IsForwardClassDeclaration(clean_lines, linenum): + return Match(r'^\s*(\btemplate\b)*.*class\s+\w+;\s*$', clean_lines[linenum]) + + +class _BlockInfo(object): + """Stores information about a generic block of code.""" + + def __init__(self, linenum, seen_open_brace): + self.starting_linenum = linenum + self.seen_open_brace = seen_open_brace + self.open_parentheses = 0 + self.inline_asm = _NO_ASM + self.check_namespace_indentation = False + + def CheckBegin(self, filename, clean_lines, linenum, error): + """Run checks that applies to text up to the opening brace. + + This is mostly for checking the text after the class identifier + and the "{", usually where the base class is specified. For other + blocks, there isn't much to check, so we always pass. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + pass + + def CheckEnd(self, filename, clean_lines, linenum, error): + """Run checks that applies to text after the closing brace. + + This is mostly used for checking end of namespace comments. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + pass + + def IsBlockInfo(self): + """Returns true if this block is a _BlockInfo. + + This is convenient for verifying that an object is an instance of + a _BlockInfo, but not an instance of any of the derived classes. + + Returns: + True for this class, False for derived classes. + """ + return self.__class__ == _BlockInfo + + +class _ExternCInfo(_BlockInfo): + """Stores information about an 'extern "C"' block.""" + + def __init__(self, linenum): + _BlockInfo.__init__(self, linenum, True) + + +class _ClassInfo(_BlockInfo): + """Stores information about a class.""" + + def __init__(self, name, class_or_struct, clean_lines, linenum): + _BlockInfo.__init__(self, linenum, False) + self.name = name + self.is_derived = False + self.check_namespace_indentation = True + if class_or_struct == 'struct': + self.access = 'public' + self.is_struct = True + else: + self.access = 'private' + self.is_struct = False + + # Remember initial indentation level for this class. Using raw_lines here + # instead of elided to account for leading comments. + self.class_indent = GetIndentLevel(clean_lines.raw_lines[linenum]) + + # Try to find the end of the class. This will be confused by things like: + # class A { + # } *x = { ... + # + # But it's still good enough for CheckSectionSpacing. + self.last_line = 0 + depth = 0 + for i in range(linenum, clean_lines.NumLines()): + line = clean_lines.elided[i] + depth += line.count('{') - line.count('}') + if not depth: + self.last_line = i + break + + def CheckBegin(self, filename, clean_lines, linenum, error): + # Look for a bare ':' + if Search('(^|[^:]):($|[^:])', clean_lines.elided[linenum]): + self.is_derived = True + + def CheckEnd(self, filename, clean_lines, linenum, error): + # If there is a DISALLOW macro, it should appear near the end of + # the class. + seen_last_thing_in_class = False + for i in xrange(linenum - 1, self.starting_linenum, -1): + match = Search( + r'\b(DISALLOW_COPY_AND_ASSIGN|DISALLOW_IMPLICIT_CONSTRUCTORS)\(' + + self.name + r'\)', + clean_lines.elided[i]) + if match: + if seen_last_thing_in_class: + error(filename, i, 'readability/constructors', 3, + match.group(1) + ' should be the last thing in the class') + break + + if not Match(r'^\s*$', clean_lines.elided[i]): + seen_last_thing_in_class = True + + # Check that closing brace is aligned with beginning of the class. + # Only do this if the closing brace is indented by only whitespaces. + # This means we will not check single-line class definitions. + indent = Match(r'^( *)\}', clean_lines.elided[linenum]) + if indent and len(indent.group(1)) != self.class_indent: + if self.is_struct: + parent = 'struct ' + self.name + else: + parent = 'class ' + self.name + error(filename, linenum, 'whitespace/indent', 3, + 'Closing brace should be aligned with beginning of %s' % parent) + + +class _NamespaceInfo(_BlockInfo): + """Stores information about a namespace.""" + + def __init__(self, name, linenum): + _BlockInfo.__init__(self, linenum, False) + self.name = name or '' + self.check_namespace_indentation = True + + def CheckEnd(self, filename, clean_lines, linenum, error): + """Check end of namespace comments.""" + line = clean_lines.raw_lines[linenum] + + # Check how many lines is enclosed in this namespace. Don't issue + # warning for missing namespace comments if there aren't enough + # lines. However, do apply checks if there is already an end of + # namespace comment and it's incorrect. + # + # TODO(unknown): We always want to check end of namespace comments + # if a namespace is large, but sometimes we also want to apply the + # check if a short namespace contained nontrivial things (something + # other than forward declarations). There is currently no logic on + # deciding what these nontrivial things are, so this check is + # triggered by namespace size only, which works most of the time. + if (linenum - self.starting_linenum < 10 + and not Match(r'^\s*};*\s*(//|/\*).*\bnamespace\b', line)): + return + + # Look for matching comment at end of namespace. + # + # Note that we accept C style "/* */" comments for terminating + # namespaces, so that code that terminate namespaces inside + # preprocessor macros can be cpplint clean. + # + # We also accept stuff like "// end of namespace ." with the + # period at the end. + # + # Besides these, we don't accept anything else, otherwise we might + # get false negatives when existing comment is a substring of the + # expected namespace. + if self.name: + # Named namespace + if not Match((r'^\s*};*\s*(//|/\*).*\bnamespace\s+' + + re.escape(self.name) + r'[\*/\.\\\s]*$'), + line): + error(filename, linenum, 'readability/namespace', 5, + 'Namespace should be terminated with "// namespace %s"' % + self.name) + else: + # Anonymous namespace + if not Match(r'^\s*};*\s*(//|/\*).*\bnamespace[\*/\.\\\s]*$', line): + # If "// namespace anonymous" or "// anonymous namespace (more text)", + # mention "// anonymous namespace" as an acceptable form + if Match(r'^\s*}.*\b(namespace anonymous|anonymous namespace)\b', line): + error(filename, linenum, 'readability/namespace', 5, + 'Anonymous namespace should be terminated with "// namespace"' + ' or "// anonymous namespace"') + else: + error(filename, linenum, 'readability/namespace', 5, + 'Anonymous namespace should be terminated with "// namespace"') + + +class _PreprocessorInfo(object): + """Stores checkpoints of nesting stacks when #if/#else is seen.""" + + def __init__(self, stack_before_if): + # The entire nesting stack before #if + self.stack_before_if = stack_before_if + + # The entire nesting stack up to #else + self.stack_before_else = [] + + # Whether we have already seen #else or #elif + self.seen_else = False + + +class NestingState(object): + """Holds states related to parsing braces.""" + + def __init__(self): + # Stack for tracking all braces. An object is pushed whenever we + # see a "{", and popped when we see a "}". Only 3 types of + # objects are possible: + # - _ClassInfo: a class or struct. + # - _NamespaceInfo: a namespace. + # - _BlockInfo: some other type of block. + self.stack = [] + + # Top of the previous stack before each Update(). + # + # Because the nesting_stack is updated at the end of each line, we + # had to do some convoluted checks to find out what is the current + # scope at the beginning of the line. This check is simplified by + # saving the previous top of nesting stack. + # + # We could save the full stack, but we only need the top. Copying + # the full nesting stack would slow down cpplint by ~10%. + self.previous_stack_top = [] + + # Stack of _PreprocessorInfo objects. + self.pp_stack = [] + + def SeenOpenBrace(self): + """Check if we have seen the opening brace for the innermost block. + + Returns: + True if we have seen the opening brace, False if the innermost + block is still expecting an opening brace. + """ + return (not self.stack) or self.stack[-1].seen_open_brace + + def InNamespaceBody(self): + """Check if we are currently one level inside a namespace body. + + Returns: + True if top of the stack is a namespace block, False otherwise. + """ + return self.stack and isinstance(self.stack[-1], _NamespaceInfo) + + def InExternC(self): + """Check if we are currently one level inside an 'extern "C"' block. + + Returns: + True if top of the stack is an extern block, False otherwise. + """ + return self.stack and isinstance(self.stack[-1], _ExternCInfo) + + def InClassDeclaration(self): + """Check if we are currently one level inside a class or struct declaration. + + Returns: + True if top of the stack is a class/struct, False otherwise. + """ + return self.stack and isinstance(self.stack[-1], _ClassInfo) + + def InAsmBlock(self): + """Check if we are currently one level inside an inline ASM block. + + Returns: + True if the top of the stack is a block containing inline ASM. + """ + return self.stack and self.stack[-1].inline_asm != _NO_ASM + + def InTemplateArgumentList(self, clean_lines, linenum, pos): + """Check if current position is inside template argument list. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + pos: position just after the suspected template argument. + Returns: + True if (linenum, pos) is inside template arguments. + """ + while linenum < clean_lines.NumLines(): + # Find the earliest character that might indicate a template argument + line = clean_lines.elided[linenum] + match = Match(r'^[^{};=\[\]\.<>]*(.)', line[pos:]) + if not match: + linenum += 1 + pos = 0 + continue + token = match.group(1) + pos += len(match.group(0)) + + # These things do not look like template argument list: + # class Suspect { + # class Suspect x; } + if token in ('{', '}', ';'): return False + + # These things look like template argument list: + # template + # template + # template + # template + if token in ('>', '=', '[', ']', '.'): return True + + # Check if token is an unmatched '<'. + # If not, move on to the next character. + if token != '<': + pos += 1 + if pos >= len(line): + linenum += 1 + pos = 0 + continue + + # We can't be sure if we just find a single '<', and need to + # find the matching '>'. + (_, end_line, end_pos) = CloseExpression(clean_lines, linenum, pos - 1) + if end_pos < 0: + # Not sure if template argument list or syntax error in file + return False + linenum = end_line + pos = end_pos + return False + + def UpdatePreprocessor(self, line): + """Update preprocessor stack. + + We need to handle preprocessors due to classes like this: + #ifdef SWIG + struct ResultDetailsPageElementExtensionPoint { + #else + struct ResultDetailsPageElementExtensionPoint : public Extension { + #endif + + We make the following assumptions (good enough for most files): + - Preprocessor condition evaluates to true from #if up to first + #else/#elif/#endif. + + - Preprocessor condition evaluates to false from #else/#elif up + to #endif. We still perform lint checks on these lines, but + these do not affect nesting stack. + + Args: + line: current line to check. + """ + if Match(r'^\s*#\s*(if|ifdef|ifndef)\b', line): + # Beginning of #if block, save the nesting stack here. The saved + # stack will allow us to restore the parsing state in the #else case. + self.pp_stack.append(_PreprocessorInfo(copy.deepcopy(self.stack))) + elif Match(r'^\s*#\s*(else|elif)\b', line): + # Beginning of #else block + if self.pp_stack: + if not self.pp_stack[-1].seen_else: + # This is the first #else or #elif block. Remember the + # whole nesting stack up to this point. This is what we + # keep after the #endif. + self.pp_stack[-1].seen_else = True + self.pp_stack[-1].stack_before_else = copy.deepcopy(self.stack) + + # Restore the stack to how it was before the #if + self.stack = copy.deepcopy(self.pp_stack[-1].stack_before_if) + else: + # TODO(unknown): unexpected #else, issue warning? + pass + elif Match(r'^\s*#\s*endif\b', line): + # End of #if or #else blocks. + if self.pp_stack: + # If we saw an #else, we will need to restore the nesting + # stack to its former state before the #else, otherwise we + # will just continue from where we left off. + if self.pp_stack[-1].seen_else: + # Here we can just use a shallow copy since we are the last + # reference to it. + self.stack = self.pp_stack[-1].stack_before_else + # Drop the corresponding #if + self.pp_stack.pop() + else: + # TODO(unknown): unexpected #endif, issue warning? + pass + + # TODO(unknown): Update() is too long, but we will refactor later. + def Update(self, filename, clean_lines, linenum, error): + """Update nesting state with current line. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # Remember top of the previous nesting stack. + # + # The stack is always pushed/popped and not modified in place, so + # we can just do a shallow copy instead of copy.deepcopy. Using + # deepcopy would slow down cpplint by ~28%. + if self.stack: + self.previous_stack_top = self.stack[-1] + else: + self.previous_stack_top = None + + # Update pp_stack + self.UpdatePreprocessor(line) + + # Count parentheses. This is to avoid adding struct arguments to + # the nesting stack. + if self.stack: + inner_block = self.stack[-1] + depth_change = line.count('(') - line.count(')') + inner_block.open_parentheses += depth_change + + # Also check if we are starting or ending an inline assembly block. + if inner_block.inline_asm in (_NO_ASM, _END_ASM): + if (depth_change != 0 and + inner_block.open_parentheses == 1 and + _MATCH_ASM.match(line)): + # Enter assembly block + inner_block.inline_asm = _INSIDE_ASM + else: + # Not entering assembly block. If previous line was _END_ASM, + # we will now shift to _NO_ASM state. + inner_block.inline_asm = _NO_ASM + elif (inner_block.inline_asm == _INSIDE_ASM and + inner_block.open_parentheses == 0): + # Exit assembly block + inner_block.inline_asm = _END_ASM + + # Consume namespace declaration at the beginning of the line. Do + # this in a loop so that we catch same line declarations like this: + # namespace proto2 { namespace bridge { class MessageSet; } } + while True: + # Match start of namespace. The "\b\s*" below catches namespace + # declarations even if it weren't followed by a whitespace, this + # is so that we don't confuse our namespace checker. The + # missing spaces will be flagged by CheckSpacing. + namespace_decl_match = Match(r'^\s*namespace\b\s*([:\w]+)?(.*)$', line) + if not namespace_decl_match: + break + + new_namespace = _NamespaceInfo(namespace_decl_match.group(1), linenum) + self.stack.append(new_namespace) + + line = namespace_decl_match.group(2) + if line.find('{') != -1: + new_namespace.seen_open_brace = True + line = line[line.find('{') + 1:] + + # Look for a class declaration in whatever is left of the line + # after parsing namespaces. The regexp accounts for decorated classes + # such as in: + # class LOCKABLE API Object { + # }; + class_decl_match = Match( + r'^(\s*(?:template\s*<[\w\s<>,:]*>\s*)?' + r'(class|struct)\s+(?:[A-Z_]+\s+)*(\w+(?:::\w+)*))' + r'(.*)$', line) + if (class_decl_match and + (not self.stack or self.stack[-1].open_parentheses == 0)): + # We do not want to accept classes that are actually template arguments: + # template , + # template class Ignore3> + # void Function() {}; + # + # To avoid template argument cases, we scan forward and look for + # an unmatched '>'. If we see one, assume we are inside a + # template argument list. + end_declaration = len(class_decl_match.group(1)) + if not self.InTemplateArgumentList(clean_lines, linenum, end_declaration): + self.stack.append(_ClassInfo( + class_decl_match.group(3), class_decl_match.group(2), + clean_lines, linenum)) + line = class_decl_match.group(4) + + # If we have not yet seen the opening brace for the innermost block, + # run checks here. + if not self.SeenOpenBrace(): + self.stack[-1].CheckBegin(filename, clean_lines, linenum, error) + + # Update access control if we are inside a class/struct + if self.stack and isinstance(self.stack[-1], _ClassInfo): + classinfo = self.stack[-1] + access_match = Match( + r'^(.*)\b(public|private|protected|signals)(\s+(?:slots\s*)?)?' + r':(?:[^:]|$)', + line) + if access_match: + classinfo.access = access_match.group(2) + + # Check that access keywords are indented +1 space. Skip this + # check if the keywords are not preceded by whitespaces. + indent = access_match.group(1) + if (len(indent) != classinfo.class_indent + 1 and + Match(r'^\s*$', indent)): + if classinfo.is_struct: + parent = 'struct ' + classinfo.name + else: + parent = 'class ' + classinfo.name + slots = '' + if access_match.group(3): + slots = access_match.group(3) + error(filename, linenum, 'whitespace/indent', 3, + '%s%s: should be indented +1 space inside %s' % ( + access_match.group(2), slots, parent)) + + # Consume braces or semicolons from what's left of the line + while True: + # Match first brace, semicolon, or closed parenthesis. + matched = Match(r'^[^{;)}]*([{;)}])(.*)$', line) + if not matched: + break + + token = matched.group(1) + if token == '{': + # If namespace or class hasn't seen a opening brace yet, mark + # namespace/class head as complete. Push a new block onto the + # stack otherwise. + if not self.SeenOpenBrace(): + self.stack[-1].seen_open_brace = True + elif Match(r'^extern\s*"[^"]*"\s*\{', line): + self.stack.append(_ExternCInfo(linenum)) + else: + self.stack.append(_BlockInfo(linenum, True)) + if _MATCH_ASM.match(line): + self.stack[-1].inline_asm = _BLOCK_ASM + + elif token == ';' or token == ')': + # If we haven't seen an opening brace yet, but we already saw + # a semicolon, this is probably a forward declaration. Pop + # the stack for these. + # + # Similarly, if we haven't seen an opening brace yet, but we + # already saw a closing parenthesis, then these are probably + # function arguments with extra "class" or "struct" keywords. + # Also pop these stack for these. + if not self.SeenOpenBrace(): + self.stack.pop() + else: # token == '}' + # Perform end of block checks and pop the stack. + if self.stack: + self.stack[-1].CheckEnd(filename, clean_lines, linenum, error) + self.stack.pop() + line = matched.group(2) + + def InnermostClass(self): + """Get class info on the top of the stack. + + Returns: + A _ClassInfo object if we are inside a class, or None otherwise. + """ + for i in range(len(self.stack), 0, -1): + classinfo = self.stack[i - 1] + if isinstance(classinfo, _ClassInfo): + return classinfo + return None + + def CheckCompletedBlocks(self, filename, error): + """Checks that all classes and namespaces have been completely parsed. + + Call this when all lines in a file have been processed. + Args: + filename: The name of the current file. + error: The function to call with any errors found. + """ + # Note: This test can result in false positives if #ifdef constructs + # get in the way of brace matching. See the testBuildClass test in + # cpplint_unittest.py for an example of this. + for obj in self.stack: + if isinstance(obj, _ClassInfo): + error(filename, obj.starting_linenum, 'build/class', 5, + 'Failed to find complete declaration of class %s' % + obj.name) + elif isinstance(obj, _NamespaceInfo): + error(filename, obj.starting_linenum, 'build/namespaces', 5, + 'Failed to find complete declaration of namespace %s' % + obj.name) + + +def CheckForNonStandardConstructs(filename, clean_lines, linenum, + nesting_state, error): + r"""Logs an error if we see certain non-ANSI constructs ignored by gcc-2. + + Complain about several constructs which gcc-2 accepts, but which are + not standard C++. Warning about these in lint is one way to ease the + transition to new compilers. + - put storage class first (e.g. "static const" instead of "const static"). + - "%lld" instead of %qd" in printf-type functions. + - "%1$d" is non-standard in printf-type functions. + - "\%" is an undefined character escape sequence. + - text after #endif is not allowed. + - invalid inner-style forward declaration. + - >? and ?= and )\?=?\s*(\w+|[+-]?\d+)(\.\d*)?', + line): + error(filename, linenum, 'build/deprecated', 3, + '>? and ))?' + # r'\s*const\s*' + type_name + '\s*&\s*\w+\s*;' + error(filename, linenum, 'runtime/member_string_references', 2, + 'const string& members are dangerous. It is much better to use ' + 'alternatives, such as pointers or simple constants.') + + # Everything else in this function operates on class declarations. + # Return early if the top of the nesting stack is not a class, or if + # the class head is not completed yet. + classinfo = nesting_state.InnermostClass() + if not classinfo or not classinfo.seen_open_brace: + return + + # The class may have been declared with namespace or classname qualifiers. + # The constructor and destructor will not have those qualifiers. + base_classname = classinfo.name.split('::')[-1] + + # Look for single-argument constructors that aren't marked explicit. + # Technically a valid construct, but against style. + explicit_constructor_match = Match( + r'\s+(?:(?:inline|constexpr)\s+)*(explicit\s+)?' + r'(?:(?:inline|constexpr)\s+)*%s\s*' + r'\(((?:[^()]|\([^()]*\))*)\)' + % re.escape(base_classname), + line) + + if explicit_constructor_match: + is_marked_explicit = explicit_constructor_match.group(1) + + if not explicit_constructor_match.group(2): + constructor_args = [] + else: + constructor_args = explicit_constructor_match.group(2).split(',') + + # collapse arguments so that commas in template parameter lists and function + # argument parameter lists don't split arguments in two + i = 0 + while i < len(constructor_args): + constructor_arg = constructor_args[i] + while (constructor_arg.count('<') > constructor_arg.count('>') or + constructor_arg.count('(') > constructor_arg.count(')')): + constructor_arg += ',' + constructor_args[i + 1] + del constructor_args[i + 1] + constructor_args[i] = constructor_arg + i += 1 + + defaulted_args = [arg for arg in constructor_args if '=' in arg] + noarg_constructor = (not constructor_args or # empty arg list + # 'void' arg specifier + (len(constructor_args) == 1 and + constructor_args[0].strip() == 'void')) + onearg_constructor = ((len(constructor_args) == 1 and # exactly one arg + not noarg_constructor) or + # all but at most one arg defaulted + (len(constructor_args) >= 1 and + not noarg_constructor and + len(defaulted_args) >= len(constructor_args) - 1)) + initializer_list_constructor = bool( + onearg_constructor and + Search(r'\bstd\s*::\s*initializer_list\b', constructor_args[0])) + copy_constructor = bool( + onearg_constructor and + Match(r'(const\s+)?%s(\s*<[^>]*>)?(\s+const)?\s*(?:<\w+>\s*)?&' + % re.escape(base_classname), constructor_args[0].strip())) + + if (not is_marked_explicit and + onearg_constructor and + not initializer_list_constructor and + not copy_constructor): + if defaulted_args: + error(filename, linenum, 'runtime/explicit', 5, + 'Constructors callable with one argument ' + 'should be marked explicit.') + else: + error(filename, linenum, 'runtime/explicit', 5, + 'Single-parameter constructors should be marked explicit.') + elif is_marked_explicit and not onearg_constructor: + if noarg_constructor: + error(filename, linenum, 'runtime/explicit', 5, + 'Zero-parameter constructors should not be marked explicit.') + + +def CheckSpacingForFunctionCall(filename, clean_lines, linenum, error): + """Checks for the correctness of various spacing around function calls. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # Since function calls often occur inside if/for/while/switch + # expressions - which have their own, more liberal conventions - we + # first see if we should be looking inside such an expression for a + # function call, to which we can apply more strict standards. + fncall = line # if there's no control flow construct, look at whole line + for pattern in (r'\bif\s*\((.*)\)\s*{', + r'\bfor\s*\((.*)\)\s*{', + r'\bwhile\s*\((.*)\)\s*[{;]', + r'\bswitch\s*\((.*)\)\s*{'): + match = Search(pattern, line) + if match: + fncall = match.group(1) # look inside the parens for function calls + break + + # Except in if/for/while/switch, there should never be space + # immediately inside parens (eg "f( 3, 4 )"). We make an exception + # for nested parens ( (a+b) + c ). Likewise, there should never be + # a space before a ( when it's a function argument. I assume it's a + # function argument when the char before the whitespace is legal in + # a function name (alnum + _) and we're not starting a macro. Also ignore + # pointers and references to arrays and functions coz they're too tricky: + # we use a very simple way to recognize these: + # " (something)(maybe-something)" or + # " (something)(maybe-something," or + # " (something)[something]" + # Note that we assume the contents of [] to be short enough that + # they'll never need to wrap. + if ( # Ignore control structures. + not Search(r'\b(if|for|while|switch|return|new|delete|catch|sizeof)\b', + fncall) and + # Ignore pointers/references to functions. + not Search(r' \([^)]+\)\([^)]*(\)|,$)', fncall) and + # Ignore pointers/references to arrays. + not Search(r' \([^)]+\)\[[^\]]+\]', fncall)): + if Search(r'\w\s*\(\s(?!\s*\\$)', fncall): # a ( used for a fn call + error(filename, linenum, 'whitespace/parens', 4, + 'Extra space after ( in function call') + elif Search(r'\(\s+(?!(\s*\\)|\()', fncall): + error(filename, linenum, 'whitespace/parens', 2, + 'Extra space after (') + if (Search(r'\w\s+\(', fncall) and + not Search(r'_{0,2}asm_{0,2}\s+_{0,2}volatile_{0,2}\s+\(', fncall) and + not Search(r'#\s*define|typedef|using\s+\w+\s*=', fncall) and + not Search(r'\w\s+\((\w+::)*\*\w+\)\(', fncall) and + not Search(r'\bcase\s+\(', fncall)): + # TODO(unknown): Space after an operator function seem to be a common + # error, silence those for now by restricting them to highest verbosity. + if Search(r'\boperator_*\b', line): + error(filename, linenum, 'whitespace/parens', 0, + 'Extra space before ( in function call') + else: + error(filename, linenum, 'whitespace/parens', 4, + 'Extra space before ( in function call') + # If the ) is followed only by a newline or a { + newline, assume it's + # part of a control statement (if/while/etc), and don't complain + if Search(r'[^)]\s+\)\s*[^{\s]', fncall): + # If the closing parenthesis is preceded by only whitespaces, + # try to give a more descriptive error message. + if Search(r'^\s+\)', fncall): + error(filename, linenum, 'whitespace/parens', 2, + 'Closing ) should be moved to the previous line') + else: + error(filename, linenum, 'whitespace/parens', 2, + 'Extra space before )') + + +def IsBlankLine(line): + """Returns true if the given line is blank. + + We consider a line to be blank if the line is empty or consists of + only white spaces. + + Args: + line: A line of a string. + + Returns: + True, if the given line is blank. + """ + return not line or line.isspace() + + +def CheckForNamespaceIndentation(filename, nesting_state, clean_lines, line, + error): + is_namespace_indent_item = ( + len(nesting_state.stack) > 1 and + nesting_state.stack[-1].check_namespace_indentation and + isinstance(nesting_state.previous_stack_top, _NamespaceInfo) and + nesting_state.previous_stack_top == nesting_state.stack[-2]) + + if ShouldCheckNamespaceIndentation(nesting_state, is_namespace_indent_item, + clean_lines.elided, line): + CheckItemIndentationInNamespace(filename, clean_lines.elided, + line, error) + + +def CheckForFunctionLengths(filename, clean_lines, linenum, + function_state, error): + """Reports for long function bodies. + + For an overview why this is done, see: + https://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Write_Short_Functions + + Uses a simplistic algorithm assuming other style guidelines + (especially spacing) are followed. + Only checks unindented functions, so class members are unchecked. + Trivial bodies are unchecked, so constructors with huge initializer lists + may be missed. + Blank/comment lines are not counted so as to avoid encouraging the removal + of vertical space and comments just to get through a lint check. + NOLINT *on the last line of a function* disables this check. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + function_state: Current function name and lines in body so far. + error: The function to call with any errors found. + """ + lines = clean_lines.lines + line = lines[linenum] + joined_line = '' + + starting_func = False + regexp = r'(\w(\w|::|\*|\&|\s)*)\(' # decls * & space::name( ... + match_result = Match(regexp, line) + if match_result: + # If the name is all caps and underscores, figure it's a macro and + # ignore it, unless it's TEST or TEST_F. + function_name = match_result.group(1).split()[-1] + if function_name == 'TEST' or function_name == 'TEST_F' or ( + not Match(r'[A-Z_]+$', function_name)): + starting_func = True + + if starting_func: + body_found = False + for start_linenum in xrange(linenum, clean_lines.NumLines()): + start_line = lines[start_linenum] + joined_line += ' ' + start_line.lstrip() + if Search(r'(;|})', start_line): # Declarations and trivial functions + body_found = True + break # ... ignore + elif Search(r'{', start_line): + body_found = True + function = Search(r'((\w|:)*)\(', line).group(1) + if Match(r'TEST', function): # Handle TEST... macros + parameter_regexp = Search(r'(\(.*\))', joined_line) + if parameter_regexp: # Ignore bad syntax + function += parameter_regexp.group(1) + else: + function += '()' + function_state.Begin(function) + break + if not body_found: + # No body for the function (or evidence of a non-function) was found. + error(filename, linenum, 'readability/fn_size', 5, + 'Lint failed to find start of function body.') + elif Match(r'^\}\s*$', line): # function end + function_state.Check(error, filename, linenum) + function_state.End() + elif not Match(r'^\s*$', line): + function_state.Count() # Count non-blank/non-comment lines. + + +_RE_PATTERN_TODO = re.compile(r'^//(\s*)TODO(\(.+?\))?:?(\s|$)?') + + +def CheckComment(line, filename, linenum, next_line_start, error): + """Checks for common mistakes in comments. + + Args: + line: The line in question. + filename: The name of the current file. + linenum: The number of the line to check. + next_line_start: The first non-whitespace column of the next line. + error: The function to call with any errors found. + """ + commentpos = line.find('//') + if commentpos != -1: + # Check if the // may be in quotes. If so, ignore it + if re.sub(r'\\.', '', line[0:commentpos]).count('"') % 2 == 0: + # Allow one space for new scopes, two spaces otherwise: + if (not (Match(r'^.*{ *//', line) and next_line_start == commentpos) and + ((commentpos >= 1 and + line[commentpos-1] not in string.whitespace) or + (commentpos >= 2 and + line[commentpos-2] not in string.whitespace))): + error(filename, linenum, 'whitespace/comments', 2, + 'At least two spaces is best between code and comments') + + # Checks for common mistakes in TODO comments. + comment = line[commentpos:] + match = _RE_PATTERN_TODO.match(comment) + if match: + # One whitespace is correct; zero whitespace is handled elsewhere. + leading_whitespace = match.group(1) + if len(leading_whitespace) > 1: + error(filename, linenum, 'whitespace/todo', 2, + 'Too many spaces before TODO') + + username = match.group(2) + if not username: + error(filename, linenum, 'readability/todo', 2, + 'Missing username in TODO; it should look like ' + '"// TODO(my_username): Stuff."') + + middle_whitespace = match.group(3) + # Comparisons made explicit for correctness -- pylint: disable=g-explicit-bool-comparison + if middle_whitespace != ' ' and middle_whitespace != '': + error(filename, linenum, 'whitespace/todo', 2, + 'TODO(my_username) should be followed by a space') + + # If the comment contains an alphanumeric character, there + # should be a space somewhere between it and the // unless + # it's a /// or //! Doxygen comment. + if (Match(r'//[^ ]*\w', comment) and + not Match(r'(///|//\!)(\s+|$)', comment)): + error(filename, linenum, 'whitespace/comments', 4, + 'Should have a space between // and comment') + + +def CheckSpacing(filename, clean_lines, linenum, nesting_state, error): + """Checks for the correctness of various spacing issues in the code. + + Things we check for: spaces around operators, spaces after + if/for/while/switch, no spaces around parens in function calls, two + spaces between code and comment, don't start a block with a blank + line, don't end a function with a blank line, don't add a blank line + after public/protected/private, don't have too many blank lines in a row. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + nesting_state: A NestingState instance which maintains information about + the current stack of nested blocks being parsed. + error: The function to call with any errors found. + """ + + # Don't use "elided" lines here, otherwise we can't check commented lines. + # Don't want to use "raw" either, because we don't want to check inside C++11 + # raw strings, + raw = clean_lines.lines_without_raw_strings + line = raw[linenum] + + # Before nixing comments, check if the line is blank for no good + # reason. This includes the first line after a block is opened, and + # blank lines at the end of a function (ie, right before a line like '}' + # + # Skip all the blank line checks if we are immediately inside a + # namespace body. In other words, don't issue blank line warnings + # for this block: + # namespace { + # + # } + # + # A warning about missing end of namespace comments will be issued instead. + # + # Also skip blank line checks for 'extern "C"' blocks, which are formatted + # like namespaces. + if (IsBlankLine(line) and + not nesting_state.InNamespaceBody() and + not nesting_state.InExternC()): + elided = clean_lines.elided + prev_line = elided[linenum - 1] + prevbrace = prev_line.rfind('{') + # TODO(unknown): Don't complain if line before blank line, and line after, + # both start with alnums and are indented the same amount. + # This ignores whitespace at the start of a namespace block + # because those are not usually indented. + if prevbrace != -1 and prev_line[prevbrace:].find('}') == -1: + # OK, we have a blank line at the start of a code block. Before we + # complain, we check if it is an exception to the rule: The previous + # non-empty line has the parameters of a function header that are indented + # 4 spaces (because they did not fit in a 80 column line when placed on + # the same line as the function name). We also check for the case where + # the previous line is indented 6 spaces, which may happen when the + # initializers of a constructor do not fit into a 80 column line. + exception = False + if Match(r' {6}\w', prev_line): # Initializer list? + # We are looking for the opening column of initializer list, which + # should be indented 4 spaces to cause 6 space indentation afterwards. + search_position = linenum-2 + while (search_position >= 0 + and Match(r' {6}\w', elided[search_position])): + search_position -= 1 + exception = (search_position >= 0 + and elided[search_position][:5] == ' :') + else: + # Search for the function arguments or an initializer list. We use a + # simple heuristic here: If the line is indented 4 spaces; and we have a + # closing paren, without the opening paren, followed by an opening brace + # or colon (for initializer lists) we assume that it is the last line of + # a function header. If we have a colon indented 4 spaces, it is an + # initializer list. + exception = (Match(r' {4}\w[^\(]*\)\s*(const\s*)?(\{\s*$|:)', + prev_line) + or Match(r' {4}:', prev_line)) + + if not exception: + error(filename, linenum, 'whitespace/blank_line', 2, + 'Redundant blank line at the start of a code block ' + 'should be deleted.') + # Ignore blank lines at the end of a block in a long if-else + # chain, like this: + # if (condition1) { + # // Something followed by a blank line + # + # } else if (condition2) { + # // Something else + # } + if linenum + 1 < clean_lines.NumLines(): + next_line = raw[linenum + 1] + if (next_line + and Match(r'\s*}', next_line) + and next_line.find('} else ') == -1): + error(filename, linenum, 'whitespace/blank_line', 3, + 'Redundant blank line at the end of a code block ' + 'should be deleted.') + + matched = Match(r'\s*(public|protected|private):', prev_line) + if matched: + error(filename, linenum, 'whitespace/blank_line', 3, + 'Do not leave a blank line after "%s:"' % matched.group(1)) + + # Next, check comments + next_line_start = 0 + if linenum + 1 < clean_lines.NumLines(): + next_line = raw[linenum + 1] + next_line_start = len(next_line) - len(next_line.lstrip()) + CheckComment(line, filename, linenum, next_line_start, error) + + # get rid of comments and strings + line = clean_lines.elided[linenum] + + # You shouldn't have spaces before your brackets, except maybe after + # 'delete []' or 'return []() {};' + if Search(r'\w\s+\[', line) and not Search(r'(?:delete|return)\s+\[', line): + error(filename, linenum, 'whitespace/braces', 5, + 'Extra space before [') + + # In range-based for, we wanted spaces before and after the colon, but + # not around "::" tokens that might appear. + if (Search(r'for *\(.*[^:]:[^: ]', line) or + Search(r'for *\(.*[^: ]:[^:]', line)): + error(filename, linenum, 'whitespace/forcolon', 2, + 'Missing space around colon in range-based for loop') + + +def CheckOperatorSpacing(filename, clean_lines, linenum, error): + """Checks for horizontal spacing around operators. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # Don't try to do spacing checks for operator methods. Do this by + # replacing the troublesome characters with something else, + # preserving column position for all other characters. + # + # The replacement is done repeatedly to avoid false positives from + # operators that call operators. + while True: + match = Match(r'^(.*\boperator\b)(\S+)(\s*\(.*)$', line) + if match: + line = match.group(1) + ('_' * len(match.group(2))) + match.group(3) + else: + break + + # We allow no-spaces around = within an if: "if ( (a=Foo()) == 0 )". + # Otherwise not. Note we only check for non-spaces on *both* sides; + # sometimes people put non-spaces on one side when aligning ='s among + # many lines (not that this is behavior that I approve of...) + if ((Search(r'[\w.]=', line) or + Search(r'=[\w.]', line)) + and not Search(r'\b(if|while|for) ', line) + # Operators taken from [lex.operators] in C++11 standard. + and not Search(r'(>=|<=|==|!=|&=|\^=|\|=|\+=|\*=|\/=|\%=)', line) + and not Search(r'operator=', line)): + error(filename, linenum, 'whitespace/operators', 4, + 'Missing spaces around =') + + # It's ok not to have spaces around binary operators like + - * /, but if + # there's too little whitespace, we get concerned. It's hard to tell, + # though, so we punt on this one for now. TODO. + + # You should always have whitespace around binary operators. + # + # Check <= and >= first to avoid false positives with < and >, then + # check non-include lines for spacing around < and >. + # + # If the operator is followed by a comma, assume it's be used in a + # macro context and don't do any checks. This avoids false + # positives. + # + # Note that && is not included here. This is because there are too + # many false positives due to RValue references. + match = Search(r'[^<>=!\s](==|!=|<=|>=|\|\|)[^<>=!\s,;\)]', line) + if match: + error(filename, linenum, 'whitespace/operators', 3, + 'Missing spaces around %s' % match.group(1)) + elif not Match(r'#.*include', line): + # Look for < that is not surrounded by spaces. This is only + # triggered if both sides are missing spaces, even though + # technically should should flag if at least one side is missing a + # space. This is done to avoid some false positives with shifts. + match = Match(r'^(.*[^\s<])<[^\s=<,]', line) + if match: + (_, _, end_pos) = CloseExpression( + clean_lines, linenum, len(match.group(1))) + if end_pos <= -1: + error(filename, linenum, 'whitespace/operators', 3, + 'Missing spaces around <') + + # Look for > that is not surrounded by spaces. Similar to the + # above, we only trigger if both sides are missing spaces to avoid + # false positives with shifts. + match = Match(r'^(.*[^-\s>])>[^\s=>,]', line) + if match: + (_, _, start_pos) = ReverseCloseExpression( + clean_lines, linenum, len(match.group(1))) + if start_pos <= -1: + error(filename, linenum, 'whitespace/operators', 3, + 'Missing spaces around >') + + # We allow no-spaces around << when used like this: 10<<20, but + # not otherwise (particularly, not when used as streams) + # + # We also allow operators following an opening parenthesis, since + # those tend to be macros that deal with operators. + match = Search(r'(operator|[^\s(<])(?:L|UL|LL|ULL|l|ul|ll|ull)?<<([^\s,=<])', line) + if (match and not (match.group(1).isdigit() and match.group(2).isdigit()) and + not (match.group(1) == 'operator' and match.group(2) == ';')): + error(filename, linenum, 'whitespace/operators', 3, + 'Missing spaces around <<') + + # We allow no-spaces around >> for almost anything. This is because + # C++11 allows ">>" to close nested templates, which accounts for + # most cases when ">>" is not followed by a space. + # + # We still warn on ">>" followed by alpha character, because that is + # likely due to ">>" being used for right shifts, e.g.: + # value >> alpha + # + # When ">>" is used to close templates, the alphanumeric letter that + # follows would be part of an identifier, and there should still be + # a space separating the template type and the identifier. + # type> alpha + match = Search(r'>>[a-zA-Z_]', line) + if match: + error(filename, linenum, 'whitespace/operators', 3, + 'Missing spaces around >>') + + # There shouldn't be space around unary operators + match = Search(r'(!\s|~\s|[\s]--[\s;]|[\s]\+\+[\s;])', line) + if match: + error(filename, linenum, 'whitespace/operators', 4, + 'Extra space for operator %s' % match.group(1)) + + +def CheckParenthesisSpacing(filename, clean_lines, linenum, error): + """Checks for horizontal spacing around parentheses. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # No spaces after an if, while, switch, or for + match = Search(r' (if\(|for\(|while\(|switch\()', line) + if match: + error(filename, linenum, 'whitespace/parens', 5, + 'Missing space before ( in %s' % match.group(1)) + + # For if/for/while/switch, the left and right parens should be + # consistent about how many spaces are inside the parens, and + # there should either be zero or one spaces inside the parens. + # We don't want: "if ( foo)" or "if ( foo )". + # Exception: "for ( ; foo; bar)" and "for (foo; bar; )" are allowed. + match = Search(r'\b(if|for|while|switch)\s*' + r'\(([ ]*)(.).*[^ ]+([ ]*)\)\s*{\s*$', + line) + if match: + if len(match.group(2)) != len(match.group(4)): + if not (match.group(3) == ';' and + len(match.group(2)) == 1 + len(match.group(4)) or + not match.group(2) and Search(r'\bfor\s*\(.*; \)', line)): + error(filename, linenum, 'whitespace/parens', 5, + 'Mismatching spaces inside () in %s' % match.group(1)) + if len(match.group(2)) not in [0, 1]: + error(filename, linenum, 'whitespace/parens', 5, + 'Should have zero or one spaces inside ( and ) in %s' % + match.group(1)) + + +def CheckCommaSpacing(filename, clean_lines, linenum, error): + """Checks for horizontal spacing near commas and semicolons. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + raw = clean_lines.lines_without_raw_strings + line = clean_lines.elided[linenum] + + # You should always have a space after a comma (either as fn arg or operator) + # + # This does not apply when the non-space character following the + # comma is another comma, since the only time when that happens is + # for empty macro arguments. + # + # We run this check in two passes: first pass on elided lines to + # verify that lines contain missing whitespaces, second pass on raw + # lines to confirm that those missing whitespaces are not due to + # elided comments. + if (Search(r',[^,\s]', ReplaceAll(r'\boperator\s*,\s*\(', 'F(', line)) and + Search(r',[^,\s]', raw[linenum])): + error(filename, linenum, 'whitespace/comma', 3, + 'Missing space after ,') + + # You should always have a space after a semicolon + # except for few corner cases + # TODO(unknown): clarify if 'if (1) { return 1;}' is requires one more + # space after ; + if Search(r';[^\s};\\)/]', line): + error(filename, linenum, 'whitespace/semicolon', 3, + 'Missing space after ;') + + +def _IsType(clean_lines, nesting_state, expr): + """Check if expression looks like a type name, returns true if so. + + Args: + clean_lines: A CleansedLines instance containing the file. + nesting_state: A NestingState instance which maintains information about + the current stack of nested blocks being parsed. + expr: The expression to check. + Returns: + True, if token looks like a type. + """ + # Keep only the last token in the expression + last_word = Match(r'^.*(\b\S+)$', expr) + if last_word: + token = last_word.group(1) + else: + token = expr + + # Match native types and stdint types + if _TYPES.match(token): + return True + + # Try a bit harder to match templated types. Walk up the nesting + # stack until we find something that resembles a typename + # declaration for what we are looking for. + typename_pattern = (r'\b(?:typename|class|struct)\s+' + re.escape(token) + + r'\b') + block_index = len(nesting_state.stack) - 1 + while block_index >= 0: + if isinstance(nesting_state.stack[block_index], _NamespaceInfo): + return False + + # Found where the opening brace is. We want to scan from this + # line up to the beginning of the function, minus a few lines. + # template + # class C + # : public ... { // start scanning here + last_line = nesting_state.stack[block_index].starting_linenum + + next_block_start = 0 + if block_index > 0: + next_block_start = nesting_state.stack[block_index - 1].starting_linenum + first_line = last_line + while first_line >= next_block_start: + if clean_lines.elided[first_line].find('template') >= 0: + break + first_line -= 1 + if first_line < next_block_start: + # Didn't find any "template" keyword before reaching the next block, + # there are probably no template things to check for this block + block_index -= 1 + continue + + # Look for typename in the specified range + for i in xrange(first_line, last_line + 1, 1): + if Search(typename_pattern, clean_lines.elided[i]): + return True + block_index -= 1 + + return False + + +def CheckBracesSpacing(filename, clean_lines, linenum, nesting_state, error): + """Checks for horizontal spacing near commas. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + nesting_state: A NestingState instance which maintains information about + the current stack of nested blocks being parsed. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # Except after an opening paren, or after another opening brace (in case of + # an initializer list, for instance), you should have spaces before your + # braces when they are delimiting blocks, classes, namespaces etc. + # And since you should never have braces at the beginning of a line, + # this is an easy test. Except that braces used for initialization don't + # follow the same rule; we often don't want spaces before those. + match = Match(r'^(.*[^ ({>]){', line) + + if match: + # Try a bit harder to check for brace initialization. This + # happens in one of the following forms: + # Constructor() : initializer_list_{} { ... } + # Constructor{}.MemberFunction() + # Type variable{}; + # FunctionCall(type{}, ...); + # LastArgument(..., type{}); + # LOG(INFO) << type{} << " ..."; + # map_of_type[{...}] = ...; + # ternary = expr ? new type{} : nullptr; + # OuterTemplate{}> + # + # We check for the character following the closing brace, and + # silence the warning if it's one of those listed above, i.e. + # "{.;,)<>]:". + # + # To account for nested initializer list, we allow any number of + # closing braces up to "{;,)<". We can't simply silence the + # warning on first sight of closing brace, because that would + # cause false negatives for things that are not initializer lists. + # Silence this: But not this: + # Outer{ if (...) { + # Inner{...} if (...){ // Missing space before { + # }; } + # + # There is a false negative with this approach if people inserted + # spurious semicolons, e.g. "if (cond){};", but we will catch the + # spurious semicolon with a separate check. + leading_text = match.group(1) + (endline, endlinenum, endpos) = CloseExpression( + clean_lines, linenum, len(match.group(1))) + trailing_text = '' + if endpos > -1: + trailing_text = endline[endpos:] + for offset in xrange(endlinenum + 1, + min(endlinenum + 3, clean_lines.NumLines() - 1)): + trailing_text += clean_lines.elided[offset] + # We also suppress warnings for `uint64_t{expression}` etc., as the style + # guide recommends brace initialization for integral types to avoid + # overflow/truncation. + if (not Match(r'^[\s}]*[{.;,)<>\]:]', trailing_text) + and not _IsType(clean_lines, nesting_state, leading_text)): + error(filename, linenum, 'whitespace/braces', 5, + 'Missing space before {') + + # Make sure '} else {' has spaces. + if Search(r'}else', line): + error(filename, linenum, 'whitespace/braces', 5, + 'Missing space before else') + + # You shouldn't have a space before a semicolon at the end of the line. + # There's a special case for "for" since the style guide allows space before + # the semicolon there. + if Search(r':\s*;\s*$', line): + error(filename, linenum, 'whitespace/semicolon', 5, + 'Semicolon defining empty statement. Use {} instead.') + elif Search(r'^\s*;\s*$', line): + error(filename, linenum, 'whitespace/semicolon', 5, + 'Line contains only semicolon. If this should be an empty statement, ' + 'use {} instead.') + elif (Search(r'\s+;\s*$', line) and + not Search(r'\bfor\b', line)): + error(filename, linenum, 'whitespace/semicolon', 5, + 'Extra space before last semicolon. If this should be an empty ' + 'statement, use {} instead.') + + +def IsDecltype(clean_lines, linenum, column): + """Check if the token ending on (linenum, column) is decltype(). + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: the number of the line to check. + column: end column of the token to check. + Returns: + True if this token is decltype() expression, False otherwise. + """ + (text, _, start_col) = ReverseCloseExpression(clean_lines, linenum, column) + if start_col < 0: + return False + if Search(r'\bdecltype\s*$', text[0:start_col]): + return True + return False + + +def CheckSectionSpacing(filename, clean_lines, class_info, linenum, error): + """Checks for additional blank line issues related to sections. + + Currently the only thing checked here is blank line before protected/private. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + class_info: A _ClassInfo objects. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + # Skip checks if the class is small, where small means 25 lines or less. + # 25 lines seems like a good cutoff since that's the usual height of + # terminals, and any class that can't fit in one screen can't really + # be considered "small". + # + # Also skip checks if we are on the first line. This accounts for + # classes that look like + # class Foo { public: ... }; + # + # If we didn't find the end of the class, last_line would be zero, + # and the check will be skipped by the first condition. + if (class_info.last_line - class_info.starting_linenum <= 24 or + linenum <= class_info.starting_linenum): + return + + matched = Match(r'\s*(public|protected|private):', clean_lines.lines[linenum]) + if matched: + # Issue warning if the line before public/protected/private was + # not a blank line, but don't do this if the previous line contains + # "class" or "struct". This can happen two ways: + # - We are at the beginning of the class. + # - We are forward-declaring an inner class that is semantically + # private, but needed to be public for implementation reasons. + # Also ignores cases where the previous line ends with a backslash as can be + # common when defining classes in C macros. + prev_line = clean_lines.lines[linenum - 1] + if (not IsBlankLine(prev_line) and + not Search(r'\b(class|struct)\b', prev_line) and + not Search(r'\\$', prev_line)): + # Try a bit harder to find the beginning of the class. This is to + # account for multi-line base-specifier lists, e.g.: + # class Derived + # : public Base { + end_class_head = class_info.starting_linenum + for i in range(class_info.starting_linenum, linenum): + if Search(r'\{\s*$', clean_lines.lines[i]): + end_class_head = i + break + if end_class_head < linenum - 1: + error(filename, linenum, 'whitespace/blank_line', 3, + '"%s:" should be preceded by a blank line' % matched.group(1)) + + +def GetPreviousNonBlankLine(clean_lines, linenum): + """Return the most recent non-blank line and its line number. + + Args: + clean_lines: A CleansedLines instance containing the file contents. + linenum: The number of the line to check. + + Returns: + A tuple with two elements. The first element is the contents of the last + non-blank line before the current line, or the empty string if this is the + first non-blank line. The second is the line number of that line, or -1 + if this is the first non-blank line. + """ + + prevlinenum = linenum - 1 + while prevlinenum >= 0: + prevline = clean_lines.elided[prevlinenum] + if not IsBlankLine(prevline): # if not a blank line... + return (prevline, prevlinenum) + prevlinenum -= 1 + return ('', -1) + + +def CheckBraces(filename, clean_lines, linenum, error): + """Looks for misplaced braces (e.g. at the end of line). + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + + line = clean_lines.elided[linenum] # get rid of comments and strings + + if Match(r'\s*{\s*$', line): + # We allow an open brace to start a line in the case where someone is using + # braces in a block to explicitly create a new scope, which is commonly used + # to control the lifetime of stack-allocated variables. Braces are also + # used for brace initializers inside function calls. We don't detect this + # perfectly: we just don't complain if the last non-whitespace character on + # the previous non-blank line is ',', ';', ':', '(', '{', or '}', or if the + # previous line starts a preprocessor block. We also allow a brace on the + # following line if it is part of an array initialization and would not fit + # within the 80 character limit of the preceding line. + prevline = GetPreviousNonBlankLine(clean_lines, linenum)[0] + if (not Search(r'[,;:}{(]\s*$', prevline) and + not Match(r'\s*#', prevline) and + not (GetLineWidth(prevline) > _line_length - 2 and '[]' in prevline)): + error(filename, linenum, 'whitespace/braces', 4, + '{ should almost always be at the end of the previous line') + + # An else clause should be on the same line as the preceding closing brace. + if Match(r'\s*else\b\s*(?:if\b|\{|$)', line): + prevline = GetPreviousNonBlankLine(clean_lines, linenum)[0] + if Match(r'\s*}\s*$', prevline): + error(filename, linenum, 'whitespace/newline', 4, + 'An else should appear on the same line as the preceding }') + + # If braces come on one side of an else, they should be on both. + # However, we have to worry about "else if" that spans multiple lines! + if Search(r'else if\s*\(', line): # could be multi-line if + brace_on_left = bool(Search(r'}\s*else if\s*\(', line)) + # find the ( after the if + pos = line.find('else if') + pos = line.find('(', pos) + if pos > 0: + (endline, _, endpos) = CloseExpression(clean_lines, linenum, pos) + brace_on_right = endline[endpos:].find('{') != -1 + if brace_on_left != brace_on_right: # must be brace after if + error(filename, linenum, 'readability/braces', 5, + 'If an else has a brace on one side, it should have it on both') + elif Search(r'}\s*else[^{]*$', line) or Match(r'[^}]*else\s*{', line): + error(filename, linenum, 'readability/braces', 5, + 'If an else has a brace on one side, it should have it on both') + + # Likewise, an else should never have the else clause on the same line + if Search(r'\belse [^\s{]', line) and not Search(r'\belse if\b', line): + error(filename, linenum, 'whitespace/newline', 4, + 'Else clause should never be on same line as else (use 2 lines)') + + # In the same way, a do/while should never be on one line + if Match(r'\s*do [^\s{]', line): + error(filename, linenum, 'whitespace/newline', 4, + 'do/while clauses should not be on a single line') + + # Check single-line if/else bodies. The style guide says 'curly braces are not + # required for single-line statements'. We additionally allow multi-line, + # single statements, but we reject anything with more than one semicolon in + # it. This means that the first semicolon after the if should be at the end of + # its line, and the line after that should have an indent level equal to or + # lower than the if. We also check for ambiguous if/else nesting without + # braces. + if_else_match = Search(r'\b(if\s*\(|else\b)', line) + if if_else_match and not Match(r'\s*#', line): + if_indent = GetIndentLevel(line) + endline, endlinenum, endpos = line, linenum, if_else_match.end() + if_match = Search(r'\bif\s*\(', line) + if if_match: + # This could be a multiline if condition, so find the end first. + pos = if_match.end() - 1 + (endline, endlinenum, endpos) = CloseExpression(clean_lines, linenum, pos) + # Check for an opening brace, either directly after the if or on the next + # line. If found, this isn't a single-statement conditional. + if (not Match(r'\s*{', endline[endpos:]) + and not (Match(r'\s*$', endline[endpos:]) + and endlinenum < (len(clean_lines.elided) - 1) + and Match(r'\s*{', clean_lines.elided[endlinenum + 1]))): + while (endlinenum < len(clean_lines.elided) + and ';' not in clean_lines.elided[endlinenum][endpos:]): + endlinenum += 1 + endpos = 0 + if endlinenum < len(clean_lines.elided): + endline = clean_lines.elided[endlinenum] + # We allow a mix of whitespace and closing braces (e.g. for one-liner + # methods) and a single \ after the semicolon (for macros) + endpos = endline.find(';') + if not Match(r';[\s}]*(\\?)$', endline[endpos:]): + # Semicolon isn't the last character, there's something trailing. + # Output a warning if the semicolon is not contained inside + # a lambda expression. + if not Match(r'^[^{};]*\[[^\[\]]*\][^{}]*\{[^{}]*\}\s*\)*[;,]\s*$', + endline): + error(filename, linenum, 'readability/braces', 4, + 'If/else bodies with multiple statements require braces') + elif endlinenum < len(clean_lines.elided) - 1: + # Make sure the next line is dedented + next_line = clean_lines.elided[endlinenum + 1] + next_indent = GetIndentLevel(next_line) + # With ambiguous nested if statements, this will error out on the + # if that *doesn't* match the else, regardless of whether it's the + # inner one or outer one. + if (if_match and Match(r'\s*else\b', next_line) + and next_indent != if_indent): + error(filename, linenum, 'readability/braces', 4, + 'Else clause should be indented at the same level as if. ' + 'Ambiguous nested if/else chains require braces.') + elif next_indent > if_indent: + error(filename, linenum, 'readability/braces', 4, + 'If/else bodies with multiple statements require braces') + + +def CheckTrailingSemicolon(filename, clean_lines, linenum, error): + """Looks for redundant trailing semicolon. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + + line = clean_lines.elided[linenum] + + # Block bodies should not be followed by a semicolon. Due to C++11 + # brace initialization, there are more places where semicolons are + # required than not, so we use a whitelist approach to check these + # rather than a blacklist. These are the places where "};" should + # be replaced by just "}": + # 1. Some flavor of block following closing parenthesis: + # for (;;) {}; + # while (...) {}; + # switch (...) {}; + # Function(...) {}; + # if (...) {}; + # if (...) else if (...) {}; + # + # 2. else block: + # if (...) else {}; + # + # 3. const member function: + # Function(...) const {}; + # + # 4. Block following some statement: + # x = 42; + # {}; + # + # 5. Block at the beginning of a function: + # Function(...) { + # {}; + # } + # + # Note that naively checking for the preceding "{" will also match + # braces inside multi-dimensional arrays, but this is fine since + # that expression will not contain semicolons. + # + # 6. Block following another block: + # while (true) {} + # {}; + # + # 7. End of namespaces: + # namespace {}; + # + # These semicolons seems far more common than other kinds of + # redundant semicolons, possibly due to people converting classes + # to namespaces. For now we do not warn for this case. + # + # Try matching case 1 first. + match = Match(r'^(.*\)\s*)\{', line) + if match: + # Matched closing parenthesis (case 1). Check the token before the + # matching opening parenthesis, and don't warn if it looks like a + # macro. This avoids these false positives: + # - macro that defines a base class + # - multi-line macro that defines a base class + # - macro that defines the whole class-head + # + # But we still issue warnings for macros that we know are safe to + # warn, specifically: + # - TEST, TEST_F, TEST_P, MATCHER, MATCHER_P + # - TYPED_TEST + # - INTERFACE_DEF + # - EXCLUSIVE_LOCKS_REQUIRED, SHARED_LOCKS_REQUIRED, LOCKS_EXCLUDED: + # + # We implement a whitelist of safe macros instead of a blacklist of + # unsafe macros, even though the latter appears less frequently in + # google code and would have been easier to implement. This is because + # the downside for getting the whitelist wrong means some extra + # semicolons, while the downside for getting the blacklist wrong + # would result in compile errors. + # + # In addition to macros, we also don't want to warn on + # - Compound literals + # - Lambdas + # - alignas specifier with anonymous structs + # - decltype + closing_brace_pos = match.group(1).rfind(')') + opening_parenthesis = ReverseCloseExpression( + clean_lines, linenum, closing_brace_pos) + if opening_parenthesis[2] > -1: + line_prefix = opening_parenthesis[0][0:opening_parenthesis[2]] + macro = Search(r'\b([A-Z_][A-Z0-9_]*)\s*$', line_prefix) + func = Match(r'^(.*\])\s*$', line_prefix) + if ((macro and + macro.group(1) not in ( + 'TEST', 'TEST_F', 'MATCHER', 'MATCHER_P', 'TYPED_TEST', + 'EXCLUSIVE_LOCKS_REQUIRED', 'SHARED_LOCKS_REQUIRED', + 'LOCKS_EXCLUDED', 'INTERFACE_DEF')) or + (func and not Search(r'\boperator\s*\[\s*\]', func.group(1))) or + Search(r'\b(?:struct|union)\s+alignas\s*$', line_prefix) or + Search(r'\bdecltype$', line_prefix) or + Search(r'\s+=\s*$', line_prefix)): + match = None + if (match and + opening_parenthesis[1] > 1 and + Search(r'\]\s*$', clean_lines.elided[opening_parenthesis[1] - 1])): + # Multi-line lambda-expression + match = None + + else: + # Try matching cases 2-3. + match = Match(r'^(.*(?:else|\)\s*const)\s*)\{', line) + if not match: + # Try matching cases 4-6. These are always matched on separate lines. + # + # Note that we can't simply concatenate the previous line to the + # current line and do a single match, otherwise we may output + # duplicate warnings for the blank line case: + # if (cond) { + # // blank line + # } + prevline = GetPreviousNonBlankLine(clean_lines, linenum)[0] + if prevline and Search(r'[;{}]\s*$', prevline): + match = Match(r'^(\s*)\{', line) + + # Check matching closing brace + if match: + (endline, endlinenum, endpos) = CloseExpression( + clean_lines, linenum, len(match.group(1))) + if endpos > -1 and Match(r'^\s*;', endline[endpos:]): + # Current {} pair is eligible for semicolon check, and we have found + # the redundant semicolon, output warning here. + # + # Note: because we are scanning forward for opening braces, and + # outputting warnings for the matching closing brace, if there are + # nested blocks with trailing semicolons, we will get the error + # messages in reversed order. + + # We need to check the line forward for NOLINT + raw_lines = clean_lines.raw_lines + ParseNolintSuppressions(filename, raw_lines[endlinenum-1], endlinenum-1, + error) + ParseNolintSuppressions(filename, raw_lines[endlinenum], endlinenum, + error) + + error(filename, endlinenum, 'readability/braces', 4, + "You don't need a ; after a }") + + +def CheckEmptyBlockBody(filename, clean_lines, linenum, error): + """Look for empty loop/conditional body with only a single semicolon. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + + # Search for loop keywords at the beginning of the line. Because only + # whitespaces are allowed before the keywords, this will also ignore most + # do-while-loops, since those lines should start with closing brace. + # + # We also check "if" blocks here, since an empty conditional block + # is likely an error. + line = clean_lines.elided[linenum] + matched = Match(r'\s*(for|while|if)\s*\(', line) + if matched: + # Find the end of the conditional expression. + (end_line, end_linenum, end_pos) = CloseExpression( + clean_lines, linenum, line.find('(')) + + # Output warning if what follows the condition expression is a semicolon. + # No warning for all other cases, including whitespace or newline, since we + # have a separate check for semicolons preceded by whitespace. + if end_pos >= 0 and Match(r';', end_line[end_pos:]): + if matched.group(1) == 'if': + error(filename, end_linenum, 'whitespace/empty_conditional_body', 5, + 'Empty conditional bodies should use {}') + else: + error(filename, end_linenum, 'whitespace/empty_loop_body', 5, + 'Empty loop bodies should use {} or continue') + + # Check for if statements that have completely empty bodies (no comments) + # and no else clauses. + if end_pos >= 0 and matched.group(1) == 'if': + # Find the position of the opening { for the if statement. + # Return without logging an error if it has no brackets. + opening_linenum = end_linenum + opening_line_fragment = end_line[end_pos:] + # Loop until EOF or find anything that's not whitespace or opening {. + while not Search(r'^\s*\{', opening_line_fragment): + if Search(r'^(?!\s*$)', opening_line_fragment): + # Conditional has no brackets. + return + opening_linenum += 1 + if opening_linenum == len(clean_lines.elided): + # Couldn't find conditional's opening { or any code before EOF. + return + opening_line_fragment = clean_lines.elided[opening_linenum] + # Set opening_line (opening_line_fragment may not be entire opening line). + opening_line = clean_lines.elided[opening_linenum] + + # Find the position of the closing }. + opening_pos = opening_line_fragment.find('{') + if opening_linenum == end_linenum: + # We need to make opening_pos relative to the start of the entire line. + opening_pos += end_pos + (closing_line, closing_linenum, closing_pos) = CloseExpression( + clean_lines, opening_linenum, opening_pos) + if closing_pos < 0: + return + + # Now construct the body of the conditional. This consists of the portion + # of the opening line after the {, all lines until the closing line, + # and the portion of the closing line before the }. + if (clean_lines.raw_lines[opening_linenum] != + CleanseComments(clean_lines.raw_lines[opening_linenum])): + # Opening line ends with a comment, so conditional isn't empty. + return + if closing_linenum > opening_linenum: + # Opening line after the {. Ignore comments here since we checked above. + body = list(opening_line[opening_pos+1:]) + # All lines until closing line, excluding closing line, with comments. + body.extend(clean_lines.raw_lines[opening_linenum+1:closing_linenum]) + # Closing line before the }. Won't (and can't) have comments. + body.append(clean_lines.elided[closing_linenum][:closing_pos-1]) + body = '\n'.join(body) + else: + # If statement has brackets and fits on a single line. + body = opening_line[opening_pos+1:closing_pos-1] + + # Check if the body is empty + if not _EMPTY_CONDITIONAL_BODY_PATTERN.search(body): + return + # The body is empty. Now make sure there's not an else clause. + current_linenum = closing_linenum + current_line_fragment = closing_line[closing_pos:] + # Loop until EOF or find anything that's not whitespace or else clause. + while Search(r'^\s*$|^(?=\s*else)', current_line_fragment): + if Search(r'^(?=\s*else)', current_line_fragment): + # Found an else clause, so don't log an error. + return + current_linenum += 1 + if current_linenum == len(clean_lines.elided): + break + current_line_fragment = clean_lines.elided[current_linenum] + + # The body is empty and there's no else clause until EOF or other code. + error(filename, end_linenum, 'whitespace/empty_if_body', 4, + ('If statement had no body and no else clause')) + + +def FindCheckMacro(line): + """Find a replaceable CHECK-like macro. + + Args: + line: line to search on. + Returns: + (macro name, start position), or (None, -1) if no replaceable + macro is found. + """ + for macro in _CHECK_MACROS: + i = line.find(macro) + if i >= 0: + # Find opening parenthesis. Do a regular expression match here + # to make sure that we are matching the expected CHECK macro, as + # opposed to some other macro that happens to contain the CHECK + # substring. + matched = Match(r'^(.*\b' + macro + r'\s*)\(', line) + if not matched: + continue + return (macro, len(matched.group(1))) + return (None, -1) + + +def CheckCheck(filename, clean_lines, linenum, error): + """Checks the use of CHECK and EXPECT macros. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + + # Decide the set of replacement macros that should be suggested + lines = clean_lines.elided + (check_macro, start_pos) = FindCheckMacro(lines[linenum]) + if not check_macro: + return + + # Find end of the boolean expression by matching parentheses + (last_line, end_line, end_pos) = CloseExpression( + clean_lines, linenum, start_pos) + if end_pos < 0: + return + + # If the check macro is followed by something other than a + # semicolon, assume users will log their own custom error messages + # and don't suggest any replacements. + if not Match(r'\s*;', last_line[end_pos:]): + return + + if linenum == end_line: + expression = lines[linenum][start_pos + 1:end_pos - 1] + else: + expression = lines[linenum][start_pos + 1:] + for i in xrange(linenum + 1, end_line): + expression += lines[i] + expression += last_line[0:end_pos - 1] + + # Parse expression so that we can take parentheses into account. + # This avoids false positives for inputs like "CHECK((a < 4) == b)", + # which is not replaceable by CHECK_LE. + lhs = '' + rhs = '' + operator = None + while expression: + matched = Match(r'^\s*(<<|<<=|>>|>>=|->\*|->|&&|\|\||' + r'==|!=|>=|>|<=|<|\()(.*)$', expression) + if matched: + token = matched.group(1) + if token == '(': + # Parenthesized operand + expression = matched.group(2) + (end, _) = FindEndOfExpressionInLine(expression, 0, ['(']) + if end < 0: + return # Unmatched parenthesis + lhs += '(' + expression[0:end] + expression = expression[end:] + elif token in ('&&', '||'): + # Logical and/or operators. This means the expression + # contains more than one term, for example: + # CHECK(42 < a && a < b); + # + # These are not replaceable with CHECK_LE, so bail out early. + return + elif token in ('<<', '<<=', '>>', '>>=', '->*', '->'): + # Non-relational operator + lhs += token + expression = matched.group(2) + else: + # Relational operator + operator = token + rhs = matched.group(2) + break + else: + # Unparenthesized operand. Instead of appending to lhs one character + # at a time, we do another regular expression match to consume several + # characters at once if possible. Trivial benchmark shows that this + # is more efficient when the operands are longer than a single + # character, which is generally the case. + matched = Match(r'^([^-=!<>()&|]+)(.*)$', expression) + if not matched: + matched = Match(r'^(\s*\S)(.*)$', expression) + if not matched: + break + lhs += matched.group(1) + expression = matched.group(2) + + # Only apply checks if we got all parts of the boolean expression + if not (lhs and operator and rhs): + return + + # Check that rhs do not contain logical operators. We already know + # that lhs is fine since the loop above parses out && and ||. + if rhs.find('&&') > -1 or rhs.find('||') > -1: + return + + # At least one of the operands must be a constant literal. This is + # to avoid suggesting replacements for unprintable things like + # CHECK(variable != iterator) + # + # The following pattern matches decimal, hex integers, strings, and + # characters (in that order). + lhs = lhs.strip() + rhs = rhs.strip() + match_constant = r'^([-+]?(\d+|0[xX][0-9a-fA-F]+)[lLuU]{0,3}|".*"|\'.*\')$' + if Match(match_constant, lhs) or Match(match_constant, rhs): + # Note: since we know both lhs and rhs, we can provide a more + # descriptive error message like: + # Consider using CHECK_EQ(x, 42) instead of CHECK(x == 42) + # Instead of: + # Consider using CHECK_EQ instead of CHECK(a == b) + # + # We are still keeping the less descriptive message because if lhs + # or rhs gets long, the error message might become unreadable. + error(filename, linenum, 'readability/check', 2, + 'Consider using %s instead of %s(a %s b)' % ( + _CHECK_REPLACEMENT[check_macro][operator], + check_macro, operator)) + + +def CheckAltTokens(filename, clean_lines, linenum, error): + """Check alternative keywords being used in boolean expressions. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # Avoid preprocessor lines + if Match(r'^\s*#', line): + return + + # Last ditch effort to avoid multi-line comments. This will not help + # if the comment started before the current line or ended after the + # current line, but it catches most of the false positives. At least, + # it provides a way to workaround this warning for people who use + # multi-line comments in preprocessor macros. + # + # TODO(unknown): remove this once cpplint has better support for + # multi-line comments. + if line.find('/*') >= 0 or line.find('*/') >= 0: + return + + for match in _ALT_TOKEN_REPLACEMENT_PATTERN.finditer(line): + error(filename, linenum, 'readability/alt_tokens', 2, + 'Use operator %s instead of %s' % ( + _ALT_TOKEN_REPLACEMENT[match.group(1)], match.group(1))) + + +def GetLineWidth(line): + """Determines the width of the line in column positions. + + Args: + line: A string, which may be a Unicode string. + + Returns: + The width of the line in column positions, accounting for Unicode + combining characters and wide characters. + """ + if isinstance(line, unicode): + width = 0 + for uc in unicodedata.normalize('NFC', line): + if unicodedata.east_asian_width(uc) in ('W', 'F'): + width += 2 + elif not unicodedata.combining(uc): + width += 1 + return width + else: + return len(line) + + +def CheckStyle(filename, clean_lines, linenum, file_extension, nesting_state, + error): + """Checks rules from the 'C++ style rules' section of cppguide.html. + + Most of these rules are hard to test (naming, comment style), but we + do what we can. In particular we check for 2-space indents, line lengths, + tab usage, spaces inside code, etc. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + file_extension: The extension (without the dot) of the filename. + nesting_state: A NestingState instance which maintains information about + the current stack of nested blocks being parsed. + error: The function to call with any errors found. + """ + + # Don't use "elided" lines here, otherwise we can't check commented lines. + # Don't want to use "raw" either, because we don't want to check inside C++11 + # raw strings, + raw_lines = clean_lines.lines_without_raw_strings + line = raw_lines[linenum] + prev = raw_lines[linenum - 1] if linenum > 0 else '' + + if line.find('\t') != -1: + error(filename, linenum, 'whitespace/tab', 1, + 'Tab found; better to use spaces') + + # One or three blank spaces at the beginning of the line is weird; it's + # hard to reconcile that with 2-space indents. + # NOTE: here are the conditions rob pike used for his tests. Mine aren't + # as sophisticated, but it may be worth becoming so: RLENGTH==initial_spaces + # if(RLENGTH > 20) complain = 0; + # if(match($0, " +(error|private|public|protected):")) complain = 0; + # if(match(prev, "&& *$")) complain = 0; + # if(match(prev, "\\|\\| *$")) complain = 0; + # if(match(prev, "[\",=><] *$")) complain = 0; + # if(match($0, " <<")) complain = 0; + # if(match(prev, " +for \\(")) complain = 0; + # if(prevodd && match(prevprev, " +for \\(")) complain = 0; + scope_or_label_pattern = r'\s*\w+\s*:\s*\\?$' + classinfo = nesting_state.InnermostClass() + initial_spaces = 0 + cleansed_line = clean_lines.elided[linenum] + while initial_spaces < len(line) and line[initial_spaces] == ' ': + initial_spaces += 1 + # There are certain situations we allow one space, notably for + # section labels, and also lines containing multi-line raw strings. + # We also don't check for lines that look like continuation lines + # (of lines ending in double quotes, commas, equals, or angle brackets) + # because the rules for how to indent those are non-trivial. + if (not Search(r'[",=><] *$', prev) and + (initial_spaces == 1 or initial_spaces == 3) and + not Match(scope_or_label_pattern, cleansed_line) and + not (clean_lines.raw_lines[linenum] != line and + Match(r'^\s*""', line))): + error(filename, linenum, 'whitespace/indent', 3, + 'Weird number of spaces at line-start. ' + 'Are you using a 2-space indent?') + + if line and line[-1].isspace(): + error(filename, linenum, 'whitespace/end_of_line', 4, + 'Line ends in whitespace. Consider deleting these extra spaces.') + + # Check if the line is a header guard. + is_header_guard = False + if IsHeaderExtension(file_extension): + cppvar = GetHeaderGuardCPPVariable(filename) + if (line.startswith('#ifndef %s' % cppvar) or + line.startswith('#define %s' % cppvar) or + line.startswith('#endif // %s' % cppvar)): + is_header_guard = True + # #include lines and header guards can be long, since there's no clean way to + # split them. + # + # URLs can be long too. It's possible to split these, but it makes them + # harder to cut&paste. + # + # The "$Id:...$" comment may also get very long without it being the + # developers fault. + if (not line.startswith('#include') and not is_header_guard and + not Match(r'^\s*//.*http(s?)://\S*$', line) and + not Match(r'^\s*//\s*[^\s]*$', line) and + not Match(r'^// \$Id:.*#[0-9]+ \$$', line)): + line_width = GetLineWidth(line) + if line_width > _line_length: + error(filename, linenum, 'whitespace/line_length', 2, + 'Lines should be <= %i characters long' % _line_length) + + if (cleansed_line.count(';') > 1 and + # for loops are allowed two ;'s (and may run over two lines). + cleansed_line.find('for') == -1 and + (GetPreviousNonBlankLine(clean_lines, linenum)[0].find('for') == -1 or + GetPreviousNonBlankLine(clean_lines, linenum)[0].find(';') != -1) and + # It's ok to have many commands in a switch case that fits in 1 line + not ((cleansed_line.find('case ') != -1 or + cleansed_line.find('default:') != -1) and + cleansed_line.find('break;') != -1)): + error(filename, linenum, 'whitespace/newline', 0, + 'More than one command on the same line') + + # Some more style checks + CheckBraces(filename, clean_lines, linenum, error) + CheckTrailingSemicolon(filename, clean_lines, linenum, error) + CheckEmptyBlockBody(filename, clean_lines, linenum, error) + CheckSpacing(filename, clean_lines, linenum, nesting_state, error) + CheckOperatorSpacing(filename, clean_lines, linenum, error) + CheckParenthesisSpacing(filename, clean_lines, linenum, error) + CheckCommaSpacing(filename, clean_lines, linenum, error) + CheckBracesSpacing(filename, clean_lines, linenum, nesting_state, error) + CheckSpacingForFunctionCall(filename, clean_lines, linenum, error) + CheckCheck(filename, clean_lines, linenum, error) + CheckAltTokens(filename, clean_lines, linenum, error) + classinfo = nesting_state.InnermostClass() + if classinfo: + CheckSectionSpacing(filename, clean_lines, classinfo, linenum, error) + + +_RE_PATTERN_INCLUDE = re.compile(r'^\s*#\s*include\s*([<"])([^>"]*)[>"].*$') +# Matches the first component of a filename delimited by -s and _s. That is: +# _RE_FIRST_COMPONENT.match('foo').group(0) == 'foo' +# _RE_FIRST_COMPONENT.match('foo.cc').group(0) == 'foo' +# _RE_FIRST_COMPONENT.match('foo-bar_baz.cc').group(0) == 'foo' +# _RE_FIRST_COMPONENT.match('foo_bar-baz.cc').group(0) == 'foo' +_RE_FIRST_COMPONENT = re.compile(r'^[^-_.]+') + + +def _DropCommonSuffixes(filename): + """Drops common suffixes like _test.cc or -inl.h from filename. + + For example: + >>> _DropCommonSuffixes('foo/foo-inl.h') + 'foo/foo' + >>> _DropCommonSuffixes('foo/bar/foo.cc') + 'foo/bar/foo' + >>> _DropCommonSuffixes('foo/foo_internal.h') + 'foo/foo' + >>> _DropCommonSuffixes('foo/foo_unusualinternal.h') + 'foo/foo_unusualinternal' + + Args: + filename: The input filename. + + Returns: + The filename with the common suffix removed. + """ + for suffix in ('test.cc', 'regtest.cc', 'unittest.cc', + 'inl.h', 'impl.h', 'internal.h'): + if (filename.endswith(suffix) and len(filename) > len(suffix) and + filename[-len(suffix) - 1] in ('-', '_')): + return filename[:-len(suffix) - 1] + return os.path.splitext(filename)[0] + + +def _ClassifyInclude(fileinfo, include, is_system): + """Figures out what kind of header 'include' is. + + Args: + fileinfo: The current file cpplint is running over. A FileInfo instance. + include: The path to a #included file. + is_system: True if the #include used <> rather than "". + + Returns: + One of the _XXX_HEADER constants. + + For example: + >>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'stdio.h', True) + _C_SYS_HEADER + >>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'string', True) + _CPP_SYS_HEADER + >>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'foo/foo.h', False) + _LIKELY_MY_HEADER + >>> _ClassifyInclude(FileInfo('foo/foo_unknown_extension.cc'), + ... 'bar/foo_other_ext.h', False) + _POSSIBLE_MY_HEADER + >>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'foo/bar.h', False) + _OTHER_HEADER + """ + # This is a list of all standard c++ header files, except + # those already checked for above. + is_cpp_h = include in _CPP_HEADERS + + if is_system: + if is_cpp_h: + return _CPP_SYS_HEADER + else: + return _C_SYS_HEADER + + # If the target file and the include we're checking share a + # basename when we drop common extensions, and the include + # lives in . , then it's likely to be owned by the target file. + target_dir, target_base = ( + os.path.split(_DropCommonSuffixes(fileinfo.RepositoryName()))) + include_dir, include_base = os.path.split(_DropCommonSuffixes(include)) + if target_base == include_base and ( + include_dir == target_dir or + include_dir == os.path.normpath(target_dir + '/../public')): + return _LIKELY_MY_HEADER + + # If the target and include share some initial basename + # component, it's possible the target is implementing the + # include, so it's allowed to be first, but we'll never + # complain if it's not there. + target_first_component = _RE_FIRST_COMPONENT.match(target_base) + include_first_component = _RE_FIRST_COMPONENT.match(include_base) + if (target_first_component and include_first_component and + target_first_component.group(0) == + include_first_component.group(0)): + return _POSSIBLE_MY_HEADER + + return _OTHER_HEADER + + + +def CheckIncludeLine(filename, clean_lines, linenum, include_state, error): + """Check rules that are applicable to #include lines. + + Strings on #include lines are NOT removed from elided line, to make + certain tasks easier. However, to prevent false positives, checks + applicable to #include lines in CheckLanguage must be put here. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + include_state: An _IncludeState instance in which the headers are inserted. + error: The function to call with any errors found. + """ + fileinfo = FileInfo(filename) + line = clean_lines.lines[linenum] + + # "include" should use the new style "foo/bar.h" instead of just "bar.h" + # Only do this check if the included header follows google naming + # conventions. If not, assume that it's a 3rd party API that + # requires special include conventions. + # + # We also make an exception for Lua headers, which follow google + # naming convention but not the include convention. + match = Match(r'#include\s*"([^/]+\.h)"', line) + if match and not _THIRD_PARTY_HEADERS_PATTERN.match(match.group(1)): + error(filename, linenum, 'build/include', 4, + 'Include the directory when naming .h files') + + # we shouldn't include a file more than once. actually, there are a + # handful of instances where doing so is okay, but in general it's + # not. + match = _RE_PATTERN_INCLUDE.search(line) + if match: + include = match.group(2) + is_system = (match.group(1) == '<') + duplicate_line = include_state.FindHeader(include) + if duplicate_line >= 0: + error(filename, linenum, 'build/include', 4, + '"%s" already included at %s:%s' % + (include, filename, duplicate_line)) + elif (include.endswith('.cc') and + os.path.dirname(fileinfo.RepositoryName()) != os.path.dirname(include)): + error(filename, linenum, 'build/include', 4, + 'Do not include .cc files from other packages') + elif not _THIRD_PARTY_HEADERS_PATTERN.match(include): + include_state.include_list[-1].append((include, linenum)) + + # We want to ensure that headers appear in the right order: + # 1) for foo.cc, foo.h (preferred location) + # 2) c system files + # 3) cpp system files + # 4) for foo.cc, foo.h (deprecated location) + # 5) other google headers + # + # We classify each include statement as one of those 5 types + # using a number of techniques. The include_state object keeps + # track of the highest type seen, and complains if we see a + # lower type after that. + error_message = include_state.CheckNextIncludeOrder( + _ClassifyInclude(fileinfo, include, is_system)) + if error_message: + error(filename, linenum, 'build/include_order', 4, + '%s. Should be: %s.h, c system, c++ system, other.' % + (error_message, fileinfo.BaseName())) + canonical_include = include_state.CanonicalizeAlphabeticalOrder(include) + if not include_state.IsInAlphabeticalOrder( + clean_lines, linenum, canonical_include): + error(filename, linenum, 'build/include_alpha', 4, + 'Include "%s" not in alphabetical order' % include) + include_state.SetLastHeader(canonical_include) + + + +def _GetTextInside(text, start_pattern): + r"""Retrieves all the text between matching open and close parentheses. + + Given a string of lines and a regular expression string, retrieve all the text + following the expression and between opening punctuation symbols like + (, [, or {, and the matching close-punctuation symbol. This properly nested + occurrences of the punctuations, so for the text like + printf(a(), b(c())); + a call to _GetTextInside(text, r'printf\(') will return 'a(), b(c())'. + start_pattern must match string having an open punctuation symbol at the end. + + Args: + text: The lines to extract text. Its comments and strings must be elided. + It can be single line and can span multiple lines. + start_pattern: The regexp string indicating where to start extracting + the text. + Returns: + The extracted text. + None if either the opening string or ending punctuation could not be found. + """ + # TODO(unknown): Audit cpplint.py to see what places could be profitably + # rewritten to use _GetTextInside (and use inferior regexp matching today). + + # Give opening punctuations to get the matching close-punctuations. + matching_punctuation = {'(': ')', '{': '}', '[': ']'} + closing_punctuation = set(matching_punctuation.itervalues()) + + # Find the position to start extracting text. + match = re.search(start_pattern, text, re.M) + if not match: # start_pattern not found in text. + return None + start_position = match.end(0) + + assert start_position > 0, ( + 'start_pattern must ends with an opening punctuation.') + assert text[start_position - 1] in matching_punctuation, ( + 'start_pattern must ends with an opening punctuation.') + # Stack of closing punctuations we expect to have in text after position. + punctuation_stack = [matching_punctuation[text[start_position - 1]]] + position = start_position + while punctuation_stack and position < len(text): + if text[position] == punctuation_stack[-1]: + punctuation_stack.pop() + elif text[position] in closing_punctuation: + # A closing punctuation without matching opening punctuations. + return None + elif text[position] in matching_punctuation: + punctuation_stack.append(matching_punctuation[text[position]]) + position += 1 + if punctuation_stack: + # Opening punctuations left without matching close-punctuations. + return None + # punctuations match. + return text[start_position:position - 1] + + +# Patterns for matching call-by-reference parameters. +# +# Supports nested templates up to 2 levels deep using this messy pattern: +# < (?: < (?: < [^<>]* +# > +# | [^<>] )* +# > +# | [^<>] )* +# > +_RE_PATTERN_IDENT = r'[_a-zA-Z]\w*' # =~ [[:alpha:]][[:alnum:]]* +_RE_PATTERN_TYPE = ( + r'(?:const\s+)?(?:typename\s+|class\s+|struct\s+|union\s+|enum\s+)?' + r'(?:\w|' + r'\s*<(?:<(?:<[^<>]*>|[^<>])*>|[^<>])*>|' + r'::)+') +# A call-by-reference parameter ends with '& identifier'. +_RE_PATTERN_REF_PARAM = re.compile( + r'(' + _RE_PATTERN_TYPE + r'(?:\s*(?:\bconst\b|[*]))*\s*' + r'&\s*' + _RE_PATTERN_IDENT + r')\s*(?:=[^,()]+)?[,)]') +# A call-by-const-reference parameter either ends with 'const& identifier' +# or looks like 'const type& identifier' when 'type' is atomic. +_RE_PATTERN_CONST_REF_PARAM = ( + r'(?:.*\s*\bconst\s*&\s*' + _RE_PATTERN_IDENT + + r'|const\s+' + _RE_PATTERN_TYPE + r'\s*&\s*' + _RE_PATTERN_IDENT + r')') +# Stream types. +_RE_PATTERN_REF_STREAM_PARAM = ( + r'(?:.*stream\s*&\s*' + _RE_PATTERN_IDENT + r')') + + +def CheckLanguage(filename, clean_lines, linenum, file_extension, + include_state, nesting_state, error): + """Checks rules from the 'C++ language rules' section of cppguide.html. + + Some of these rules are hard to test (function overloading, using + uint32 inappropriately), but we do the best we can. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + file_extension: The extension (without the dot) of the filename. + include_state: An _IncludeState instance in which the headers are inserted. + nesting_state: A NestingState instance which maintains information about + the current stack of nested blocks being parsed. + error: The function to call with any errors found. + """ + # If the line is empty or consists of entirely a comment, no need to + # check it. + line = clean_lines.elided[linenum] + if not line: + return + + match = _RE_PATTERN_INCLUDE.search(line) + if match: + CheckIncludeLine(filename, clean_lines, linenum, include_state, error) + return + + # Reset include state across preprocessor directives. This is meant + # to silence warnings for conditional includes. + match = Match(r'^\s*#\s*(if|ifdef|ifndef|elif|else|endif)\b', line) + if match: + include_state.ResetSection(match.group(1)) + + # Make Windows paths like Unix. + fullname = os.path.abspath(filename).replace('\\', '/') + + # Perform other checks now that we are sure that this is not an include line + CheckCasts(filename, clean_lines, linenum, error) + CheckGlobalStatic(filename, clean_lines, linenum, error) + CheckPrintf(filename, clean_lines, linenum, error) + + if IsHeaderExtension(file_extension): + # TODO(unknown): check that 1-arg constructors are explicit. + # How to tell it's a constructor? + # (handled in CheckForNonStandardConstructs for now) + # TODO(unknown): check that classes declare or disable copy/assign + # (level 1 error) + pass + + # Check if people are using the verboten C basic types. The only exception + # we regularly allow is "unsigned short port" for port. + if Search(r'\bshort port\b', line): + if not Search(r'\bunsigned short port\b', line): + error(filename, linenum, 'runtime/int', 4, + 'Use "unsigned short" for ports, not "short"') + else: + match = Search(r'\b(short|long(?! +double)|long long)\b', line) + if match: + error(filename, linenum, 'runtime/int', 4, + 'Use int16/int64/etc, rather than the C type %s' % match.group(1)) + + # Check if some verboten operator overloading is going on + # TODO(unknown): catch out-of-line unary operator&: + # class X {}; + # int operator&(const X& x) { return 42; } // unary operator& + # The trick is it's hard to tell apart from binary operator&: + # class Y { int operator&(const Y& x) { return 23; } }; // binary operator& + if Search(r'\boperator\s*&\s*\(\s*\)', line): + error(filename, linenum, 'runtime/operator', 4, + 'Unary operator& is dangerous. Do not use it.') + + # Check for suspicious usage of "if" like + # } if (a == b) { + if Search(r'\}\s*if\s*\(', line): + error(filename, linenum, 'readability/braces', 4, + 'Did you mean "else if"? If not, start a new line for "if".') + + # Check for potential format string bugs like printf(foo). + # We constrain the pattern not to pick things like DocidForPrintf(foo). + # Not perfect but it can catch printf(foo.c_str()) and printf(foo->c_str()) + # TODO(unknown): Catch the following case. Need to change the calling + # convention of the whole function to process multiple line to handle it. + # printf( + # boy_this_is_a_really_long_variable_that_cannot_fit_on_the_prev_line); + printf_args = _GetTextInside(line, r'(?i)\b(string)?printf\s*\(') + if printf_args: + match = Match(r'([\w.\->()]+)$', printf_args) + if match and match.group(1) != '__VA_ARGS__': + function_name = re.search(r'\b((?:string)?printf)\s*\(', + line, re.I).group(1) + error(filename, linenum, 'runtime/printf', 4, + 'Potential format string bug. Do %s("%%s", %s) instead.' + % (function_name, match.group(1))) + + # Check for potential memset bugs like memset(buf, sizeof(buf), 0). + match = Search(r'memset\s*\(([^,]*),\s*([^,]*),\s*0\s*\)', line) + if match and not Match(r"^''|-?[0-9]+|0x[0-9A-Fa-f]$", match.group(2)): + error(filename, linenum, 'runtime/memset', 4, + 'Did you mean "memset(%s, 0, %s)"?' + % (match.group(1), match.group(2))) + + if Search(r'\busing namespace\b', line): + error(filename, linenum, 'build/namespaces', 5, + 'Do not use namespace using-directives. ' + 'Use using-declarations instead.') + + # Detect variable-length arrays. + match = Match(r'\s*(.+::)?(\w+) [a-z]\w*\[(.+)];', line) + if (match and match.group(2) != 'return' and match.group(2) != 'delete' and + match.group(3).find(']') == -1): + # Split the size using space and arithmetic operators as delimiters. + # If any of the resulting tokens are not compile time constants then + # report the error. + tokens = re.split(r'\s|\+|\-|\*|\/|<<|>>]', match.group(3)) + is_const = True + skip_next = False + for tok in tokens: + if skip_next: + skip_next = False + continue + + if Search(r'sizeof\(.+\)', tok): continue + if Search(r'arraysize\(\w+\)', tok): continue + + tok = tok.lstrip('(') + tok = tok.rstrip(')') + if not tok: continue + if Match(r'\d+', tok): continue + if Match(r'0[xX][0-9a-fA-F]+', tok): continue + if Match(r'k[A-Z0-9]\w*', tok): continue + if Match(r'(.+::)?k[A-Z0-9]\w*', tok): continue + if Match(r'(.+::)?[A-Z][A-Z0-9_]*', tok): continue + # A catch all for tricky sizeof cases, including 'sizeof expression', + # 'sizeof(*type)', 'sizeof(const type)', 'sizeof(struct StructName)' + # requires skipping the next token because we split on ' ' and '*'. + if tok.startswith('sizeof'): + skip_next = True + continue + is_const = False + break + if not is_const: + error(filename, linenum, 'runtime/arrays', 1, + 'Do not use variable-length arrays. Use an appropriately named ' + "('k' followed by CamelCase) compile-time constant for the size.") + + # Check for use of unnamed namespaces in header files. Registration + # macros are typically OK, so we allow use of "namespace {" on lines + # that end with backslashes. + if (IsHeaderExtension(file_extension) + and Search(r'\bnamespace\s*{', line) + and line[-1] != '\\'): + error(filename, linenum, 'build/namespaces', 4, + 'Do not use unnamed namespaces in header files. See ' + 'https://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Namespaces' + ' for more information.') + + +def CheckGlobalStatic(filename, clean_lines, linenum, error): + """Check for unsafe global or static objects. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # Match two lines at a time to support multiline declarations + if linenum + 1 < clean_lines.NumLines() and not Search(r'[;({]', line): + line += clean_lines.elided[linenum + 1].strip() + + # Check for people declaring static/global STL strings at the top level. + # This is dangerous because the C++ language does not guarantee that + # globals with constructors are initialized before the first access, and + # also because globals can be destroyed when some threads are still running. + # TODO(unknown): Generalize this to also find static unique_ptr instances. + # TODO(unknown): File bugs for clang-tidy to find these. + match = Match( + r'((?:|static +)(?:|const +))(?::*std::)?string( +const)? +' + r'([a-zA-Z0-9_:]+)\b(.*)', + line) + + # Remove false positives: + # - String pointers (as opposed to values). + # string *pointer + # const string *pointer + # string const *pointer + # string *const pointer + # + # - Functions and template specializations. + # string Function(... + # string Class::Method(... + # + # - Operators. These are matched separately because operator names + # cross non-word boundaries, and trying to match both operators + # and functions at the same time would decrease accuracy of + # matching identifiers. + # string Class::operator*() + if (match and + not Search(r'\bstring\b(\s+const)?\s*[\*\&]\s*(const\s+)?\w', line) and + not Search(r'\boperator\W', line) and + not Match(r'\s*(<.*>)?(::[a-zA-Z0-9_]+)*\s*\(([^"]|$)', match.group(4))): + if Search(r'\bconst\b', line): + error(filename, linenum, 'runtime/string', 4, + 'For a static/global string constant, use a C style string ' + 'instead: "%schar%s %s[]".' % + (match.group(1), match.group(2) or '', match.group(3))) + else: + error(filename, linenum, 'runtime/string', 4, + 'Static/global string variables are not permitted.') + + if (Search(r'\b([A-Za-z0-9_]*_)\(\1\)', line) or + Search(r'\b([A-Za-z0-9_]*_)\(CHECK_NOTNULL\(\1\)\)', line)): + error(filename, linenum, 'runtime/init', 4, + 'You seem to be initializing a member variable with itself.') + + +def CheckPrintf(filename, clean_lines, linenum, error): + """Check for printf related issues. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # When snprintf is used, the second argument shouldn't be a literal. + match = Search(r'snprintf\s*\(([^,]*),\s*([0-9]*)\s*,', line) + if match and match.group(2) != '0': + # If 2nd arg is zero, snprintf is used to calculate size. + error(filename, linenum, 'runtime/printf', 3, + 'If you can, use sizeof(%s) instead of %s as the 2nd arg ' + 'to snprintf.' % (match.group(1), match.group(2))) + + # Check if some verboten C functions are being used. + if Search(r'\bsprintf\s*\(', line): + error(filename, linenum, 'runtime/printf', 5, + 'Never use sprintf. Use snprintf instead.') + match = Search(r'\b(strcpy|strcat)\s*\(', line) + if match: + error(filename, linenum, 'runtime/printf', 4, + 'Almost always, snprintf is better than %s' % match.group(1)) + + +def IsDerivedFunction(clean_lines, linenum): + """Check if current line contains an inherited function. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + Returns: + True if current line contains a function with "override" + virt-specifier. + """ + # Scan back a few lines for start of current function + for i in xrange(linenum, max(-1, linenum - 10), -1): + match = Match(r'^([^()]*\w+)\(', clean_lines.elided[i]) + if match: + # Look for "override" after the matching closing parenthesis + line, _, closing_paren = CloseExpression( + clean_lines, i, len(match.group(1))) + return (closing_paren >= 0 and + Search(r'\boverride\b', line[closing_paren:])) + return False + + +def IsOutOfLineMethodDefinition(clean_lines, linenum): + """Check if current line contains an out-of-line method definition. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + Returns: + True if current line contains an out-of-line method definition. + """ + # Scan back a few lines for start of current function + for i in xrange(linenum, max(-1, linenum - 10), -1): + if Match(r'^([^()]*\w+)\(', clean_lines.elided[i]): + return Match(r'^[^()]*\w+::\w+\(', clean_lines.elided[i]) is not None + return False + + +def IsInitializerList(clean_lines, linenum): + """Check if current line is inside constructor initializer list. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + Returns: + True if current line appears to be inside constructor initializer + list, False otherwise. + """ + for i in xrange(linenum, 1, -1): + line = clean_lines.elided[i] + if i == linenum: + remove_function_body = Match(r'^(.*)\{\s*$', line) + if remove_function_body: + line = remove_function_body.group(1) + + if Search(r'\s:\s*\w+[({]', line): + # A lone colon tend to indicate the start of a constructor + # initializer list. It could also be a ternary operator, which + # also tend to appear in constructor initializer lists as + # opposed to parameter lists. + return True + if Search(r'\}\s*,\s*$', line): + # A closing brace followed by a comma is probably the end of a + # brace-initialized member in constructor initializer list. + return True + if Search(r'[{};]\s*$', line): + # Found one of the following: + # - A closing brace or semicolon, probably the end of the previous + # function. + # - An opening brace, probably the start of current class or namespace. + # + # Current line is probably not inside an initializer list since + # we saw one of those things without seeing the starting colon. + return False + + # Got to the beginning of the file without seeing the start of + # constructor initializer list. + return False + + +def CheckForNonConstReference(filename, clean_lines, linenum, + nesting_state, error): + """Check for non-const references. + + Separate from CheckLanguage since it scans backwards from current + line, instead of scanning forward. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + nesting_state: A NestingState instance which maintains information about + the current stack of nested blocks being parsed. + error: The function to call with any errors found. + """ + # Do nothing if there is no '&' on current line. + line = clean_lines.elided[linenum] + if '&' not in line: + return + + # If a function is inherited, current function doesn't have much of + # a choice, so any non-const references should not be blamed on + # derived function. + if IsDerivedFunction(clean_lines, linenum): + return + + # Don't warn on out-of-line method definitions, as we would warn on the + # in-line declaration, if it isn't marked with 'override'. + if IsOutOfLineMethodDefinition(clean_lines, linenum): + return + + # Long type names may be broken across multiple lines, usually in one + # of these forms: + # LongType + # ::LongTypeContinued &identifier + # LongType:: + # LongTypeContinued &identifier + # LongType< + # ...>::LongTypeContinued &identifier + # + # If we detected a type split across two lines, join the previous + # line to current line so that we can match const references + # accordingly. + # + # Note that this only scans back one line, since scanning back + # arbitrary number of lines would be expensive. If you have a type + # that spans more than 2 lines, please use a typedef. + if linenum > 1: + previous = None + if Match(r'\s*::(?:[\w<>]|::)+\s*&\s*\S', line): + # previous_line\n + ::current_line + previous = Search(r'\b((?:const\s*)?(?:[\w<>]|::)+[\w<>])\s*$', + clean_lines.elided[linenum - 1]) + elif Match(r'\s*[a-zA-Z_]([\w<>]|::)+\s*&\s*\S', line): + # previous_line::\n + current_line + previous = Search(r'\b((?:const\s*)?(?:[\w<>]|::)+::)\s*$', + clean_lines.elided[linenum - 1]) + if previous: + line = previous.group(1) + line.lstrip() + else: + # Check for templated parameter that is split across multiple lines + endpos = line.rfind('>') + if endpos > -1: + (_, startline, startpos) = ReverseCloseExpression( + clean_lines, linenum, endpos) + if startpos > -1 and startline < linenum: + # Found the matching < on an earlier line, collect all + # pieces up to current line. + line = '' + for i in xrange(startline, linenum + 1): + line += clean_lines.elided[i].strip() + + # Check for non-const references in function parameters. A single '&' may + # found in the following places: + # inside expression: binary & for bitwise AND + # inside expression: unary & for taking the address of something + # inside declarators: reference parameter + # We will exclude the first two cases by checking that we are not inside a + # function body, including one that was just introduced by a trailing '{'. + # TODO(unknown): Doesn't account for 'catch(Exception& e)' [rare]. + if (nesting_state.previous_stack_top and + not (isinstance(nesting_state.previous_stack_top, _ClassInfo) or + isinstance(nesting_state.previous_stack_top, _NamespaceInfo))): + # Not at toplevel, not within a class, and not within a namespace + return + + # Avoid initializer lists. We only need to scan back from the + # current line for something that starts with ':'. + # + # We don't need to check the current line, since the '&' would + # appear inside the second set of parentheses on the current line as + # opposed to the first set. + if linenum > 0: + for i in xrange(linenum - 1, max(0, linenum - 10), -1): + previous_line = clean_lines.elided[i] + if not Search(r'[),]\s*$', previous_line): + break + if Match(r'^\s*:\s+\S', previous_line): + return + + # Avoid preprocessors + if Search(r'\\\s*$', line): + return + + # Avoid constructor initializer lists + if IsInitializerList(clean_lines, linenum): + return + + # We allow non-const references in a few standard places, like functions + # called "swap()" or iostream operators like "<<" or ">>". Do not check + # those function parameters. + # + # We also accept & in static_assert, which looks like a function but + # it's actually a declaration expression. + whitelisted_functions = (r'(?:[sS]wap(?:<\w:+>)?|' + r'operator\s*[<>][<>]|' + r'static_assert|COMPILE_ASSERT' + r')\s*\(') + if Search(whitelisted_functions, line): + return + elif not Search(r'\S+\([^)]*$', line): + # Don't see a whitelisted function on this line. Actually we + # didn't see any function name on this line, so this is likely a + # multi-line parameter list. Try a bit harder to catch this case. + for i in xrange(2): + if (linenum > i and + Search(whitelisted_functions, clean_lines.elided[linenum - i - 1])): + return + + decls = ReplaceAll(r'{[^}]*}', ' ', line) # exclude function body + for parameter in re.findall(_RE_PATTERN_REF_PARAM, decls): + if (not Match(_RE_PATTERN_CONST_REF_PARAM, parameter) and + not Match(_RE_PATTERN_REF_STREAM_PARAM, parameter)): + error(filename, linenum, 'runtime/references', 2, + 'Is this a non-const reference? ' + 'If so, make const or use a pointer: ' + + ReplaceAll(' *<', '<', parameter)) + + +def CheckCasts(filename, clean_lines, linenum, error): + """Various cast related checks. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # Check to see if they're using an conversion function cast. + # I just try to capture the most common basic types, though there are more. + # Parameterless conversion functions, such as bool(), are allowed as they are + # probably a member operator declaration or default constructor. + match = Search( + r'(\bnew\s+(?:const\s+)?|\S<\s*(?:const\s+)?)?\b' + r'(int|float|double|bool|char|int32|uint32|int64|uint64)' + r'(\([^)].*)', line) + expecting_function = ExpectingFunctionArgs(clean_lines, linenum) + if match and not expecting_function: + matched_type = match.group(2) + + # matched_new_or_template is used to silence two false positives: + # - New operators + # - Template arguments with function types + # + # For template arguments, we match on types immediately following + # an opening bracket without any spaces. This is a fast way to + # silence the common case where the function type is the first + # template argument. False negative with less-than comparison is + # avoided because those operators are usually followed by a space. + # + # function // bracket + no space = false positive + # value < double(42) // bracket + space = true positive + matched_new_or_template = match.group(1) + + # Avoid arrays by looking for brackets that come after the closing + # parenthesis. + if Match(r'\([^()]+\)\s*\[', match.group(3)): + return + + # Other things to ignore: + # - Function pointers + # - Casts to pointer types + # - Placement new + # - Alias declarations + matched_funcptr = match.group(3) + if (matched_new_or_template is None and + not (matched_funcptr and + (Match(r'\((?:[^() ]+::\s*\*\s*)?[^() ]+\)\s*\(', + matched_funcptr) or + matched_funcptr.startswith('(*)'))) and + not Match(r'\s*using\s+\S+\s*=\s*' + matched_type, line) and + not Search(r'new\(\S+\)\s*' + matched_type, line)): + error(filename, linenum, 'readability/casting', 4, + 'Using deprecated casting style. ' + 'Use static_cast<%s>(...) instead' % + matched_type) + + if not expecting_function: + CheckCStyleCast(filename, clean_lines, linenum, 'static_cast', + r'\((int|float|double|bool|char|u?int(16|32|64))\)', error) + + # This doesn't catch all cases. Consider (const char * const)"hello". + # + # (char *) "foo" should always be a const_cast (reinterpret_cast won't + # compile). + if CheckCStyleCast(filename, clean_lines, linenum, 'const_cast', + r'\((char\s?\*+\s?)\)\s*"', error): + pass + else: + # Check pointer casts for other than string constants + CheckCStyleCast(filename, clean_lines, linenum, 'reinterpret_cast', + r'\((\w+\s?\*+\s?)\)', error) + + # In addition, we look for people taking the address of a cast. This + # is dangerous -- casts can assign to temporaries, so the pointer doesn't + # point where you think. + # + # Some non-identifier character is required before the '&' for the + # expression to be recognized as a cast. These are casts: + # expression = &static_cast(temporary()); + # function(&(int*)(temporary())); + # + # This is not a cast: + # reference_type&(int* function_param); + match = Search( + r'(?:[^\w]&\(([^)*][^)]*)\)[\w(])|' + r'(?:[^\w]&(static|dynamic|down|reinterpret)_cast\b)', line) + if match: + # Try a better error message when the & is bound to something + # dereferenced by the casted pointer, as opposed to the casted + # pointer itself. + parenthesis_error = False + match = Match(r'^(.*&(?:static|dynamic|down|reinterpret)_cast\b)<', line) + if match: + _, y1, x1 = CloseExpression(clean_lines, linenum, len(match.group(1))) + if x1 >= 0 and clean_lines.elided[y1][x1] == '(': + _, y2, x2 = CloseExpression(clean_lines, y1, x1) + if x2 >= 0: + extended_line = clean_lines.elided[y2][x2:] + if y2 < clean_lines.NumLines() - 1: + extended_line += clean_lines.elided[y2 + 1] + if Match(r'\s*(?:->|\[)', extended_line): + parenthesis_error = True + + if parenthesis_error: + error(filename, linenum, 'readability/casting', 4, + ('Are you taking an address of something dereferenced ' + 'from a cast? Wrapping the dereferenced expression in ' + 'parentheses will make the binding more obvious')) + else: + error(filename, linenum, 'runtime/casting', 4, + ('Are you taking an address of a cast? ' + 'This is dangerous: could be a temp var. ' + 'Take the address before doing the cast, rather than after')) + + +def CheckCStyleCast(filename, clean_lines, linenum, cast_type, pattern, error): + """Checks for a C-style cast by looking for the pattern. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + cast_type: The string for the C++ cast to recommend. This is either + reinterpret_cast, static_cast, or const_cast, depending. + pattern: The regular expression used to find C-style casts. + error: The function to call with any errors found. + + Returns: + True if an error was emitted. + False otherwise. + """ + line = clean_lines.elided[linenum] + match = Search(pattern, line) + if not match: + return False + + # Exclude lines with keywords that tend to look like casts + context = line[0:match.start(1) - 1] + if Match(r'.*\b(?:sizeof|alignof|alignas|[_A-Z][_A-Z0-9]*)\s*$', context): + return False + + # Try expanding current context to see if we one level of + # parentheses inside a macro. + if linenum > 0: + for i in xrange(linenum - 1, max(0, linenum - 5), -1): + context = clean_lines.elided[i] + context + if Match(r'.*\b[_A-Z][_A-Z0-9]*\s*\((?:\([^()]*\)|[^()])*$', context): + return False + + # operator++(int) and operator--(int) + if context.endswith(' operator++') or context.endswith(' operator--'): + return False + + # A single unnamed argument for a function tends to look like old style cast. + # If we see those, don't issue warnings for deprecated casts. + remainder = line[match.end(0):] + if Match(r'^\s*(?:;|const\b|throw\b|final\b|override\b|[=>{),]|->)', + remainder): + return False + + # At this point, all that should be left is actual casts. + error(filename, linenum, 'readability/casting', 4, + 'Using C-style cast. Use %s<%s>(...) instead' % + (cast_type, match.group(1))) + + return True + + +def ExpectingFunctionArgs(clean_lines, linenum): + """Checks whether where function type arguments are expected. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + + Returns: + True if the line at 'linenum' is inside something that expects arguments + of function types. + """ + line = clean_lines.elided[linenum] + return (Match(r'^\s*MOCK_(CONST_)?METHOD\d+(_T)?\(', line) or + (linenum >= 2 and + (Match(r'^\s*MOCK_(?:CONST_)?METHOD\d+(?:_T)?\((?:\S+,)?\s*$', + clean_lines.elided[linenum - 1]) or + Match(r'^\s*MOCK_(?:CONST_)?METHOD\d+(?:_T)?\(\s*$', + clean_lines.elided[linenum - 2]) or + Search(r'\bstd::m?function\s*\<\s*$', + clean_lines.elided[linenum - 1])))) + + +_HEADERS_CONTAINING_TEMPLATES = ( + ('', ('deque',)), + ('', ('unary_function', 'binary_function', + 'plus', 'minus', 'multiplies', 'divides', 'modulus', + 'negate', + 'equal_to', 'not_equal_to', 'greater', 'less', + 'greater_equal', 'less_equal', + 'logical_and', 'logical_or', 'logical_not', + 'unary_negate', 'not1', 'binary_negate', 'not2', + 'bind1st', 'bind2nd', + 'pointer_to_unary_function', + 'pointer_to_binary_function', + 'ptr_fun', + 'mem_fun_t', 'mem_fun', 'mem_fun1_t', 'mem_fun1_ref_t', + 'mem_fun_ref_t', + 'const_mem_fun_t', 'const_mem_fun1_t', + 'const_mem_fun_ref_t', 'const_mem_fun1_ref_t', + 'mem_fun_ref', + )), + ('', ('numeric_limits',)), + ('', ('list',)), + ('', ('map', 'multimap',)), + ('', ('allocator', 'make_shared', 'make_unique', 'shared_ptr', + 'unique_ptr', 'weak_ptr')), + ('', ('queue', 'priority_queue',)), + ('', ('set', 'multiset',)), + ('', ('stack',)), + ('', ('char_traits', 'basic_string',)), + ('', ('tuple',)), + ('', ('unordered_map', 'unordered_multimap')), + ('', ('unordered_set', 'unordered_multiset')), + ('', ('pair',)), + ('', ('vector',)), + + # gcc extensions. + # Note: std::hash is their hash, ::hash is our hash + ('', ('hash_map', 'hash_multimap',)), + ('', ('hash_set', 'hash_multiset',)), + ('', ('slist',)), + ) + +_HEADERS_MAYBE_TEMPLATES = ( + ('', ('copy', 'max', 'min', 'min_element', 'sort', + 'transform', + )), + ('', ('forward', 'make_pair', 'move', 'swap')), + ) + +_RE_PATTERN_STRING = re.compile(r'\bstring\b') + +_re_pattern_headers_maybe_templates = [] +for _header, _templates in _HEADERS_MAYBE_TEMPLATES: + for _template in _templates: + # Match max(..., ...), max(..., ...), but not foo->max, foo.max or + # type::max(). + _re_pattern_headers_maybe_templates.append( + (re.compile(r'[^>.]\b' + _template + r'(<.*?>)?\([^\)]'), + _template, + _header)) + +# Other scripts may reach in and modify this pattern. +_re_pattern_templates = [] +for _header, _templates in _HEADERS_CONTAINING_TEMPLATES: + for _template in _templates: + _re_pattern_templates.append( + (re.compile(r'(\<|\b)' + _template + r'\s*\<'), + _template + '<>', + _header)) + + +def FilesBelongToSameModule(filename_cc, filename_h): + """Check if these two filenames belong to the same module. + + The concept of a 'module' here is a as follows: + foo.h, foo-inl.h, foo.cc, foo_test.cc and foo_unittest.cc belong to the + same 'module' if they are in the same directory. + some/path/public/xyzzy and some/path/internal/xyzzy are also considered + to belong to the same module here. + + If the filename_cc contains a longer path than the filename_h, for example, + '/absolute/path/to/base/sysinfo.cc', and this file would include + 'base/sysinfo.h', this function also produces the prefix needed to open the + header. This is used by the caller of this function to more robustly open the + header file. We don't have access to the real include paths in this context, + so we need this guesswork here. + + Known bugs: tools/base/bar.cc and base/bar.h belong to the same module + according to this implementation. Because of this, this function gives + some false positives. This should be sufficiently rare in practice. + + Args: + filename_cc: is the path for the .cc file + filename_h: is the path for the header path + + Returns: + Tuple with a bool and a string: + bool: True if filename_cc and filename_h belong to the same module. + string: the additional prefix needed to open the header file. + """ + + fileinfo = FileInfo(filename_cc) + if not fileinfo.IsSource(): + return (False, '') + filename_cc = filename_cc[:-len(fileinfo.Extension())] + matched_test_suffix = Search(_TEST_FILE_SUFFIX, fileinfo.BaseName()) + if matched_test_suffix: + filename_cc = filename_cc[:-len(matched_test_suffix.group(1))] + filename_cc = filename_cc.replace('/public/', '/') + filename_cc = filename_cc.replace('/internal/', '/') + + if not filename_h.endswith('.h'): + return (False, '') + filename_h = filename_h[:-len('.h')] + if filename_h.endswith('-inl'): + filename_h = filename_h[:-len('-inl')] + filename_h = filename_h.replace('/public/', '/') + filename_h = filename_h.replace('/internal/', '/') + + files_belong_to_same_module = filename_cc.endswith(filename_h) + common_path = '' + if files_belong_to_same_module: + common_path = filename_cc[:-len(filename_h)] + return files_belong_to_same_module, common_path + + +def UpdateIncludeState(filename, include_dict, io=codecs): + """Fill up the include_dict with new includes found from the file. + + Args: + filename: the name of the header to read. + include_dict: a dictionary in which the headers are inserted. + io: The io factory to use to read the file. Provided for testability. + + Returns: + True if a header was successfully added. False otherwise. + """ + headerfile = None + try: + headerfile = io.open(filename, 'r', 'utf8', 'replace') + except IOError: + return False + linenum = 0 + for line in headerfile: + linenum += 1 + clean_line = CleanseComments(line) + match = _RE_PATTERN_INCLUDE.search(clean_line) + if match: + include = match.group(2) + include_dict.setdefault(include, linenum) + return True + + +def CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error, + io=codecs): + """Reports for missing stl includes. + + This function will output warnings to make sure you are including the headers + necessary for the stl containers and functions that you use. We only give one + reason to include a header. For example, if you use both equal_to<> and + less<> in a .h file, only one (the latter in the file) of these will be + reported as a reason to include the . + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + include_state: An _IncludeState instance. + error: The function to call with any errors found. + io: The IO factory to use to read the header file. Provided for unittest + injection. + """ + required = {} # A map of header name to linenumber and the template entity. + # Example of required: { '': (1219, 'less<>') } + + for linenum in xrange(clean_lines.NumLines()): + line = clean_lines.elided[linenum] + if not line or line[0] == '#': + continue + + # String is special -- it is a non-templatized type in STL. + matched = _RE_PATTERN_STRING.search(line) + if matched: + # Don't warn about strings in non-STL namespaces: + # (We check only the first match per line; good enough.) + prefix = line[:matched.start()] + if prefix.endswith('std::') or not prefix.endswith('::'): + required[''] = (linenum, 'string') + + for pattern, template, header in _re_pattern_headers_maybe_templates: + if pattern.search(line): + required[header] = (linenum, template) + + # The following function is just a speed up, no semantics are changed. + if not '<' in line: # Reduces the cpu time usage by skipping lines. + continue + + for pattern, template, header in _re_pattern_templates: + matched = pattern.search(line) + if matched: + # Don't warn about IWYU in non-STL namespaces: + # (We check only the first match per line; good enough.) + prefix = line[:matched.start()] + if prefix.endswith('std::') or not prefix.endswith('::'): + required[header] = (linenum, template) + + # The policy is that if you #include something in foo.h you don't need to + # include it again in foo.cc. Here, we will look at possible includes. + # Let's flatten the include_state include_list and copy it into a dictionary. + include_dict = dict([item for sublist in include_state.include_list + for item in sublist]) + + # Did we find the header for this file (if any) and successfully load it? + header_found = False + + # Use the absolute path so that matching works properly. + abs_filename = FileInfo(filename).FullName() + + # For Emacs's flymake. + # If cpplint is invoked from Emacs's flymake, a temporary file is generated + # by flymake and that file name might end with '_flymake.cc'. In that case, + # restore original file name here so that the corresponding header file can be + # found. + # e.g. If the file name is 'foo_flymake.cc', we should search for 'foo.h' + # instead of 'foo_flymake.h' + abs_filename = re.sub(r'_flymake\.cc$', '.cc', abs_filename) + + # include_dict is modified during iteration, so we iterate over a copy of + # the keys. + header_keys = include_dict.keys() + for header in header_keys: + (same_module, common_path) = FilesBelongToSameModule(abs_filename, header) + fullpath = common_path + header + if same_module and UpdateIncludeState(fullpath, include_dict, io): + header_found = True + + # If we can't find the header file for a .cc, assume it's because we don't + # know where to look. In that case we'll give up as we're not sure they + # didn't include it in the .h file. + # TODO(unknown): Do a better job of finding .h files so we are confident that + # not having the .h file means there isn't one. + if filename.endswith('.cc') and not header_found: + return + + # All the lines have been processed, report the errors found. + for required_header_unstripped in required: + template = required[required_header_unstripped][1] + if required_header_unstripped.strip('<>"') not in include_dict: + error(filename, required[required_header_unstripped][0], + 'build/include_what_you_use', 4, + 'Add #include ' + required_header_unstripped + ' for ' + template) + + +_RE_PATTERN_EXPLICIT_MAKEPAIR = re.compile(r'\bmake_pair\s*<') + + +def CheckMakePairUsesDeduction(filename, clean_lines, linenum, error): + """Check that make_pair's template arguments are deduced. + + G++ 4.6 in C++11 mode fails badly if make_pair's template arguments are + specified explicitly, and such use isn't intended in any case. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + match = _RE_PATTERN_EXPLICIT_MAKEPAIR.search(line) + if match: + error(filename, linenum, 'build/explicit_make_pair', + 4, # 4 = high confidence + 'For C++11-compatibility, omit template arguments from make_pair' + ' OR use pair directly OR if appropriate, construct a pair directly') + + +def CheckRedundantVirtual(filename, clean_lines, linenum, error): + """Check if line contains a redundant "virtual" function-specifier. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + # Look for "virtual" on current line. + line = clean_lines.elided[linenum] + virtual = Match(r'^(.*)(\bvirtual\b)(.*)$', line) + if not virtual: return + + # Ignore "virtual" keywords that are near access-specifiers. These + # are only used in class base-specifier and do not apply to member + # functions. + if (Search(r'\b(public|protected|private)\s+$', virtual.group(1)) or + Match(r'^\s+(public|protected|private)\b', virtual.group(3))): + return + + # Ignore the "virtual" keyword from virtual base classes. Usually + # there is a column on the same line in these cases (virtual base + # classes are rare in google3 because multiple inheritance is rare). + if Match(r'^.*[^:]:[^:].*$', line): return + + # Look for the next opening parenthesis. This is the start of the + # parameter list (possibly on the next line shortly after virtual). + # TODO(unknown): doesn't work if there are virtual functions with + # decltype() or other things that use parentheses, but csearch suggests + # that this is rare. + end_col = -1 + end_line = -1 + start_col = len(virtual.group(2)) + for start_line in xrange(linenum, min(linenum + 3, clean_lines.NumLines())): + line = clean_lines.elided[start_line][start_col:] + parameter_list = Match(r'^([^(]*)\(', line) + if parameter_list: + # Match parentheses to find the end of the parameter list + (_, end_line, end_col) = CloseExpression( + clean_lines, start_line, start_col + len(parameter_list.group(1))) + break + start_col = 0 + + if end_col < 0: + return # Couldn't find end of parameter list, give up + + # Look for "override" or "final" after the parameter list + # (possibly on the next few lines). + for i in xrange(end_line, min(end_line + 3, clean_lines.NumLines())): + line = clean_lines.elided[i][end_col:] + match = Search(r'\b(override|final)\b', line) + if match: + error(filename, linenum, 'readability/inheritance', 4, + ('"virtual" is redundant since function is ' + 'already declared as "%s"' % match.group(1))) + + # Set end_col to check whole lines after we are done with the + # first line. + end_col = 0 + if Search(r'[^\w]\s*$', line): + break + + +def CheckRedundantOverrideOrFinal(filename, clean_lines, linenum, error): + """Check if line contains a redundant "override" or "final" virt-specifier. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + # Look for closing parenthesis nearby. We need one to confirm where + # the declarator ends and where the virt-specifier starts to avoid + # false positives. + line = clean_lines.elided[linenum] + declarator_end = line.rfind(')') + if declarator_end >= 0: + fragment = line[declarator_end:] + else: + if linenum > 1 and clean_lines.elided[linenum - 1].rfind(')') >= 0: + fragment = line + else: + return + + # Check that at most one of "override" or "final" is present, not both + if Search(r'\boverride\b', fragment) and Search(r'\bfinal\b', fragment): + error(filename, linenum, 'readability/inheritance', 4, + ('"override" is redundant since function is ' + 'already declared as "final"')) + + + + +# Returns true if we are at a new block, and it is directly +# inside of a namespace. +def IsBlockInNameSpace(nesting_state, is_forward_declaration): + """Checks that the new block is directly in a namespace. + + Args: + nesting_state: The _NestingState object that contains info about our state. + is_forward_declaration: If the class is a forward declared class. + Returns: + Whether or not the new block is directly in a namespace. + """ + if is_forward_declaration: + if len(nesting_state.stack) >= 1 and ( + isinstance(nesting_state.stack[-1], _NamespaceInfo)): + return True + else: + return False + + return (len(nesting_state.stack) > 1 and + nesting_state.stack[-1].check_namespace_indentation and + isinstance(nesting_state.stack[-2], _NamespaceInfo)) + + +def ShouldCheckNamespaceIndentation(nesting_state, is_namespace_indent_item, + raw_lines_no_comments, linenum): + """This method determines if we should apply our namespace indentation check. + + Args: + nesting_state: The current nesting state. + is_namespace_indent_item: If we just put a new class on the stack, True. + If the top of the stack is not a class, or we did not recently + add the class, False. + raw_lines_no_comments: The lines without the comments. + linenum: The current line number we are processing. + + Returns: + True if we should apply our namespace indentation check. Currently, it + only works for classes and namespaces inside of a namespace. + """ + + is_forward_declaration = IsForwardClassDeclaration(raw_lines_no_comments, + linenum) + + if not (is_namespace_indent_item or is_forward_declaration): + return False + + # If we are in a macro, we do not want to check the namespace indentation. + if IsMacroDefinition(raw_lines_no_comments, linenum): + return False + + return IsBlockInNameSpace(nesting_state, is_forward_declaration) + + +# Call this method if the line is directly inside of a namespace. +# If the line above is blank (excluding comments) or the start of +# an inner namespace, it cannot be indented. +def CheckItemIndentationInNamespace(filename, raw_lines_no_comments, linenum, + error): + line = raw_lines_no_comments[linenum] + if Match(r'^\s+', line): + error(filename, linenum, 'runtime/indentation_namespace', 4, + 'Do not indent within a namespace') + + +def ProcessLine(filename, file_extension, clean_lines, line, + include_state, function_state, nesting_state, error, + extra_check_functions=[]): + """Processes a single line in the file. + + Args: + filename: Filename of the file that is being processed. + file_extension: The extension (dot not included) of the file. + clean_lines: An array of strings, each representing a line of the file, + with comments stripped. + line: Number of line being processed. + include_state: An _IncludeState instance in which the headers are inserted. + function_state: A _FunctionState instance which counts function lines, etc. + nesting_state: A NestingState instance which maintains information about + the current stack of nested blocks being parsed. + error: A callable to which errors are reported, which takes 4 arguments: + filename, line number, error level, and message + extra_check_functions: An array of additional check functions that will be + run on each source line. Each function takes 4 + arguments: filename, clean_lines, line, error + """ + raw_lines = clean_lines.raw_lines + ParseNolintSuppressions(filename, raw_lines[line], line, error) + nesting_state.Update(filename, clean_lines, line, error) + CheckForNamespaceIndentation(filename, nesting_state, clean_lines, line, + error) + if nesting_state.InAsmBlock(): return + CheckForFunctionLengths(filename, clean_lines, line, function_state, error) + CheckForMultilineCommentsAndStrings(filename, clean_lines, line, error) + CheckStyle(filename, clean_lines, line, file_extension, nesting_state, error) + CheckLanguage(filename, clean_lines, line, file_extension, include_state, + nesting_state, error) + CheckForNonConstReference(filename, clean_lines, line, nesting_state, error) + CheckForNonStandardConstructs(filename, clean_lines, line, + nesting_state, error) + CheckVlogArguments(filename, clean_lines, line, error) + CheckPosixThreading(filename, clean_lines, line, error) + CheckInvalidIncrement(filename, clean_lines, line, error) + CheckMakePairUsesDeduction(filename, clean_lines, line, error) + CheckRedundantVirtual(filename, clean_lines, line, error) + CheckRedundantOverrideOrFinal(filename, clean_lines, line, error) + for check_fn in extra_check_functions: + check_fn(filename, clean_lines, line, error) + +def FlagCxx11Features(filename, clean_lines, linenum, error): + """Flag those c++11 features that we only allow in certain places. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + include = Match(r'\s*#\s*include\s+[<"]([^<"]+)[">]', line) + + # Flag unapproved C++ TR1 headers. + if include and include.group(1).startswith('tr1/'): + error(filename, linenum, 'build/c++tr1', 5, + ('C++ TR1 headers such as <%s> are unapproved.') % include.group(1)) + + # Flag unapproved C++11 headers. + if include and include.group(1) in ('cfenv', + 'condition_variable', + 'fenv.h', + 'future', + 'mutex', + 'thread', + 'chrono', + 'ratio', + 'regex', + 'system_error', + ): + error(filename, linenum, 'build/c++11', 5, + ('<%s> is an unapproved C++11 header.') % include.group(1)) + + # The only place where we need to worry about C++11 keywords and library + # features in preprocessor directives is in macro definitions. + if Match(r'\s*#', line) and not Match(r'\s*#\s*define\b', line): return + + # These are classes and free functions. The classes are always + # mentioned as std::*, but we only catch the free functions if + # they're not found by ADL. They're alphabetical by header. + for top_name in ( + # type_traits + 'alignment_of', + 'aligned_union', + ): + if Search(r'\bstd::%s\b' % top_name, line): + error(filename, linenum, 'build/c++11', 5, + ('std::%s is an unapproved C++11 class or function. Send c-style ' + 'an example of where it would make your code more readable, and ' + 'they may let you use it.') % top_name) + + +def FlagCxx14Features(filename, clean_lines, linenum, error): + """Flag those C++14 features that we restrict. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + include = Match(r'\s*#\s*include\s+[<"]([^<"]+)[">]', line) + + # Flag unapproved C++14 headers. + if include and include.group(1) in ('scoped_allocator', 'shared_mutex'): + error(filename, linenum, 'build/c++14', 5, + ('<%s> is an unapproved C++14 header.') % include.group(1)) + + +def ProcessFileData(filename, file_extension, lines, error, + extra_check_functions=[]): + """Performs lint checks and reports any errors to the given error function. + + Args: + filename: Filename of the file that is being processed. + file_extension: The extension (dot not included) of the file. + lines: An array of strings, each representing a line of the file, with the + last element being empty if the file is terminated with a newline. + error: A callable to which errors are reported, which takes 4 arguments: + filename, line number, error level, and message + extra_check_functions: An array of additional check functions that will be + run on each source line. Each function takes 4 + arguments: filename, clean_lines, line, error + """ + lines = (['// marker so line numbers and indices both start at 1'] + lines + + ['// marker so line numbers end in a known way']) + + include_state = _IncludeState() + function_state = _FunctionState() + nesting_state = NestingState() + + ResetNolintSuppressions() + + CheckForCopyright(filename, lines, error) + ProcessGlobalSuppresions(lines) + RemoveMultiLineComments(filename, lines, error) + clean_lines = CleansedLines(lines) + + if IsHeaderExtension(file_extension): + CheckForHeaderGuard(filename, clean_lines, error) + + for line in xrange(clean_lines.NumLines()): + ProcessLine(filename, file_extension, clean_lines, line, + include_state, function_state, nesting_state, error, + extra_check_functions) + FlagCxx11Features(filename, clean_lines, line, error) + nesting_state.CheckCompletedBlocks(filename, error) + + CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error) + + # Check that the .cc file has included its header if it exists. + if _IsSourceExtension(file_extension): + CheckHeaderFileIncluded(filename, include_state, error) + + # We check here rather than inside ProcessLine so that we see raw + # lines rather than "cleaned" lines. + CheckForBadCharacters(filename, lines, error) + + CheckForNewlineAtEOF(filename, lines, error) + +def ProcessConfigOverrides(filename): + """ Loads the configuration files and processes the config overrides. + + Args: + filename: The name of the file being processed by the linter. + + Returns: + False if the current |filename| should not be processed further. + """ + + abs_filename = os.path.abspath(filename) + cfg_filters = [] + keep_looking = True + while keep_looking: + abs_path, base_name = os.path.split(abs_filename) + if not base_name: + break # Reached the root directory. + + cfg_file = os.path.join(abs_path, "CPPLINT.cfg") + abs_filename = abs_path + if not os.path.isfile(cfg_file): + continue + + try: + with open(cfg_file) as file_handle: + for line in file_handle: + line, _, _ = line.partition('#') # Remove comments. + if not line.strip(): + continue + + name, _, val = line.partition('=') + name = name.strip() + val = val.strip() + if name == 'set noparent': + keep_looking = False + elif name == 'filter': + cfg_filters.append(val) + elif name == 'exclude_files': + # When matching exclude_files pattern, use the base_name of + # the current file name or the directory name we are processing. + # For example, if we are checking for lint errors in /foo/bar/baz.cc + # and we found the .cfg file at /foo/CPPLINT.cfg, then the config + # file's "exclude_files" filter is meant to be checked against "bar" + # and not "baz" nor "bar/baz.cc". + if base_name: + pattern = re.compile(val) + if pattern.match(base_name): + sys.stderr.write('Ignoring "%s": file excluded by "%s". ' + 'File path component "%s" matches ' + 'pattern "%s"\n' % + (filename, cfg_file, base_name, val)) + return False + elif name == 'linelength': + global _line_length + try: + _line_length = int(val) + except ValueError: + sys.stderr.write('Line length must be numeric.') + elif name == 'root': + global _root + _root = val + elif name == 'headers': + ProcessHppHeadersOption(val) + else: + sys.stderr.write( + 'Invalid configuration option (%s) in file %s\n' % + (name, cfg_file)) + + except IOError: + sys.stderr.write( + "Skipping config file '%s': Can't open for reading\n" % cfg_file) + keep_looking = False + + # Apply all the accumulated filters in reverse order (top-level directory + # config options having the least priority). + for filter in reversed(cfg_filters): + _AddFilters(filter) + + return True + + +def ProcessFile(filename, vlevel, extra_check_functions=[]): + """Does google-lint on a single file. + + Args: + filename: The name of the file to parse. + + vlevel: The level of errors to report. Every error of confidence + >= verbose_level will be reported. 0 is a good default. + + extra_check_functions: An array of additional check functions that will be + run on each source line. Each function takes 4 + arguments: filename, clean_lines, line, error + """ + + _SetVerboseLevel(vlevel) + _BackupFilters() + + if not ProcessConfigOverrides(filename): + _RestoreFilters() + return + + lf_lines = [] + crlf_lines = [] + try: + # Support the UNIX convention of using "-" for stdin. Note that + # we are not opening the file with universal newline support + # (which codecs doesn't support anyway), so the resulting lines do + # contain trailing '\r' characters if we are reading a file that + # has CRLF endings. + # If after the split a trailing '\r' is present, it is removed + # below. + if filename == '-': + lines = codecs.StreamReaderWriter(sys.stdin, + codecs.getreader('utf8'), + codecs.getwriter('utf8'), + 'replace').read().split('\n') + else: + lines = codecs.open(filename, 'r', 'utf8', 'replace').read().split('\n') + + # Remove trailing '\r'. + # The -1 accounts for the extra trailing blank line we get from split() + for linenum in range(len(lines) - 1): + if lines[linenum].endswith('\r'): + lines[linenum] = lines[linenum].rstrip('\r') + crlf_lines.append(linenum + 1) + else: + lf_lines.append(linenum + 1) + + except IOError: + sys.stderr.write( + "Skipping input '%s': Can't open for reading\n" % filename) + _RestoreFilters() + return + + # Note, if no dot is found, this will give the entire filename as the ext. + file_extension = filename[filename.rfind('.') + 1:] + + # When reading from stdin, the extension is unknown, so no cpplint tests + # should rely on the extension. + if filename != '-' and file_extension not in _valid_extensions: + sys.stderr.write('Ignoring %s; not a valid file name ' + '(%s)\n' % (filename, ', '.join(_valid_extensions))) + else: + ProcessFileData(filename, file_extension, lines, Error, + extra_check_functions) + + # If end-of-line sequences are a mix of LF and CR-LF, issue + # warnings on the lines with CR. + # + # Don't issue any warnings if all lines are uniformly LF or CR-LF, + # since critique can handle these just fine, and the style guide + # doesn't dictate a particular end of line sequence. + # + # We can't depend on os.linesep to determine what the desired + # end-of-line sequence should be, since that will return the + # server-side end-of-line sequence. + if lf_lines and crlf_lines: + # Warn on every line with CR. An alternative approach might be to + # check whether the file is mostly CRLF or just LF, and warn on the + # minority, we bias toward LF here since most tools prefer LF. + for linenum in crlf_lines: + Error(filename, linenum, 'whitespace/newline', 1, + 'Unexpected \\r (^M) found; better to use only \\n') + + sys.stdout.write('Done processing %s\n' % filename) + _RestoreFilters() + + +def PrintUsage(message): + """Prints a brief usage string and exits, optionally with an error message. + + Args: + message: The optional error message. + """ + sys.stderr.write(_USAGE) + if message: + sys.exit('\nFATAL ERROR: ' + message) + else: + sys.exit(1) + + +def PrintCategories(): + """Prints a list of all the error-categories used by error messages. + + These are the categories used to filter messages via --filter. + """ + sys.stderr.write(''.join(' %s\n' % cat for cat in _ERROR_CATEGORIES)) + sys.exit(0) + + +def ParseArguments(args): + """Parses the command line arguments. + + This may set the output format and verbosity level as side-effects. + + Args: + args: The command line arguments: + + Returns: + The list of filenames to lint. + """ + try: + (opts, filenames) = getopt.getopt(args, '', ['help', 'output=', 'verbose=', + 'counting=', + 'filter=', + 'root=', + 'linelength=', + 'extensions=', + 'headers=']) + except getopt.GetoptError: + PrintUsage('Invalid arguments.') + + verbosity = _VerboseLevel() + output_format = _OutputFormat() + filters = '' + counting_style = '' + + for (opt, val) in opts: + if opt == '--help': + PrintUsage(None) + elif opt == '--output': + if val not in ('emacs', 'vs7', 'eclipse'): + PrintUsage('The only allowed output formats are emacs, vs7 and eclipse.') + output_format = val + elif opt == '--verbose': + verbosity = int(val) + elif opt == '--filter': + filters = val + if not filters: + PrintCategories() + elif opt == '--counting': + if val not in ('total', 'toplevel', 'detailed'): + PrintUsage('Valid counting options are total, toplevel, and detailed') + counting_style = val + elif opt == '--root': + global _root + _root = val + elif opt == '--linelength': + global _line_length + try: + _line_length = int(val) + except ValueError: + PrintUsage('Line length must be digits.') + elif opt == '--extensions': + global _valid_extensions + try: + _valid_extensions = set(val.split(',')) + except ValueError: + PrintUsage('Extensions must be comma seperated list.') + elif opt == '--headers': + ProcessHppHeadersOption(val) + + if not filenames: + PrintUsage('No files were specified.') + + _SetOutputFormat(output_format) + _SetVerboseLevel(verbosity) + _SetFilters(filters) + _SetCountingStyle(counting_style) + + return filenames + + +def main(): + filenames = ParseArguments(sys.argv[1:]) + + # Change stderr to write with replacement characters so we don't die + # if we try to print something containing non-ASCII characters. + sys.stderr = codecs.StreamReaderWriter(sys.stderr, + codecs.getreader('utf8'), + codecs.getwriter('utf8'), + 'replace') + + _cpplint_state.ResetErrorCounts() + for filename in filenames: + ProcessFile(filename, _cpplint_state.verbose_level) + _cpplint_state.PrintErrorCounts() + + sys.exit(_cpplint_state.error_count > 0) + + +if __name__ == '__main__': + main() diff --git a/ut/include/appwindow.h b/ut/include/appwindow.h new file mode 100755 index 0000000..0d17a5b --- /dev/null +++ b/ut/include/appwindow.h @@ -0,0 +1,97 @@ +// +// @ Copyright [2017] +// + +#ifndef __PLUSPLAYER_UT_INCLUDE_APPWINDOW_H__ +#define __PLUSPLAYER_UT_INCLUDE_APPWINDOW_H__ + +#define ECORE_WAYLAND_DISPLAY_TEST 1 + +#include +#include +#include + +#include "Ecore.h" +#include "Ecore_Wayland.h" +#include "Elementary.h" +#if ECORE_WAYLAND_DISPLAY_TEST +#include "Ecore_Wl2.h" +#endif + +namespace plusplayer_ut { + +class AppWindow { + public: + struct Window { + Evas_Object* obj = nullptr; + int x = 0, y = 0; + int w = 0, h = 0; // width , height + }; + + AppWindow(int x, int y, int width, int height) : thread_init_done_(false) { + window_.x = x, window_.y = y, window_.w = width, window_.h = height; + thread_ = std::thread(AppWindow::WindowThread, this); + while (!thread_init_done_) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + } + + ~AppWindow() { + ecore_thread_main_loop_begin(); + elm_exit(); + ecore_thread_main_loop_end(); + thread_.join(); + } + + static void WindowThread(AppWindow* appwindow) { + elm_init(1, NULL); + appwindow->CreateWindow_(); + appwindow->thread_init_done_ = true; + elm_run(); + elm_shutdown(); + } + + Window GetWindow() { return window_; } + +#if ECORE_WAYLAND_DISPLAY_TEST + Ecore_Wl2_Window* GetEcoreWL2Window() { + return ecore_wl2_window_; + } +#endif + + private: + void CreateWindow_() { + ecore_thread_main_loop_begin(); + + window_.obj = elm_win_add(NULL, "player", ELM_WIN_BASIC); + + assert(window_.obj && "window is NULL."); + + elm_win_title_set(window_.obj, "Plusplayer"); + + elm_win_autodel_set(window_.obj, EINA_TRUE); + elm_win_aux_hint_add(window_.obj, "wm.policy.win.user.geometry", "1"); + evas_object_move(window_.obj, window_.x, window_.y); + evas_object_resize(window_.obj, window_.w, window_.h); + evas_object_show(window_.obj); + +#if ECORE_WAYLAND_DISPLAY_TEST + Ecore_Evas *ee = + ecore_evas_ecore_evas_get(evas_object_evas_get(window_.obj)); + ecore_wl2_window_ = ecore_evas_wayland2_window_get(ee); +#endif + ecore_thread_main_loop_end(); + } + + private: + bool thread_init_done_; + Window window_; +#if ECORE_WAYLAND_DISPLAY_TEST + Ecore_Wl2_Window* ecore_wl2_window_; +#endif + std::thread thread_; +}; + +} // namespace plusplayer_ut + +#endif // __PLUSPLAYER_UT_INCLUDE_APPWINDOW_H__ diff --git a/ut/include/esplusplayer/eseventlistener.hpp b/ut/include/esplusplayer/eseventlistener.hpp new file mode 100755 index 0000000..016a47d --- /dev/null +++ b/ut/include/esplusplayer/eseventlistener.hpp @@ -0,0 +1,215 @@ +// +// @ Copyright [2018] +// + +#ifndef __PLUSPLAYER_UT_INCLUDE_ES_EVENT_LISTENER_H__ +#define __PLUSPLAYER_UT_INCLUDE_ES_EVENT_LISTENER_H__ + +#include +#include +#include + +#include "esplusplayer_capi/esplusplayer_capi.h" +#include "ut/include/esplusplayer/esreader.hpp" + +class EsPlayerEventCallback { + public: + EsPlayerEventCallback(esplusplayer_handle handle, + EsStreamReader* video_reader, + EsStreamReader* audio_reader) + : handle_(handle), + video_reader_(video_reader), + audio_reader_(audio_reader) {} + ~EsPlayerEventCallback() { + if (audio_feeding_task_.joinable()) audio_feeding_task_.join(); + if (video_feeding_task_.joinable()) video_feeding_task_.join(); + } + void SetCallback(void) { + esplusplayer_set_error_cb(handle_, OnError, this); + esplusplayer_set_buffer_status_cb(handle_, OnBufferStatus, this); + esplusplayer_set_buffer_byte_status_cb(handle_, OnBufferByteStatus, this); + esplusplayer_set_buffer_time_status_cb(handle_, OnBufferTimeStatus, this); + esplusplayer_set_resource_conflicted_cb(handle_, OnResourceConflicted, + this); + esplusplayer_set_eos_cb(handle_, OnEos, this); + esplusplayer_set_ready_to_prepare_cb(handle_, OnReadyToPrepare, this); + esplusplayer_set_prepare_async_done_cb(handle_, OnPrepareDone, this); + esplusplayer_set_seek_done_cb(handle_, OnSeekDone, this); + esplusplayer_set_ready_to_seek_cb(handle_, OnReadyToSeek, this); + esplusplayer_set_media_packet_video_decoded_cb(handle_, OnVideoDecoded, + this); + esplusplayer_set_closed_caption_cb(handle_, OnClosedCaption, this); + esplusplayer_set_flush_done_cb(handle_, OnFlushDone, this); + esplusplayer_set_event_cb(handle_, OnEvent, this); + } + + static void DataFeedingTask(EsStreamReader* stream, + esplusplayer_handle esplayer) { + esplusplayer_es_packet pkt; + while (true) { + memset(&pkt, 0, sizeof(esplusplayer_es_packet)); + if (!stream->ReadNextPacket(pkt)) break; + esplusplayer_submit_packet(esplayer, &pkt); + delete []pkt.buffer; + } + } + static void OnError(const esplusplayer_error_type err_code, void* userdata) { + std::cout << "OnError" << std::endl; + } + static void OnResourceConflicted(void* userdata) { + std::cout << "OnResourceConflicted" << std::endl; + } + static void OnSeekDone(void* userdata) { + std::cout << "OnSeekDone" << std::endl; + } + static void OnBufferByteStatus(const esplusplayer_stream_type, + const esplusplayer_buffer_status, uint64_t, + void* userdata) { + std::cout << "OnBufferByteStatus" << std::endl; + } + static void OnBufferTimeStatus(const esplusplayer_stream_type, + const esplusplayer_buffer_status, uint64_t, + void* userdata) { + std::cout << "OnBufferTimeStatus" << std::endl; + } + static void OnVideoDecoded(const esplusplayer_decoded_video_packet*, + void* userdata) { + std::cout << "OnVideoDecoded" << std::endl; + } + static void OnClosedCaption(const char* data, const int size, + void* userdata) { + std::cout << "OnVideoDecoded" << std::endl; + } + static void OnFlushDone(void* userdata) { + std::cout << "OnFlushDone" << std::endl; + } + static void OnEvent(const esplusplayer_event_type, + const esplusplayer_event_msg, void*) { + std::cout << "OnEvent" << std::endl; + } + static void OnEos(void* userdata) { + EsPlayerEventCallback* cb = static_cast(userdata); + std::unique_lock lk(cb->eos_m_); + std::cout << "OnEos" << std::endl; + cb->eos_ = true; + lk.unlock(); + cb->eos_cv_.notify_all(); + } + static void OnPrepareDone(bool ret, void* userdata) { + EsPlayerEventCallback* cb = static_cast(userdata); + std::unique_lock lk(cb->prepare_done_m_); + std::cout << "OnPrepareDone" << std::endl; + cb->prepare_done_ = true; + cb->prepare_done_result_ = ret; + lk.unlock(); + cb->prepare_done_cv_.notify_all(); + } + static void OnBufferStatus(const esplusplayer_stream_type type, + const esplusplayer_buffer_status status, + void* userdata) { + // auto buffer_status = + // status == ESPLUSPLAYER_BUFFER_STATUS_UNDERRUN ? "underrun" : + // "overrun"; + // std::cout << "OnBufferStatus " << buffer_status << std::endl; + } + static void OnReadyToPrepare(const esplusplayer_stream_type type, + void* userdata) { + EsPlayerEventCallback* cb = static_cast(userdata); + std::cout << "OnReadyToPrepare" << std::endl; + std::unique_lock lk(cb->data_m_); + if (type == ESPLUSPLAYER_STREAM_TYPE_AUDIO) { + cb->ready_audio_data_ = true; + if (cb->audio_reader_ != nullptr) { + cb->audio_feeding_task_ = + std::thread(DataFeedingTask, std::ref(cb->audio_reader_), + std::ref(cb->handle_)); + } + std::cout << "Audio ready to prepare" << std::endl; + } else if (type == ESPLUSPLAYER_STREAM_TYPE_VIDEO) { + cb->ready_video_data_ = true; + if (cb->video_reader_ != nullptr) { + cb->video_feeding_task_ = + std::thread(DataFeedingTask, std::ref(cb->video_reader_), + std::ref(cb->handle_)); + } + std::cout << "Video ready to prepare" << std::endl; + } + lk.unlock(); + cb->data_cv_.notify_all(); + } + static void OnReadyToSeek(const esplusplayer_stream_type type, + const uint64_t offset, void* userdata) { + EsPlayerEventCallback* cb = static_cast(userdata); + std::cout << "OnReadyToSeek" << std::endl; + std::unique_lock lk(cb->data_m_); + if (type == ESPLUSPLAYER_STREAM_TYPE_AUDIO) + cb->ready_audio_data_ = true; + else if (type == ESPLUSPLAYER_STREAM_TYPE_VIDEO) + cb->ready_video_data_ = true; + lk.unlock(); + cb->data_cv_.notify_all(); + } + void WaitAllStreamData() { + std::unique_lock lk(data_m_); + std::cout << "WaitAllStreamData start" << std::endl; + data_cv_.wait_for(lk, std::chrono::seconds(1), [this]() -> bool { + return ready_audio_data_ && ready_video_data_; + }); + std::cout << "WaitAllStreamData stop" << std::endl; + lk.unlock(); + } + void WaitAudioStreamData() { + std::unique_lock lk(data_m_); + std::cout << "WaitAudioStreamData start" << std::endl; + data_cv_.wait_for(lk, std::chrono::seconds(1), + [this]() -> bool { return ready_audio_data_; }); + std::cout << "WaitAudioStreamData stop" << std::endl; + lk.unlock(); + } + void WaitVideoStreamData() { + std::unique_lock lk(data_m_); + std::cout << "WaitVideoStreamData start" << std::endl; + data_cv_.wait_for(lk, std::chrono::seconds(1), + [this]() -> bool { return ready_audio_data_; }); + std::cout << "WaitVideoStreamData stop" << std::endl; + lk.unlock(); + } + void WaitForEos() { + std::cout << "WaitForEos start" << std::endl; + std::unique_lock lk(eos_m_); + eos_cv_.wait_for(lk, std::chrono::minutes(1), + [this]() -> bool { return eos_; }); + std::cout << "WaitForEos stop" << std::endl; + lk.unlock(); + } + bool WaitForPrepareDone() { + std::cout << "WaitForPrepareDone start" << std::endl; + std::unique_lock lk(prepare_done_m_); + prepare_done_cv_.wait_for(lk, std::chrono::seconds(10), + [this]() -> bool { return prepare_done_; }); + std::cout << "WaitForPrepareDone stop" << std::endl; + lk.unlock(); + return prepare_done_result_; + } + + private: + esplusplayer_handle handle_ = nullptr; + EsStreamReader* video_reader_ = nullptr; + EsStreamReader* audio_reader_ = nullptr; + std::thread video_feeding_task_; + std::thread audio_feeding_task_; + + bool eos_ = false; + std::mutex eos_m_; + std::condition_variable eos_cv_; + bool ready_audio_data_ = false; + bool ready_video_data_ = false; + std::mutex data_m_; + std::condition_variable data_cv_; + bool prepare_done_ = false; + bool prepare_done_result_ = false; + std::mutex prepare_done_m_; + std::condition_variable prepare_done_cv_; +}; + +#endif // __PLUSPLAYER_UT_INCLUDE_ES_EVENT_LISTENER_H__ \ No newline at end of file diff --git a/ut/include/esplusplayer/esreader.hpp b/ut/include/esplusplayer/esreader.hpp new file mode 100755 index 0000000..30f9e45 --- /dev/null +++ b/ut/include/esplusplayer/esreader.hpp @@ -0,0 +1,160 @@ +// +// @ Copyright [2018] +// + +#ifndef __PLUSPLAYER_UT_INCLUDE_ES_READER_H__ +#define __PLUSPLAYER_UT_INCLUDE_ES_READER_H__ + + +#include +#include + +#include "esplusplayer_capi/esplusplayer_capi.h" + +static const std::string tc_root_dir = "/tmp/esdata/"; + +class EsStreamReader { + public: + explicit EsStreamReader(const std::string dirpath, + esplusplayer_stream_type type) { + dir_path_ = tc_root_dir + dirpath; + es_data_file_ = dir_path_ + "ESP.es"; + es_info_file_ = dir_path_ + "ESP.info"; + extra_codec_file_ = dir_path_ + "ESP.codec_extradata"; + type_ = type; + std::cout << "ES data file " << es_data_file_ << std::endl; + } + ~EsStreamReader() { + if (stream_.is_open()) { + stream_.close(); + } + } + + bool SetStreamInfo(esplusplayer_handle& esplayer) { + if (type_ == ESPLUSPLAYER_STREAM_TYPE_AUDIO) { + esplusplayer_audio_stream_info audio_stream; + audio_stream.codec_data = nullptr; + audio_stream.codec_data_length = 0; + GetExtraData_(audio_stream.codec_data, audio_stream.codec_data_length); + if (!GetMediaInfo_(audio_stream)) { + if (audio_stream.codec_data != nullptr) + delete []audio_stream.codec_data; + return false; + } + + esplusplayer_set_audio_stream_info(esplayer, &audio_stream); + if (audio_stream.codec_data != nullptr) + delete []audio_stream.codec_data; + } else if (type_ == ESPLUSPLAYER_STREAM_TYPE_VIDEO) { + esplusplayer_video_stream_info video_stream; + video_stream.codec_data = nullptr; + video_stream.codec_data_length = 0; + GetExtraData_(video_stream.codec_data, video_stream.codec_data_length); + if (!GetMediaInfo_(video_stream)) { + if (video_stream.codec_data != nullptr) + delete []video_stream.codec_data; + return false; + } + esplusplayer_set_video_stream_info(esplayer, &video_stream); + if (video_stream.codec_data != nullptr) + delete []video_stream.codec_data; + } + return true; + } + + bool ReadNextPacket(esplusplayer_es_packet& pkt) { + if (!stream_.is_open()) { + stream_ = std::ifstream(es_data_file_, std::ifstream::binary); + if (!stream_.is_open()) return false; + } + if (stream_.eof() || GetFileLeftSize_() < 24) { + std::cout << type_ << " stream EOF" << std::endl; + return false; + } + pkt.type = type_; + std::uint64_t size; + stream_.read(reinterpret_cast(&pkt.pts), sizeof(pkt.pts)); + pkt.pts = pkt.pts / 1000000; //ns -> ms + stream_.read(reinterpret_cast(&pkt.duration), sizeof(pkt.duration)); + pkt.duration = pkt.duration / 1000000; //ns -> ms + stream_.read(reinterpret_cast(&size), sizeof(size)); + pkt.buffer_size = static_cast(size); + if (pkt.buffer_size == 0) return false; + pkt.buffer = new char[pkt.buffer_size]; + stream_.read(reinterpret_cast(pkt.buffer), pkt.buffer_size); + pkt.matroska_color_info = nullptr; + // std::cout << "Read audio/video buffer: " << type_ << std::endl; + // std::cout << "Type: " << type_ << "Pts: " << pkt.pts << "duration: " << + // pkt.duration << "size: " << size << std::endl; + if (pkt.pts > 10000) return false; //max buffer 10sec + return true; + } + + void ResetReader() { + stream_.seekg(0,std::ios::beg); + } + +private: + int GetFileLeftSize_(void) { + if (!stream_.is_open()) return 0; + int cur = stream_.tellg(); + stream_.seekg(0, stream_.end); + int total = stream_.tellg(); + stream_.seekg(cur); + return total - cur; + } + bool GetExtraData_(char*& data, unsigned int& size) { + auto stream = std::ifstream(extra_codec_file_, std::ifstream::binary); + if (!stream.is_open()) return false; + stream.read(reinterpret_cast(&size), sizeof(size)); + if (size == 0) return false; + data = new char[size]; + stream.read(data, size); + stream.close(); + return true; + } + + bool GetMediaInfo_(esplusplayer_video_stream_info& info) { + auto stream = std::ifstream(es_info_file_, std::ifstream::in); + if (!stream.is_open()) { + std::cout << "No video es file: " << es_info_file_ << std::endl; + return false; + } + + uint32_t mime_type; + stream >> mime_type >> info.width >> info.height >> info.max_width >> info.max_height >> + info.framerate_num >> info.framerate_den; + info.mime_type = static_cast(mime_type); + std::cout << "mime_type: " << info.mime_type << std::endl; + std::cout << "info.width: " << info.width << std::endl; + stream.close(); + return true; + } + + bool GetMediaInfo_(esplusplayer_audio_stream_info& info) { + auto stream = std::ifstream(es_info_file_, std::ifstream::in); + if (!stream.is_open()) { + std::cout << "No audio es file: " << es_info_file_ << std::endl; + return false; + } + uint32_t mime_type; + stream >> mime_type >> info.sample_rate >> info.channels; + info.mime_type = static_cast(mime_type); + std::cout << "mime_type: " << info.mime_type << std::endl; + std::cout << "info.sample_rate: " << info.sample_rate << std::endl; + stream.close(); + return true; + } + + + + private: + std::string dir_path_; + std::string es_data_file_; + std::string es_info_file_; + std::string extra_codec_file_; + std::ifstream stream_; + esplusplayer_stream_type type_; +}; + +#endif // __PLUSPLAYER_UT_INCLUDE_ES_READER_H__ \ No newline at end of file diff --git a/ut/include/esplusplayer/tclist.h b/ut/include/esplusplayer/tclist.h new file mode 100755 index 0000000..260353d --- /dev/null +++ b/ut/include/esplusplayer/tclist.h @@ -0,0 +1,19 @@ +// +// @ Copyright [2019] +// + +#ifndef __PLUSPLAYER_UT_INCLUDE_ESPLUSPLAYER_TCLIST_H__ +#define __PLUSPLAYER_UT_INCLUDE_ESPLUSPLAYER_TCLIST_H__ + +namespace es_tc { + static const std::string es_h264_aac = "es_h264_aac/"; + static const std::string es_hevc_ac3 = "es_hevc_ac3/"; + static const std::string es_vp9_opus = "es_vp9_opus/"; + std::vector tc_list = { + es_h264_aac, + //es_hevc_ac3, + es_vp9_opus, + }; +} + +#endif // __PLUSPLAYER_UT_INCLUDE_ESPLUSPLAYER_TCLIST_H__ \ No newline at end of file diff --git a/ut/include/mixer/constant.h b/ut/include/mixer/constant.h new file mode 100755 index 0000000..5cf3ac4 --- /dev/null +++ b/ut/include/mixer/constant.h @@ -0,0 +1,83 @@ +#ifndef __PLUSPLAYER_MIXER_UT_CONSTANT_H__ +#define __PLUSPLAYER_MIXER_UT_CONSTANT_H__ + +#include + +#include +#include + +#include "mixer/mixer.h" +#include "mixer/types/buffertype.h" +#include "mixer/types/planecomponent.h" +#include "mixer/types/videoplanemanipinfo.h" +#include "plusplayer/types/display.h" + +namespace plusplayer_ut { +using namespace plusplayer; + +VideoPlaneManipulableInfo GetVideoPlaneManipulableInfo( + BufferHandleType handle, const PlaneComponent& comp, + const std::uint32_t& linesize, const Geometry& geom); + +Geometry GetGeometry(const int& x, const int& y, const int& w, const int& h); + +CropArea GetCropArea(const double& x, const double& y, const double& w, + const double& h); + +Mixer::ResolutionInfo GetResolutionInfo(int width, int height, int fnum, + int fden); + +static const std::uint32_t kDefaultWidth = 1920; +static const std::uint32_t kDefaultHeight = 1080; +static const std::uint32_t kSmallWidth = 192; +static const std::uint32_t kSmallHeight = 108; +static const std::uint32_t kInvalidWidth = 0; +static const std::uint32_t kInvalidHeight = 0; +static const std::uint32_t kExpectedDefaultByteSize = + (kDefaultWidth * kDefaultHeight * 3) >> 1; + +static const std::uint32_t kDefaultFramerateNum = 30; +static const std::uint32_t kDefaultFramerateDen = 1; +static const std::uint32_t kFastFramerateNum = 60; +static const std::uint32_t kFastFramerateDen = 1; +static const std::uint32_t kInvalidFramerateNum = 0; +static const std::uint32_t kInvalidFramerateDen = 0; + +static const std::uint32_t kDefaultX = 10; +static const std::uint32_t kDefaultY = 20; +static const std::uint32_t kDefaultW = 30; +static const std::uint32_t kDefaultH = 40; + +static const std::uint32_t kDefaultLineSize = kDefaultWidth; +static const BufferHandleType kDefaultBufferHandle = 1; +static const BufferHandleType kInvalidBufferHandle = 0; +static const BufferKeyType kDefaultBufferKey = 1; +static const BufferKeyType kInvalidBufferKey = 0; + +extern BufferDefaultType kDefaultBuffer; +extern BufferUnionHandleType kDefaultMappedHandle; + +static const auto kYComponentSrcVMInfo = GetVideoPlaneManipulableInfo( + kDefaultBufferHandle, PlaneComponent::kYComponent, kDefaultLineSize, + GetGeometry(kDefaultX, kDefaultY, kDefaultW, kDefaultH)); +static const auto kUVComponentSrcVMInfo = GetVideoPlaneManipulableInfo( + kDefaultBufferHandle, PlaneComponent::kUVComponent, kDefaultLineSize, + GetGeometry(kDefaultX, kDefaultY, kDefaultW, kDefaultH)); + +static const auto kYComponentDestVMInfo = GetVideoPlaneManipulableInfo( + kDefaultBufferHandle, PlaneComponent::kYComponent, kDefaultLineSize, + GetGeometry(0, 0, kDefaultWidth, kDefaultHeight)); +static const auto kUVComponentDestVMInfo = GetVideoPlaneManipulableInfo( + kDefaultBufferHandle, PlaneComponent::kUVComponent, kDefaultLineSize, + GetGeometry(0, kDefaultHeight, kDefaultWidth >> 1, kDefaultHeight >> 1)); + +static const auto kCroppedYComponentDestVMInfo = GetVideoPlaneManipulableInfo( + kDefaultBufferHandle, PlaneComponent::kYComponent, kDefaultLineSize, + GetGeometry(kDefaultX, kDefaultY, kDefaultW, kDefaultH)); +static const auto kCroppedUVComponentDestVMInfo = GetVideoPlaneManipulableInfo( + kDefaultBufferHandle, PlaneComponent::kUVComponent, kDefaultLineSize, + GetGeometry(kDefaultX >> 1, (kDefaultY >> 1) + kDefaultHeight, + kDefaultW >> 1, kDefaultH >> 1)); +} // namespace plusplayer_ut + +#endif // __PLUSPLAYER_MIXER_UT_CONSTANT_H__ \ No newline at end of file diff --git a/ut/include/mixer/matcher.h b/ut/include/mixer/matcher.h new file mode 100755 index 0000000..a44a661 --- /dev/null +++ b/ut/include/mixer/matcher.h @@ -0,0 +1,105 @@ +#ifndef __PLUSPLAYER_MIXER_UT_MOCK_MATCHER_H__ +#define __PLUSPLAYER_MIXER_UT_MOCK_MATCHER_H__ + +#include +#include + +#include +#include +#include + +#include "mixer/types/videoplanemanipinfo.h" + +namespace plusplayer_ut { +using namespace plusplayer; + +using ::testing::MakeMatcher; +using ::testing::Matcher; +using ::testing::MatcherInterface; +using ::testing::MatchResultListener; + +class IsSameVideoPlaneManipulableInfoMatcher + : public MatcherInterface { + public: + explicit IsSameVideoPlaneManipulableInfoMatcher( + const VideoPlaneManipulableInfo& comparer) + : comparer_(comparer) {} + virtual ~IsSameVideoPlaneManipulableInfoMatcher() {} + bool MatchAndExplain(const VideoPlaneManipulableInfo& info, + MatchResultListener* listener) const override { + if (info.handle != comparer_.handle) { + *listener << "handle: " << info.handle << " vs " << comparer_.handle; + return false; + } + if (info.component != comparer_.component) { + *listener << "component: " << static_cast(info.component) << " vs " + << static_cast(comparer_.component); + return false; + } + if (info.linesize != comparer_.linesize) { + *listener << "linesize: " << info.linesize << " vs " + << comparer_.linesize; + return false; + } + if (info.rect.x != comparer_.rect.x) { + *listener << "rect.x: " << info.rect.x << " vs " << comparer_.rect.x; + return false; + } + if (info.rect.y != comparer_.rect.y) { + *listener << "rect.y: " << info.rect.y << " vs " << comparer_.rect.y; + return false; + } + if (info.rect.w != comparer_.rect.w) { + *listener << "rect.w: " << info.rect.w << " vs " << comparer_.rect.w; + return false; + } + if (info.rect.h != comparer_.rect.h) { + *listener << "rect.h: " << info.rect.h << " vs " << comparer_.rect.h; + return false; + } + return true; + } + + void DescribeTo(std::ostream* os) const override { + *os << "is same with your param"; + } + + void DescribeNegationTo(std::ostream* os) const override { + *os << "is not same"; + } + + private: + const VideoPlaneManipulableInfo& comparer_; +}; + +Matcher IsSameVideoPlaneManipulableInfo( + const VideoPlaneManipulableInfo& comparer); +} // namespace plusplayer_ut + +using ::testing::PrintToString; + +template +std::string StringFormat(const std::string& format, Args... args) { + size_t size = snprintf(nullptr, 0, format.c_str(), args...) + + 1; // Extra space for '\0' + if (size <= 0) { + throw std::runtime_error("Error during formatting."); + } + std::unique_ptr buf(new char[size]); + snprintf(buf.get(), size, format.c_str(), args...); + return std::string(buf.get(), + buf.get() + size - 1); // We don't want the '\0' inside +} + +MATCHER_P(IsSameGeometry, geom, + StringFormat("%s in (x, y, w, h) [%d, %d, %d, %d]", + negation ? "isn't" : "is", geom.x, geom.y, geom.w, + geom.h)) { + if (geom.x != arg.x) return false; + if (geom.y != arg.y) return false; + if (geom.w != arg.w) return false; + if (geom.h != arg.h) return false; + return true; +} + +#endif // __PLUSPLAYER_MIXER_UT_MOCK_MATCHER_H__ \ No newline at end of file diff --git a/ut/include/mixer/mock/fakebuffer.h b/ut/include/mixer/mock/fakebuffer.h new file mode 100755 index 0000000..053c8f1 --- /dev/null +++ b/ut/include/mixer/mock/fakebuffer.h @@ -0,0 +1,42 @@ +#ifndef __PLUSPLAYER_MIXER_UT_FAKE_BUFFER_H__ +#define __PLUSPLAYER_MIXER_UT_FAKE_BUFFER_H__ + +#include + +#include +#include + +using ::testing::MakePolymorphicAction; +using ::testing::PolymorphicAction; + +namespace plusplayer_ut { +struct FakeBuffer { + FakeBuffer(const std::uint32_t& size) + : ptr(std::unique_ptr(new char[size])), size(size) {} + std::unique_ptr ptr = nullptr; + const std::uint32_t size = 0; +}; +using FakeBufferPtr = std::unique_ptr; + +class CreateFakeBufferAction { + public: + explicit CreateFakeBufferAction(FakeBufferPtr& buffer) : buffer_(buffer) {} + template + Result Perform(const ArgumentTuple& args) const { + auto size = std::get<0>(args); + if (size == 0) return; + buffer_.reset(new FakeBuffer(size)); + } + + private: + FakeBufferPtr& buffer_; +}; + +static PolymorphicAction CreateFakeBuffer( + FakeBufferPtr& buffer) { + return MakePolymorphicAction(CreateFakeBufferAction(buffer)); +} + +} // namespace plusplayer_ut + +#endif // __PLUSPLAYER_MIXER_UT_FAKE_BUFFER_H__ \ No newline at end of file diff --git a/ut/include/mixer/mock/mock_bufferobject.h b/ut/include/mixer/mock/mock_bufferobject.h new file mode 100755 index 0000000..958d737 --- /dev/null +++ b/ut/include/mixer/mock/mock_bufferobject.h @@ -0,0 +1,37 @@ +#ifndef __PLUSPLAYER_MIXER_UT_MOCK_BUFFER_OBJECT_H__ +#define __PLUSPLAYER_MIXER_UT_MOCK_BUFFER_OBJECT_H__ + +#include + +#include "mixer/constant.h" +#include "mixer/interfaces/accessiblebuffer.h" +#include "mixer/interfaces/bufferobject.h" +#include "mixer/mock/movable.h" + +namespace plusplayer_ut { +using namespace plusplayer; +class MockBufferObject : public BufferObject { + public: + MOCK_CONST_METHOD0(GetBufferHandle, BufferHandleType()); + MOCK_CONST_METHOD0(Export, BufferKeyType()); + MOCK_CONST_METHOD0(GetSize, std::uint32_t()); +}; + +class MockAccessibleBufferObject : public BufferObject, + public AccessibleBuffer { + public: + MOCK_CONST_METHOD0(GetReadableAddress_, Mover()); + MOCK_CONST_METHOD0(GetWritableAddress_, Mover()); + MOCK_CONST_METHOD0(GetBufferHandle, BufferHandleType()); + MOCK_CONST_METHOD0(Export, BufferKeyType()); + MOCK_CONST_METHOD0(GetSize, std::uint32_t()); + PhyAddrAccessorPtr GetReadableAddress() const { + return std::move(GetReadableAddress_().get()); + } + PhyAddrAccessorPtr GetWritableAddress() const { + return std::move(GetWritableAddress_().get()); + } +}; +} // namespace plusplayer_ut + +#endif // __PLUSPLAYER_MIXER_UT_MOCK_BUFFER_OBJECT_H__ diff --git a/ut/include/mixer/mock/mock_memallocator.h b/ut/include/mixer/mock/mock_memallocator.h new file mode 100755 index 0000000..cc89f29 --- /dev/null +++ b/ut/include/mixer/mock/mock_memallocator.h @@ -0,0 +1,16 @@ +#ifndef __PLUSPLAYER_MIXER_UT_MOCK_MEMORY_ALLOCATOR_H__ +#define __PLUSPLAYER_MIXER_UT_MOCK_MEMORY_ALLOCATOR_H__ + +#include + +#include "mixer/constant.h" +#include "mixer/interfaces/memoryallocator.h" + +namespace plusplayer_ut { +using namespace plusplayer; +class MockMemoryAllocator : public MemoryAllocator { + public: + MOCK_CONST_METHOD1(Allocate, BufferObject*(const std::uint32_t&)); +}; +} // namespace plusplayer_ut +#endif // __PLUSPLAYER_MIXER_UT_MOCK_MEMORY_ALLOCATOR_H__ diff --git a/ut/include/mixer/mock/mock_phyaddraccessor.h b/ut/include/mixer/mock/mock_phyaddraccessor.h new file mode 100755 index 0000000..637150f --- /dev/null +++ b/ut/include/mixer/mock/mock_phyaddraccessor.h @@ -0,0 +1,16 @@ +#ifndef __PLUSPLAYER_MIXER_UT_MOCK_PHYSICAL_ADDRESS_ACCESSOR_H__ +#define __PLUSPLAYER_MIXER_UT_MOCK_PHYSICAL_ADDRESS_ACCESSOR_H__ + +#include + +#include "mixer/interfaces/phyaddraccessor.h" + +namespace plusplayer { +class MockPhyAddrAccessor : public PhysicalAddressAccessor { + public: + virtual ~MockPhyAddrAccessor() = default; + MOCK_METHOD0(GetAddress, BufferPhysicalAddrType()); +}; +} // namespace plusplayer + +#endif // __PLUSPLAYER_MIXER_UT_MOCK_PHYSICAL_ADDRESS_ACCESSOR_H__ \ No newline at end of file diff --git a/ut/include/mixer/mock/mock_renderableobj.h b/ut/include/mixer/mock/mock_renderableobj.h new file mode 100755 index 0000000..c729e53 --- /dev/null +++ b/ut/include/mixer/mock/mock_renderableobj.h @@ -0,0 +1,37 @@ +#ifndef __PLUSPLAYER_MIXER_UT_MOCK_RENDERABLE_OBJECT_H__ +#define __PLUSPLAYER_MIXER_UT_MOCK_RENDERABLE_OBJECT_H__ + +#include + +#include +#include +#include + +#include "mixer/constant.h" +#include "mixer/interfaces/renderableobject.h" +#include "mixer/interfaces/videoplanemanipulator.h" +#include "mixer/types/videoplanemanipinfo.h" +#include "plusplayer/types/display.h" + +namespace plusplayer_ut { + +class MockRenderableObject : public RenderableObject { + public: + MOCK_CONST_METHOD0(GetVideoPlaneManipInfo, + const std::vector()); + MOCK_CONST_METHOD0(IsValid, bool()); + MOCK_CONST_METHOD0(GetWidth, std::uint32_t()); + MOCK_CONST_METHOD0(GetHeight, std::uint32_t()); + MOCK_CONST_METHOD0(GetSize, std::uint32_t()); + MOCK_METHOD3(Render, bool(const VideoPlaneManipulator* const, + const std::vector&, + const Geometry&)); + MOCK_METHOD4(Fill, bool(const VideoPlaneColorManipulator* const, + const PlaneComponent&, const std::uint32_t&, + const Geometry&)); + MOCK_CONST_METHOD1(Export, bool(BufferKeyType&)); +}; + +} // namespace plusplayer_ut + +#endif // __PLUSPLAYER_MIXER_UT_MOCK_RENDERABLE_OBJECT_H__ \ No newline at end of file diff --git a/ut/include/mixer/mock/mock_renderableobj_factory.h b/ut/include/mixer/mock/mock_renderableobj_factory.h new file mode 100755 index 0000000..46b022d --- /dev/null +++ b/ut/include/mixer/mock/mock_renderableobj_factory.h @@ -0,0 +1,20 @@ +#ifndef __PLUSPLAYER_MIXER_UT_MOCK_RENDERABLE_OBJECT_FACTORY_H__ +#define __PLUSPLAYER_MIXER_UT_MOCK_RENDERABLE_OBJECT_FACTORY_H__ + +#include + +#include + +#include "mixer/constant.h" +#include "mixer/interfaces/renderableobj_factory.h" + +namespace plusplayer_ut { +class MockRenderableObjectFactory : public RenderableObjectFactory { + public: + MOCK_CONST_METHOD2(CreateRenderableObject, + RenderableObject*(const std::uint32_t width, + const std::uint32_t height)); +}; +} // namespace plusplayer_ut + +#endif // __PLUSPLAYER_MIXER_UT_MOCK_RENDERABLE_OBJECT_FACTORY_H__ \ No newline at end of file diff --git a/ut/include/mixer/mock/mock_renderer_evtlistener.h b/ut/include/mixer/mock/mock_renderer_evtlistener.h new file mode 100755 index 0000000..7b574ed --- /dev/null +++ b/ut/include/mixer/mock/mock_renderer_evtlistener.h @@ -0,0 +1,16 @@ +#ifndef __PLUSPLAYER_MIXER_UT_MOCK_RENDERER_EVENT_LISTENER_H__ +#define __PLUSPLAYER_MIXER_UT_MOCK_RENDERER_EVENT_LISTENER_H__ + +#include + +#include "mixer/constant.h" +#include "mixer/renderer.h" + +namespace plusplayer_ut { +class MockRendererEventListener : public RendererEventListener { + public: + MOCK_METHOD1(OnRenderingRelease, bool(const BufferKeyType&)); +}; +} // namespace plusplayer_ut + +#endif // __PLUSPLAYER_MIXER_UT_MOCK_RENDERER_EVENT_LISTENER_H__ \ No newline at end of file diff --git a/ut/include/mixer/mock/mock_vpcollection.h b/ut/include/mixer/mock/mock_vpcollection.h new file mode 100755 index 0000000..60527e4 --- /dev/null +++ b/ut/include/mixer/mock/mock_vpcollection.h @@ -0,0 +1,17 @@ +#ifndef __PLUSPLAYER_MIXER_UT_MOCK_VIDEOPLANECOLLECTION_H__ +#define __PLUSPLAYER_MIXER_UT_MOCK_VIDEOPLANECOLLECTION_H__ + +#include + +#include "mixer/constant.h" +#include "mixer/interfaces/videoplanecollection.h" + +namespace plusplayer_ut { +class MockVideoPlaneCollection : public VideoPlaneCollection { + public: + MOCK_CONST_METHOD0(GetVideoPlaneManipInfo, + const std::vector()); +}; +} // namespace plusplayer_ut + +#endif // __PLUSPLAYER_MIXER_UT_MOCK_VIDEOPLANECOLLECTION_H__ \ No newline at end of file diff --git a/ut/include/mixer/mock/mock_vpmanipulator.h b/ut/include/mixer/mock/mock_vpmanipulator.h new file mode 100755 index 0000000..8b0d0e1 --- /dev/null +++ b/ut/include/mixer/mock/mock_vpmanipulator.h @@ -0,0 +1,19 @@ +#ifndef __PLUSPLAYER_MIXER_UT_MOCK_VIDEO_PLANE_MANIPULATOR_H__ +#define __PLUSPLAYER_MIXER_UT_MOCK_VIDEO_PLANE_MANIPULATOR_H__ + +#include + +#include "mixer/constant.h" +#include "mixer/interfaces/videoplanemanipulator.h" + +namespace plusplayer_ut { +using namespace plusplayer; + +class MockVideoPlaneManipulator : public VideoPlaneManipulator { + public: + MOCK_CONST_METHOD2(Do, bool(const VideoPlaneManipulableInfo&, + const VideoPlaneManipulableInfo&)); +}; +} // namespace plusplayer_ut + +#endif // __PLUSPLAYER_MIXER_UT_MOCK_VIDEO_PLANE_MANIPULATOR_H__ \ No newline at end of file diff --git a/ut/include/mixer/mock/mock_vpscaler.h b/ut/include/mixer/mock/mock_vpscaler.h new file mode 100755 index 0000000..c2c673f --- /dev/null +++ b/ut/include/mixer/mock/mock_vpscaler.h @@ -0,0 +1,17 @@ +#ifndef __PLUSPLAYER_MIXER_MOCK_VIDEO_PLANE_SCALER_H__ +#define __PLUSPLAYER_MIXER_MOCK_VIDEO_PLANE_SCALER_H__ + +#include + +#include "mixer/constant.h" +#include "mixer/interfaces/videoplanescaler.h" + +namespace plusplayer { +class MockVideoPlaneScaler : public VideoPlaneScaler { + public: + virtual ~MockVideoPlaneScaler() = default; + MOCK_CONST_METHOD0(GetScaleManipulator, const VideoPlaneManipulator* const()); +}; +} // namespace plusplayer + +#endif // __PLUSPLAYER_MIXER_MOCK_VIDEO_PLANE_SCALER_H__ \ No newline at end of file diff --git a/ut/include/mixer/mock/movable.h b/ut/include/mixer/mock/movable.h new file mode 100755 index 0000000..5f427a6 --- /dev/null +++ b/ut/include/mixer/mock/movable.h @@ -0,0 +1,47 @@ +#ifndef __PLUSPLAYER_MIXER_UT_MOCK_MOVABLE_H__ +#define __PLUSPLAYER_MIXER_UT_MOCK_MOVABLE_H__ + +#include + +namespace plusplayer_ut { + +template +class Mover { + public: + Mover(T&& object) : object(std::move(object)), valid(true) {} + + Mover(const Mover& other) + : object(const_cast(other.object)), valid(true) { + assert(other.valid); + other.valid = false; + } + + Mover& operator=(const Mover& other) { + assert(other.valid); + object = const_cast(other.object); + other.valid = false; + valid = true; + } + + T& get() { + assert(valid); + return object; + } + + const T& get() const { + assert(valid); + return *object; + } + + private: + T object; + mutable bool valid; +}; + +template +inline Mover Movable(T&& object) { + return Mover(std::move(object)); +} +} // namespace plusplayer_ut + +#endif // __PLUSPLAYER_MIXER_UT_MOCK_MOVABLE_H__ diff --git a/ut/include/mixer/mock/moveobj_wrapper.h b/ut/include/mixer/mock/moveobj_wrapper.h new file mode 100755 index 0000000..aaabbf3 --- /dev/null +++ b/ut/include/mixer/mock/moveobj_wrapper.h @@ -0,0 +1,30 @@ +#ifndef __PLUSPLAYER_MIXER_UT_MOCK_MOVE_OBJECT_WRAPPER_H__ +#define __PLUSPLAYER_MIXER_UT_MOCK_MOVE_OBJECT_WRAPPER_H__ + +#include + +namespace plusplayer_ut { +template +class MoveObjectWrapper { + public: + explicit MoveObjectWrapper(T* obj) : obj_(obj) {} + virtual ~MoveObjectWrapper() { + if (moved_ == false) { + delete obj_; + std::cout << "CALLED dtor" << std::endl; + } + } + MoveObjectWrapper& operator=(T* obj) { obj_ = obj; } + T& Get() { return *obj_; } + T* Move() { + moved_ = true; + return obj_; + } + + private: + T* obj_ = nullptr; + bool moved_ = false; +}; +} // namespace plusplayer_ut + +#endif // __PLUSPLAYER_MIXER_UT_MOCK_MOVE_OBJECT_WRAPPER_H__ \ No newline at end of file diff --git a/ut/include/plusplayer/imagesimilarity.h b/ut/include/plusplayer/imagesimilarity.h new file mode 100755 index 0000000..7df4535 --- /dev/null +++ b/ut/include/plusplayer/imagesimilarity.h @@ -0,0 +1,34 @@ +#ifndef __IMAGESIMILARITY_H_ +#define __IMAGESIMILARITY_H_ + +class ImageSimilarity { + +public: +/** + * @brief measuring the similarity of the images using PSNR (Peak Signal-to-Noise Ratio) + * @remarks + * @param[in] image1 first image that measures the similarity + * @param[in] image2 second image that measures the similarity with image1 + * @return Normalized Similarity value (0~100) on success, higher is better. + * otherwise a negative error value + * @pre the image1 and image2 should be properly allocated. + * @post None + * @exception None + */ + double CompareImagesWithPSNR(char *reference_image, char *comparison_image); + +/** + * @brief measuring the similarity of tho images using SSIM (Structural Similarity) + * @remarks MSSIM (Mean of Structureal Similarity) + * @param[in] image1 first image that measures the similarity + * @param[in] image2 second image that measures the similarity with image1 + * @return Normalized Similarity value (0~100) on success, higher is better. + * otherwise a negative error value + * @pre the image1 and image2 should be properly allocated. + * @post None + * @exception None + */ + double CompareImagesWithMSSIM(char *reference_image, char *comparison_image); +}; + +#endif diff --git a/ut/include/plusplayer/utility.h b/ut/include/plusplayer/utility.h new file mode 100755 index 0000000..e3ca27e --- /dev/null +++ b/ut/include/plusplayer/utility.h @@ -0,0 +1,226 @@ +// +// @ Copyright [2019] +// +#ifndef __PLUSPLAYER_UT_INCLUDE_PLUSPLAYER_UTILITY_H__ +#define __PLUSPLAYER_UT_INCLUDE_PLUSPLAYER_UTILITY_H__ + +#include + +#include "gmock/gmock.h" + +#include "core/utils/plusplayer_log.h" +#include "esplusplayer_capi/esplusplayer_capi.h" +#include "mixer_capi/mixer_capi.h" +//#include "plusplayer/plusplayer.h" +#include "plusplayer/types/display.h" +#include "ut/include/appwindow.h" + +#include +#include +#include + +#include +#include +#include +#include + +#define DEFAULT_TIMEOUT_FOR_PREPARING (95 * 1000) +#define DEFAULT_TIMEOUT_FOR_SEEKING (25 * 1000) +#define EOS_WAIT_TIME 10000 + +class IAudioControl; +class DiagnosisAudioControl; + +namespace utils { +class Utility; +using evas_h = Evas_Object*; + +enum EsType { + kAudio = 0x01, + kVideo = 0x02, + kBoth = (kAudio | kVideo) +}; + +class Utility { + public: + static Utility& Instance(); + static void ThreadSleep(long ms); + static void Kill(); + virtual ~Utility(); + + const char* GetCurrentTestName(void); + +#ifndef IS_AUDIO_PRODUCT + +#if 0 + plusplayer::PlusPlayer::Ptr GetOpenedMixPlusPlayer(std::string& uri, + plusplayer::Mixer* mixer, + plusplayer::Geometry& roi); + + plusplayer::PlusPlayer::Ptr GetPreparedMixPlusPlayer( + std::string& uri, plusplayer::Mixer* mixer, plusplayer::Geometry& roi); + + plusplayer::PlusPlayer::Ptr GetStartedMixPlusPlayer( + std::string& uri, plusplayer::Mixer* mixer, plusplayer::Geometry& roi); +#endif + + esplusplayer_handle GetOpenedMixESPP(mixer_handle mixer, + plusplayer::Geometry& roi); + + esplusplayer_handle GetPreparedMixESPP(std::string& uri, mixer_handle mixer, + plusplayer::Geometry& roi); + + esplusplayer_handle GetStartedMixESPP(std::string& uri, mixer_handle mixer, + plusplayer::Geometry& roi); +#endif + esplusplayer_handle GetOpenedESPP(plusplayer::Geometry& roi); + + esplusplayer_handle GetPreparedESPP(std::string& uri, + plusplayer::Geometry& roi); + + esplusplayer_handle GetStartedESPP(std::string& uri, plusplayer::Geometry& roi); + + bool PrepareESPP(esplusplayer_handle player, std::string& uri, + EsType type = EsType::kBoth); + + void FeedingEsPacket(esplusplayer_handle player, + const esplusplayer_stream_type type, + const std::string& uri); + + void DestroyESPP(esplusplayer_handle player); + + evas_h GetWindow() const; + + int CaptureYUV(int capture_width, int capture_height, char* ybuff, + char* cbuff, int ysize, int csize, + std::string result_file_path); + + int CaptureJPG(const int capture_width, const int capture_height, char* ybuff, + char* cbuff, const int ysize, const int csize, + std::string img_file_path); + + int CheckYUV(int x, int y); + bool IsAudioDisconnected(); + bool IsAudioConnected() {return !IsAudioDisconnected();} + bool IsAudioMute(); + bool IsAudioUnmute() {return !IsAudioMute();} + + Utility(const Utility& rhs) = delete; + Utility& operator=(const Utility& rhs) = delete; + + private: + explicit Utility(); + + private: + std::unique_ptr appwindow_; + IAudioControl* audioControl = nullptr; + DiagnosisAudioControl* audioDiagnoser = nullptr; +}; + +enum class WatcherStatus { + kSuccess, + kFail +}; + +enum PrepareStatus { + kReady, + kSuccess, + kFail +}; + +static const int TIMEOUT_10_SEC = 10 * 1000; + +template +class Watcher { + public: + Watcher() = default; + ~Watcher() { + this->SendSignalToWait(); + std::unique_lock(this->destructor_mutex_); + } + + explicit Watcher(T&& t) : value_(t) { LOGD("object : 0x%p", this); } + Watcher(Watcher&& t) : value_(std::move(std::forward(t))) { + LOGD("object&& : 0x%p", this); + } + + std::future WaitForChange(T expected, int timeout_ms) { + LOG_DEBUG("WaitForChange ::Start "); + return std::async(std::launch::async, &Watcher::Evaluate, std::ref(*this), + expected, timeout_ms); + } + + void SendSignalToWait() { cv_.notify_one(); } + + T GetValue() { return value_; } + + Watcher& operator=(const T& rhs) { + std::unique_lock locker(this->mutex_); + value_ = rhs; + this->SendSignalToWait(); + // LOGD("operator=(const T& rhs)"); + return *this; + } + + Watcher& operator=(const Watcher& rhs) { + std::unique_lock locker(this->mutex_); + value_ = rhs.value_; + this->SendSignalToWait(); + // LOGD("operator=(const Watcher& rhs)"); + return *this; + } + + bool operator==(const Watcher& rhs) const { + return this->value_ == rhs.value_; + } + + bool operator!=(const Watcher& rhs) const { return !(this == rhs); } + + bool operator==(const T& rhs) const { return this->value_ == rhs; } + + bool operator!=(const T& rhs) const { return !(this->value_ == rhs); } + + bool operator<(const T& rhs) const { return this->value_ < rhs; } + + bool operator>(const T& rhs) const { return this->value_ > rhs; } + + bool operator>=(const T& rhs) const { return this->value_ >= rhs; } + + bool operator<=(const T& rhs) const { return this->value_ <= rhs; } + + operator T() { return value_; } + + private: + static WatcherStatus Evaluate(Watcher& w, T expected, int timeout_ms) { + std::unique_lock(w.destructor_mutex_); + auto until = std::chrono::system_clock::now() + + std::chrono::milliseconds(timeout_ms); + std::cv_status status = std::cv_status::timeout; + + while (true) { + std::unique_lock locker(w.mutex_); + + if (w.value_ == expected) { + return WatcherStatus::kSuccess; + } + + status = w.cv_.wait_until(locker, until); + + if (status == std::cv_status::timeout) { + return WatcherStatus::kFail; + } + } + + return WatcherStatus::kFail; + } + + private: + T value_; + std::mutex mutex_; + std::mutex destructor_mutex_; + std::condition_variable cv_; +}; + +} // namespace utils + +#endif // __PLUSPLAYER_UT_INCLUDE_PLUSPLAYER_UTILITY_H__ diff --git a/ut/include/streamreader.hpp b/ut/include/streamreader.hpp new file mode 100755 index 0000000..00069cc --- /dev/null +++ b/ut/include/streamreader.hpp @@ -0,0 +1,214 @@ +// +// @ Copyright [2018] +// + +#ifndef __PLUSPLAYER_UT_INCLUDE_ESCOMMON_H__ +#define __PLUSPLAYER_UT_INCLUDE_ESCOMMON_H__ + +#include +#include +#include +#include +#include +#include +#include + +#include "gtest/gtest.h" + +#include "plusplayer/espacket.h" +#include "ut/include/appwindow.h" + +namespace pp = plusplayer; + +namespace utils { +inline bool Exists(const std::string &path) { + std::ifstream ifile(path); + return static_cast(ifile); +} +} // namespace utils + +namespace { +class Environment { + public: + Environment() { + appwindow_.reset(new plusplayer_ut::AppWindow(0, 0, 1920, 1080)); + } + void *Window() { return appwindow_->GetWindow().obj; } + void *EcoreWindow() { return appwindow_->GetEcoreWL2Window(); } + + private: + std::shared_ptr appwindow_; +}; + +class ESPacketDownloader { + public: + static void Init() { + if (utils::Exists("/tmp/esdata")) { + return; + } + std::cout << "Download ESData via http - path : /tmp/esdata" << std::endl; + int ret = std::system( + "/usr/bin/wget " + "--recursive --directory-prefix=/tmp/esdata --force-directories " + "--no-host-directories --cut-dirs=2 --no-parent --reject=index.html " + ""); + assert(ret == 0 && "wget is failed, please retry"); + } +}; +} // namespace + +namespace es { +inline std::uint64_t ConvertNsToMs(std::uint64_t ms) { + constexpr std::uint64_t ns_unit = 1000000; + return ms / ns_unit; +} +struct Packet { + std::uint64_t pts; + std::uint64_t duration; + std::uint64_t size; + std::shared_ptr data; + + pp::EsPacketPtr MakeEsPacket(pp::StreamType type) { + return std::move( + pp::EsPacket::Create(type, data, static_cast(size), + // pts/1000000,duration/1000000)); + ConvertNsToMs(pts), ConvertNsToMs(duration))); + } + + static pp::EsPacketPtr MakeEosPacket(pp::StreamType type) { + return std::move(pp::EsPacket::CreateEos(type)); + } +}; + +struct CodecExtraData { + std::uint32_t size; + std::shared_ptr data; +}; +using PacketPtr = std::shared_ptr; +using CodecExtraDataPtr = std::shared_ptr; + +template +struct VideoInfo { + std::string mimetype; + int width, height; + int maxwidth, maxheight; + int framerate_num, framerate_den; + + void ExtractMediaInfoFrom(std::ifstream &stream) { + stream >> mimetype >> width >> height >> maxwidth >> maxheight >> + framerate_num >> framerate_den; + } + + TRACK GetTrack() { + TRACK t; + t.active = true; + t.index = 0; + t.type = TRACKTYPE::kTrackTypeVideo; + t.mimetype = mimetype; + t.width = width; + t.height = height; + t.maxwidth = maxwidth; + t.maxheight = maxheight; + t.framerate_num = framerate_num; + t.framerate_den = framerate_den; + return t; + } +}; + +template +struct AudioInfo { + std::string mimetype; + int samplerate; + int channels; + int version; + + void ExtractMediaInfoFrom(std::ifstream &stream) { + stream >> mimetype >> samplerate >> channels >> version; + } + TRACK GetTrack() { + TRACK t; + t.active = true; + t.index = 0; + t.type = TRACKTYPE::kTrackTypeAudio; + t.mimetype = mimetype; + t.sample_rate = samplerate; + t.channels = channels; + t.version = version; + return t; + } +}; + +template +class StreamReader { + public: + using Ptr = std::shared_ptr>; + friend Ptr; + static Ptr Create(const std::string &dirpath, const TRACKTYPE &type) { + return Ptr(new StreamReader(dirpath, type)); + } + virtual ~StreamReader() { + if (stream_.is_open()) { + stream_.close(); + } + } + + private: + explicit StreamReader(const std::string dirpath, const TRACKTYPE &type) + : type_(type) { + dirpath_ = "/tmp/esdata/" + dirpath; + if (utils::Exists(dirpath_) == false) { + throw std::runtime_error(dirpath_ + "doesn't exist"); + } + stream_ = std::ifstream(dirpath_ + "/ESP.es", std::ifstream::binary); + } + + public: + CodecExtraDataPtr GetExtraData() { + std::string path = dirpath_ + "/ESP.codec_extradata"; + auto stream = std::ifstream(path, std::ifstream::binary); + auto extradata = CodecExtraDataPtr(new CodecExtraData); + stream.read(reinterpret_cast(&extradata->size), + sizeof extradata->size); + using DataPtr = std::shared_ptr; + extradata->data = DataPtr(new char[extradata->size]); + stream.read(reinterpret_cast(extradata->data.get()), + extradata->size); + stream.close(); + return extradata; + } + + PacketPtr ReadNextPacket() { + if (stream_.eof()) { + return nullptr; + } + auto pkt = PacketPtr(new Packet); + stream_.read(reinterpret_cast(&pkt->pts), sizeof pkt->pts); + stream_.read(reinterpret_cast(&pkt->duration), + sizeof pkt->duration); + stream_.read(reinterpret_cast(&pkt->size), sizeof pkt->size); + using DataPtr = std::unique_ptr; + pkt->data = DataPtr(new char[pkt->size]); + stream_.read(reinterpret_cast(pkt->data.get()), pkt->size); + return pkt; + } + + template + T GetMediaInfo() { + std::string path = dirpath_ + "/ESP.info"; + T media; + auto stream = std::ifstream(path, std::ifstream::in); + media.ExtractMediaInfoFrom(stream); + stream.close(); + return media; + } + + TRACKTYPE GetTrackType() { return type_; } + + private: + std::string dirpath_; + std::ifstream stream_; + TRACKTYPE type_; +}; + +} // namespace es +#endif // __PLUSPLAYER_UT_INCLUDE_ESCOMMON_H__ \ No newline at end of file diff --git a/ut/src/esplusplayer/ut_basic.cpp b/ut/src/esplusplayer/ut_basic.cpp new file mode 100755 index 0000000..5eea648 --- /dev/null +++ b/ut/src/esplusplayer/ut_basic.cpp @@ -0,0 +1,992 @@ + +// +// @ Copyright [2017] +// + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "esplusplayer_capi/esplusplayer_capi.h" +#include "gmock/gmock.h" +#include "gst/gst.h" +#include "gtest/gtest.h" +#include "ut/include/appwindow.h" +#include "ut/include/esplusplayer/eseventlistener.hpp" +#include "ut/include/esplusplayer/esreader.hpp" +#include "ut/include/esplusplayer/tclist.h" +#include "ut/include/streamreader.hpp" + +using namespace plusplayer; + +class EsTest : public ::testing::Test { + public: + EsTest() { std::cout << "EsTest()" << std::endl; } + ~EsTest() { std::cout << "~EsTest()" << std::endl; } + + virtual void SetUp() override { std::cout << "SetUp()" << std::endl; } + + virtual void TearDown() override { std::cout << "TearDown()" << std::endl; } +}; + +class EsBasicTest : public ::testing::TestWithParam { + public: + EsBasicTest() { std::cout << "EsBasicTest()" << std::endl; } + ~EsBasicTest() { std::cout << "~EsBasicTest()" << std::endl; } + + static void SetUpTestCase() { + gst_init_check(nullptr, nullptr, nullptr); + window_ = new Environment(); + ESPacketDownloader::Init(); + esplayer_ = esplusplayer_create(); + ASSERT_NE(nullptr, esplayer_); + std::cout << "SetUpTestCase()" << std::endl; + } + static void TearDownTestCase() { + if (window_) { + delete window_; + window_ = nullptr; + } + esplusplayer_destroy(esplayer_); + esplayer_ = nullptr; + std::cout << "TearDownTestCase()" << std::endl; + } + + virtual void SetUp() override { + uri_ = GetParam(); + std::cout << "uri_: " << uri_ << std::endl; + video_reader_ = + new EsStreamReader(uri_ + "video/", ESPLUSPLAYER_STREAM_TYPE_VIDEO); + audio_reader_ = + new EsStreamReader(uri_ + "audio/", ESPLUSPLAYER_STREAM_TYPE_AUDIO); + callback_ = + new EsPlayerEventCallback(esplayer_, video_reader_, audio_reader_); + callback_->SetCallback(); + + std::cout << "SetUp()" << std::endl; + } + + virtual void TearDown() override { + if (nullptr != esplayer_) { + ASSERT_EQ(esplusplayer_close(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + } + delete callback_; + delete video_reader_; + delete audio_reader_; + + std::cout << "TearDown()" << std::endl; + } + + public: + std::string uri_; + EsStreamReader* video_reader_; + EsStreamReader* audio_reader_; + static esplusplayer_handle esplayer_; + EsPlayerEventCallback* callback_; + static Environment* window_; +}; +Environment* EsBasicTest::window_ = nullptr; +esplusplayer_handle EsBasicTest::esplayer_ = nullptr; + +TEST_F(EsTest, vdapi_basic_esplusplayer_create_p_1) { + esplusplayer_handle esplayer = esplusplayer_create(); + ASSERT_NE(nullptr, esplayer); + ASSERT_EQ(esplusplayer_close(esplayer), ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(esplusplayer_destroy(esplayer), ESPLUSPLAYER_ERROR_TYPE_NONE); +} + +TEST_F(EsTest, vdapi_basic_esplusplayer_open_p_1) { + esplusplayer_handle esplayer = esplusplayer_create(); + ASSERT_NE(nullptr, esplayer); + ASSERT_EQ(esplusplayer_open(esplayer), ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(esplusplayer_close(esplayer), ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(esplusplayer_destroy(esplayer), ESPLUSPLAYER_ERROR_TYPE_NONE); +} + +TEST_F(EsTest, vdapi_basic_esplusplayer_close_p_1) { + esplusplayer_handle esplayer = esplusplayer_create(); + ASSERT_NE(nullptr, esplayer); + ASSERT_EQ(esplusplayer_open(esplayer), ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(esplusplayer_close(esplayer), ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(esplusplayer_destroy(esplayer), ESPLUSPLAYER_ERROR_TYPE_NONE); +} + +TEST_F(EsTest, vdapi_basic_esplusplayer_stop_p_1) { + esplusplayer_handle esplayer = esplusplayer_create(); + ASSERT_NE(nullptr, esplayer); + ASSERT_EQ(esplusplayer_open(esplayer), ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(esplusplayer_stop(esplayer), ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(esplusplayer_close(esplayer), ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(esplusplayer_destroy(esplayer), ESPLUSPLAYER_ERROR_TYPE_NONE); +} + +TEST_F(EsTest, vdapi_basic_esplusplayer_get_error_string_p_1) { + esplusplayer_handle esplayer = esplusplayer_create(); + ASSERT_NE(nullptr, esplayer); + ASSERT_EQ(esplusplayer_open(esplayer), ESPLUSPLAYER_ERROR_TYPE_NONE); + const char* error = new char[100]; + error = + esplusplayer_get_error_string(ESPLUSPLAYER_ERROR_TYPE_NOT_SUPPORTED_FILE); + std::cout << "error type" << error << std::endl; + ASSERT_EQ(esplusplayer_close(esplayer), ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(esplusplayer_destroy(esplayer), ESPLUSPLAYER_ERROR_TYPE_NONE); +} + +TEST_F(EsTest, vdapi_basic_esplusplayer_set_tz_use_p_1) { + esplusplayer_handle esplayer = esplusplayer_create(); + ASSERT_NE(nullptr, esplayer); + ASSERT_EQ(esplusplayer_open(esplayer), ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(esplusplayer_set_tz_use(esplayer, true), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(esplusplayer_close(esplayer), ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(esplusplayer_destroy(esplayer), ESPLUSPLAYER_ERROR_TYPE_NONE); +} + +TEST_F(EsTest, vdapi_basic_esplusplayer_set_video_frame_buffer_type_p_1) { + esplusplayer_handle esplayer = esplusplayer_create(); + ASSERT_NE(nullptr, esplayer); + ASSERT_EQ(esplusplayer_open(esplayer), ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(esplusplayer_set_video_frame_buffer_type( + esplayer, ESPLUSPLAYER_DECODED_VIDEO_FRAME_BUFFER_TYPE_NONE), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(esplusplayer_close(esplayer), ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(esplusplayer_destroy(esplayer), ESPLUSPLAYER_ERROR_TYPE_NONE); +} + +TEST_F(EsTest, vdapi_basic_esplusplayer_set_buffer_size_p_1) { + esplusplayer_handle esplayer = esplusplayer_create(); + ASSERT_NE(nullptr, esplayer); + ASSERT_EQ(esplusplayer_open(esplayer), ESPLUSPLAYER_ERROR_TYPE_NONE); + esplusplayer_set_buffer_size(esplayer, + ESPLUSPLAYER_BUFFER_AUDIO_MAX_BYTE_SIZE, 10240); + ASSERT_EQ(esplusplayer_close(esplayer), ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(esplusplayer_destroy(esplayer), ESPLUSPLAYER_ERROR_TYPE_NONE); +} + +TEST_F(EsTest, vdapi_basic_esplusplayer_submit_encrypted_packet_p_1) { + esplusplayer_handle esplayer = NULL; + ASSERT_EQ(esplusplayer_submit_encrypted_packet(esplayer, NULL, NULL), + ESPLUSPLAYER_SUBMIT_STATUS_NOT_PREPARED); +} + +TEST_F(EsTest, vdapi_basic_esplusplayer_submit_trust_zone_packet_p_1) { + esplusplayer_handle esplayer = NULL; + ASSERT_EQ(esplusplayer_submit_trust_zone_packet(esplayer, NULL, 0), + ESPLUSPLAYER_SUBMIT_STATUS_NOT_PREPARED); +} + +TEST_F(EsTest, vdapi_basic_esplusplayer_submit_eos_packet_p_1) { + esplusplayer_handle esplayer = NULL; + ASSERT_EQ( + esplusplayer_submit_eos_packet(esplayer, ESPLUSPLAYER_STREAM_TYPE_VIDEO), + ESPLUSPLAYER_SUBMIT_STATUS_NOT_PREPARED); +} + +TEST_F(EsTest, vdapi_basic_esplusplayer_decoded_buffer_destroy_p_1) { + esplusplayer_handle esplayer = NULL; + ASSERT_EQ(esplusplayer_decoded_buffer_destroy(esplayer, NULL), + ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER); +} + +TEST_F(EsTest, vdapi_basic_esplusplayer_set_unlimited_max_buffer_mode_p_1) { + esplusplayer_handle esplayer = NULL; + ASSERT_EQ(esplusplayer_set_unlimited_max_buffer_mode(esplayer), + ESPLUSPLAYER_ERROR_TYPE_INVALID_PARAMETER); +} + +TEST_F(EsTest, vdapi_basic_esplusplayer_get_state_p_1) { + esplusplayer_handle esplayer = esplusplayer_create(); + ASSERT_NE(nullptr, esplayer); + ASSERT_EQ(esplusplayer_open(esplayer), ESPLUSPLAYER_ERROR_TYPE_NONE); + std::cout << "get state" << std::endl; + esplusplayer_state ret = ESPLUSPLAYER_STATE_NONE; + ret = esplusplayer_get_state(esplayer); + ASSERT_EQ(ret, ESPLUSPLAYER_STATE_IDLE); + std::cout << "get state" << ret << std::endl; + ASSERT_EQ(esplusplayer_close(esplayer), ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(esplusplayer_destroy(esplayer), ESPLUSPLAYER_ERROR_TYPE_NONE); +} + +TEST_F(EsTest, vdapi_basic_esplusplayer_set_low_latency_mode_p_1) { + esplusplayer_handle esplayer = esplusplayer_create(); + ASSERT_NE(nullptr, esplayer); + ASSERT_EQ(esplusplayer_open(esplayer), ESPLUSPLAYER_ERROR_TYPE_NONE); + esplusplayer_set_low_latency_mode(esplayer, + ESPLUSPLAYER_LOW_LATENCY_MODE_NONE); + ASSERT_EQ(esplusplayer_close(esplayer), ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(esplusplayer_destroy(esplayer), ESPLUSPLAYER_ERROR_TYPE_NONE); +} + +TEST_F(EsTest, + vdapi_basic_esplusplayer_esplusplayer_set_video_frame_peek_mode_p_1) { + esplusplayer_handle esplayer = esplusplayer_create(); + ASSERT_NE(nullptr, esplayer); + ASSERT_EQ(esplusplayer_open(esplayer), ESPLUSPLAYER_ERROR_TYPE_NONE); + esplusplayer_set_video_frame_peek_mode(esplayer); + ASSERT_EQ(esplusplayer_close(esplayer), ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(esplusplayer_destroy(esplayer), ESPLUSPLAYER_ERROR_TYPE_NONE); +} + +TEST_P(EsBasicTest, vdapi_basic_esplusplayer_set_audio_mute_p_1) { + ASSERT_EQ(esplusplayer_open(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ( + esplusplayer_set_display(esplayer_, ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY, + window_->Window()), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_TRUE(video_reader_->SetStreamInfo(esplayer_)); + ASSERT_TRUE(audio_reader_->SetStreamInfo(esplayer_)); + ASSERT_EQ(esplusplayer_prepare_async(esplayer_), + ESPLUSPLAYER_ERROR_TYPE_NONE); + callback_->WaitForPrepareDone(); + ASSERT_EQ(esplusplayer_set_audio_mute(esplayer_, true), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(esplusplayer_start(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + std::this_thread::sleep_for(std::chrono::seconds(10)); +} + +TEST_F(EsTest, vdapi_basic_esplusplayer_set_app_info_p_1) { + esplusplayer_handle esplayer = esplusplayer_create(); + ASSERT_NE(nullptr, esplayer); + ASSERT_EQ(esplusplayer_open(esplayer), ESPLUSPLAYER_ERROR_TYPE_NONE); + char a[20], appid[] = "youtube"; + char v[20], version[] = "3.0"; + char t[20], type[] = "MSE"; + esplusplayer_app_info appinfo; + strncpy(a, appid, sizeof(a)-1); + a[sizeof(a)-1] = 0x00; + strncpy(v, version, sizeof(v)-1); + v[sizeof(v)-1] = 0x00; + strncpy(t, type, sizeof(t)-1); + t[sizeof(t)-1] = 0x00; + appinfo.id = a; + appinfo.version = v; + appinfo.type = t; + ASSERT_EQ(esplusplayer_set_app_info(esplayer, &appinfo), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(esplusplayer_close(esplayer), ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(esplusplayer_destroy(esplayer), ESPLUSPLAYER_ERROR_TYPE_NONE); +} + +TEST_P(EsBasicTest, vdapi_basic_esplusplayer_close_p_2) { + ASSERT_EQ(esplusplayer_open(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + + ASSERT_EQ( + esplusplayer_set_display(esplayer_, ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY, + window_->Window()), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_TRUE(video_reader_->SetStreamInfo(esplayer_)); + ASSERT_TRUE(audio_reader_->SetStreamInfo(esplayer_)); + ASSERT_EQ(esplusplayer_prepare_async(esplayer_), + ESPLUSPLAYER_ERROR_TYPE_NONE); + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + std::cout << "BasicTest, Play, END" << std::endl; +} + +TEST_P(EsBasicTest, vdapi_basic_esplusplayer_prepare_async_p_1) { + ASSERT_EQ(esplusplayer_open(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + + ASSERT_EQ( + esplusplayer_set_display(esplayer_, ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY, + window_->Window()), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_TRUE(video_reader_->SetStreamInfo(esplayer_)); + ASSERT_TRUE(audio_reader_->SetStreamInfo(esplayer_)); + ASSERT_EQ(esplusplayer_prepare_async(esplayer_), + ESPLUSPLAYER_ERROR_TYPE_NONE); + + callback_->WaitForPrepareDone(); + ASSERT_EQ(esplusplayer_start(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + std::this_thread::sleep_for(std::chrono::seconds(10)); + + std::cout << "BasicTest, Play, END" << std::endl; +} + +TEST_P(EsBasicTest, vdapi_basic_esplusplayer_start_p_1) { + ASSERT_EQ(esplusplayer_open(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + + ASSERT_EQ( + esplusplayer_set_display(esplayer_, ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY, + window_->Window()), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_TRUE(video_reader_->SetStreamInfo(esplayer_)); + ASSERT_TRUE(audio_reader_->SetStreamInfo(esplayer_)); + ASSERT_EQ(esplusplayer_prepare_async(esplayer_), + ESPLUSPLAYER_ERROR_TYPE_NONE); + + callback_->WaitForPrepareDone(); + ASSERT_EQ(esplusplayer_start(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + std::this_thread::sleep_for(std::chrono::seconds(10)); + + std::cout << "BasicTest, Play, END" << std::endl; +} + +TEST_P(EsBasicTest, vdapi_basic_esplusplayer_pause_p_1) { + ASSERT_EQ(esplusplayer_open(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + + ASSERT_EQ( + esplusplayer_set_display(esplayer_, ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY, + window_->Window()), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_TRUE(video_reader_->SetStreamInfo(esplayer_)); + ASSERT_TRUE(audio_reader_->SetStreamInfo(esplayer_)); + ASSERT_EQ(esplusplayer_prepare_async(esplayer_), + ESPLUSPLAYER_ERROR_TYPE_NONE); + callback_->WaitForPrepareDone(); + ASSERT_EQ(esplusplayer_start(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + std::this_thread::sleep_for(std::chrono::seconds(1)); + std::cout << "Pause player" << std::endl; + ASSERT_EQ(esplusplayer_pause(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + std::this_thread::sleep_for(std::chrono::seconds(3)); + std::cout << "BasicTest, Play, END" << std::endl; +} + +TEST_P(EsBasicTest, vdapi_basic_esplusplayer_resume_p_1) { + ASSERT_EQ(esplusplayer_open(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + + ASSERT_EQ( + esplusplayer_set_display(esplayer_, ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY, + window_->Window()), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_TRUE(video_reader_->SetStreamInfo(esplayer_)); + ASSERT_TRUE(audio_reader_->SetStreamInfo(esplayer_)); + ASSERT_EQ(esplusplayer_prepare_async(esplayer_), + ESPLUSPLAYER_ERROR_TYPE_NONE); + callback_->WaitForPrepareDone(); + ASSERT_EQ(esplusplayer_start(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + std::this_thread::sleep_for(std::chrono::seconds(1)); + std::cout << "Pause player" << std::endl; + ASSERT_EQ(esplusplayer_pause(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + std::this_thread::sleep_for(std::chrono::seconds(1)); + std::cout << "resume player" << std::endl; + ASSERT_EQ(esplusplayer_resume(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + std::this_thread::sleep_for(std::chrono::seconds(5)); + std::cout << "BasicTest, Play, END" << std::endl; +} + +TEST_P(EsBasicTest, vdapi_basic_esplusplayer_deactive_p_1) { + ASSERT_EQ(esplusplayer_open(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + + ASSERT_EQ( + esplusplayer_set_display(esplayer_, ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY, + window_->Window()), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_TRUE(video_reader_->SetStreamInfo(esplayer_)); + ASSERT_TRUE(audio_reader_->SetStreamInfo(esplayer_)); + ASSERT_EQ(esplusplayer_prepare_async(esplayer_), + ESPLUSPLAYER_ERROR_TYPE_NONE); + callback_->WaitForPrepareDone(); + ASSERT_EQ(esplusplayer_start(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + std::this_thread::sleep_for(std::chrono::seconds(1)); + ASSERT_EQ(esplusplayer_deactivate(esplayer_, ESPLUSPLAYER_STREAM_TYPE_AUDIO), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(esplusplayer_deactivate(esplayer_, ESPLUSPLAYER_STREAM_TYPE_VIDEO), + ESPLUSPLAYER_ERROR_TYPE_NONE); +} + +TEST_P(EsBasicTest, vdapi_basic_esplusplayer_active_p_1) { + ASSERT_EQ(esplusplayer_open(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + + ASSERT_EQ( + esplusplayer_set_display(esplayer_, ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY, + window_->Window()), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_TRUE(video_reader_->SetStreamInfo(esplayer_)); + ASSERT_TRUE(audio_reader_->SetStreamInfo(esplayer_)); + ASSERT_EQ(esplusplayer_prepare_async(esplayer_), + ESPLUSPLAYER_ERROR_TYPE_NONE); + callback_->WaitForPrepareDone(); + ASSERT_EQ(esplusplayer_start(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + std::this_thread::sleep_for(std::chrono::seconds(1)); + ASSERT_EQ(esplusplayer_deactivate(esplayer_, ESPLUSPLAYER_STREAM_TYPE_AUDIO), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(esplusplayer_deactivate(esplayer_, ESPLUSPLAYER_STREAM_TYPE_VIDEO), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_TRUE(video_reader_->SetStreamInfo(esplayer_)); + ASSERT_TRUE(audio_reader_->SetStreamInfo(esplayer_)); + video_reader_->ResetReader(); + audio_reader_->ResetReader(); + ASSERT_EQ(esplusplayer_activate(esplayer_, ESPLUSPLAYER_STREAM_TYPE_AUDIO), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(esplusplayer_activate(esplayer_, ESPLUSPLAYER_STREAM_TYPE_VIDEO), + ESPLUSPLAYER_ERROR_TYPE_NONE); + std::this_thread::sleep_for(std::chrono::seconds(5)); + std::cout << "BasicTest, Play, END" << std::endl; +} + +TEST_P(EsBasicTest, vdapi_basic_esplusplayer_get_playing_time_p_1) { + ASSERT_EQ(esplusplayer_open(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + + ASSERT_EQ( + esplusplayer_set_display(esplayer_, ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY, + window_->Window()), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_TRUE(video_reader_->SetStreamInfo(esplayer_)); + ASSERT_TRUE(audio_reader_->SetStreamInfo(esplayer_)); + ASSERT_EQ(esplusplayer_prepare_async(esplayer_), + ESPLUSPLAYER_ERROR_TYPE_NONE); + callback_->WaitForPrepareDone(); + ASSERT_EQ(esplusplayer_start(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + std::this_thread::sleep_for(std::chrono::seconds(1)); + uint64_t cur_time1 = 0; + uint64_t cur_time2 = 0; + ASSERT_EQ(esplusplayer_get_playing_time(esplayer_, &cur_time1), + ESPLUSPLAYER_ERROR_TYPE_NONE); + std::cout << "current time is" << cur_time1 << std::endl; + ASSERT_EQ(esplusplayer_get_playing_time(esplayer_, &cur_time2), + ESPLUSPLAYER_ERROR_TYPE_NONE); + std::cout << "current time is" << cur_time2 << std::endl; + ASSERT_LE(cur_time1, cur_time2); + std::this_thread::sleep_for(std::chrono::seconds(5)); +} + +TEST_P(EsBasicTest, vdapi_basic_esplusplayer_seek_p_1) { + ASSERT_EQ(esplusplayer_open(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + + ASSERT_EQ( + esplusplayer_set_display(esplayer_, ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY, + window_->Window()), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_TRUE(video_reader_->SetStreamInfo(esplayer_)); + ASSERT_TRUE(audio_reader_->SetStreamInfo(esplayer_)); + ASSERT_EQ(esplusplayer_prepare_async(esplayer_), + ESPLUSPLAYER_ERROR_TYPE_NONE); + callback_->WaitForPrepareDone(); + ASSERT_EQ(esplusplayer_start(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + std::this_thread::sleep_for(std::chrono::seconds(1)); + ASSERT_EQ(esplusplayer_seek(esplayer_, 0), ESPLUSPLAYER_ERROR_TYPE_NONE); + std::this_thread::sleep_for(std::chrono::seconds(5)); + std::cout << "BasicTest, Play, END" << std::endl; +} + +TEST_P(EsBasicTest, vdapi_basic_esplusplayer_set_playback_rate_p_1) { + ASSERT_EQ(esplusplayer_open(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + + ASSERT_EQ( + esplusplayer_set_display(esplayer_, ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY, + window_->Window()), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_TRUE(video_reader_->SetStreamInfo(esplayer_)); + ASSERT_TRUE(audio_reader_->SetStreamInfo(esplayer_)); + ASSERT_EQ(esplusplayer_prepare_async(esplayer_), + ESPLUSPLAYER_ERROR_TYPE_NONE); + callback_->WaitForPrepareDone(); + ASSERT_EQ(esplusplayer_start(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + std::this_thread::sleep_for(std::chrono::seconds(1)); + ASSERT_EQ(esplusplayer_set_playback_rate(esplayer_, 2.0, true), + ESPLUSPLAYER_ERROR_TYPE_NONE); + std::this_thread::sleep_for(std::chrono::seconds(5)); + std::cout << "BasicTest, Play, END" << std::endl; +} + +TEST_P(EsBasicTest, vdapi_basic_esplusplayer_flush_p_1) { + ASSERT_EQ(esplusplayer_open(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ( + esplusplayer_set_display(esplayer_, ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY, + window_->Window()), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_TRUE(video_reader_->SetStreamInfo(esplayer_)); + ASSERT_TRUE(audio_reader_->SetStreamInfo(esplayer_)); + ASSERT_EQ(esplusplayer_prepare_async(esplayer_), + ESPLUSPLAYER_ERROR_TYPE_NONE); + callback_->WaitForPrepareDone(); + ASSERT_EQ(esplusplayer_start(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + std::this_thread::sleep_for(std::chrono::seconds(1)); + ASSERT_EQ(esplusplayer_flush(esplayer_, ESPLUSPLAYER_STREAM_TYPE_AUDIO), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(esplusplayer_flush(esplayer_, ESPLUSPLAYER_STREAM_TYPE_VIDEO), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(esplusplayer_resume(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + std::this_thread::sleep_for(std::chrono::seconds(5)); + std::cout << "BasicTest, Play, END" << std::endl; +} + +TEST_P(EsBasicTest, vdapi_basic_esplusplayer_set_volume_p_1) { + ASSERT_EQ(esplusplayer_open(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + + ASSERT_EQ( + esplusplayer_set_display(esplayer_, ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY, + window_->Window()), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_TRUE(video_reader_->SetStreamInfo(esplayer_)); + ASSERT_TRUE(audio_reader_->SetStreamInfo(esplayer_)); + ASSERT_EQ(esplusplayer_prepare_async(esplayer_), + ESPLUSPLAYER_ERROR_TYPE_NONE); + callback_->WaitForPrepareDone(); + int vol = 80; + int vol1 = 0; + ASSERT_EQ(esplusplayer_set_volume(esplayer_, vol), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(esplusplayer_start(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + std::this_thread::sleep_for(std::chrono::seconds(1)); + ASSERT_EQ(esplusplayer_get_volume(esplayer_, &vol1), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(vol, vol1); + std::cout << "BasicTest, Play, END" << std::endl; +} + +TEST_P(EsBasicTest, vdapi_basic_esplusplayer_get_volume_p_1) { + ASSERT_EQ(esplusplayer_open(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + + ASSERT_EQ( + esplusplayer_set_display(esplayer_, ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY, + window_->Window()), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_TRUE(video_reader_->SetStreamInfo(esplayer_)); + ASSERT_TRUE(audio_reader_->SetStreamInfo(esplayer_)); + ASSERT_EQ(esplusplayer_prepare_async(esplayer_), + ESPLUSPLAYER_ERROR_TYPE_NONE); + callback_->WaitForPrepareDone(); + int vol = 80; + int vol1 = 0; + ASSERT_EQ(esplusplayer_set_volume(esplayer_, vol), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(esplusplayer_start(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + std::this_thread::sleep_for(std::chrono::seconds(1)); + ASSERT_EQ(esplusplayer_get_volume(esplayer_, &vol1), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(vol, vol1); + std::cout << "BasicTest, Play, END" << std::endl; +} + +TEST_P(EsBasicTest, vdapi_basic_esplusplayer_get_adaptive_info_p_1) { + ASSERT_EQ(esplusplayer_open(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + + ASSERT_EQ( + esplusplayer_set_display(esplayer_, ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY, + window_->Window()), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_TRUE(video_reader_->SetStreamInfo(esplayer_)); + ASSERT_TRUE(audio_reader_->SetStreamInfo(esplayer_)); + ASSERT_EQ(esplusplayer_prepare_async(esplayer_), + ESPLUSPLAYER_ERROR_TYPE_NONE); + callback_->WaitForPrepareDone(); + ASSERT_EQ(esplusplayer_start(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + std::this_thread::sleep_for(std::chrono::seconds(3)); + uint64_t count = 0; + ASSERT_EQ(esplusplayer_get_adaptive_info( + esplayer_, static_cast(&count), + ESPLUSPLAYER_ADAPT_INFO_TYPE_DROPPED_FRAMES), + ESPLUSPLAYER_ERROR_TYPE_NONE); + std::cout << "count = " << count << std::endl; +} + +TEST_P(EsBasicTest, vdapi_basic_esplusplayer_set_submit_data_type_p_1) { + ASSERT_EQ(esplusplayer_open(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + + ASSERT_EQ( + esplusplayer_set_display(esplayer_, ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY, + window_->Window()), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(esplusplayer_set_submit_data_type( + esplayer_, ESPLUSPLAYER_SUBMIT_DATA_TYPE_CLEAN_DATA), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_TRUE(audio_reader_->SetStreamInfo(esplayer_)); + ASSERT_TRUE(audio_reader_->SetStreamInfo(esplayer_)); + ASSERT_EQ(esplusplayer_prepare_async(esplayer_), + ESPLUSPLAYER_ERROR_TYPE_NONE); + callback_->WaitForPrepareDone(); + ASSERT_EQ(esplusplayer_start(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + std::this_thread::sleep_for(std::chrono::seconds(3)); +} + +TEST_P(EsBasicTest, vdapi_basic_esplusplayer_render_video_frame_p_1) { + ASSERT_EQ(esplusplayer_open(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + + ASSERT_EQ( + esplusplayer_set_display(esplayer_, ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY, + window_->Window()), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(esplusplayer_set_video_frame_peek_mode(esplayer_), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_TRUE(video_reader_->SetStreamInfo(esplayer_)); + ASSERT_TRUE(audio_reader_->SetStreamInfo(esplayer_)); + ASSERT_EQ(esplusplayer_prepare_async(esplayer_), + ESPLUSPLAYER_ERROR_TYPE_NONE); + callback_->WaitForPrepareDone(); + ASSERT_EQ(esplusplayer_start(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + std::this_thread::sleep_for(std::chrono::seconds(3)); + ASSERT_EQ(esplusplayer_pause(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(esplusplayer_seek(esplayer_, 0), ESPLUSPLAYER_ERROR_TYPE_NONE); + std::this_thread::sleep_for(std::chrono::seconds(5)); + ASSERT_EQ(esplusplayer_render_video_frame(esplayer_), + ESPLUSPLAYER_ERROR_TYPE_NONE); + std::this_thread::sleep_for(std::chrono::seconds(2)); + ASSERT_EQ(esplusplayer_resume(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + std::this_thread::sleep_for(std::chrono::seconds(5)); + std::cout << "BasicTest, Play, END" << std::endl; +} + +TEST_P(EsBasicTest, vdapi_basic_esplusplayer_set_render_time_offset_p_1) { + ASSERT_EQ(esplusplayer_open(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ( + esplusplayer_set_display(esplayer_, ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY, + window_->Window()), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_TRUE(video_reader_->SetStreamInfo(esplayer_)); + ASSERT_TRUE(audio_reader_->SetStreamInfo(esplayer_)); + ASSERT_EQ(esplusplayer_set_low_latency_mode( + esplayer_, ESPLUSPLAYER_LOW_LATENCY_MODE_DISABLE_PREROLL), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(esplusplayer_prepare_async(esplayer_), + ESPLUSPLAYER_ERROR_TYPE_NONE); + callback_->WaitForPrepareDone(); + ASSERT_EQ(esplusplayer_start(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + + int64_t set_offset = 10; + ASSERT_EQ(esplusplayer_set_render_time_offset( + esplayer_, ESPLUSPLAYER_STREAM_TYPE_VIDEO, set_offset), + ESPLUSPLAYER_ERROR_TYPE_NONE); + int64_t get_offset = 0; + ASSERT_EQ(esplusplayer_get_render_time_offset( + esplayer_, ESPLUSPLAYER_STREAM_TYPE_VIDEO, &get_offset), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(set_offset, get_offset); + std::cout << "BasicTest, Play, END" << std::endl; +} + +TEST_P(EsBasicTest, vdapi_basic_esplusplayer_set_render_time_offset_p_2) { + ASSERT_EQ(esplusplayer_open(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_TRUE(video_reader_->SetStreamInfo(esplayer_)); + ASSERT_TRUE(audio_reader_->SetStreamInfo(esplayer_)); + ASSERT_EQ(esplusplayer_set_low_latency_mode( + esplayer_, ESPLUSPLAYER_LOW_LATENCY_MODE_DISABLE_PREROLL), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(esplusplayer_prepare_async(esplayer_), + ESPLUSPLAYER_ERROR_TYPE_NONE); + callback_->WaitForPrepareDone(); + ASSERT_EQ(esplusplayer_start(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + + int64_t set_offset = 10; + ASSERT_EQ(esplusplayer_set_render_time_offset( + esplayer_, ESPLUSPLAYER_STREAM_TYPE_AUDIO, set_offset), + ESPLUSPLAYER_ERROR_TYPE_NONE); + int64_t get_offset = 0; + ASSERT_EQ(esplusplayer_get_render_time_offset( + esplayer_, ESPLUSPLAYER_STREAM_TYPE_AUDIO, &get_offset), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(set_offset, get_offset); + std::cout << "BasicTest, Play, END" << std::endl; +} + +TEST_P(EsBasicTest, vdapi_basic_esplusplayer_set_render_time_offset_n_1) { + ASSERT_EQ(esplusplayer_open(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ( + esplusplayer_set_display(esplayer_, ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY, + window_->Window()), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_TRUE(video_reader_->SetStreamInfo(esplayer_)); + ASSERT_TRUE(audio_reader_->SetStreamInfo(esplayer_)); + ASSERT_EQ(esplusplayer_prepare_async(esplayer_), + ESPLUSPLAYER_ERROR_TYPE_NONE); + callback_->WaitForPrepareDone(); + ASSERT_EQ(esplusplayer_start(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + + int64_t set_offset = 10; + ASSERT_EQ(esplusplayer_set_render_time_offset( + esplayer_, ESPLUSPLAYER_STREAM_TYPE_VIDEO, set_offset), + ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION); + int64_t get_offset = 0; + ASSERT_EQ(esplusplayer_get_render_time_offset( + esplayer_, ESPLUSPLAYER_STREAM_TYPE_VIDEO, &get_offset), + ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION); + std::cout << "BasicTest, Play, END" << std::endl; +} + +TEST_P(EsBasicTest, vdapi_basic_esplusplayer_set_render_time_offset_n_2) { + ASSERT_EQ(esplusplayer_open(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ( + esplusplayer_set_display(esplayer_, ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY, + window_->Window()), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_TRUE(video_reader_->SetStreamInfo(esplayer_)); + ASSERT_TRUE(audio_reader_->SetStreamInfo(esplayer_)); + ASSERT_EQ(esplusplayer_set_low_latency_mode( + esplayer_, ESPLUSPLAYER_LOW_LATENCY_MODE_DISABLE_SYNC), + ESPLUSPLAYER_ERROR_TYPE_NONE); + + int64_t set_offset = 10; + ASSERT_EQ(esplusplayer_set_render_time_offset( + esplayer_, ESPLUSPLAYER_STREAM_TYPE_VIDEO, set_offset), + ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION); + int64_t get_offset = 0; + ASSERT_EQ(esplusplayer_get_render_time_offset( + esplayer_, ESPLUSPLAYER_STREAM_TYPE_VIDEO, &get_offset), + ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION); + std::cout << "BasicTest, Play, END" << std::endl; +} + +TEST_P(EsBasicTest, vdapi_basic_esplusplayer_switch_audio_stream_onthefly_p_1) { + if (uri_.find("aac") == std::string::npos) return; + ASSERT_EQ(esplusplayer_open(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ( + esplusplayer_set_display(esplayer_, ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY, + window_->Window()), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_TRUE(video_reader_->SetStreamInfo(esplayer_)); + ASSERT_TRUE(audio_reader_->SetStreamInfo(esplayer_)); + ASSERT_EQ(esplusplayer_prepare_async(esplayer_), + ESPLUSPLAYER_ERROR_TYPE_NONE); + callback_->WaitForPrepareDone(); + esplusplayer_audio_stream_info audio_stream; + memset(&audio_stream, 0, sizeof(esplusplayer_audio_stream_info)); + audio_stream.mime_type = ESPLUSPLAYER_AUDIO_MIME_TYPE_AC3; + audio_stream.sample_rate = 48000; + audio_stream.channels = 2; + ASSERT_EQ(esplusplayer_switch_audio_stream_onthefly(esplayer_, &audio_stream), + ESPLUSPLAYER_ERROR_TYPE_NONE); + std::cout << "set audio stream info after flush" << std::endl; + std::cout << "BasicTest, Play, END" << std::endl; +} + +TEST_P(EsBasicTest, vdapi_basic_esplusplayer_switch_audio_stream_onthefly_n_1) { + if (uri_.find("aac") == std::string::npos) return; + ASSERT_EQ(esplusplayer_open(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ( + esplusplayer_set_display(esplayer_, ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY, + window_->Window()), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_TRUE(video_reader_->SetStreamInfo(esplayer_)); + ASSERT_TRUE(audio_reader_->SetStreamInfo(esplayer_)); + ASSERT_EQ(esplusplayer_prepare_async(esplayer_), + ESPLUSPLAYER_ERROR_TYPE_NONE); + callback_->WaitForPrepareDone(); + + esplusplayer_audio_stream_info audio_stream; + memset(&audio_stream, 0, sizeof(esplusplayer_audio_stream_info)); + audio_stream.mime_type = ESPLUSPLAYER_AUDIO_MIME_TYPE_OPUS; + audio_stream.sample_rate = 48000; + audio_stream.channels = 2; + ASSERT_EQ(esplusplayer_switch_audio_stream_onthefly(esplayer_, &audio_stream), + ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION); + std::cout << "BasicTest, Play, END" << std::endl; +} + +TEST_P(EsBasicTest, vdapi_basic_esplusplayer_set_video_codec_type_p_1) { + ASSERT_EQ(esplusplayer_open(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + + ASSERT_EQ( + esplusplayer_set_display(esplayer_, ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY, + window_->Window()), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(esplusplayer_set_video_codec_type(esplayer_, + ESPLUSPLAYER_VIDEO_CODEC_TYPE_HW), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_TRUE(video_reader_->SetStreamInfo(esplayer_)); + ASSERT_TRUE(audio_reader_->SetStreamInfo(esplayer_)); + ASSERT_EQ(esplusplayer_prepare_async(esplayer_), + ESPLUSPLAYER_ERROR_TYPE_NONE); + callback_->WaitForPrepareDone(); + ASSERT_EQ(esplusplayer_start(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + std::this_thread::sleep_for(std::chrono::seconds(3)); + std::cout << "BasicTest, Play, END" << std::endl; +} + +TEST_P(EsBasicTest, vdapi_basic_esplusplayer_set_audio_codec_type_p_1) { + ASSERT_EQ(esplusplayer_open(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + + ASSERT_EQ( + esplusplayer_set_display(esplayer_, ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY, + window_->Window()), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(esplusplayer_set_audio_codec_type(esplayer_, + ESPLUSPLAYER_AUDIO_CODEC_TYPE_HW), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_TRUE(video_reader_->SetStreamInfo(esplayer_)); + ASSERT_TRUE(audio_reader_->SetStreamInfo(esplayer_)); + ASSERT_EQ(esplusplayer_prepare_async(esplayer_), + ESPLUSPLAYER_ERROR_TYPE_NONE); + callback_->WaitForPrepareDone(); + ASSERT_EQ(esplusplayer_start(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + std::this_thread::sleep_for(std::chrono::seconds(3)); + std::cout << "BasicTest, Play, END" << std::endl; +} + +TEST_P(EsBasicTest, vdapi_basic_esplusplayer_get_virtual_rsc_id_p_1) { + ASSERT_EQ(esplusplayer_open(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + + ASSERT_EQ( + esplusplayer_set_display(esplayer_, ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY, + window_->Window()), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_TRUE(video_reader_->SetStreamInfo(esplayer_)); + ASSERT_TRUE(audio_reader_->SetStreamInfo(esplayer_)); + ASSERT_EQ(esplusplayer_prepare_async(esplayer_), + ESPLUSPLAYER_ERROR_TYPE_NONE); + callback_->WaitForPrepareDone(); + + int virtual_id = -1; + ASSERT_EQ(esplusplayer_get_virtual_rsc_id( + esplayer_, ESPLUSPLAYER_RSC_TYPE_VIDEO_RENDERER, &virtual_id), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_TRUE(virtual_id != -1); + std::cout << "BasicTest, Play, END" << std::endl; +} + +TEST_P(EsBasicTest, vdapi_basic_esplusplayer_init_audio_easing_info_p_1) { + ASSERT_EQ(esplusplayer_open(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ( + esplusplayer_set_display(esplayer_, ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY, + window_->Window()), + ESPLUSPLAYER_ERROR_TYPE_NONE); + const esplusplayer_target_audio_easing_info init_info = { + 0, 1000, ESPLUSPLAYER_AUDIO_EASING_LINEAR}; + ASSERT_EQ(esplusplayer_init_audio_easing_info(esplayer_, 100, 0, &init_info), + ESPLUSPLAYER_ERROR_TYPE_NONE); +} + +TEST_P(EsBasicTest, vdapi_basic_esplusplayer_init_audio_easing_info_n_1) { + const esplusplayer_target_audio_easing_info init_info = { + 0, 1000, ESPLUSPLAYER_AUDIO_EASING_LINEAR}; + ASSERT_EQ(esplusplayer_init_audio_easing_info(esplayer_, 100, 0, &init_info), + ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION); +} + +TEST_P(EsBasicTest, vdapi_basic_esplusplayer_update_audio_easing_info_p_1) { + ASSERT_EQ(esplusplayer_open(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ( + esplusplayer_set_display(esplayer_, ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY, + window_->Window()), + ESPLUSPLAYER_ERROR_TYPE_NONE); + + const esplusplayer_target_audio_easing_info init_info = { + 0, 1000, ESPLUSPLAYER_AUDIO_EASING_LINEAR}; + ASSERT_EQ(esplusplayer_init_audio_easing_info(esplayer_, 100, 0, &init_info), + ESPLUSPLAYER_ERROR_TYPE_NONE); + + const esplusplayer_target_audio_easing_info update_info = { + 100, 1000, ESPLUSPLAYER_AUDIO_EASING_LINEAR}; + ASSERT_EQ(esplusplayer_update_audio_easing_info(esplayer_, &update_info), + ESPLUSPLAYER_ERROR_TYPE_NONE); +} + +TEST_P(EsBasicTest, vdapi_basic_esplusplayer_update_audio_easing_info_n_1) { + ASSERT_EQ(esplusplayer_open(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ( + esplusplayer_set_display(esplayer_, ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY, + window_->Window()), + ESPLUSPLAYER_ERROR_TYPE_NONE); + + const esplusplayer_target_audio_easing_info update_info = { + 100, 1000, ESPLUSPLAYER_AUDIO_EASING_LINEAR}; + ASSERT_EQ(esplusplayer_update_audio_easing_info(esplayer_, &update_info), + ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION); +} + +TEST_P(EsBasicTest, vdapi_basic_esplusplayer_get_audio_easing_info_p_1) { + ASSERT_EQ(esplusplayer_open(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ( + esplusplayer_set_display(esplayer_, ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY, + window_->Window()), + ESPLUSPLAYER_ERROR_TYPE_NONE); + + uint32_t init_volume = 100; + uint32_t init_elapsed_time = 50; + const esplusplayer_target_audio_easing_info info = { + 50, 1000, ESPLUSPLAYER_AUDIO_EASING_LINEAR}; + ASSERT_EQ(esplusplayer_init_audio_easing_info(esplayer_, init_volume, + init_elapsed_time, &info), + ESPLUSPLAYER_ERROR_TYPE_NONE); + + uint32_t cur_volume = 0; + uint32_t elapsed_time = 0; + esplusplayer_target_audio_easing_info get_info; + ASSERT_EQ(esplusplayer_get_audio_easing_info(esplayer_, &cur_volume, + &elapsed_time, &get_info), + ESPLUSPLAYER_ERROR_TYPE_NONE); + + EXPECT_EQ(init_volume, cur_volume); + EXPECT_EQ(init_elapsed_time, elapsed_time); + EXPECT_EQ(info.volume, get_info.volume); + EXPECT_EQ(info.duration, get_info.duration); + EXPECT_EQ(info.type, get_info.type); +} + +TEST_P(EsBasicTest, vdapi_basic_esplusplayer_get_audio_easing_info_n_1) { + ASSERT_EQ(esplusplayer_open(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ( + esplusplayer_set_display(esplayer_, ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY, + window_->Window()), + ESPLUSPLAYER_ERROR_TYPE_NONE); + + uint32_t cur_volume = 0; + uint32_t elapsed_time = 0; + esplusplayer_target_audio_easing_info get_info; + ASSERT_EQ(esplusplayer_get_audio_easing_info(esplayer_, &cur_volume, + &elapsed_time, &get_info), + ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION); +} + +TEST_P(EsBasicTest, vdapi_basic_esplusplayer_start_audio_easing_p_1) { + ASSERT_EQ(esplusplayer_open(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ( + esplusplayer_set_display(esplayer_, ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY, + window_->Window()), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_TRUE(audio_reader_->SetStreamInfo(esplayer_)); + ASSERT_EQ(esplusplayer_prepare_async(esplayer_), + ESPLUSPLAYER_ERROR_TYPE_NONE); + callback_->WaitForPrepareDone(); + + const esplusplayer_target_audio_easing_info info = { + 50, 1000, ESPLUSPLAYER_AUDIO_EASING_LINEAR}; + ASSERT_EQ(esplusplayer_init_audio_easing_info(esplayer_, 100, 0, &info), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(esplusplayer_start_audio_easing(esplayer_), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(esplusplayer_start(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); +} + +TEST_P(EsBasicTest, vdapi_basic_esplusplayer_start_audio_easing_n_1) { + ASSERT_EQ(esplusplayer_open(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ( + esplusplayer_set_display(esplayer_, ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY, + window_->Window()), + ESPLUSPLAYER_ERROR_TYPE_NONE); + + ASSERT_EQ(esplusplayer_start_audio_easing(esplayer_), + ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION); +} + +TEST_P(EsBasicTest, vdapi_basic_esplusplayer_stop_audio_easing_p_1) { + ASSERT_EQ(esplusplayer_open(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ( + esplusplayer_set_display(esplayer_, ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY, + window_->Window()), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_TRUE(audio_reader_->SetStreamInfo(esplayer_)); + ASSERT_EQ(esplusplayer_prepare_async(esplayer_), + ESPLUSPLAYER_ERROR_TYPE_NONE); + callback_->WaitForPrepareDone(); + + const esplusplayer_target_audio_easing_info info = { + 50, 1000, ESPLUSPLAYER_AUDIO_EASING_LINEAR}; + ASSERT_EQ(esplusplayer_init_audio_easing_info(esplayer_, 100, 0, &info), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(esplusplayer_start_audio_easing(esplayer_), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(esplusplayer_start(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + std::this_thread::sleep_for(std::chrono::seconds(1)); + ASSERT_EQ(esplusplayer_stop_audio_easing(esplayer_), + ESPLUSPLAYER_ERROR_TYPE_NONE); +} + +TEST_P(EsBasicTest, vdapi_basic_esplusplayer_stop_audio_easing_n_1) { + ASSERT_EQ(esplusplayer_open(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ( + esplusplayer_set_display(esplayer_, ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY, + window_->Window()), + ESPLUSPLAYER_ERROR_TYPE_NONE); + + ASSERT_EQ(esplusplayer_stop_audio_easing(esplayer_), + ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION); +} + +INSTANTIATE_TEST_CASE_P(ESPlusplayer, EsBasicTest, + ::testing::ValuesIn(es_tc::tc_list)); diff --git a/ut/src/esplusplayer/ut_display.cpp b/ut/src/esplusplayer/ut_display.cpp new file mode 100755 index 0000000..351b09b --- /dev/null +++ b/ut/src/esplusplayer/ut_display.cpp @@ -0,0 +1,216 @@ + +// +// @ Copyright [2017] +// + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "gmock/gmock.h" +#include "gst/gst.h" +#include "gtest/gtest.h" + +#include "esplusplayer_capi/esplusplayer_capi.h" +#include "esplusplayer_capi/esplusplayer_internal.h" +#include "ut/include/appwindow.h" +#include "ut/include/esplusplayer/eseventlistener.hpp" +#include "ut/include/esplusplayer/esreader.hpp" +//#include "ut/include/esplusplayer/tclist.h" +#include "ut/include/streamreader.hpp" + +using namespace plusplayer; +namespace es_tc_diaplay { + static const std::string es_h264_aac = "es_h264_aac/"; + static const std::string es_hevc_ac3 = "es_hevc_ac3/"; + static const std::string es_vp9_opus = "es_vp9_opus/"; + std::vector tc_list = { + es_h264_aac, + //es_hevc_ac3, + es_vp9_opus, + }; +} + +class EsDisplayTest : public ::testing::TestWithParam { + public: + EsDisplayTest() { std::cout << "EsDisplayTest()" << std::endl; } + ~EsDisplayTest() { std::cout << "~EsDisplayTest()" << std::endl; } + + static void SetUpTestCase() { + gst_init_check(nullptr, nullptr, nullptr); + window_ = new Environment(); + ESPacketDownloader::Init(); + std::cout << "SetUpTestCase()" << std::endl; + } + static void TearDownTestCase() { + if (window_) { + delete window_; + window_ = nullptr; + } + std::cout << "TearDownTestCase()" << std::endl; + } + + virtual void SetUp() override { + uri_ = GetParam(); + std::cout << "uri_: " << uri_ << std::endl; + video_reader_ = + new EsStreamReader(uri_ + "video/", ESPLUSPLAYER_STREAM_TYPE_VIDEO); + audio_reader_ = + new EsStreamReader(uri_ + "audio/", ESPLUSPLAYER_STREAM_TYPE_AUDIO); + esplayer_ = esplusplayer_create(); + ASSERT_NE(nullptr, esplayer_); + callback_ = + new EsPlayerEventCallback(esplayer_, video_reader_, audio_reader_); + callback_->SetCallback(); + + std::cout << "SetUp()" << std::endl; + } + + virtual void TearDown() override { + if (nullptr != esplayer_) { + ASSERT_EQ(esplusplayer_stop(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(esplusplayer_close(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + } + delete callback_; + delete video_reader_; + delete audio_reader_; + + std::cout << "TearDown()" << std::endl; + } + + public: + std::string uri_; + EsStreamReader* video_reader_; + EsStreamReader* audio_reader_; + esplusplayer_handle esplayer_; + EsPlayerEventCallback* callback_; + static Environment* window_; +}; +Environment* EsDisplayTest::window_ = nullptr; + +TEST_P(EsDisplayTest, vdapi_display_esplusplayer_set_display_p_1) { + ASSERT_EQ(esplusplayer_open(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ( + esplusplayer_set_display(esplayer_, ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY, + window_->Window()), + ESPLUSPLAYER_ERROR_TYPE_NONE); +} + +TEST_P(EsDisplayTest, vdapi_display_esplusplayer_set_ecore_display_p_1) { + ASSERT_EQ(esplusplayer_open(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(esplusplayer_set_ecore_display( + esplayer_, ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY, + window_->EcoreWindow(), 0, 0, 1920, 1080), + ESPLUSPLAYER_ERROR_TYPE_NONE); +} + +TEST_P(EsDisplayTest, vdapi_display_esplusplayer_set_surface_display_p_1) { + ASSERT_EQ(esplusplayer_open(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + unsigned int surfaceid = 1; + ASSERT_EQ(esplusplayer_set_surface_display(esplayer_, + ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY, + surfaceid, 0, 0, 1920, 1080), + ESPLUSPLAYER_ERROR_TYPE_NONE); +} + +TEST_P(EsDisplayTest, vdapi_display_esplusplayer_set_display_mode_p_1) { + ASSERT_EQ(esplusplayer_open(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(esplusplayer_set_display_mode(esplayer_, + ESPLUSPLAYER_DISPLAY_MODE_FULL_SCREEN), + ESPLUSPLAYER_ERROR_TYPE_NONE); +} + +TEST_P(EsDisplayTest, vdapi_display_esplusplayer_set_display_roi_p_1) { + ASSERT_EQ(esplusplayer_open(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ( + esplusplayer_set_display(esplayer_, ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY, + window_->Window()), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(esplusplayer_set_display_mode(esplayer_, + ESPLUSPLAYER_DISPLAY_MODE_DST_ROI), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(esplusplayer_set_display_roi(esplayer_,0,0,600,500),ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_TRUE(video_reader_->SetStreamInfo(esplayer_)); + ASSERT_TRUE(audio_reader_->SetStreamInfo(esplayer_)); + ASSERT_EQ(esplusplayer_prepare_async(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + + callback_->WaitForPrepareDone(); + ASSERT_EQ(esplusplayer_start(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + std::this_thread::sleep_for(std::chrono::seconds(5)); +} + +TEST_P(EsDisplayTest, vdapi_display_esplusplayer_set_display_visible_p_1) { + ASSERT_EQ(esplusplayer_open(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ( + esplusplayer_set_display(esplayer_, ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY, + window_->Window()), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(esplusplayer_set_display_visible(esplayer_,false),ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_TRUE(video_reader_->SetStreamInfo(esplayer_)); + ASSERT_TRUE(audio_reader_->SetStreamInfo(esplayer_)); + ASSERT_EQ(esplusplayer_prepare_async(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + + callback_->WaitForPrepareDone(); + ASSERT_EQ(esplusplayer_start(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + std::this_thread::sleep_for(std::chrono::seconds(5)); +} + +TEST_P(EsDisplayTest, vdapi_display_esplusplayer_set_video_roi_p_1) { + ASSERT_EQ(esplusplayer_open(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ( + esplusplayer_set_display(esplayer_, ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY, + window_->Window()), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(esplusplayer_set_video_roi(esplayer_,0,0,0.5,0.5),ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_TRUE(video_reader_->SetStreamInfo(esplayer_)); + ASSERT_TRUE(audio_reader_->SetStreamInfo(esplayer_)); + ASSERT_EQ(esplusplayer_prepare_async(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + + callback_->WaitForPrepareDone(); + ASSERT_EQ(esplusplayer_start(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + std::this_thread::sleep_for(std::chrono::seconds(5)); +} + +TEST_P(EsDisplayTest, vdapi_display_esplusplayer_set_display_rotation_p_1) { + ASSERT_EQ(esplusplayer_open(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ( + esplusplayer_set_display(esplayer_, ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY, + window_->Window()), + ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ(esplusplayer_set_display_rotation(esplayer_,ESPLUSPLAYER_DISPLAY_ROTATION_TYPE_90),ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_TRUE(video_reader_->SetStreamInfo(esplayer_)); + ASSERT_TRUE(audio_reader_->SetStreamInfo(esplayer_)); + ASSERT_EQ(esplusplayer_prepare_async(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + + callback_->WaitForPrepareDone(); + ASSERT_EQ(esplusplayer_start(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + std::this_thread::sleep_for(std::chrono::seconds(5)); +} + +TEST_P(EsDisplayTest, vdapi_display_esplusplayer_get_display_rotation_p_1) { + ASSERT_EQ(esplusplayer_open(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_EQ( + esplusplayer_set_display(esplayer_, ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY, + window_->Window()), + ESPLUSPLAYER_ERROR_TYPE_NONE); + esplusplayer_display_rotation_type rotation_set = ESPLUSPLAYER_DISPLAY_ROTATION_TYPE_90; + esplusplayer_display_rotation_type rotation_get = ESPLUSPLAYER_DISPLAY_ROTATION_TYPE_NONE; + ASSERT_EQ(esplusplayer_set_display_rotation(esplayer_,rotation_set),ESPLUSPLAYER_ERROR_TYPE_NONE); + ASSERT_TRUE(video_reader_->SetStreamInfo(esplayer_)); + ASSERT_TRUE(audio_reader_->SetStreamInfo(esplayer_)); + ASSERT_EQ(esplusplayer_prepare_async(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + esplusplayer_get_display_rotation(esplayer_,&rotation_get); + ASSERT_EQ(rotation_set,rotation_get); + callback_->WaitForPrepareDone(); + ASSERT_EQ(esplusplayer_start(esplayer_), ESPLUSPLAYER_ERROR_TYPE_NONE); + std::this_thread::sleep_for(std::chrono::seconds(5)); +} + +INSTANTIATE_TEST_CASE_P(ESPlusplayer, EsDisplayTest, + ::testing::ValuesIn(es_tc_diaplay::tc_list)); diff --git a/ut/src/esplusplayer/ut_setstream.cpp b/ut/src/esplusplayer/ut_setstream.cpp new file mode 100755 index 0000000..4a87e7d --- /dev/null +++ b/ut/src/esplusplayer/ut_setstream.cpp @@ -0,0 +1,51 @@ +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include +#include + +#include "esplusplayer_capi/esplusplayer_capi.h" + +class EsVideoSetStreamTest : public ::testing::Test {}; + +TEST_F(EsVideoSetStreamTest, MJPEG) { + esplusplayer_video_stream_info vstream_info; + vstream_info.codec_data = nullptr; + vstream_info.codec_data_length = 0; + vstream_info.mime_type = ESPLUSPLAYER_VIDEO_MIME_TYPE_MJPEG; + vstream_info.width = 1920; + vstream_info.height = 1080; + vstream_info.max_width = 1920; + vstream_info.max_height = 1080; + vstream_info.framerate_num = 30; + vstream_info.framerate_den = 1; + + auto* handle = esplusplayer_create(); + esplusplayer_open(handle); + esplusplayer_set_video_stream_info(handle, &vstream_info); + esplusplayer_prepare_async(handle); + std::this_thread::sleep_for(std::chrono::seconds(2)); + esplusplayer_close(handle); + esplusplayer_destroy(handle); +} + +TEST_F(EsVideoSetStreamTest, UHD_MJPEG) { + esplusplayer_video_stream_info vstream_info; + vstream_info.codec_data = nullptr; + vstream_info.codec_data_length = 0; + vstream_info.mime_type = ESPLUSPLAYER_VIDEO_MIME_TYPE_MJPEG; + vstream_info.width = 3840; + vstream_info.height = 2160; + vstream_info.max_width = 3840; + vstream_info.max_height = 2160; + vstream_info.framerate_num = 30; + vstream_info.framerate_den = 1; + + auto* handle = esplusplayer_create(); + esplusplayer_open(handle); + esplusplayer_set_video_stream_info(handle, &vstream_info); + esplusplayer_prepare_async(handle); + std::this_thread::sleep_for(std::chrono::seconds(2)); + esplusplayer_close(handle); + esplusplayer_destroy(handle); +} \ No newline at end of file diff --git a/ut/src/mixer/constant.cpp b/ut/src/mixer/constant.cpp new file mode 100755 index 0000000..4b86443 --- /dev/null +++ b/ut/src/mixer/constant.cpp @@ -0,0 +1,51 @@ +#include "mixer/constant.h" + +namespace plusplayer_ut { + +VideoPlaneManipulableInfo GetVideoPlaneManipulableInfo( + BufferHandleType handle, const PlaneComponent& comp, + const std::uint32_t& linesize, const Geometry& geom) { + VideoPlaneManipulableInfo ret; + ret.handle = handle; + ret.component = comp; + ret.linesize = linesize; + ret.rect = geom; + return ret; +} + +Geometry GetGeometry(const int& x, const int& y, const int& w, const int& h) { + Geometry geom; + geom.x = x; + geom.y = y; + geom.w = w; + geom.h = h; + return geom; +} + +CropArea GetCropArea(const double& x, const double& y, const double& w, + const double& h) { + CropArea croparea; + croparea.scale_x = x; + croparea.scale_y = y; + croparea.scale_w = w; + croparea.scale_h = h; + return croparea; +} + +Mixer::ResolutionInfo GetResolutionInfo(int width, int height, int fnum, + int fden) { + Mixer::ResolutionInfo rinfo; + rinfo.width = width; + rinfo.height = height; + rinfo.framerate_num = fnum; + rinfo.framerate_den = fden; + return rinfo; +} + +static std::uint32_t _kDefaultBuffer = 0; +BufferDefaultType kDefaultBuffer = + reinterpret_cast(&_kDefaultBuffer); + +BufferUnionHandleType kDefaultMappedHandle = {.u32 = kDefaultBufferHandle}; + +} // namespace plusplayer_ut \ No newline at end of file diff --git a/ut/src/mixer/matcher.cpp b/ut/src/mixer/matcher.cpp new file mode 100755 index 0000000..83f503c --- /dev/null +++ b/ut/src/mixer/matcher.cpp @@ -0,0 +1,10 @@ +#include "mixer/matcher.h" + +namespace plusplayer_ut { + +Matcher IsSameVideoPlaneManipulableInfo( + const VideoPlaneManipulableInfo& comparer) { + return MakeMatcher(new IsSameVideoPlaneManipulableInfoMatcher(comparer)); +} + +} // namespace plusplayer_ut \ No newline at end of file diff --git a/ut/src/mixer/ut_espp_mixerscenario.cpp b/ut/src/mixer/ut_espp_mixerscenario.cpp new file mode 100755 index 0000000..9121b83 --- /dev/null +++ b/ut/src/mixer/ut_espp_mixerscenario.cpp @@ -0,0 +1,597 @@ +// +// @ Copyright [2020] +// +#include + +#include + +#include "core/utils/plusplayer_log.h" +#include "mixer_capi/mixer_capi.h" +#include "ut/include/plusplayer/utility.h" +#include "ut/include/streamreader.hpp" + +using namespace plusplayer; +using namespace plusplayer_ut; +using namespace utils; +using namespace std; + +using utils::Utility; +std::string es_uri_1 = "youtube/"; +std::string es_uri_2 = "bunny/"; +std::string es_vp8_uri = "es_vp8_vorbis/"; + +//max playing duration : 10 sec => see EsStreamReader::ReadNextPacket() +constexpr int kPlayingTime = 2; // (sec) + +//------------------------------------------------------------ +class MixerEsppScenarioTestF : public ::testing::Test { + public: + explicit MixerEsppScenarioTestF(void) + : util_(Utility::Instance()), + mixer_(nullptr), + player1_(nullptr), + player2_(nullptr), + player3_(nullptr), + player4_(nullptr){}; + ~MixerEsppScenarioTestF(void){}; + + static void SetUpTestCase() { + ESPacketDownloader::Init(); + std::cout << "SetUpTestCase()" << std::endl; + } + + static void TearDownTestCase() {} + + virtual void SetUp(void) override { + LOG_ERROR("%s", util_.GetCurrentTestName()); + mixer_ = mixer_create(); + mixer_set_display(mixer_, MIXER_DISPLAY_TYPE_OVERLAY, util_.GetWindow()); +#if 1 + roi1_.x = 20; + roi1_.y = 20; + roi1_.w = 720; + roi1_.h = 480; + + roi2_.x = 1000; + roi2_.y = 20; + roi2_.w = 720; + roi2_.h = 480; + + roi3_.x = 1000; + roi3_.y = 520; + roi3_.w = 720; + roi3_.h = 480; + + roi4_.x = 20; + roi4_.y = 520; + roi4_.w = 720; + roi4_.h = 480; +#else + roi1_.x = 20; + roi1_.y = 20; + roi1_.w = 1180; + roi1_.h = 720; + + roi2_.x = 1220; + roi2_.y = 20; + roi2_.w = 640; + roi2_.h = 480; + + roi3_.x = 1220; + roi3_.y = 520; + roi3_.w = 640; + roi3_.h = 480; +#endif + } + + virtual void TearDown(void) override { + util_.DestroyESPP(player1_); + util_.DestroyESPP(player2_); + util_.DestroyESPP(player3_); + util_.DestroyESPP(player4_); + mixer_destroy(mixer_); + player1_ = nullptr; + player2_ = nullptr; + player3_ = nullptr; + player4_ = nullptr; + mixer_ = nullptr; + LOG_ERROR("%s", util_.GetCurrentTestName()); + } + + public: + Utility& util_; + mixer_handle mixer_; + esplusplayer_handle player1_; + esplusplayer_handle player2_; + esplusplayer_handle player3_; + esplusplayer_handle player4_; + Geometry roi1_; + Geometry roi2_; + Geometry roi3_; + Geometry roi4_; +}; + +#if 1 // normal mixer test +TEST_F(MixerEsppScenarioTestF, Basic) { + player1_ = util_.GetOpenedMixESPP(mixer_, roi1_); + ASSERT_NE(player1_, nullptr); + player2_ = util_.GetOpenedMixESPP(mixer_, roi2_); + ASSERT_NE(player2_, nullptr); + player3_ = util_.GetOpenedMixESPP(mixer_, roi3_); + ASSERT_NE(player3_, nullptr); + + EXPECT_EQ(mixer_set_audio_focus(mixer_, player1_), MIXER_ERROR_TYPE_NONE); + + EXPECT_TRUE(util_.PrepareESPP(player1_, es_uri_1)); + EXPECT_TRUE(util_.PrepareESPP(player2_, es_uri_1)); + EXPECT_TRUE(util_.PrepareESPP(player3_, es_uri_1)); + + EXPECT_EQ(esplusplayer_start(player1_), ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_start(player2_), ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_start(player3_), ESPLUSPLAYER_ERROR_TYPE_NONE); + + EXPECT_EQ(mixer_start(mixer_), MIXER_ERROR_TYPE_NONE); + std::this_thread::sleep_for(std::chrono::seconds(kPlayingTime)); + + EXPECT_EQ(mixer_stop(mixer_), MIXER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_close(player1_), ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_close(player2_), ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_close(player3_), ESPLUSPLAYER_ERROR_TYPE_NONE); +} + +TEST_F(MixerEsppScenarioTestF, DetachAttach) { + player1_ = util_.GetStartedMixESPP(es_uri_1, mixer_, roi1_); + ASSERT_NE(player1_, nullptr); + player2_ = util_.GetStartedMixESPP(es_uri_2, mixer_, roi2_); + ASSERT_NE(player2_, nullptr); + + EXPECT_EQ(mixer_start(mixer_), MIXER_ERROR_TYPE_NONE); + std::this_thread::sleep_for(std::chrono::seconds(kPlayingTime)); + EXPECT_EQ(esplusplayer_close(player1_), ESPLUSPLAYER_ERROR_TYPE_NONE); + + player3_ = util_.GetStartedMixESPP(es_uri_1, mixer_, roi3_); + ASSERT_NE(player3_, nullptr); + + std::this_thread::sleep_for(std::chrono::seconds(kPlayingTime)); + + EXPECT_EQ(mixer_stop(mixer_), MIXER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_close(player2_), ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_close(player3_), ESPLUSPLAYER_ERROR_TYPE_NONE); +} + +TEST_F(MixerEsppScenarioTestF, DetachAttach1) { + player1_ = util_.GetStartedMixESPP(es_uri_1, mixer_, roi1_); + ASSERT_NE(player1_, nullptr); + player2_ = util_.GetStartedMixESPP(es_uri_1, mixer_, roi2_); + ASSERT_NE(player2_, nullptr); + + EXPECT_EQ(mixer_start(mixer_), MIXER_ERROR_TYPE_NONE); + std::this_thread::sleep_for(std::chrono::seconds(kPlayingTime)); + EXPECT_EQ(esplusplayer_close(player1_), ESPLUSPLAYER_ERROR_TYPE_NONE); + + player3_ = util_.GetStartedMixESPP(es_uri_2, mixer_, roi1_); + ASSERT_NE(player3_, nullptr); + + std::this_thread::sleep_for(std::chrono::seconds(kPlayingTime)); + + EXPECT_EQ(mixer_stop(mixer_), MIXER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_close(player2_), ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_close(player3_), ESPLUSPLAYER_ERROR_TYPE_NONE); +} + +TEST_F(MixerEsppScenarioTestF, SetROI) { + player1_ = util_.GetStartedMixESPP(es_uri_1, mixer_, roi1_); + ASSERT_NE(player1_, nullptr); + + player2_ = util_.GetStartedMixESPP(es_uri_2, mixer_, roi2_); + ASSERT_NE(player2_, nullptr); + + EXPECT_EQ(mixer_start(mixer_), MIXER_ERROR_TYPE_NONE); + std::this_thread::sleep_for(std::chrono::seconds(kPlayingTime)); + + EXPECT_EQ(esplusplayer_set_display_roi(player1_, roi2_.x, roi2_.y, roi2_.w, + roi2_.h), + ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_set_display_roi(player2_, roi1_.x, roi1_.y, roi1_.w, + roi1_.h), + ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(mixer_commit(mixer_), MIXER_ERROR_TYPE_NONE); + + std::this_thread::sleep_for(std::chrono::seconds(kPlayingTime)); + + EXPECT_EQ(mixer_stop(mixer_), MIXER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_close(player1_), ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_close(player2_), ESPLUSPLAYER_ERROR_TYPE_NONE); +} + +TEST_F(MixerEsppScenarioTestF, SetROI1) { + player1_ = util_.GetStartedMixESPP(es_uri_1, mixer_, roi1_); + ASSERT_NE(player1_, nullptr); + + player2_ = util_.GetStartedMixESPP(es_uri_2, mixer_, roi2_); + ASSERT_NE(player2_, nullptr); + + EXPECT_EQ(mixer_start(mixer_), MIXER_ERROR_TYPE_NONE); + std::this_thread::sleep_for(std::chrono::seconds(kPlayingTime)); + + EXPECT_EQ(esplusplayer_set_display_roi(player1_, roi2_.x, roi2_.y, roi2_.w, + roi2_.h), + ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_set_display_roi(player2_, roi3_.x, roi3_.y, roi3_.w, + roi3_.h), + ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(mixer_commit(mixer_), MIXER_ERROR_TYPE_NONE); + + std::this_thread::sleep_for(std::chrono::seconds(kPlayingTime)); + + EXPECT_EQ(mixer_stop(mixer_), MIXER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_close(player1_), ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_close(player2_), ESPLUSPLAYER_ERROR_TYPE_NONE); +} + +TEST_F(MixerEsppScenarioTestF, CheckMaxMixedPlayer) { + player1_ = util_.GetOpenedMixESPP(mixer_, roi1_); + ASSERT_NE(player1_, nullptr); + player2_ = util_.GetOpenedMixESPP(mixer_, roi2_); + ASSERT_NE(player2_, nullptr); + player3_ = util_.GetOpenedMixESPP(mixer_, roi3_); + ASSERT_NE(player3_, nullptr); + esplusplayer_handle player4 = util_.GetOpenedMixESPP(mixer_, roi3_); + ASSERT_NE(player4, nullptr); + + EXPECT_TRUE(util_.PrepareESPP(player1_, es_uri_1)); + EXPECT_TRUE(util_.PrepareESPP(player2_, es_uri_1)); + EXPECT_TRUE(util_.PrepareESPP(player3_, es_uri_1)); + + EXPECT_FALSE(util_.PrepareESPP(player4, es_uri_1)); + + EXPECT_EQ(esplusplayer_close(player1_), ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_close(player2_), ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_close(player3_), ESPLUSPLAYER_ERROR_TYPE_NONE); + util_.DestroyESPP(player4); + EXPECT_EQ(mixer_stop(mixer_), MIXER_ERROR_TYPE_NONE); +} + +TEST_F(MixerEsppScenarioTestF, SetAudioFocus) { + player1_ = util_.GetOpenedMixESPP(mixer_, roi1_); + ASSERT_NE(player1_, nullptr); + + player2_ = util_.GetOpenedMixESPP(mixer_, roi2_); + ASSERT_NE(player2_, nullptr); + + EXPECT_EQ(mixer_set_audio_focus(mixer_, player1_), MIXER_ERROR_TYPE_NONE); + + EXPECT_TRUE(util_.PrepareESPP(player1_, es_uri_1)); + EXPECT_TRUE(util_.PrepareESPP(player2_, es_uri_2)); + + EXPECT_EQ(esplusplayer_start(player1_), ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_start(player2_), ESPLUSPLAYER_ERROR_TYPE_NONE); + + EXPECT_EQ(mixer_start(mixer_), MIXER_ERROR_TYPE_NONE); + + std::this_thread::sleep_for(std::chrono::seconds(kPlayingTime)); + + EXPECT_EQ(mixer_set_audio_focus(mixer_, player2_), MIXER_ERROR_TYPE_NONE); + util_.FeedingEsPacket(player2_, ESPLUSPLAYER_STREAM_TYPE_AUDIO, es_uri_2); + + std::this_thread::sleep_for(std::chrono::seconds(kPlayingTime)); + + EXPECT_EQ(esplusplayer_close(player1_), ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_close(player2_), ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(mixer_stop(mixer_), MIXER_ERROR_TYPE_NONE); +} + +TEST_F(MixerEsppScenarioTestF, MultiAudioTest) { + EXPECT_EQ(mixer_disable_audio_focus_setting(mixer_), MIXER_ERROR_TYPE_NONE); + + player1_ = util_.GetOpenedMixESPP(mixer_, roi1_); + ASSERT_NE(player1_, nullptr); + + player2_ = util_.GetOpenedMixESPP(mixer_, roi2_); + ASSERT_NE(player2_, nullptr); + + player3_ = util_.GetOpenedMixESPP(mixer_, roi3_); + ASSERT_NE(player3_, nullptr); + + EXPECT_EQ(esplusplayer_set_audio_codec_type(player2_, + ESPLUSPLAYER_AUDIO_CODEC_TYPE_SW), + ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_set_audio_codec_type(player3_, + ESPLUSPLAYER_AUDIO_CODEC_TYPE_SW), + ESPLUSPLAYER_ERROR_TYPE_NONE); + + EXPECT_TRUE(util_.PrepareESPP(player1_, es_uri_1)); + EXPECT_TRUE(util_.PrepareESPP(player2_, es_uri_2)); + EXPECT_TRUE(util_.PrepareESPP(player3_, es_uri_1)); + + EXPECT_EQ(esplusplayer_start(player1_), ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_start(player2_), ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_start(player3_), ESPLUSPLAYER_ERROR_TYPE_NONE); + + EXPECT_EQ(mixer_start(mixer_), MIXER_ERROR_TYPE_NONE); + + std::this_thread::sleep_for(std::chrono::seconds(kPlayingTime)); + + EXPECT_EQ(esplusplayer_close(player1_), ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_close(player2_), ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_close(player3_), ESPLUSPLAYER_ERROR_TYPE_NONE); + + EXPECT_EQ(mixer_stop(mixer_), MIXER_ERROR_TYPE_NONE); +} + +TEST_F(MixerEsppScenarioTestF, MultiAudioTest2) { + EXPECT_EQ(mixer_set_rsc_alloc_mode(mixer_, MIXER_RSC_ALLOC_MODE_DISABLE), + MIXER_ERROR_TYPE_NONE); + EXPECT_EQ(mixer_disable_audio_focus_setting(mixer_), MIXER_ERROR_TYPE_NONE); + + player1_ = util_.GetOpenedMixESPP(mixer_, roi1_); + ASSERT_NE(player1_, nullptr); + + player2_ = util_.GetOpenedMixESPP(mixer_, roi2_); + ASSERT_NE(player2_, nullptr); + + player3_ = util_.GetOpenedMixESPP(mixer_, roi3_); + ASSERT_NE(player3_, nullptr); + + EXPECT_EQ(esplusplayer_set_alternative_video_resource(player2_, 1), + ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_set_video_codec_type(player3_, + ESPLUSPLAYER_VIDEO_CODEC_TYPE_SW), + ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_set_audio_codec_type(player2_, + ESPLUSPLAYER_AUDIO_CODEC_TYPE_SW), + ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_set_audio_codec_type(player3_, + ESPLUSPLAYER_AUDIO_CODEC_TYPE_SW), + ESPLUSPLAYER_ERROR_TYPE_NONE); + + EXPECT_TRUE(util_.PrepareESPP(player1_, es_uri_1)); + EXPECT_TRUE(util_.PrepareESPP(player2_, es_uri_2)); + EXPECT_TRUE(util_.PrepareESPP(player3_, es_uri_1)); + + /* audio hw decoder + mmaudiosink */ + EXPECT_EQ(esplusplayer_start(player1_), ESPLUSPLAYER_ERROR_TYPE_NONE); + /* audio sw decoder + pulsesink */ + EXPECT_EQ(esplusplayer_start(player2_), ESPLUSPLAYER_ERROR_TYPE_NONE); + /* audio sw decoder + pulsesink */ + EXPECT_EQ(esplusplayer_start(player3_), ESPLUSPLAYER_ERROR_TYPE_NONE); + + EXPECT_EQ(mixer_start(mixer_), MIXER_ERROR_TYPE_NONE); + + std::this_thread::sleep_for(std::chrono::seconds(kPlayingTime)); + + EXPECT_EQ(esplusplayer_deactivate(player1_, ESPLUSPLAYER_STREAM_TYPE_AUDIO), + ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_deactivate(player3_, ESPLUSPLAYER_STREAM_TYPE_AUDIO), + ESPLUSPLAYER_ERROR_TYPE_NONE); + + /* audio hw decoder + mmaudiosink */ + EXPECT_EQ(esplusplayer_set_audio_codec_type(player3_, + ESPLUSPLAYER_AUDIO_CODEC_TYPE_HW), + ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_activate(player3_, ESPLUSPLAYER_STREAM_TYPE_AUDIO), + ESPLUSPLAYER_ERROR_TYPE_NONE); + util_.FeedingEsPacket(player3_, ESPLUSPLAYER_STREAM_TYPE_AUDIO, es_uri_1); + + std::this_thread::sleep_for(std::chrono::seconds(kPlayingTime)); + + EXPECT_EQ(esplusplayer_deactivate(player2_, ESPLUSPLAYER_STREAM_TYPE_AUDIO), + ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_deactivate(player3_, ESPLUSPLAYER_STREAM_TYPE_AUDIO), + ESPLUSPLAYER_ERROR_TYPE_NONE); + + /* audio sw decoder + alsasink */ + EXPECT_EQ(esplusplayer_activate(player2_, ESPLUSPLAYER_STREAM_TYPE_AUDIO), + ESPLUSPLAYER_ERROR_TYPE_NONE); + util_.FeedingEsPacket(player2_, ESPLUSPLAYER_STREAM_TYPE_AUDIO, es_uri_2); + + std::this_thread::sleep_for(std::chrono::seconds(kPlayingTime)); + + EXPECT_EQ(esplusplayer_close(player1_), ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_close(player2_), ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_close(player3_), ESPLUSPLAYER_ERROR_TYPE_NONE); + + EXPECT_EQ(mixer_stop(mixer_), MIXER_ERROR_TYPE_NONE); +} + +TEST_F(MixerEsppScenarioTestF, SetAudioFocus1) { + player1_ = util_.GetStartedMixESPP(es_uri_1, mixer_, roi1_); + ASSERT_NE(player1_, nullptr); + + player2_ = util_.GetStartedMixESPP(es_uri_2, mixer_, roi2_); + ASSERT_NE(player2_, nullptr); + + EXPECT_EQ(mixer_start(mixer_), MIXER_ERROR_TYPE_NONE); + + std::this_thread::sleep_for(std::chrono::seconds(kPlayingTime)); + + EXPECT_EQ(esplusplayer_close(player1_), ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_close(player2_), ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(mixer_stop(mixer_), MIXER_ERROR_TYPE_NONE); +} + +TEST_F(MixerEsppScenarioTestF, SetAudioFocus2) { + player1_ = util_.GetStartedMixESPP(es_uri_1, mixer_, roi1_); + ASSERT_NE(player1_, nullptr); + + EXPECT_EQ(mixer_set_audio_focus(mixer_, nullptr), + MIXER_ERROR_TYPE_INVALID_OPERATION); + + EXPECT_EQ(esplusplayer_close(player1_), ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(mixer_stop(mixer_), MIXER_ERROR_TYPE_NONE); +} + +TEST_F(MixerEsppScenarioTestF, MixerNPlayer) { + mixer_set_display_mode(mixer_, MIXER_DISPLAY_MODE_DST_ROI); + mixer_set_display_roi(mixer_, roi2_.x, roi2_.y, roi2_.w, roi2_.h); + mixer_set_rsc_alloc_mode(mixer_, MIXER_RSC_ALLOC_MODE_DISABLE); + mixer_disable_audio_focus_setting(mixer_); + mixer_set_alternative_video_scaler(mixer_); + + player1_ = util_.GetOpenedESPP(roi1_); + ASSERT_NE(player1_, nullptr); + + Geometry roi3; + roi3.x = 0; + roi3.y = 0; + roi3.w = roi2_.w; + roi3.h = roi2_.h; + player2_ = util_.GetOpenedMixESPP(mixer_, roi3); + ASSERT_NE(player2_, nullptr); + esplusplayer_set_alternative_video_resource(player2_, 1); + + EXPECT_TRUE(util_.PrepareESPP(player1_, es_uri_1, utils::EsType::kBoth)); + EXPECT_TRUE(util_.PrepareESPP(player2_, es_uri_2, utils::EsType::kVideo)); + + esplusplayer_start(player1_); + esplusplayer_start(player2_); + + mixer_start(mixer_); + std::this_thread::sleep_for(std::chrono::seconds(kPlayingTime)); + + EXPECT_EQ(esplusplayer_deactivate(player1_, ESPLUSPLAYER_STREAM_TYPE_VIDEO), + ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_deactivate(player2_, ESPLUSPLAYER_STREAM_TYPE_VIDEO), + ESPLUSPLAYER_ERROR_TYPE_NONE); + + EXPECT_EQ(esplusplayer_set_display(player1_, ESPLUSPLAYER_DISPLAY_TYPE_MIXER, + mixer_), + ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_set_display( + player2_, ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY, util_.GetWindow()), + ESPLUSPLAYER_ERROR_TYPE_NONE); + + esplusplayer_set_alternative_video_resource(player1_, 1); + EXPECT_EQ(esplusplayer_activate(player1_, ESPLUSPLAYER_STREAM_TYPE_VIDEO), + ESPLUSPLAYER_ERROR_TYPE_NONE); + esplusplayer_set_display_roi(player1_, roi3.x, roi3.y, roi3.w, roi3.h); + mixer_commit(mixer_); + util_.FeedingEsPacket(player1_, ESPLUSPLAYER_STREAM_TYPE_VIDEO, es_uri_1); + + esplusplayer_set_alternative_video_resource(player2_, 0); + EXPECT_EQ(esplusplayer_activate(player2_, ESPLUSPLAYER_STREAM_TYPE_VIDEO), + ESPLUSPLAYER_ERROR_TYPE_NONE); + esplusplayer_set_display_mode(player2_, ESPLUSPLAYER_DISPLAY_MODE_DST_ROI); + esplusplayer_set_display_roi(player2_, roi1_.x, roi1_.y, roi1_.w, roi1_.h); + util_.FeedingEsPacket(player2_, ESPLUSPLAYER_STREAM_TYPE_VIDEO, es_uri_2); + + std::this_thread::sleep_for(std::chrono::seconds(kPlayingTime)); + + esplusplayer_stop(player1_); + esplusplayer_stop(player2_); + mixer_stop(mixer_); +} + +TEST_F(MixerEsppScenarioTestF, NDecH264PlayerX2) { + mixer_set_rsc_alloc_mode(mixer_, MIXER_RSC_ALLOC_MODE_NDECODER); + player1_ = util_.GetOpenedMixESPP(mixer_, roi1_); + ASSERT_NE(player1_, nullptr); + player2_ = util_.GetOpenedMixESPP(mixer_, roi2_); + ASSERT_NE(player2_, nullptr); + + EXPECT_EQ(mixer_set_audio_focus(mixer_, player1_), MIXER_ERROR_TYPE_NONE); + + EXPECT_TRUE(util_.PrepareESPP(player1_, es_uri_1)); + EXPECT_TRUE(util_.PrepareESPP(player2_, es_uri_1)); + + EXPECT_EQ(esplusplayer_start(player1_), ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_start(player2_), ESPLUSPLAYER_ERROR_TYPE_NONE); + + EXPECT_EQ(mixer_start(mixer_), MIXER_ERROR_TYPE_NONE); + std::this_thread::sleep_for(std::chrono::seconds(10)); + + EXPECT_EQ(mixer_stop(mixer_), MIXER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_close(player1_), ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_close(player2_), ESPLUSPLAYER_ERROR_TYPE_NONE); +} + +TEST_F(MixerEsppScenarioTestF, NDecH264PlayerX4) { + mixer_set_rsc_alloc_mode(mixer_, MIXER_RSC_ALLOC_MODE_NDECODER); + player1_ = util_.GetOpenedMixESPP(mixer_, roi1_); + ASSERT_NE(player1_, nullptr); + player2_ = util_.GetOpenedMixESPP(mixer_, roi2_); + ASSERT_NE(player2_, nullptr); + player3_ = util_.GetOpenedMixESPP(mixer_, roi3_); + ASSERT_NE(player3_, nullptr); + player4_ = util_.GetOpenedMixESPP(mixer_, roi4_); + ASSERT_NE(player4_, nullptr); + + EXPECT_EQ(mixer_set_audio_focus(mixer_, player1_), MIXER_ERROR_TYPE_NONE); + + EXPECT_TRUE(util_.PrepareESPP(player1_, es_uri_1)); + EXPECT_TRUE(util_.PrepareESPP(player2_, es_uri_1)); + EXPECT_TRUE(util_.PrepareESPP(player3_, es_uri_1)); + EXPECT_TRUE(util_.PrepareESPP(player4_, es_uri_1)); + + EXPECT_EQ(esplusplayer_start(player1_), ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_start(player2_), ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_start(player3_), ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_start(player4_), ESPLUSPLAYER_ERROR_TYPE_NONE); + + EXPECT_EQ(mixer_start(mixer_), MIXER_ERROR_TYPE_NONE); + std::this_thread::sleep_for(std::chrono::seconds(10)); + + EXPECT_EQ(mixer_stop(mixer_), MIXER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_close(player1_), ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_close(player2_), ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_close(player3_), ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_close(player4_), ESPLUSPLAYER_ERROR_TYPE_NONE); +} + +TEST_F(MixerEsppScenarioTestF, NDecVp8PlayerX2) { + mixer_set_rsc_alloc_mode(mixer_, MIXER_RSC_ALLOC_MODE_NDECODER); + player1_ = util_.GetOpenedMixESPP(mixer_, roi1_); + ASSERT_NE(player1_, nullptr); + player2_ = util_.GetOpenedMixESPP(mixer_, roi2_); + ASSERT_NE(player2_, nullptr); + + EXPECT_EQ(mixer_set_audio_focus(mixer_, player1_), MIXER_ERROR_TYPE_NONE); + + EXPECT_TRUE(util_.PrepareESPP(player1_, es_vp8_uri)); + EXPECT_TRUE(util_.PrepareESPP(player2_, es_vp8_uri)); + + EXPECT_EQ(esplusplayer_start(player1_), ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_start(player2_), ESPLUSPLAYER_ERROR_TYPE_NONE); + + EXPECT_EQ(mixer_start(mixer_), MIXER_ERROR_TYPE_NONE); + std::this_thread::sleep_for(std::chrono::seconds(10)); + + EXPECT_EQ(mixer_stop(mixer_), MIXER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_close(player1_), ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_close(player2_), ESPLUSPLAYER_ERROR_TYPE_NONE); +} + +TEST_F(MixerEsppScenarioTestF, NDecVp8PlayerX4) { + mixer_set_rsc_alloc_mode(mixer_, MIXER_RSC_ALLOC_MODE_NDECODER); + player1_ = util_.GetOpenedMixESPP(mixer_, roi1_); + ASSERT_NE(player1_, nullptr); + player2_ = util_.GetOpenedMixESPP(mixer_, roi2_); + ASSERT_NE(player2_, nullptr); + player3_ = util_.GetOpenedMixESPP(mixer_, roi3_); + ASSERT_NE(player3_, nullptr); + player4_ = util_.GetOpenedMixESPP(mixer_, roi4_); + ASSERT_NE(player4_, nullptr); + + EXPECT_EQ(mixer_set_audio_focus(mixer_, player1_), MIXER_ERROR_TYPE_NONE); + + EXPECT_TRUE(util_.PrepareESPP(player1_, es_vp8_uri)); + EXPECT_TRUE(util_.PrepareESPP(player2_, es_vp8_uri)); + EXPECT_TRUE(util_.PrepareESPP(player3_, es_vp8_uri)); + EXPECT_TRUE(util_.PrepareESPP(player4_, es_vp8_uri)); + + EXPECT_EQ(esplusplayer_start(player1_), ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_start(player2_), ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_start(player3_), ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_start(player4_), ESPLUSPLAYER_ERROR_TYPE_NONE); + + EXPECT_EQ(mixer_start(mixer_), MIXER_ERROR_TYPE_NONE); + std::this_thread::sleep_for(std::chrono::seconds(10)); + + EXPECT_EQ(mixer_stop(mixer_), MIXER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_close(player1_), ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_close(player2_), ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_close(player3_), ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_close(player4_), ESPLUSPLAYER_ERROR_TYPE_NONE); +} + +#endif diff --git a/ut/src/mixer/ut_mixedframe.cpp b/ut/src/mixer/ut_mixedframe.cpp new file mode 100755 index 0000000..eb32864 --- /dev/null +++ b/ut/src/mixer/ut_mixedframe.cpp @@ -0,0 +1,355 @@ +#include +#include + +#include +#include + +#include "mixer/constant.h" +#include "mixer/matcher.h" +#include "mixer/mixedframe.h" +#include "mixer/mock/fakebuffer.h" +#include "mixer/mock/mock_bufferobject.h" +#include "mixer/mock/mock_memallocator.h" +#include "mixer/mock/mock_phyaddraccessor.h" +#include "mixer/mock/mock_vpmanipulator.h" +#include "mixer/mock/movable.h" +#include "mixer/mock/moveobj_wrapper.h" + +using namespace plusplayer; +using namespace plusplayer_ut; + +using ::testing::_; +using ::testing::AllOf; +using ::testing::AtLeast; +using ::testing::ByRef; +using ::testing::DoAll; +using ::testing::ElementsAre; +using ::testing::Field; +using ::testing::Invoke; +using ::testing::NotNull; +using ::testing::Return; +using ::testing::SaveArg; +using ::testing::SizeIs; + +/******************************************************************************************** + * [DEFAULT CLASS] DefaultMixedFrameTestOption + */ + +class DefaultMixedFrameTestOption { + public: + explicit DefaultMixedFrameTestOption() = default; + virtual ~DefaultMixedFrameTestOption() = default; + + protected: + virtual void Init_() { + ON_CALL(GetMemoryAllocator_(), Allocate(_)) + .WillByDefault(DoAll( + SaveArg<0>(&allocated_size_), CreateFakeBuffer(fakebuffer_), + Invoke( + this, + &DefaultMixedFrameTestOption::DelegateDefaultForBufferObject_), + Return(RelaseBufferObject_()))); + } + + protected: + MockMemoryAllocator& GetMemoryAllocator_() { return memalloc_; } + MockAccessibleBufferObject& GetBufferObject_() { return bufferobj_.Get(); } + MockAccessibleBufferObject* RelaseBufferObject_() { + return bufferobj_.Move(); + } + MockPhyAddrAccessor& GetReadablePhyAddrAccessor_() { + return r_phyaddraccessor_.Get(); + } + MockPhyAddrAccessor& GetWritablePhyAddrAccessor_() { + return w_phyaddraccessor_.Get(); + } + MockAccessibleBufferObject::PhyAddrAccessorPtr + MoveReadablePhyAddrAccessor_() { + return MockAccessibleBufferObject::PhyAddrAccessorPtr( + r_phyaddraccessor_.Move()); + } + MockAccessibleBufferObject::PhyAddrAccessorPtr + MoveWritablePhyAddrAccessor_() { + return MockAccessibleBufferObject::PhyAddrAccessorPtr( + w_phyaddraccessor_.Move()); + } + + private: + virtual void DelegateDefaultForBufferObject_(const std::uint32_t size) { + ON_CALL(GetBufferObject_(), GetReadableAddress_()) + .WillByDefault( + Return(Movable(MockAccessibleBufferObject::PhyAddrAccessorPtr( + MoveReadablePhyAddrAccessor_())))); + ON_CALL(GetBufferObject_(), GetWritableAddress_()) + .WillByDefault( + Return(Movable(MockAccessibleBufferObject::PhyAddrAccessorPtr( + MoveWritablePhyAddrAccessor_())))); + ON_CALL(GetBufferObject_(), GetBufferHandle()) + .WillByDefault(Return(kDefaultBufferHandle)); + ON_CALL(GetBufferObject_(), Export()) + .WillByDefault(Return(kDefaultBufferKey)); + ON_CALL(GetBufferObject_(), GetSize()) + .WillByDefault(Return(allocated_size_)); + + ON_CALL(GetReadablePhyAddrAccessor_(), GetAddress()) + .WillByDefault(Return(fakebuffer_->ptr.get())); + ON_CALL(GetWritablePhyAddrAccessor_(), GetAddress()) + .WillByDefault(Return(fakebuffer_->ptr.get())); + } + + private: + std::uint32_t allocated_size_; + FakeBufferPtr fakebuffer_; + + MoveObjectWrapper r_phyaddraccessor_{ + new MockPhyAddrAccessor()}; + MoveObjectWrapper w_phyaddraccessor_{ + new MockPhyAddrAccessor()}; + MoveObjectWrapper bufferobj_{ + new MockAccessibleBufferObject()}; + MockMemoryAllocator memalloc_; +}; + +/******************************************************************************************** + * InvalidMixedFrameTest + */ + +class InvalidMixedFrameTest : public ::testing::Test { + public: + virtual void SetUp() override { + mixed_frame_ = MixedFrame::Create(nullptr, kInvalidWidth, kInvalidHeight); + ASSERT_FALSE(mixed_frame_->IsValid()); + } + virtual void TearDown() override {} + + protected: + MixedFramePtr& GetMixedFrame() { return mixed_frame_; } + const MockVideoPlaneManipulator& GetVideoPlaneManipulator_() const { + return vieoplane_manip_; + } + + private: + MixedFramePtr mixed_frame_; + MockVideoPlaneManipulator vieoplane_manip_; +}; + +TEST_F(InvalidMixedFrameTest, GetVideoPlaneManipInfo) { + EXPECT_EQ(0, GetMixedFrame()->GetVideoPlaneManipInfo().size()); +} + +TEST_F(InvalidMixedFrameTest, GetSize) { + EXPECT_EQ(0, GetMixedFrame()->GetSize()); +} + +TEST_F(InvalidMixedFrameTest, Render) { + EXPECT_FALSE(GetMixedFrame()->Render( + &GetVideoPlaneManipulator_(), + {kYComponentSrcVMInfo, kUVComponentSrcVMInfo}, + GetGeometry(kDefaultX, kDefaultY, kDefaultW, kDefaultH))); +} + +TEST_F(InvalidMixedFrameTest, Export) { + BufferKeyType key; + EXPECT_FALSE(GetMixedFrame()->Export(key)); +} + +/******************************************************************************************** + * MixedFrameConstructionTest + */ +class MixedFrameConstructionTest : public ::testing::Test, + public DefaultMixedFrameTestOption { + public: + using MockBufferObjectPtr = std::unique_ptr; + virtual ~MixedFrameConstructionTest() {} + + virtual void SetUp() override { Init_(); } + virtual void TearDown() override {} +}; + +TEST_F(MixedFrameConstructionTest, IsValid_WithInvalidAllocator) { + EXPECT_FALSE( + MixedFrame::Create(nullptr, kDefaultWidth, kDefaultHeight)->IsValid()); +} + +TEST_F(MixedFrameConstructionTest, IsValid_WithInvalidResolution) { + EXPECT_FALSE( + MixedFrame::Create(&GetMemoryAllocator_(), kInvalidWidth, kInvalidHeight) + ->IsValid()); +} + +TEST_F(MixedFrameConstructionTest, IsValid_WithAllocateNullPtr) { + EXPECT_CALL(GetMemoryAllocator_(), Allocate(kExpectedDefaultByteSize)) + .WillOnce(Return(nullptr)); + + EXPECT_FALSE( + MixedFrame::Create(&GetMemoryAllocator_(), kDefaultWidth, kDefaultHeight) + ->IsValid()); +} + +TEST_F(MixedFrameConstructionTest, IsValid_WithZeroSizeAllocated) { + EXPECT_CALL(GetMemoryAllocator_(), Allocate(kExpectedDefaultByteSize)) + .Times(1); + EXPECT_CALL(GetBufferObject_(), GetSize()).WillOnce(Return(0)); + + EXPECT_FALSE( + MixedFrame::Create(&GetMemoryAllocator_(), kDefaultWidth, kDefaultHeight) + ->IsValid()); +} + +TEST_F(MixedFrameConstructionTest, IsValid_WithReturnWritableNullPtr) { + EXPECT_CALL(GetMemoryAllocator_(), Allocate(kExpectedDefaultByteSize)) + .Times(1); + EXPECT_CALL(GetBufferObject_(), GetSize()).Times(1); + EXPECT_CALL(GetBufferObject_(), GetWritableAddress_()) + .WillOnce(Return(Movable(AccessibleBuffer::PhyAddrAccessorPtr(nullptr)))); + + EXPECT_TRUE( + MixedFrame::Create(&GetMemoryAllocator_(), kDefaultWidth, kDefaultHeight) + ->IsValid()); +} + +TEST_F(MixedFrameConstructionTest, IsValid) { + EXPECT_CALL(GetMemoryAllocator_(), Allocate(kExpectedDefaultByteSize)) + .Times(1); + EXPECT_CALL(GetBufferObject_(), GetSize()).Times(1); + EXPECT_CALL(GetBufferObject_(), GetWritableAddress_()).Times(1); + EXPECT_CALL(GetWritablePhyAddrAccessor_(), GetAddress()).Times(2); + + EXPECT_TRUE( + MixedFrame::Create(&GetMemoryAllocator_(), kDefaultWidth, kDefaultHeight) + ->IsValid()); +} + +/******************************************************************************************** + * MixedFrameTest + */ + +class MixedFrameTest : public ::testing::Test, + public DefaultMixedFrameTestOption { + public: + explicit MixedFrameTest() = default; + virtual ~MixedFrameTest() = default; + + virtual void SetUp() override { + Init_(); + + ON_CALL(GetVideoPlaneManipulator_(), Do(_, _)).WillByDefault(Return(true)); + + EXPECT_CALL(GetMemoryAllocator_(), Allocate(_)).Times(1); + + EXPECT_CALL(GetBufferObject_(), GetSize()).Times(AtLeast(1)); + EXPECT_CALL(GetBufferObject_(), GetWritableAddress_()).Times(AtLeast(1)); + + EXPECT_CALL(GetWritablePhyAddrAccessor_(), GetAddress()).Times(2); + + mixed_frame_ = MixedFrame::Create(&GetMemoryAllocator_(), kDefaultWidth, + kDefaultHeight); + ASSERT_TRUE(mixed_frame_->IsValid()); + } + virtual void TearDown() override {} + + protected: + MockVideoPlaneManipulator& GetVideoPlaneManipulator_() { + return vieoplane_manip_; + } + MixedFramePtr& GetMixedFrame() { return mixed_frame_; } + + private: + MixedFramePtr mixed_frame_; + MockVideoPlaneManipulator vieoplane_manip_; +}; + +TEST_F(MixedFrameTest, GetVideoPlaneManipInfo) { + EXPECT_CALL(GetBufferObject_(), GetBufferHandle()); + EXPECT_THAT( + GetMixedFrame()->GetVideoPlaneManipInfo(), + AllOf(SizeIs(2), + ElementsAre( + IsSameVideoPlaneManipulableInfo(kYComponentDestVMInfo), + IsSameVideoPlaneManipulableInfo(kUVComponentDestVMInfo)))); +} + +TEST_F(MixedFrameTest, GetWidth) { + EXPECT_EQ(kDefaultWidth, GetMixedFrame()->GetWidth()); +} + +TEST_F(MixedFrameTest, GetHeight) { + EXPECT_EQ(kDefaultHeight, GetMixedFrame()->GetHeight()); +} + +TEST_F(MixedFrameTest, GetSize) { + EXPECT_EQ(kExpectedDefaultByteSize, GetMixedFrame()->GetSize()); +} + +TEST_F(MixedFrameTest, Render) { + ::testing::Sequence s1; + EXPECT_CALL(GetBufferObject_(), GetBufferHandle()).Times(2); + + EXPECT_CALL(GetVideoPlaneManipulator_(), + Do(IsSameVideoPlaneManipulableInfo(kYComponentSrcVMInfo), + IsSameVideoPlaneManipulableInfo(kCroppedYComponentDestVMInfo))) + .InSequence(s1); + EXPECT_CALL( + GetVideoPlaneManipulator_(), + Do(IsSameVideoPlaneManipulableInfo(kUVComponentSrcVMInfo), + IsSameVideoPlaneManipulableInfo(kCroppedUVComponentDestVMInfo))) + .InSequence(s1); + + EXPECT_TRUE(GetMixedFrame()->Render( + &GetVideoPlaneManipulator_(), + {kYComponentSrcVMInfo, kUVComponentSrcVMInfo}, + GetGeometry(kDefaultX, kDefaultY, kDefaultW, kDefaultH))); +} + +TEST_F(MixedFrameTest, Render_WithNullOperator) { + EXPECT_FALSE(GetMixedFrame()->Render( + nullptr, {kYComponentSrcVMInfo, kUVComponentSrcVMInfo}, + GetGeometry(kDefaultX, kDefaultY, kDefaultW, kDefaultH))); +} + +TEST_F(MixedFrameTest, Render_WithFirstOpFailed) { + EXPECT_CALL(GetBufferObject_(), GetBufferHandle()).Times(2); + + EXPECT_CALL(GetVideoPlaneManipulator_(), + Do(Field(&VideoPlaneManipulableInfo::component, + PlaneComponent::kYComponent), + _)) + .WillOnce(Return(false)); + EXPECT_CALL(GetVideoPlaneManipulator_(), + Do(Field(&VideoPlaneManipulableInfo::component, + PlaneComponent::kUVComponent), + _)) + .Times(1); + + EXPECT_FALSE(GetMixedFrame()->Render( + &GetVideoPlaneManipulator_(), + {kYComponentSrcVMInfo, kUVComponentSrcVMInfo}, + GetGeometry(kDefaultX, kDefaultY, kDefaultW, kDefaultH))); +} + +TEST_F(MixedFrameTest, Render_WithSecondOpFailed) { + EXPECT_CALL(GetBufferObject_(), GetBufferHandle()).Times(2); + + EXPECT_CALL(GetVideoPlaneManipulator_(), + Do(Field(&VideoPlaneManipulableInfo::component, + PlaneComponent::kYComponent), + _)) + .Times(1); + EXPECT_CALL(GetVideoPlaneManipulator_(), + Do(Field(&VideoPlaneManipulableInfo::component, + PlaneComponent::kUVComponent), + _)) + .WillOnce(Return(false)); + + EXPECT_FALSE(GetMixedFrame()->Render( + &GetVideoPlaneManipulator_(), + {kYComponentSrcVMInfo, kUVComponentSrcVMInfo}, + GetGeometry(kDefaultX, kDefaultY, kDefaultW, kDefaultH))); +} + +TEST_F(MixedFrameTest, Export) { + EXPECT_CALL(GetBufferObject_(), Export()); + BufferKeyType key; + EXPECT_TRUE(GetMixedFrame()->Export(key)); + EXPECT_EQ(kDefaultBufferKey, key); +} \ No newline at end of file diff --git a/ut/src/mixer/ut_mixer.cpp b/ut/src/mixer/ut_mixer.cpp new file mode 100755 index 0000000..9dbf976 --- /dev/null +++ b/ut/src/mixer/ut_mixer.cpp @@ -0,0 +1,257 @@ + +// +// @ Copyright [2020] +// +#include + +#include "core/utils/plusplayer_log.h" +#include "mixer/mixer.h" +#include "mixer/mixer_eventlistener.h" +#include "mixer/mixerticket.h" +#include "mixer/mixerticket_eventlistener.h" +#include "ut/include/appwindow.h" +#include "ut/include/plusplayer/utility.h" + +using namespace plusplayer; +using namespace plusplayer_ut; +using namespace utils; + +class MixerMockPlayer { + public: + MixerMockPlayer() { listener_ = new MyTicketListener(this); } + MixerMockPlayer(int x, int y, int w, int h) { + listener_ = new MyTicketListener(this); + display_info_.geometry.x = x; + display_info_.geometry.y = y; + display_info_.geometry.w = w; + display_info_.geometry.h = h; + } + ~MixerMockPlayer() { delete listener_; } + + class MyTicketListener : public MixerTicketEventListener { + public: + explicit MyTicketListener(MixerMockPlayer* handler) : handler_(handler){}; + bool OnAudioFocusChanged(bool active) { + LOG_INFO("My OnAudioFocusChanged [%d] player [%p]", active, handler_); + return true; + } + + bool OnUpdateDisplayInfo(const DisplayInfo& cur_info, + DisplayInfo* new_info) { + *new_info = handler_->display_info_; + LOG_INFO("OnUpdateDisplayInfo x[%d] y[%d]", + handler_->display_info_.geometry.x, + handler_->display_info_.geometry.y); + return true; + } + MixerMockPlayer* handler_ = nullptr; + }; + + public: + MixerTicketEventListener* listener_ = nullptr; + DisplayInfo display_info_; +}; + +class MixerMockListener : public MixerEventListener { + public: + MixerMockListener() {} + ~MixerMockListener() {} + void OnError() { + LOG_INFO(" listener [%p]", this); + } + void OnResourceConflicted() { + LOG_INFO("MyOnResourceConflicted listener[%p]", this); + } +}; + +TEST(MixerTest, MixerCreate) { + std::unique_ptr mixer = Mixer::Create(); + ASSERT_NE(mixer, nullptr); + mixer.reset(); +} + +TEST(MixerTest, MixerTicketCreate) { + MixerMockPlayer p1; + std::unique_ptr mixer = Mixer::Create(); + MixerTicket* ticket = mixer->CreateTicket(&p1); + ASSERT_NE(ticket, nullptr); + delete ticket; + mixer.reset(); +} + +TEST(MixerTest, MixerDisableAutoRscAlloc_1) { + std::unique_ptr mixer = Mixer::Create(); + EXPECT_TRUE(mixer->SetRscAllocMode(RscAllocMode::kDisable)); + mixer.reset(); +} + +TEST(MixerTest, MixerDisableAutoRscAlloc_2) { + std::unique_ptr mixer = Mixer::Create(); + MixerMockPlayer p1; + MixerTicket* ticket = mixer->CreateTicket(&p1); + ticket->RegisterListener(p1.listener_); + EXPECT_FALSE(mixer->SetRscAllocMode(RscAllocMode::kDisable)); + delete ticket; + mixer.reset(); +} + +TEST(MixerTest, MixerDisableAudioFocusSetting_1) { + std::unique_ptr mixer = Mixer::Create(); + EXPECT_TRUE(mixer->DisableAudioFocusSetting()); + mixer.reset(); +} + +TEST(MixerTest, MixerDisableAudioFocusSetting_2) { + std::unique_ptr mixer = Mixer::Create(); + MixerMockPlayer p1; + MixerTicket* ticket = mixer->CreateTicket(&p1); + ticket->RegisterListener(p1.listener_); + EXPECT_FALSE(mixer->DisableAudioFocusSetting()); + delete ticket; + mixer.reset(); +} + +TEST(MixerTest, MixerSetAudioFocus_1) { + std::unique_ptr mixer = Mixer::Create(); + MixerMockPlayer p1; + MixerTicket* ticket = mixer->CreateTicket(&p1); + ticket->RegisterListener(p1.listener_); + EXPECT_TRUE(mixer->SetAudioFocus(&p1)); + delete ticket; + mixer.reset(); +} + +TEST(MixerTest, MixerSetAudioFocus_2) { + std::unique_ptr mixer = Mixer::Create(); + MixerMockPlayer p1, p2; + + MixerTicket* ticket = mixer->CreateTicket(&p1); + MixerTicket* ticket2 = mixer->CreateTicket(&p2); + + ticket->RegisterListener(p1.listener_); + ticket2->RegisterListener(p2.listener_); + + EXPECT_TRUE(mixer->SetAudioFocus(&p2)); + + delete ticket; + delete ticket2; + + mixer.reset(); +} + +TEST(MixerTest, MixerSetAudioFocus_3) { + std::unique_ptr mixer = Mixer::Create(); + mixer->DisableAudioFocusSetting(); + MixerMockPlayer p1; + MixerTicket* ticket = mixer->CreateTicket(&p1); + ticket->RegisterListener(p1.listener_); + EXPECT_FALSE(mixer->SetAudioFocus(&p1)); + delete ticket; + mixer.reset(); +} + +TEST(MixerTest, MixerSetDisplay) { + std::unique_ptr mixer = Mixer::Create(); + EXPECT_TRUE(mixer->SetDisplay(DisplayType::kOverlay, + Utility::Instance().GetWindow())); + mixer.reset(); +} + +TEST(MixerTest, MixerCommit) { + std::unique_ptr mixer = Mixer::Create(); + MixerMockPlayer p1(1, 1, 1, 1); + MixerMockPlayer p2(2, 2, 2, 2); + + MixerTicket* ticket = mixer->CreateTicket(&p1); + MixerTicket* ticket2 = mixer->CreateTicket(&p2); + + ticket->RegisterListener(p1.listener_); + ticket2->RegisterListener(p2.listener_); + + EXPECT_TRUE(mixer->Commit()); + + delete ticket; + delete ticket2; + + mixer.reset(); +} + +TEST(MixerTest, MixerSetDisplayRoi) { + std::unique_ptr mixer = Mixer::Create(); + MixerMockPlayer p1; + + MixerTicket* ticket = mixer->CreateTicket(&p1); + + ticket->RegisterListener(p1.listener_); + Geometry geometry; + geometry.x = 11; + geometry.y = 11; + geometry.w = 22; + geometry.h = 22; + EXPECT_TRUE(mixer->SetDisplayRoi(geometry)); + + delete ticket; + + mixer.reset(); +} + +TEST(MixerTest, MixerSetDisplayMode) { + std::unique_ptr mixer = Mixer::Create(); + MixerMockPlayer p1; + + MixerTicket* ticket = mixer->CreateTicket(&p1); + + ticket->RegisterListener(p1.listener_); + EXPECT_TRUE(mixer->SetDisplayMode(DisplayMode::kDstRoi)); + + delete ticket; + + mixer.reset(); +} + +TEST(MixerTest, MixerRegisterListener) { + std::unique_ptr mixer = Mixer::Create(); + MixerMockListener* listener = new MixerMockListener; + EXPECT_TRUE(mixer->RegisterListener(listener)); + mixer.reset(); + delete listener; +} + +TEST(MixerTest, MixerStart) { + std::unique_ptr mixer = Mixer::Create(); + EXPECT_TRUE(mixer->SetDisplay(DisplayType::kOverlay, + Utility::Instance().GetWindow())); + Geometry geometry; + geometry.x = 0; + geometry.y = 0; + geometry.w = 1920; + geometry.h = 1080; + EXPECT_TRUE(mixer->SetDisplayRoi(geometry)); + MixerMockListener* listener = new MixerMockListener; + EXPECT_TRUE(mixer->RegisterListener(listener)); + EXPECT_TRUE(mixer->Start()); + std::this_thread::sleep_for(std::chrono::seconds(3)); + EXPECT_TRUE(mixer->Stop()); + mixer.reset(); + delete listener; +} + +TEST(MixerTest, MixerSetAlternativeVideoScaler) { + std::unique_ptr mixer = Mixer::Create(); + EXPECT_TRUE(mixer->SetAlternativeVideoScaler()); + EXPECT_TRUE(mixer->SetDisplay(DisplayType::kOverlay, + Utility::Instance().GetWindow())); + Geometry geometry; + geometry.x = 0; + geometry.y = 0; + geometry.w = 1920; + geometry.h = 1080; + EXPECT_TRUE(mixer->SetDisplayRoi(geometry)); + MixerMockListener* listener = new MixerMockListener; + EXPECT_TRUE(mixer->RegisterListener(listener)); + EXPECT_TRUE(mixer->Start()); + std::this_thread::sleep_for(std::chrono::seconds(3)); + EXPECT_TRUE(mixer->Stop()); + mixer.reset(); + delete listener; +} \ No newline at end of file diff --git a/ut/src/mixer/ut_mixer_capi.cpp b/ut/src/mixer/ut_mixer_capi.cpp new file mode 100755 index 0000000..663a36a --- /dev/null +++ b/ut/src/mixer/ut_mixer_capi.cpp @@ -0,0 +1,227 @@ + + +// +// @ Copyright [2020] +// +#include + +#include "core/utils/plusplayer_log.h" +#include "mixer_capi/mixer_capi.h" +#include "ut/include/appwindow.h" +#include "ut/include/plusplayer/utility.h" + +using namespace plusplayer; +using namespace plusplayer_ut; +using namespace utils; + +class CMixerMockPlayer { + public: + CMixerMockPlayer() { listener_ = new MyTicketListener(this); } + CMixerMockPlayer(int x, int y, int w, int h) { + listener_ = new MyTicketListener(this); + display_info_.geometry.x = x; + display_info_.geometry.y = y; + display_info_.geometry.w = w; + display_info_.geometry.h = h; + } + ~CMixerMockPlayer() { delete listener_; } + + class MyTicketListener : public MixerTicketEventListener { + public: + explicit MyTicketListener(CMixerMockPlayer* handler) : handler_(handler){}; + bool OnAudioFocusChanged(bool active) { + LOG_INFO("My OnAudioFocusChanged [%d] player [%p]", active, handler_); + return true; + } + + bool OnUpdateDisplayInfo(const DisplayInfo& cur_info, + DisplayInfo* new_info) { + *new_info = handler_->display_info_; + LOG_INFO("OnUpdateDisplayInfo x[%d] y[%d]", + handler_->display_info_.geometry.x, + handler_->display_info_.geometry.y); + return true; + } + CMixerMockPlayer* handler_ = nullptr; + }; + + public: + MixerTicketEventListener* listener_ = nullptr; + DisplayInfo display_info_; +}; + +TEST(CMixerTest, vdapi_mixer_create_p_1) { + mixer_handle mixer = mixer_create(); + ASSERT_NE(mixer, nullptr); + mixer_destroy(mixer); +} + +TEST(CMixerTest, vdapi_mixer_create_ticket_p_1) { + CMixerMockPlayer p1; + mixer_handle mixer = mixer_create(); + MixerTicket* ticket = (MixerTicket*)mixer_create_ticket(mixer, &p1); + ASSERT_NE(ticket, nullptr); + delete ticket; + mixer_destroy(mixer); +} + +TEST(CMixerTest, vdapi_mixer_set_rsc_alloc_mode_p_1) { + mixer_handle mixer = mixer_create(); + EXPECT_EQ(mixer_set_rsc_alloc_mode(mixer, MIXER_RSC_ALLOC_MODE_DISABLE), + MIXER_ERROR_TYPE_NONE); + mixer_destroy(mixer); +} + +TEST(CMixerTest, vdapi_mixer_set_rsc_alloc_mode_n_1) { + mixer_handle mixer = mixer_create(); + CMixerMockPlayer p1; + MixerTicket* ticket = (MixerTicket*)mixer_create_ticket(mixer, &p1); + ticket->RegisterListener(p1.listener_); + EXPECT_EQ(mixer_set_rsc_alloc_mode(mixer, MIXER_RSC_ALLOC_MODE_DISABLE), + MIXER_ERROR_TYPE_INVALID_OPERATION); + delete ticket; + mixer_destroy(mixer); +} + +TEST(CMixerTest, vdapi_mixer_disable_audio_focus_setting_p_1) { + mixer_handle mixer = mixer_create(); + EXPECT_EQ(mixer_disable_audio_focus_setting(mixer), MIXER_ERROR_TYPE_NONE); + mixer_destroy(mixer); +} + +TEST(CMixerTest, vdapi_mixer_disable_audio_focus_setting_n_1) { + mixer_handle mixer = mixer_create(); + CMixerMockPlayer p1; + MixerTicket* ticket = (MixerTicket*)mixer_create_ticket(mixer, &p1); + ticket->RegisterListener(p1.listener_); + EXPECT_EQ(mixer_disable_audio_focus_setting(mixer), + MIXER_ERROR_TYPE_INVALID_OPERATION); + delete ticket; + mixer_destroy(mixer); +} + +TEST(CMixerTest, vdapi_mixer_set_audio_focus_p_1) { + mixer_handle mixer = mixer_create(); + CMixerMockPlayer p1; + MixerTicket* ticket = (MixerTicket*)mixer_create_ticket(mixer, &p1); + ticket->RegisterListener(p1.listener_); + EXPECT_EQ(mixer_set_audio_focus(mixer, &p1), MIXER_ERROR_TYPE_NONE); + delete ticket; + mixer_destroy(mixer); +} + +TEST(CMixerTest, vdapi_mixer_set_audio_focus_p_2) { + mixer_handle mixer = mixer_create(); + CMixerMockPlayer p1, p2; + + MixerTicket* ticket = (MixerTicket*)mixer_create_ticket(mixer, &p1); + MixerTicket* ticket2 = (MixerTicket*)mixer_create_ticket(mixer, &p2); + + ticket->RegisterListener(p1.listener_); + ticket2->RegisterListener(p2.listener_); + + EXPECT_EQ(mixer_set_audio_focus(mixer, &p2), MIXER_ERROR_TYPE_NONE); + + delete ticket; + delete ticket2; + mixer_destroy(mixer); +} + +TEST(CMixerTest, vdapi_mixer_set_audio_focus_n_2) { + mixer_handle mixer = mixer_create(); + mixer_disable_audio_focus_setting(mixer); + CMixerMockPlayer p1; + MixerTicket* ticket = (MixerTicket*)mixer_create_ticket(mixer, &p1); + ticket->RegisterListener(p1.listener_); + EXPECT_EQ(mixer_set_audio_focus(mixer, &p1), + MIXER_ERROR_TYPE_INVALID_OPERATION); + delete ticket; + mixer_destroy(mixer); +} + +TEST(CMixerTest, vdapi_mixer_set_display_p_1) { + mixer_handle mixer = mixer_create(); + EXPECT_EQ(mixer_set_display(mixer, MIXER_DISPLAY_TYPE_OVERLAY, + Utility::Instance().GetWindow()), + MIXER_ERROR_TYPE_NONE); + mixer_destroy(mixer); +} + +TEST(CMixerTest, vdapi_mixer_commit_p_1) { + mixer_handle mixer = mixer_create(); + CMixerMockPlayer p1(1, 1, 1, 1); + CMixerMockPlayer p2(2, 2, 2, 2); + + MixerTicket* ticket = (MixerTicket*)mixer_create_ticket(mixer, &p1); + MixerTicket* ticket2 = (MixerTicket*)mixer_create_ticket(mixer, &p2); + + ticket->RegisterListener(p1.listener_); + ticket2->RegisterListener(p2.listener_); + + EXPECT_EQ(mixer_commit(mixer), MIXER_ERROR_TYPE_NONE); + + delete ticket; + delete ticket2; + mixer_destroy(mixer); +} + +TEST(CMixerTest, vdapi_mixer_set_display_roi_p_1) { + mixer_handle mixer = mixer_create(); + CMixerMockPlayer p1; + + MixerTicket* ticket = (MixerTicket*)mixer_create_ticket(mixer, &p1); + + ticket->RegisterListener(p1.listener_); + EXPECT_EQ(mixer_set_display_roi(mixer, 11, 11, 22, 22), + MIXER_ERROR_TYPE_NONE); + + delete ticket; + mixer_destroy(mixer); +} + +TEST(CMixerTest, vdapi_mixer_set_display_mode_p_1) { + mixer_handle mixer = mixer_create(); + CMixerMockPlayer p1; + + MixerTicket* ticket = (MixerTicket*)mixer_create_ticket(mixer, &p1); + + ticket->RegisterListener(p1.listener_); + EXPECT_EQ(mixer_set_display_mode(mixer, MIXER_DISPLAY_MODE_DST_ROI), + MIXER_ERROR_TYPE_NONE); + + delete ticket; + mixer_destroy(mixer); +} + +TEST(CMixerTest, vdapi_mixer_start_p_1) { + mixer_handle mixer = mixer_create(); + mixer_set_display(mixer, MIXER_DISPLAY_TYPE_OVERLAY, + Utility::Instance().GetWindow()); + mixer_set_display_roi(mixer, 0, 0, 1920, 1080); + EXPECT_EQ(mixer_start(mixer), MIXER_ERROR_TYPE_NONE); + + std::this_thread::sleep_for(std::chrono::seconds(3)); + EXPECT_EQ(mixer_stop(mixer), MIXER_ERROR_TYPE_NONE); + mixer_destroy(mixer); +} + +TEST(CMixerTest, vdapi_mixer_set_alternative_video_scaler_p_1) { + mixer_handle mixer = mixer_create(); + EXPECT_EQ(mixer_set_alternative_video_scaler(mixer), MIXER_ERROR_TYPE_NONE); + mixer_set_display(mixer, MIXER_DISPLAY_TYPE_OVERLAY, + Utility::Instance().GetWindow()); + mixer_set_display_roi(mixer, 0, 0, 1920, 1080); + + EXPECT_EQ(mixer_start(mixer), MIXER_ERROR_TYPE_NONE); + std::this_thread::sleep_for(std::chrono::seconds(3)); + EXPECT_EQ(mixer_stop(mixer), MIXER_ERROR_TYPE_NONE); + mixer_destroy(mixer); +} + +TEST(CMixerTest, vdapi_mixer_set_on_resource_conflict_cb_p_1) { + mixer_handle mixer = mixer_create(); + EXPECT_EQ(mixer_set_resource_conflicted_cb(mixer, nullptr, nullptr), + MIXER_ERROR_TYPE_NONE); + mixer_destroy(mixer); +} + diff --git a/ut/src/mixer/ut_mixer_espp_capi.cpp b/ut/src/mixer/ut_mixer_espp_capi.cpp new file mode 100755 index 0000000..244b025 --- /dev/null +++ b/ut/src/mixer/ut_mixer_espp_capi.cpp @@ -0,0 +1,351 @@ +// +// @ Copyright [2020] +// +#include + +#include "esplusplayer_capi/esplusplayer_capi.h" +#include "mixer_capi/mixer_capi.h" +#include "ut/include/plusplayer/utility.h" +#include "ut/include/streamreader.hpp" + +using namespace plusplayer; +using namespace plusplayer_ut; +using namespace utils; +using namespace std; + +using utils::Utility; +std::string uri_1 = "youtube/"; +std::string uri_2 = "bunny/"; + +// max playing duration : 10 sec => see EsStreamReader::ReadNextPacket() +constexpr int kEsPlayingTime = 1; // (sec) + +class CMixerEsppTestF : public ::testing::Test { + public: + explicit CMixerEsppTestF(void) + : util_(Utility::Instance()), + mixer_(nullptr), + player1_(nullptr), + player2_(nullptr), + player3_(nullptr), + player4_(nullptr){}; + ~CMixerEsppTestF(void){}; + + static void SetUpTestCase() { + ESPacketDownloader::Init(); + std::cout << "SetUpTestCase()" << std::endl; + } + + static void TearDownTestCase() {} + + virtual void SetUp(void) override { + LOG_ERROR("%s", util_.GetCurrentTestName()); + mixer_ = mixer_create(); + mixer_set_display(mixer_, MIXER_DISPLAY_TYPE_OVERLAY, util_.GetWindow()); + + roi1_.x = 20; + roi1_.y = 20; + roi1_.w = 960; + roi1_.h = 540; + + roi2_.x = 1000; + roi2_.y = 20; + roi2_.w = 720; + roi2_.h = 480; + + roi3_.x = 20; + roi3_.y = 520; + roi3_.w = 720; + roi3_.h = 480; + + roi4_.x = 1000; + roi4_.y = 520; + roi4_.w = 720; + roi4_.h = 480; + } + + virtual void TearDown(void) override { + util_.DestroyESPP(player1_); + util_.DestroyESPP(player2_); + util_.DestroyESPP(player3_); + util_.DestroyESPP(player4_); + mixer_destroy(mixer_); + player1_ = nullptr; + player2_ = nullptr; + player3_ = nullptr; + player4_ = nullptr; + mixer_ = nullptr; + LOG_ERROR("%s", util_.GetCurrentTestName()); + } + + public: + Utility& util_; + mixer_handle mixer_; + esplusplayer_handle player1_; + esplusplayer_handle player2_; + esplusplayer_handle player3_; + esplusplayer_handle player4_; + Geometry roi1_; + Geometry roi2_; + Geometry roi3_; + Geometry roi4_; +}; + +TEST(CMixerEsppTest, vdapi_mixer_create_destroy_p_1) { + mixer_handle mixer = mixer_create(); + ASSERT_NE(mixer, nullptr); + EXPECT_EQ(mixer_destroy(mixer), MIXER_ERROR_TYPE_NONE); +} + +TEST_F(CMixerEsppTestF, vdapi_mixer_start_stop_p_1) { + player1_ = util_.GetPreparedMixESPP(uri_2, mixer_, roi1_); + ASSERT_NE(player1_, nullptr); + player2_ = util_.GetPreparedMixESPP(uri_2, mixer_, roi2_); + ASSERT_NE(player2_, nullptr); + + EXPECT_EQ(esplusplayer_start(player1_), ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_start(player2_), ESPLUSPLAYER_ERROR_TYPE_NONE); + + EXPECT_EQ(mixer_start(mixer_), MIXER_ERROR_TYPE_NONE); + + std::this_thread::sleep_for(std::chrono::seconds(kEsPlayingTime)); + + EXPECT_EQ(mixer_stop(mixer_), MIXER_ERROR_TYPE_NONE); + + EXPECT_EQ(esplusplayer_close(player1_), ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_close(player2_), ESPLUSPLAYER_ERROR_TYPE_NONE); +} + +TEST_F(CMixerEsppTestF, vdapi_mixer_get_max_allowed_number_of_player_p_1) { + int max_num = mixer_get_max_allowed_number_of_player(mixer_); + constexpr int MAX_NUM = 3; + EXPECT_EQ(MAX_NUM, max_num); +} + +TEST(CMixerEsppTest, vdapi_mixer_set_display_p_1) { + mixer_handle mixer = mixer_create(); + ASSERT_NE(mixer, nullptr); + Utility& util = Utility::Instance(); + EXPECT_EQ( + mixer_set_display(mixer, MIXER_DISPLAY_TYPE_OVERLAY, util.GetWindow()), + MIXER_ERROR_TYPE_NONE); + EXPECT_EQ(mixer_destroy(mixer), MIXER_ERROR_TYPE_NONE); +} + +TEST_F(CMixerEsppTestF, vdapi_mixer_set_display_mode_p_1) { + EXPECT_EQ(mixer_set_display_mode(mixer_, MIXER_DISPLAY_MODE_DST_ROI), + MIXER_ERROR_TYPE_NONE); +} + +TEST_F(CMixerEsppTestF, vdapi_mixer_set_display_roi_p_1) { + EXPECT_EQ(mixer_set_display_mode(mixer_, MIXER_DISPLAY_MODE_DST_ROI), + MIXER_ERROR_TYPE_NONE); + EXPECT_EQ(mixer_set_display_roi(mixer_, 0, 0, 1920, 1080), + MIXER_ERROR_TYPE_NONE); + + player1_ = util_.GetStartedMixESPP(uri_2, mixer_, roi1_); + ASSERT_NE(player1_, nullptr); + + mixer_start(mixer_); + std::this_thread::sleep_for(std::chrono::seconds(kEsPlayingTime)); + + EXPECT_EQ(mixer_set_display_roi(mixer_, roi2_.x, roi2_.y, roi2_.w, roi2_.h), + MIXER_ERROR_TYPE_NONE); + + std::this_thread::sleep_for(std::chrono::seconds(kEsPlayingTime)); + + EXPECT_EQ(mixer_stop(mixer_), MIXER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_close(player1_), ESPLUSPLAYER_ERROR_TYPE_NONE); +} + +TEST_F(CMixerEsppTestF, vdapi_mixer_set_rsc_alloc_mode_p_1) { + EXPECT_EQ(mixer_set_rsc_alloc_mode(mixer_, MIXER_RSC_ALLOC_MODE_DISABLE), + MIXER_ERROR_TYPE_NONE); + mixer_stop(mixer_); +} + +TEST_F(CMixerEsppTestF, vdapi_mixer_set_rsc_alloc_mode_n_1) { + player1_ = util_.GetOpenedMixESPP(mixer_, roi1_); + ASSERT_NE(player1_, nullptr); + EXPECT_EQ(mixer_set_rsc_alloc_mode(mixer_, MIXER_RSC_ALLOC_MODE_DISABLE), + MIXER_ERROR_TYPE_INVALID_OPERATION); + esplusplayer_close(player1_); +} + +TEST_F(CMixerEsppTestF, vdapi_mixer_esplusplayer_set_video_codec_type_p_1) { + EXPECT_EQ(mixer_set_rsc_alloc_mode(mixer_, MIXER_RSC_ALLOC_MODE_DISABLE), + MIXER_ERROR_TYPE_NONE); + player1_ = util_.GetOpenedMixESPP(mixer_, roi1_); + ASSERT_NE(player1_, nullptr); + EXPECT_EQ(esplusplayer_set_video_codec_type( + player1_, ESPLUSPLAYER_VIDEO_CODEC_TYPE_HW_N_DECODING), + ESPLUSPLAYER_ERROR_TYPE_NONE); + mixer_stop(mixer_); + esplusplayer_close(player1_); +} + +TEST_F(CMixerEsppTestF, vdapi_mixer_esplusplayer_set_video_codec_type_p_2) { + EXPECT_EQ(mixer_set_rsc_alloc_mode(mixer_, MIXER_RSC_ALLOC_MODE_DISABLE), + MIXER_ERROR_TYPE_NONE); + player1_ = util_.GetOpenedMixESPP(mixer_, roi1_); + ASSERT_NE(player1_, nullptr); + EXPECT_EQ(esplusplayer_set_video_codec_type(player1_, + ESPLUSPLAYER_VIDEO_CODEC_TYPE_HW), + ESPLUSPLAYER_ERROR_TYPE_NONE); + mixer_stop(mixer_); + esplusplayer_close(player1_); +} + +TEST_F(CMixerEsppTestF, vdapi_mixer_esplusplayer_set_video_codec_type_n_1) { + player1_ = util_.GetOpenedMixESPP(mixer_, roi1_); + ASSERT_NE(player1_, nullptr); + EXPECT_EQ(esplusplayer_set_video_codec_type( + player1_, ESPLUSPLAYER_VIDEO_CODEC_TYPE_HW_N_DECODING), + ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION); + mixer_stop(mixer_); + esplusplayer_close(player1_); +} + +TEST_F(CMixerEsppTestF, vdapi_mixer_esplusplayer_set_video_codec_type_n_2) { + player1_ = util_.GetOpenedESPP(roi1_); + ASSERT_NE(player1_, nullptr); + EXPECT_EQ(esplusplayer_set_video_codec_type( + player1_, ESPLUSPLAYER_VIDEO_CODEC_TYPE_HW_N_DECODING), + ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION); + mixer_stop(mixer_); + esplusplayer_close(player1_); +} + +TEST_F(CMixerEsppTestF, vdapi_mixer_esplusplayer_set_video_codec_type_n_3) { + player1_ = util_.GetOpenedMixESPP(mixer_, roi1_); + ASSERT_NE(player1_, nullptr); + EXPECT_EQ(esplusplayer_set_video_codec_type(player1_, + ESPLUSPLAYER_VIDEO_CODEC_TYPE_HW), + ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION); + mixer_stop(mixer_); + esplusplayer_close(player1_); +} + +TEST_F(CMixerEsppTestF, vdapi_esplusplayer_set_alternative_video_resource_p_1) { + EXPECT_EQ(mixer_set_rsc_alloc_mode(mixer_, MIXER_RSC_ALLOC_MODE_DISABLE), + MIXER_ERROR_TYPE_NONE); + player1_ = util_.GetOpenedMixESPP(mixer_, roi1_); + ASSERT_NE(player1_, nullptr); + EXPECT_EQ(esplusplayer_set_alternative_video_resource(player1_, 0), + ESPLUSPLAYER_ERROR_TYPE_NONE); + esplusplayer_close(player1_); +} + +TEST_F(CMixerEsppTestF, vdapi_mixer_esplusplayer_set_alternative_video_resource_n_1) { + player1_ = util_.GetOpenedMixESPP(mixer_, roi1_); + ASSERT_NE(player1_, nullptr); + EXPECT_EQ(esplusplayer_set_alternative_video_resource(player1_, 0), + ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION); + esplusplayer_close(player1_); +} + +TEST_F(CMixerEsppTestF, vdapi_mixer_esplusplayer_set_alternative_video_resource_n_2) { + EXPECT_EQ(mixer_set_rsc_alloc_mode(mixer_, MIXER_RSC_ALLOC_MODE_DEFAULT), + MIXER_ERROR_TYPE_NONE); + player1_ = util_.GetPreparedMixESPP(uri_2, mixer_, roi1_); + ASSERT_NE(player1_, nullptr); + + EXPECT_EQ(esplusplayer_deactivate(player1_, ESPLUSPLAYER_STREAM_TYPE_VIDEO), + ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_set_alternative_video_resource(player1_, 0), + ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION); + + mixer_stop(mixer_); + esplusplayer_close(player1_); +} + +TEST_F(CMixerEsppTestF, vdapi_mixer_disable_audio_focus_setting_p_1) { + EXPECT_EQ(mixer_disable_audio_focus_setting(mixer_), MIXER_ERROR_TYPE_NONE); +} + +TEST_F(CMixerEsppTestF, vdapi_mixer_disable_audio_focus_setting_p_2) { + EXPECT_EQ(mixer_disable_audio_focus_setting(mixer_), MIXER_ERROR_TYPE_NONE); + player1_ = util_.GetPreparedMixESPP(uri_2, mixer_, roi1_); + ASSERT_NE(player1_, nullptr); + mixer_start(mixer_); + + EXPECT_EQ(esplusplayer_deactivate(player1_, ESPLUSPLAYER_STREAM_TYPE_AUDIO), + MIXER_ERROR_TYPE_NONE); + + mixer_stop(mixer_); + esplusplayer_close(player1_); +} + +TEST_F(CMixerEsppTestF, vdapi_mixer_disable_audio_focus_setting_n_1) { + player1_ = util_.GetOpenedMixESPP(mixer_, roi1_); + EXPECT_EQ(mixer_disable_audio_focus_setting(mixer_), + MIXER_ERROR_TYPE_INVALID_OPERATION); + esplusplayer_close(player1_); +} + +TEST_F(CMixerEsppTestF, vdapi_mixer_esplusplayer_deactivate_p_1) { + EXPECT_EQ(mixer_disable_audio_focus_setting(mixer_), MIXER_ERROR_TYPE_NONE); + + player1_ = util_.GetPreparedMixESPP(uri_2, mixer_, roi1_); + ASSERT_NE(player1_, nullptr); + EXPECT_EQ(esplusplayer_deactivate(player1_, ESPLUSPLAYER_STREAM_TYPE_AUDIO), + ESPLUSPLAYER_ERROR_TYPE_NONE); + esplusplayer_close(player1_); +} + +TEST_F(CMixerEsppTestF, vdapi_mixer_esplusplayer_deactivate_n_1) { + player1_ = util_.GetPreparedMixESPP(uri_2, mixer_, roi1_); + ASSERT_NE(player1_, nullptr); + EXPECT_EQ(esplusplayer_deactivate(player1_, ESPLUSPLAYER_STREAM_TYPE_AUDIO), + ESPLUSPLAYER_ERROR_TYPE_INVALID_OPERATION); + esplusplayer_close(player1_); +} + +TEST_F(CMixerEsppTestF, vdapi_mixer_set_alternative_video_scaler_p_1) { + EXPECT_EQ(mixer_set_alternative_video_scaler(mixer_), + MIXER_ERROR_TYPE_NONE); +} + +TEST_F(CMixerEsppTestF, vdapi_mixer_set_audio_focus_p_1) { + player1_ = util_.GetOpenedMixESPP(mixer_, roi1_); + ASSERT_NE(player1_, nullptr); + + EXPECT_EQ(mixer_set_audio_focus(mixer_, player1_), MIXER_ERROR_TYPE_NONE); + + EXPECT_EQ(esplusplayer_close(player1_), ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(mixer_stop(mixer_), MIXER_ERROR_TYPE_NONE); +} + +TEST_F(CMixerEsppTestF, vdapi_mixer_commit_p_1) { + player1_ = util_.GetStartedMixESPP(uri_2, mixer_, roi1_); + ASSERT_NE(player1_, nullptr); + mixer_start(mixer_); + + std::this_thread::sleep_for(std::chrono::seconds(kEsPlayingTime)); + + esplusplayer_set_display_roi(player1_, roi3_.x, roi3_.y, roi3_.w, roi3_.h); + EXPECT_EQ(mixer_commit(mixer_), MIXER_ERROR_TYPE_NONE); + + std::this_thread::sleep_for(std::chrono::seconds(kEsPlayingTime)); + + mixer_stop(mixer_); + esplusplayer_close(player1_); +} + +TEST_F(CMixerEsppTestF, vdapi_mixer_set_resolution_p_1) { + EXPECT_EQ(mixer_set_resolution(mixer_, 1920, 1080, 30, 1), MIXER_ERROR_TYPE_NONE); +} + +TEST_F(CMixerEsppTestF, vdapi_mixer_create_ticket_p_1) { + player1_ = util_.GetOpenedMixESPP(mixer_, roi1_); + ASSERT_NE(player1_, nullptr); + void* ticket = mixer_create_ticket(mixer_, player1_); + ASSERT_NE(ticket, nullptr); + esplusplayer_close(player1_); +} + +TEST_F(CMixerEsppTestF, vdapi_mixer_set_resource_conflicted_cb_p_1) { + EXPECT_EQ(mixer_set_resource_conflicted_cb(mixer_, nullptr, nullptr), + MIXER_ERROR_TYPE_NONE); +} + diff --git a/ut/src/mixer/ut_mixerscenario.cpp b/ut/src/mixer/ut_mixerscenario.cpp new file mode 100755 index 0000000..9be8e0f --- /dev/null +++ b/ut/src/mixer/ut_mixerscenario.cpp @@ -0,0 +1,628 @@ +// +// @ Copyright [2020] +// +#include + +#include + +#include "core/utils/plusplayer_log.h" +#include "mixer/mixer.h" +#include "mixer/mixer_eventlistener.h" +#include "mixer/mixerticket.h" +#include "mixer/mixerticket_eventlistener.h" +#include "ut/include/plusplayer/utility.h" + +using namespace plusplayer; +using namespace plusplayer_ut; +using namespace utils; +using namespace std; + +using utils::Utility; +std::string dashuri = + ""; +std::string httpuri = ""; +std::string hlsuri = ""; +std::string fhd1 = + ""; +std::string fhd2 = + ""; +std::string fhd3 = + ""; +constexpr int kPlayingTime = 5; // sec + +//------------------------------------------------------------ +class MixerScenarioTestF : public ::testing::Test { + public: + explicit MixerScenarioTestF(void) + : util_(Utility::Instance()), + mixer_(nullptr), + player1_(nullptr), + player2_(nullptr), + player3_(nullptr){}; + ~MixerScenarioTestF(void){}; + + virtual void SetUp(void) override { + LOG_ERROR("%s", util_.GetCurrentTestName()); + mixer_ = Mixer::Create(); + mixer_->SetDisplay(DisplayType::kOverlay, util_.GetWindow()); +#if 1 + geometry1_.x = 20; + geometry1_.y = 20; + geometry1_.w = 960; + geometry1_.h = 540; + + geometry2_.x = 1000; + geometry2_.y = 20; + geometry2_.w = 720; + geometry2_.h = 480; + + geometry3_.x = 20; + geometry3_.y = 520; + geometry3_.w = 720; + geometry3_.h = 480; + + geometry4_.x = 1000; + geometry4_.y = 520; + geometry4_.w = 720; + geometry4_.h = 480; +#else + geometry1_.x = 20; + geometry1_.y = 20; + geometry1_.w = 1180; + geometry1_.h = 720; + + geometry2_.x = 1220; + geometry2_.y = 20; + geometry2_.w = 640; + geometry2_.h = 480; + + geometry3_.x = 1220; + geometry3_.y = 520; + geometry3_.w = 640; + geometry3_.h = 480; +#endif + } + + virtual void TearDown(void) override { + util_.DestroyPlayer(player1_); + util_.DestroyPlayer(player2_); + util_.DestroyPlayer(player3_); + mixer_.reset(); + LOG_ERROR("%s", util_.GetCurrentTestName()); + } + + public: + Utility& util_; + std::unique_ptr mixer_; + PlusPlayer::Ptr player1_; + PlusPlayer::Ptr player2_; + PlusPlayer::Ptr player3_; + PlusPlayer::Ptr player4_; + Geometry geometry1_; + Geometry geometry2_; + Geometry geometry3_; + Geometry geometry4_; +}; + +#if 0 // prepare async test +TEST(MixerScenarioTest, OnePlayer) { + AppWindow appwin(0, 0, 1920, 1080); + // create player1 + auto player1 = plusplayer::PlusPlayer::Create(); + + shared_ptr user_data = std::make_shared(); + shared_ptr mock_eventlistener = + std::make_shared(); + shared_ptr fake_eventlistener = + std::make_shared(); + mock_eventlistener->Bind( + std::dynamic_pointer_cast(fake_eventlistener)); + + ASSERT_NE(player1, nullptr); + EXPECT_TRUE(player1->Open(hlsuri.c_str())); + player1->RegisterListener(mock_eventlistener.get(), (void*)user_data.get()); + // set mixer mode + EXPECT_TRUE( + player1->SetDisplay(DisplayType::kOverlay, appwin.GetWindow().obj)); + Geometry geometry1; + geometry1.x = 0; + geometry1.y = 0; + geometry1.w = 1920; + geometry1.h = 1080; + EXPECT_TRUE(player1->SetDisplayMode(DisplayMode::kDstRoi)); + EXPECT_TRUE(player1->SetDisplayRoi(geometry1)); + EXPECT_TRUE(player1->PrepareAsync()); + ASSERT_EQ( + user_data->onPrepareDone + .WaitForChange(PrepareStatus::kSuccess, DEFAULT_TIMEOUT_FOR_PREPARING) + .get(), + WatcherStatus::kSuccess); + EXPECT_TRUE(player1->Start()); + std::this_thread::sleep_for(std::chrono::seconds(kPlayingTime)); + EXPECT_TRUE(player1->Stop()); + EXPECT_TRUE(player1->Close()); + player1.reset(); +} + +TEST(MixerScenarioTest, Basic) { + shared_ptr user_data1 = std::make_shared(); + shared_ptr mock_eventlistener1 = + std::make_shared(); + shared_ptr fake_eventlistener1 = + std::make_shared(); + mock_eventlistener1->Bind( + std::dynamic_pointer_cast(fake_eventlistener1)); + + shared_ptr user_data2 = std::make_shared(); + shared_ptr mock_eventlistener2 = + std::make_shared(); + shared_ptr fake_eventlistener2 = + std::make_shared(); + mock_eventlistener2->Bind( + std::dynamic_pointer_cast(fake_eventlistener2)); + + shared_ptr user_data3 = std::make_shared(); + shared_ptr mock_eventlistener3 = + std::make_shared(); + shared_ptr fake_eventlistener3 = + std::make_shared(); + mock_eventlistener3->Bind( + std::dynamic_pointer_cast(fake_eventlistener3)); + + AppWindow appwin(0, 0, 1920, 1080); + + // create mixer + auto mixer = Mixer::Create(); + ASSERT_NE(mixer, nullptr); + EXPECT_TRUE(mixer->SetDisplay(DisplayType::kOverlay, appwin.GetWindow().obj)); + + // create player1 + auto player1 = plusplayer::PlusPlayer::Create(); + ASSERT_NE(player1, nullptr); + EXPECT_TRUE(player1->Open(hlsuri.c_str())); + player1->RegisterListener(mock_eventlistener1.get(), (void*)user_data1.get()); + // set mixer mode + EXPECT_TRUE(player1->SetDisplay(DisplayType::kMixer, mixer.get())); + Geometry geometry1; + geometry1.x = 20; + geometry1.y = 20; + geometry1.w = 1180; + geometry1.h = 720; + EXPECT_TRUE(player1->SetDisplayRoi(geometry1)); + // set audio focus + EXPECT_TRUE(mixer->SetAudioFocus(player1.get())); + EXPECT_TRUE(player1->PrepareAsync()); + + // create player2 + auto player2 = plusplayer::PlusPlayer::Create(); + EXPECT_NE(player2, nullptr); + EXPECT_TRUE(player2->Open(hlsuri.c_str())); + player2->RegisterListener(mock_eventlistener2.get(), (void*)user_data2.get()); + // set mixer mode + EXPECT_TRUE(player2->SetDisplay(DisplayType::kMixer, mixer.get())); + Geometry geometry2; + geometry2.x = 1220; + geometry2.y = 20; + geometry2.w = 640; + geometry2.h = 480; + EXPECT_TRUE(player2->SetDisplayRoi(geometry2)); + EXPECT_TRUE(player2->PrepareAsync()); + + // create player3 + auto player3 = plusplayer::PlusPlayer::Create(); + EXPECT_NE(player3, nullptr); + EXPECT_TRUE(player3->Open(hlsuri.c_str())); + player3->RegisterListener(mock_eventlistener3.get(), (void*)user_data3.get()); + // set mixer mode + EXPECT_TRUE(player3->SetDisplay(DisplayType::kMixer, mixer.get())); + Geometry geometry3; + geometry3.x = 1220; + geometry3.y = 520; + geometry3.w = 640; + geometry3.h = 480; + EXPECT_TRUE(player3->SetDisplayRoi(geometry3)); + EXPECT_TRUE(player3->PrepareAsync()); + + ASSERT_EQ( + user_data1->onPrepareDone + .WaitForChange(PrepareStatus::kSuccess, DEFAULT_TIMEOUT_FOR_PREPARING) + .get(), + WatcherStatus::kSuccess); + + ASSERT_EQ( + user_data2->onPrepareDone + .WaitForChange(PrepareStatus::kSuccess, DEFAULT_TIMEOUT_FOR_PREPARING) + .get(), + WatcherStatus::kSuccess); + + ASSERT_EQ( + user_data3->onPrepareDone + .WaitForChange(PrepareStatus::kSuccess, DEFAULT_TIMEOUT_FOR_PREPARING) + .get(), + WatcherStatus::kSuccess); + + EXPECT_TRUE(player1->Start()); + EXPECT_TRUE(player2->Start()); + EXPECT_TRUE(player3->Start()); + + EXPECT_TRUE(mixer->Start()); + std::this_thread::sleep_for(std::chrono::seconds(kPlayingTime)); + + EXPECT_TRUE(mixer->Stop()); + EXPECT_TRUE(player1->Stop()); + EXPECT_TRUE(player2->Stop()); + EXPECT_TRUE(player3->Stop()); + + EXPECT_TRUE(player1->Close()); + EXPECT_TRUE(player2->Close()); + EXPECT_TRUE(player3->Close()); + + player1.reset(); + player2.reset(); + player3.reset(); + mixer.reset(); +} +#endif +#if 0 // memory check TC +class MixerScenarioMemTestF : public ::testing::Test { + public: + explicit MixerScenarioMemTestF(void) : util_(Utility::Instance()){}; + ~MixerScenarioMemTestF(void){}; + + virtual void SetUp(void) override { + LOG_ERROR("%s", util_.GetCurrentTestName()); + geometry1_.x = 20; + geometry1_.y = 20; + geometry1_.w = 960; + geometry1_.h = 540; + + geometry2_.x = 1000; + geometry2_.y = 20; + geometry2_.w = 720; + geometry2_.h = 480; + + geometry3_.x = 1000; + geometry3_.y = 520; + geometry3_.w = 720; + geometry3_.h = 480; + } + + virtual void TearDown(void) override { + LOG_ERROR("%s", util_.GetCurrentTestName()); + } + + public: + Utility& util_; + Geometry geometry1_; + Geometry geometry2_; + Geometry geometry3_; +}; + +TEST(MixerScenarioMemTestF, RealNothing) { + std::this_thread::sleep_for(std::chrono::seconds(20)); +} + +TEST_F(MixerScenarioMemTestF, OnlyWindow) { + std::this_thread::sleep_for(std::chrono::seconds(20)); +} + +TEST_F(MixerScenarioMemTestF, OnePlayer) { + // create player1 + auto player1_ = util_.GetOpenedPlusPlayer(hlsuri); + ASSERT_NE(player1_, nullptr); + EXPECT_TRUE(player1_->Prepare()); + EXPECT_TRUE(player1_->Start()); + std::this_thread::sleep_for(std::chrono::seconds(40)); + EXPECT_TRUE(player1_->Stop()); + EXPECT_TRUE(player1_->Close()); + player1_.reset(); +} + +TEST_F(MixerScenarioMemTestF, MixerThreePlayer) { + auto mixer_ = Mixer::Create(); + mixer_->SetDisplay(DisplayType::kOverlay, util_.GetWindow()); + auto player1_ = + util_.GetOpenedMixPlusPlayer(hlsuri, mixer_.get(), geometry1_); + ASSERT_NE(player1_, nullptr); + auto player2_ = + util_.GetOpenedMixPlusPlayer(hlsuri, mixer_.get(), geometry2_); + ASSERT_NE(player2_, nullptr); + auto player3_ = + util_.GetOpenedMixPlusPlayer(hlsuri, mixer_.get(), geometry3_); + ASSERT_NE(player3_, nullptr); + + EXPECT_TRUE(mixer_->SetAudioFocus(player1_.get())); + + EXPECT_TRUE(player1_->Prepare()); + EXPECT_TRUE(player2_->Prepare()); + EXPECT_TRUE(player3_->Prepare()); + + EXPECT_TRUE(player1_->Start()); + EXPECT_TRUE(player2_->Start()); + EXPECT_TRUE(player3_->Start()); + + EXPECT_TRUE(mixer_->Start()); + std::this_thread::sleep_for(std::chrono::seconds(40)); + + EXPECT_TRUE(mixer_->Stop()); + EXPECT_TRUE(player1_->Stop()); + EXPECT_TRUE(player2_->Stop()); + EXPECT_TRUE(player3_->Stop()); + + EXPECT_TRUE(player1_->Close()); + EXPECT_TRUE(player2_->Close()); + EXPECT_TRUE(player3_->Close()); + + player1_.reset(); + player2_.reset(); + player3_.reset(); + + mixer_.reset(); +} + +TEST_F(MixerScenarioMemTestF, MixerOnePlayer) { + auto mixer_ = Mixer::Create(); + mixer_->SetDisplay(DisplayType::kOverlay, util_.GetWindow()); + auto player1_ = + util_.GetOpenedMixPlusPlayer(hlsuri, mixer_.get(), geometry1_); + ASSERT_NE(player1_, nullptr); + + EXPECT_TRUE(mixer_->SetAudioFocus(player1_.get())); + + EXPECT_TRUE(player1_->Prepare()); + + EXPECT_TRUE(player1_->Start()); + + EXPECT_TRUE(mixer_->Start()); + std::this_thread::sleep_for(std::chrono::seconds(40)); + + EXPECT_TRUE(mixer_->Stop()); + EXPECT_TRUE(player1_->Stop()); + + EXPECT_TRUE(player1_->Close()); + + player1_.reset(); + mixer_.reset(); +} +#endif + +#if 1 // normal mixer test +TEST_F(MixerScenarioTestF, Basic) { + player1_ = util_.GetOpenedMixPlusPlayer(httpuri, mixer_.get(), geometry1_); + ASSERT_NE(player1_, nullptr); + player2_ = util_.GetOpenedMixPlusPlayer(httpuri, mixer_.get(), geometry2_); + ASSERT_NE(player2_, nullptr); + + EXPECT_TRUE(mixer_->SetAudioFocus(player1_.get())); + + EXPECT_TRUE(player1_->Prepare()); + EXPECT_TRUE(player2_->Prepare()); + + EXPECT_TRUE(player1_->Start()); + EXPECT_TRUE(player2_->Start()); + + EXPECT_TRUE(mixer_->Start()); + std::this_thread::sleep_for(std::chrono::seconds(kPlayingTime)); + + EXPECT_TRUE(mixer_->Stop()); + EXPECT_TRUE(player1_->Stop()); + EXPECT_TRUE(player2_->Stop()); +} + +TEST_F(MixerScenarioTestF, SinglePlay) { + player1_ = util_.GetOpenedMixPlusPlayer(httpuri, mixer_.get(), geometry1_); + ASSERT_NE(player1_, nullptr); + + EXPECT_TRUE(mixer_->SetAudioFocus(player1_.get())); + + EXPECT_TRUE(player1_->Prepare()); + + EXPECT_TRUE(player1_->Start()); + + EXPECT_TRUE(mixer_->Start()); + std::this_thread::sleep_for(std::chrono::seconds(kPlayingTime)); + + EXPECT_TRUE(mixer_->Stop()); + EXPECT_TRUE(player1_->Stop()); +} + +#if 0 // n-decoding test ut : need to call n-decoding mode set api of player +TEST_F(MixerScenarioTestF, MaxPlay) { + player1_ = util_.GetOpenedMixPlusPlayer(httpuri, mixer_.get(), geometry1_); + ASSERT_NE(player1_, nullptr); + player2_ = util_.GetOpenedMixPlusPlayer(httpuri, mixer_.get(), geometry2_); + ASSERT_NE(player2_, nullptr); + player3_ = util_.GetOpenedMixPlusPlayer(httpuri, mixer_.get(), geometry2_); + ASSERT_NE(player3_, nullptr); + player4_ = util_.GetOpenedMixPlusPlayer(httpuri, mixer_.get(), geometry2_); + ASSERT_NE(player4_, nullptr); + + EXPECT_TRUE(mixer_->SetAudioFocus(player1_.get())); + + EXPECT_TRUE(player1_->Prepare()); + EXPECT_TRUE(player2_->Prepare()); + EXPECT_TRUE(player3_->Prepare()); + EXPECT_TRUE(player4_->Prepare()); + + EXPECT_TRUE(player1_->Start()); + EXPECT_TRUE(player2_->Start()); + EXPECT_TRUE(player3_->Start()); + EXPECT_TRUE(player4_->Start()); + + EXPECT_TRUE(mixer_->Start()); + std::this_thread::sleep_for(std::chrono::seconds(kPlayingTime)); + + EXPECT_TRUE(mixer_->Stop()); + EXPECT_TRUE(player1_->Stop()); + EXPECT_TRUE(player2_->Stop()); + EXPECT_TRUE(player3_->Stop()); + EXPECT_TRUE(player4_->Stop()); +} +#endif + +TEST_F(MixerScenarioTestF, DetachAttach) { + player1_ = util_.GetStartedMixPlusPlayer(hlsuri, mixer_.get(), geometry1_); + ASSERT_NE(player1_, nullptr); + player2_ = util_.GetStartedMixPlusPlayer(httpuri, mixer_.get(), geometry2_); + ASSERT_NE(player2_, nullptr); + + EXPECT_TRUE(mixer_->Start()); + std::this_thread::sleep_for(std::chrono::seconds(kPlayingTime)); + EXPECT_TRUE(player1_->Stop()); + + player3_ = util_.GetStartedMixPlusPlayer(dashuri, mixer_.get(), geometry3_); + ASSERT_NE(player3_, nullptr); + + std::this_thread::sleep_for(std::chrono::seconds(kPlayingTime)); + + EXPECT_TRUE(mixer_->Stop()); + EXPECT_TRUE(player2_->Stop()); + EXPECT_TRUE(player3_->Stop()); +} + +TEST_F(MixerScenarioTestF, DetachAttach1) { + player1_ = util_.GetStartedMixPlusPlayer(hlsuri, mixer_.get(), geometry1_); + ASSERT_NE(player1_, nullptr); + player2_ = util_.GetStartedMixPlusPlayer(httpuri, mixer_.get(), geometry2_); + ASSERT_NE(player2_, nullptr); + + EXPECT_TRUE(mixer_->Start()); + std::this_thread::sleep_for(std::chrono::seconds(kPlayingTime)); + EXPECT_TRUE(player1_->Stop()); + + player3_ = util_.GetStartedMixPlusPlayer(dashuri, mixer_.get(), geometry1_); + ASSERT_NE(player3_, nullptr); + + std::this_thread::sleep_for(std::chrono::seconds(kPlayingTime)); + + EXPECT_TRUE(mixer_->Stop()); + EXPECT_TRUE(player2_->Stop()); + EXPECT_TRUE(player3_->Stop()); +} + +TEST_F(MixerScenarioTestF, SetROI) { + player1_ = util_.GetStartedMixPlusPlayer(hlsuri, mixer_.get(), geometry1_); + ASSERT_NE(player1_, nullptr); + + player2_ = util_.GetStartedMixPlusPlayer(httpuri, mixer_.get(), geometry2_); + ASSERT_NE(player2_, nullptr); + + EXPECT_TRUE(mixer_->Start()); + std::this_thread::sleep_for(std::chrono::seconds(kPlayingTime)); + + EXPECT_TRUE(player1_->SetDisplayRoi(geometry2_)); + EXPECT_TRUE(player2_->SetDisplayRoi(geometry1_)); + EXPECT_TRUE(mixer_->Commit()); + + std::this_thread::sleep_for(std::chrono::seconds(kPlayingTime)); + + EXPECT_TRUE(mixer_->Stop()); + EXPECT_TRUE(player1_->Stop()); + EXPECT_TRUE(player2_->Stop()); +} + +TEST_F(MixerScenarioTestF, SetROI1) { + player1_ = util_.GetStartedMixPlusPlayer(hlsuri, mixer_.get(), geometry1_); + ASSERT_NE(player1_, nullptr); + + player2_ = util_.GetStartedMixPlusPlayer(httpuri, mixer_.get(), geometry2_); + ASSERT_NE(player2_, nullptr); + + EXPECT_TRUE(mixer_->Start()); + std::this_thread::sleep_for(std::chrono::seconds(kPlayingTime)); + + EXPECT_TRUE(player1_->SetDisplayRoi(geometry2_)); + EXPECT_TRUE(player2_->SetDisplayRoi(geometry3_)); + EXPECT_TRUE(mixer_->Commit()); + + std::this_thread::sleep_for(std::chrono::seconds(kPlayingTime)); + + EXPECT_TRUE(mixer_->Stop()); + EXPECT_TRUE(player1_->Stop()); + EXPECT_TRUE(player2_->Stop()); +} + +TEST_F(MixerScenarioTestF, CheckMaxMixedPlayer) { + player1_ = util_.GetOpenedMixPlusPlayer(hlsuri, mixer_.get(), geometry1_); + ASSERT_NE(player1_, nullptr); + player2_ = util_.GetOpenedMixPlusPlayer(httpuri, mixer_.get(), geometry2_); + ASSERT_NE(player2_, nullptr); + player3_ = util_.GetOpenedMixPlusPlayer(dashuri, mixer_.get(), geometry3_); + ASSERT_NE(player3_, nullptr); + auto player4 = + util_.GetOpenedMixPlusPlayer(httpuri, mixer_.get(), geometry3_); + ASSERT_NE(player4, nullptr); + + EXPECT_TRUE(player1_->Prepare()); + EXPECT_TRUE(player2_->Prepare()); + EXPECT_TRUE(player3_->Prepare()); + + EXPECT_FALSE(player4->Prepare()); + + EXPECT_TRUE(player4->Close()); + EXPECT_TRUE(mixer_->Stop()); +} + +TEST_F(MixerScenarioTestF, SetAudioFocus) { + player1_ = util_.GetOpenedMixPlusPlayer(hlsuri, mixer_.get(), geometry1_); + ASSERT_NE(player1_, nullptr); + + player2_ = util_.GetOpenedMixPlusPlayer(httpuri, mixer_.get(), geometry2_); + ASSERT_NE(player2_, nullptr); + + EXPECT_TRUE(mixer_->SetAudioFocus(player1_.get())); + + EXPECT_TRUE(player1_->Prepare()); + EXPECT_TRUE(player2_->Prepare()); + + EXPECT_TRUE(player1_->Start()); + EXPECT_TRUE(player2_->Start()); + + EXPECT_TRUE(mixer_->Start()); + + std::this_thread::sleep_for(std::chrono::seconds(kPlayingTime)); + + EXPECT_TRUE(mixer_->SetAudioFocus(player2_.get())); + std::this_thread::sleep_for(std::chrono::seconds(kPlayingTime)); + + EXPECT_TRUE(mixer_->SetAudioFocus(player1_.get())); + std::this_thread::sleep_for(std::chrono::seconds(kPlayingTime)); + + EXPECT_TRUE(player1_->Stop()); + EXPECT_TRUE(player2_->Stop()); + EXPECT_TRUE(mixer_->Stop()); +} + +TEST_F(MixerScenarioTestF, SetAudioFocus1) { + player1_ = util_.GetStartedMixPlusPlayer(hlsuri, mixer_.get(), geometry1_); + ASSERT_NE(player1_, nullptr); + + player2_ = util_.GetStartedMixPlusPlayer(httpuri, mixer_.get(), geometry2_); + ASSERT_NE(player2_, nullptr); + + EXPECT_TRUE(mixer_->Start()); + + std::this_thread::sleep_for(std::chrono::seconds(kPlayingTime)); + + EXPECT_TRUE(player1_->Stop()); + EXPECT_TRUE(player2_->Stop()); + EXPECT_TRUE(mixer_->Stop()); +} + +TEST_F(MixerScenarioTestF, SetAudioFocus2) { + player1_ = util_.GetStartedMixPlusPlayer(hlsuri, mixer_.get(), geometry1_); + ASSERT_NE(player1_, nullptr); + + EXPECT_FALSE(mixer_->SetAudioFocus(nullptr)); + player2_ = util_.GetStartedMixPlusPlayer(httpuri, mixer_.get(), geometry2_); + ASSERT_NE(player2_, nullptr); + + EXPECT_TRUE(mixer_->Start()); + + std::this_thread::sleep_for(std::chrono::seconds(kPlayingTime)); + + EXPECT_TRUE(player1_->Stop()); + EXPECT_TRUE(player2_->Stop()); + EXPECT_TRUE(mixer_->Stop()); +} +#endif \ No newline at end of file diff --git a/ut/src/mixer/ut_mixerticket.cpp b/ut/src/mixer/ut_mixerticket.cpp new file mode 100755 index 0000000..440135d --- /dev/null +++ b/ut/src/mixer/ut_mixerticket.cpp @@ -0,0 +1,231 @@ +// +// @ Copyright [2020] +// +#include + +#include "core/utils/plusplayer_log.h" +#include "mixer/mixer.h" +#include "mixer/mixer_eventlistener.h" +#include "mixer/mixerticket.h" +#include "mixer/mixerticket_eventlistener.h" +#include "ut/include/appwindow.h" + +using namespace plusplayer; +using namespace plusplayer_ut; + +class MixerTicketMockPlayer { + public: + MixerTicketMockPlayer() { listener_ = new MyTicketListener(this); } + MixerTicketMockPlayer(int x, int y, int w, int h) { + listener_ = new MyTicketListener(this); + display_info_.geometry.x = x; + display_info_.geometry.y = y; + display_info_.geometry.w = w; + display_info_.geometry.h = h; + } + ~MixerTicketMockPlayer() { delete listener_; } + + class MyTicketListener : public MixerTicketEventListener { + public: + explicit MyTicketListener(MixerTicketMockPlayer* handler) + : handler_(handler){}; + bool OnAudioFocusChanged(bool active) { + LOG_INFO("My OnAudioFocusChanged [%d] player [%p]", active, handler_); + return true; + } + + bool OnUpdateDisplayInfo(const DisplayInfo& cur_info, + DisplayInfo* new_info) { + *new_info = handler_->display_info_; + LOG_INFO("OnUpdateDisplayInfo x[%d] y[%d]", + handler_->display_info_.geometry.x, + handler_->display_info_.geometry.y); + return true; + } + MixerTicketMockPlayer* handler_ = nullptr; + }; + + public: + MixerTicketEventListener* listener_ = nullptr; + DisplayInfo display_info_; +}; + +class MyMixerListener : public MixerEventListener { + public: + MyMixerListener() {} + ~MyMixerListener() {} + void OnError() { + LOG_INFO(" listener [%p]", this); + } + void OnResourceConflicted() { + LOG_INFO("MyOnResourceConflicted listener[%p]", this); + } +}; + +TEST(MixerticketTest, MixerTicketRenderBasic) { + std::unique_ptr mixer = Mixer::Create(); + MixerTicketMockPlayer p1, p2; + MixerTicket* ticket = mixer->CreateTicket(&p1); + MixerTicket* ticket2 = mixer->CreateTicket(&p2); + // EXPECT_TRUE(ticket->Render()); + // EXPECT_TRUE(ticket2->Render()); + delete ticket; + delete ticket2; + mixer.reset(); +} + +TEST(MixerticketTest, MixerTicketRegisterListener) { + std::unique_ptr mixer = Mixer::Create(); + MixerTicketMockPlayer p1; + MixerTicket* ticket = mixer->CreateTicket(&p1); + EXPECT_TRUE(ticket->RegisterListener(p1.listener_)); + delete ticket; + mixer.reset(); +} + +TEST(MixerticketTest, MixerTicketPrepare) { + std::unique_ptr mixer = Mixer::Create(); + MixerTicketMockPlayer p1; + MixerTicket* ticket = mixer->CreateTicket(&p1); + ticket->RegisterListener(p1.listener_); + EXPECT_TRUE(ticket->Prepare()); + delete ticket; + mixer.reset(); +} + +TEST(MixerticketTest, MixerTiecketGetAvailableResourceType_1) { + std::unique_ptr mixer = Mixer::Create(); + MixerTicketMockPlayer p1; + + MixerTicket* ticket = mixer->CreateTicket(&p1); + + ticket->RegisterListener(p1.listener_); + ResourceType type; + EXPECT_TRUE( + ticket->GetAvailableResourceType(ResourceCategory::kVideoDecoder, &type)); + + delete ticket; + mixer.reset(); +} + +TEST(MixerticketTest, MixerTiecketGetAvailableResourceType_2) { + std::unique_ptr mixer = Mixer::Create(); + MixerTicketMockPlayer p1; + + mixer->SetRscAllocMode(RscAllocMode::kDisable); + MixerTicket* ticket = mixer->CreateTicket(&p1); + + ticket->RegisterListener(p1.listener_); + ResourceType type; + EXPECT_FALSE( + ticket->GetAvailableResourceType(ResourceCategory::kVideoDecoder, &type)); + + delete ticket; + mixer.reset(); +} + +TEST(MixerticketTest, MixerTiecketAlloc) { + std::unique_ptr mixer = Mixer::Create(); + MixerTicketMockPlayer p1; + + MixerTicket* ticket = mixer->CreateTicket(&p1); + + ticket->RegisterListener(p1.listener_); + ResourceType type; + EXPECT_TRUE( + ticket->GetAvailableResourceType(ResourceCategory::kVideoDecoder, &type)); + EXPECT_TRUE(ticket->Alloc(ResourceCategory::kVideoDecoder, type)); + + EXPECT_TRUE( + ticket->GetAvailableResourceType(ResourceCategory::kVideoDecoder, &type)); + EXPECT_TRUE(ticket->Alloc(ResourceCategory::kVideoDecoder, type)); + + EXPECT_TRUE( + ticket->GetAvailableResourceType(ResourceCategory::kVideoDecoder, &type)); + EXPECT_TRUE(ticket->Alloc(ResourceCategory::kVideoDecoder, type)); + + EXPECT_FALSE( + ticket->GetAvailableResourceType(ResourceCategory::kVideoDecoder, &type)); + EXPECT_FALSE(ticket->Alloc(ResourceCategory::kVideoDecoder, type)); + + delete ticket; + mixer.reset(); +} + +TEST(MixerticketTest, MixerTiecketDeallocResource) { + std::unique_ptr mixer = Mixer::Create(); + MixerTicketMockPlayer p1, p2, p3; + + MixerTicket* ticket1 = mixer->CreateTicket(&p1); + + ResourceType type; + EXPECT_TRUE(ticket1->GetAvailableResourceType(ResourceCategory::kVideoDecoder, + &type)); + EXPECT_TRUE(ticket1->Alloc(ResourceCategory::kVideoDecoder, type)); + + delete ticket1; + + MixerTicket* ticket2 = mixer->CreateTicket(&p2); + EXPECT_TRUE(ticket2->GetAvailableResourceType(ResourceCategory::kVideoDecoder, + &type)); + EXPECT_TRUE(ticket2->Alloc(ResourceCategory::kVideoDecoder, type)); + + MixerTicket* ticket3 = mixer->CreateTicket(&p3); + EXPECT_TRUE(ticket3->GetAvailableResourceType(ResourceCategory::kVideoDecoder, + &type)); + EXPECT_TRUE(ticket3->Alloc(ResourceCategory::kVideoDecoder, type)); + + delete ticket2; + delete ticket3; + mixer.reset(); +} + +TEST(MixerticketTest, MixerTiecketIsAudioFocusHandler_1) { + std::unique_ptr mixer = Mixer::Create(); + MixerTicketMockPlayer p1; + + mixer->DisableAudioFocusSetting(); + MixerTicket* ticket = mixer->CreateTicket(&p1); + ticket->RegisterListener(p1.listener_); + + EXPECT_FALSE(ticket->IsAudioFocusHandler()); + delete ticket; + mixer.reset(); +} + +TEST(MixerticketTest, MixerTiecketIsAudioFocusHandler_2) { + std::unique_ptr mixer = Mixer::Create(); + MixerTicketMockPlayer p1; + + MixerTicket* ticket = mixer->CreateTicket(&p1); + ticket->RegisterListener(p1.listener_); + + EXPECT_TRUE(ticket->IsAudioFocusHandler()); + delete ticket; + mixer.reset(); +} + +TEST(MixerticketTest, MixerTiecketIsRscAllocHandler_1) { + std::unique_ptr mixer = Mixer::Create(); + MixerTicketMockPlayer p1; + + mixer->SetRscAllocMode(RscAllocMode::kDisable); + MixerTicket* ticket = mixer->CreateTicket(&p1); + ticket->RegisterListener(p1.listener_); + + EXPECT_FALSE(ticket->IsRscAllocHandler()); + delete ticket; + mixer.reset(); +} + +TEST(MixerticketTest, MixerTiecketIsRscAllocHandler_2) { + std::unique_ptr mixer = Mixer::Create(); + MixerTicketMockPlayer p1; + + MixerTicket* ticket = mixer->CreateTicket(&p1); + ticket->RegisterListener(p1.listener_); + + EXPECT_TRUE(ticket->IsRscAllocHandler()); + delete ticket; + mixer.reset(); +} \ No newline at end of file diff --git a/ut/src/mixer/ut_renderer.cpp b/ut/src/mixer/ut_renderer.cpp new file mode 100755 index 0000000..95cd291 --- /dev/null +++ b/ut/src/mixer/ut_renderer.cpp @@ -0,0 +1,361 @@ +#include +#include + +#include "mixer/constant.h" +#include "mixer/matcher.h" +#include "mixer/mock/mock_memallocator.h" +#include "mixer/mock/mock_renderableobj.h" +#include "mixer/mock/mock_renderableobj_factory.h" +#include "mixer/mock/mock_renderer_evtlistener.h" +#include "mixer/mock/mock_vpcollection.h" +#include "mixer/mock/mock_vpmanipulator.h" +#include "mixer/mock/mock_vpscaler.h" +#include "mixer/renderer.h" + +using namespace plusplayer; +using namespace plusplayer_ut; + +using ::testing::_; +using ::testing::AllOf; +using ::testing::AtLeast; +using ::testing::DoAll; +using ::testing::Eq; +using ::testing::Field; +using ::testing::Return; +using ::testing::ReturnPointee; +using ::testing::SetArgReferee; +using ::testing::SizeIs; + +/******************************************************************************************** + * InvalidRendererTest + */ +class InvalidRendererTest : public ::testing::Test { + public: + virtual void SetUp() override { + ON_CALL(GetRenderableObjectFactory(), CreateRenderableObject(_, _)) + .WillByDefault(Return(nullptr)); + + EXPECT_CALL(GetRenderableObjectFactory(), + CreateRenderableObject(kInvalidWidth, kInvalidHeight)); + + renderer_ = std::unique_ptr(new Renderer( + GetRenderableObjectFactory(), + GetResolutionInfo(kInvalidWidth, kInvalidHeight, kInvalidFramerateNum, + kInvalidFramerateDen), + nullptr)); + + ASSERT_FALSE(renderer_->IsValid()); + } + virtual void TearDown() override {} + + protected: + Renderer& GetRenderer() { return *renderer_; } + + const MockRenderableObjectFactory& GetRenderableObjectFactory() const { + return mock_mixed_frame_factory_; + } + const MockVideoPlaneScaler& GetVideoPlaneScaler() const { + return mock_vpscaler_; + } + const MockVideoPlaneCollection* GetVideoPlaneCollection() const { + return &videoplanecollection_; + } + + private: + std::unique_ptr renderer_; + MockRenderableObjectFactory mock_mixed_frame_factory_; + MockVideoPlaneScaler mock_vpscaler_; + MockVideoPlaneCollection videoplanecollection_; +}; + +TEST_F(InvalidRendererTest, Start) { EXPECT_FALSE(GetRenderer().Start()); } + +TEST_F(InvalidRendererTest, Stop) { EXPECT_FALSE(GetRenderer().Stop()); } + +TEST_F(InvalidRendererTest, ChangeRenderingSpeed) { + EXPECT_FALSE( + GetRenderer().ChangeRenderingSpeed(kFastFramerateNum, kFastFramerateDen)); +} + +TEST_F(InvalidRendererTest, Render) { + EXPECT_FALSE(GetRenderer().Render( + &GetVideoPlaneScaler(), GetVideoPlaneCollection(), + GetGeometry(kDefaultX, kDefaultY, kDefaultW, kDefaultH))); +} + +/******************************************************************************************** + * RendererConstructionTest + */ +class RendererConstructionTest : public ::testing::Test { + public: + explicit RendererConstructionTest() {} + virtual ~RendererConstructionTest() {} + virtual void SetUp() override { + mock_mixed_frame_ = new MockRenderableObject(); + + ON_CALL(GetRenderableObject(), IsValid()).WillByDefault(Return(true)); + + ON_CALL(GetRenderableObjectFactory(), CreateRenderableObject(_, _)) + .WillByDefault(Return(mock_mixed_frame_)); + } + virtual void TearDown() override {} + + protected: + MockRenderableObject& GetRenderableObject() { return *mock_mixed_frame_; } + const MockRenderableObjectFactory& GetRenderableObjectFactory() const { + return mock_mixed_frame_factory_; + } + MockRendererEventListener* GetListener() { return nullptr; } + + private: + MockRenderableObject* mock_mixed_frame_; + MockRenderableObjectFactory mock_mixed_frame_factory_; +}; + +TEST_F(RendererConstructionTest, IsValid) { + EXPECT_CALL(GetRenderableObject(), IsValid()).Times(AtLeast(1)); + EXPECT_CALL(GetRenderableObjectFactory(), + CreateRenderableObject(kDefaultWidth, kDefaultHeight)) + .Times(1); + + Renderer renderer( + GetRenderableObjectFactory(), + GetResolutionInfo(kDefaultWidth, kDefaultHeight, kDefaultFramerateNum, + kDefaultFramerateDen), + GetListener()); + + EXPECT_TRUE(renderer.IsValid()); +} + +TEST_F(RendererConstructionTest, IsValid_WithReturnNullMixedFrame) { + // FIXME(bayle.park): memleak will occur by mock_mixed_frame_ + EXPECT_CALL(GetRenderableObjectFactory(), + CreateRenderableObject(kDefaultWidth, kDefaultHeight)) + .WillOnce(Return(nullptr)); + + Renderer renderer( + GetRenderableObjectFactory(), + GetResolutionInfo(kDefaultWidth, kDefaultHeight, kDefaultFramerateNum, + kDefaultFramerateDen), + GetListener()); + + EXPECT_FALSE(renderer.IsValid()); +} + +TEST_F(RendererConstructionTest, IsValid_WithInvalidMixedFrame) { + EXPECT_CALL(GetRenderableObject(), IsValid()).WillRepeatedly(Return(false)); + EXPECT_CALL(GetRenderableObjectFactory(), + CreateRenderableObject(kDefaultWidth, kDefaultHeight)) + .Times(1); + + Renderer renderer( + GetRenderableObjectFactory(), + GetResolutionInfo(kDefaultWidth, kDefaultHeight, kDefaultFramerateNum, + kDefaultFramerateDen), + GetListener()); + + EXPECT_FALSE(renderer.IsValid()); +} + +/******************************************************************************************** + * RendererTest + */ +class RendererTest : public ::testing::Test { + public: + explicit RendererTest() {} + virtual ~RendererTest() {} + virtual void SetUp() override { + mock_mixed_frame_ = new MockRenderableObject(); + + ON_CALL(GetRenderableObject(), GetVideoPlaneManipInfo()) + .WillByDefault(Return(std::vector( + {kYComponentDestVMInfo, kUVComponentDestVMInfo}))); + ON_CALL(GetRenderableObject(), IsValid()).WillByDefault(Return(true)); + ON_CALL(GetRenderableObject(), GetWidth()) + .WillByDefault(Return(kDefaultWidth)); + ON_CALL(GetRenderableObject(), GetHeight()) + .WillByDefault(Return(kDefaultHeight)); + ON_CALL(GetRenderableObject(), GetSize()) + .WillByDefault(Return(kExpectedDefaultByteSize)); + ON_CALL(GetRenderableObject(), Render(_, _, _)).WillByDefault(Return(true)); + ON_CALL(GetRenderableObject(), Export(_)) + .WillByDefault( + DoAll(SetArgReferee<0>(kDefaultBufferKey), Return(true))); + + ON_CALL(*GetVideoPlaneCollection(), GetVideoPlaneManipInfo()) + .WillByDefault(Return(std::vector( + {kYComponentDestVMInfo, kUVComponentDestVMInfo}))); + + ON_CALL(GetRenderableObjectFactory(), CreateRenderableObject(_, _)) + .WillByDefault(Return(mock_mixed_frame_)); + + ON_CALL(GetVideoPlaneScaler(), GetScaleManipulator()) + .WillByDefault(Return(&GetScaler())); + + ON_CALL(GetScaler(), Do(_, _)).WillByDefault(Return(true)); + + ON_CALL(*GetListener(), OnRenderingRelease(_)).WillByDefault(Return(true)); + + EXPECT_CALL(GetRenderableObjectFactory(), + CreateRenderableObject(kDefaultWidth, kDefaultHeight)); + + renderer_ = std::unique_ptr(new Renderer( + GetRenderableObjectFactory(), + GetResolutionInfo(kDefaultWidth, kDefaultHeight, kDefaultFramerateNum, + kDefaultFramerateDen), + GetListener())); + + EXPECT_CALL(GetRenderableObject(), IsValid()); + ASSERT_TRUE(renderer_->IsValid()); + } + virtual void TearDown() override {} + + protected: + Renderer& GetRenderer() { return *renderer_; } + + MockRenderableObject& GetRenderableObject() { return *mock_mixed_frame_; } + const MockVideoPlaneScaler& GetVideoPlaneScaler() const { + return mock_vpscaler_; + } + const MockVideoPlaneManipulator& GetScaler() const { return scaler_; } + const MockRenderableObjectFactory& GetRenderableObjectFactory() const { + return mock_mixed_frame_factory_; + } + MockRendererEventListener* GetListener() { return &listener_; } + const MockVideoPlaneCollection* GetVideoPlaneCollection() const { + return &videoplanecollection_; + } + + private: + std::unique_ptr renderer_; + MockVideoPlaneScaler mock_vpscaler_; + MockVideoPlaneManipulator scaler_; + MockRenderableObject* mock_mixed_frame_; + MockRenderableObjectFactory mock_mixed_frame_factory_; + MockRendererEventListener listener_; + MockVideoPlaneCollection videoplanecollection_; +}; + +TEST_F(RendererTest, Start) { + EXPECT_CALL(GetRenderableObject(), IsValid()).Times(AtLeast(1)); + + EXPECT_TRUE(GetRenderer().Start()); +} + +TEST_F(RendererTest, Start_WithTwiceCall) { + EXPECT_CALL(GetRenderableObject(), IsValid()).Times(AtLeast(1)); + + EXPECT_TRUE(GetRenderer().Start()); + EXPECT_FALSE(GetRenderer().Start()); +} + +TEST_F(RendererTest, Stop) { + EXPECT_CALL(GetRenderableObject(), IsValid()).Times(AtLeast(1)); + + EXPECT_FALSE(GetRenderer().Stop()); +} + +TEST_F(RendererTest, Stop_AfterStart) { + EXPECT_CALL(GetRenderableObject(), IsValid()).Times(AtLeast(1)); + + EXPECT_TRUE(GetRenderer().Start()); + EXPECT_TRUE(GetRenderer().Stop()); +} + +TEST_F(RendererTest, ChangeResolution) { + auto* new_frame = new MockRenderableObject(); + EXPECT_CALL(*new_frame, IsValid()).WillOnce(Return(true)); + EXPECT_CALL(GetRenderableObjectFactory(), + CreateRenderableObject(kSmallWidth, kSmallHeight)) + .WillOnce(Return(new_frame)); + + EXPECT_TRUE(GetRenderer().ChangeResolution(GetRenderableObjectFactory(), + kSmallWidth, kSmallHeight)); +} + +TEST_F(RendererTest, ChangeResolution_WithInvalidResolution) { + EXPECT_FALSE(GetRenderer().ChangeResolution(GetRenderableObjectFactory(), + kInvalidWidth, kInvalidHeight)); + EXPECT_FALSE(GetRenderer().ChangeResolution(GetRenderableObjectFactory(), + kSmallWidth, kInvalidHeight)); + EXPECT_FALSE(GetRenderer().ChangeResolution(GetRenderableObjectFactory(), + kInvalidWidth, kSmallHeight)); +} + +TEST_F(RendererTest, ChangeResolution_WithSameResolution) { + EXPECT_FALSE(GetRenderer().ChangeResolution(GetRenderableObjectFactory(), + kDefaultWidth, kDefaultHeight)); +} + +TEST_F(RendererTest, ChangeResolution_WithReturnNullFrame) { + EXPECT_CALL(GetRenderableObjectFactory(), + CreateRenderableObject(kSmallWidth, kSmallHeight)) + .WillOnce(Return(nullptr)); + + EXPECT_FALSE(GetRenderer().ChangeResolution(GetRenderableObjectFactory(), + kSmallWidth, kSmallHeight)); +} + +TEST_F(RendererTest, ChangeResolution_WithInvalidNewFrame) { + auto* new_frame = new MockRenderableObject(); + EXPECT_CALL(*new_frame, IsValid()).WillOnce(Return(false)); + EXPECT_CALL(GetRenderableObjectFactory(), + CreateRenderableObject(kSmallWidth, kSmallHeight)) + .WillOnce(Return(new_frame)); + + EXPECT_FALSE(GetRenderer().ChangeResolution(GetRenderableObjectFactory(), + kSmallWidth, kSmallHeight)); +} + +TEST_F(RendererTest, ChangeRenderingSpeed) { + EXPECT_CALL(GetRenderableObject(), IsValid()).Times(AtLeast(1)); + + EXPECT_TRUE( + GetRenderer().ChangeRenderingSpeed(kFastFramerateNum, kFastFramerateDen)); +} + +TEST_F(RendererTest, ChangeRenderingSpeed_WithInvalidFramerate) { + EXPECT_FALSE(GetRenderer().ChangeRenderingSpeed(kInvalidFramerateNum, + kInvalidFramerateDen)); + EXPECT_FALSE(GetRenderer().ChangeRenderingSpeed(kFastFramerateNum, + kInvalidFramerateDen)); + EXPECT_FALSE(GetRenderer().ChangeRenderingSpeed(kInvalidFramerateNum, + kFastFramerateDen)); +} + +TEST_F(RendererTest, Render) { + EXPECT_CALL( + GetRenderableObject(), + Render( + Eq(&GetScaler()), + AllOf(SizeIs(2), + ElementsAre( + IsSameVideoPlaneManipulableInfo(kYComponentDestVMInfo), + IsSameVideoPlaneManipulableInfo(kUVComponentDestVMInfo))), + AllOf(Field(&Geometry::x, Eq(kDefaultX)), + Field(&Geometry::y, Eq(kDefaultY)), + Field(&Geometry::w, Eq(kDefaultW)), + Field(&Geometry::h, Eq(kDefaultH))))) + .Times(1); + EXPECT_CALL(*GetVideoPlaneCollection(), GetVideoPlaneManipInfo()).Times(1); + EXPECT_CALL(GetRenderableObject(), IsValid()).Times(AtLeast(1)); + EXPECT_CALL(GetVideoPlaneScaler(), GetScaleManipulator()).Times(1); + + EXPECT_TRUE(GetRenderer().Render( + &GetVideoPlaneScaler(), GetVideoPlaneCollection(), + GetGeometry(kDefaultX, kDefaultY, kDefaultW, kDefaultH))); +} + +TEST_F(RendererTest, Render_WithNullCollection) { + EXPECT_FALSE(GetRenderer().Render( + &GetVideoPlaneScaler(), nullptr, + GetGeometry(kDefaultX, kDefaultY, kDefaultW, kDefaultH))); +} + +TEST_F(RendererTest, Render_WithNullScaler) { + EXPECT_CALL(GetRenderableObject(), Render(_, _, _)).Times(0); + + EXPECT_FALSE(GetRenderer().Render( + nullptr, GetVideoPlaneCollection(), + GetGeometry(kDefaultX, kDefaultY, kDefaultW, kDefaultH))); +} \ No newline at end of file diff --git a/ut/src/mixer/ut_tizenbuffermgr.cpp b/ut/src/mixer/ut_tizenbuffermgr.cpp new file mode 100755 index 0000000..2236a39 --- /dev/null +++ b/ut/src/mixer/ut_tizenbuffermgr.cpp @@ -0,0 +1,183 @@ +#include +#include + +#include "mixer/constant.h" +#include "mixer/mock/mock_bufferobject.h" +#include "mixer/tizen/tizenbuffermgr.h" + +using ::testing::_; +using ::testing::AllOf; +using ::testing::Field; +using ::testing::NotNull; +using ::testing::Return; + +namespace { +using namespace plusplayer; +struct MockTBM { + public: + class Fake { + public: + MOCK_METHOD3(BoAlloc, BufferDefaultType(tbm_bufmgr, int, int)); + MOCK_METHOD1(BoUnRef, void(BufferDefaultType)); + MOCK_METHOD2(BoImport, BufferDefaultType(tbm_bufmgr, BufferKeyType)); + MOCK_METHOD2(GACopy, int(tbm_bufmgr, GraphicsGABltRopInfo*)); + MOCK_METHOD2(GAScale, int(tbm_bufmgr, GraphicsGAScaleInfo*)); + MOCK_METHOD2(GAFill, int(tbm_bufmgr, GraphicsGAFillRectInfo*)); + }; + + static Fake* instance; + + static void BoUnRef(BufferDefaultType bo) { instance->BoUnRef(bo); } + + static BufferDefaultType BoAlloc(tbm_bufmgr bufmgr, int size, int flag) { + return instance->BoAlloc(bufmgr, size, flag); + } + static BufferDefaultType BoImport(tbm_bufmgr bufmgr, BufferKeyType key) { + return instance->BoImport(bufmgr, key); + } + + static int GAScale(tbm_bufmgr bufmgr, GraphicsGAScaleInfo* info) { + return instance->GAScale(bufmgr, info); + } + + static int GACopy(tbm_bufmgr bufmgr, GraphicsGABltRopInfo* info) { + return instance->GACopy(bufmgr, info); + } + + static int GAFill(tbm_bufmgr bufmgr, GraphicsGAFillRectInfo* info) { + return instance->GAFill(bufmgr, info); + } +}; + +MockTBM::Fake* MockTBM::instance = nullptr; +} // namespace + +namespace plusplayer_ut { + +using TizenBufferManager = BufferManagerWithType; + +class TizenBufferManagerTest : public ::testing::Test { + public: + virtual void SetUp() override { + MockTBM::instance = &GetTBM(); + + ON_CALL(GetTBM(), BoAlloc(_, _, _)).WillByDefault(Return(kDefaultBuffer)); + ON_CALL(GetTBM(), BoImport(_, _)).WillByDefault(Return(kDefaultBuffer)); + } + virtual void TearDown() override {} + + protected: + MockTBM::Fake& GetTBM() { return fake_tbm_; } + TizenBufferManager& GetBufferManager() { return bufmgr_; } + + private: + MockTBM::Fake fake_tbm_; + TizenBufferManager bufmgr_; +}; + +TEST_F(TizenBufferManagerTest, IsValid) { + EXPECT_TRUE(GetBufferManager().IsValid()); +} + +TEST_F(TizenBufferManagerTest, Allocate) { + EXPECT_CALL(GetTBM(), BoAlloc(NotNull(), kExpectedDefaultByteSize, + TBM_BO_SCANOUT | (1 << 17))); + EXPECT_CALL(GetTBM(), BoUnRef(NotNull())); + + auto* bufferobj = GetBufferManager().Allocate(kExpectedDefaultByteSize); + EXPECT_NE(nullptr, bufferobj); + if (bufferobj) delete bufferobj; +} + +TEST_F(TizenBufferManagerTest, Allocate_WithNullBO) { + EXPECT_CALL(GetTBM(), BoAlloc(NotNull(), kExpectedDefaultByteSize, + TBM_BO_SCANOUT | (1 << 17))) + .WillOnce(Return(nullptr)); + + EXPECT_EQ(nullptr, GetBufferManager().Allocate(kExpectedDefaultByteSize)); +} + +TEST_F(TizenBufferManagerTest, Allocate_WithInvalidBufferSize) { + EXPECT_EQ(nullptr, GetBufferManager().Allocate(kInvalidBufferSize)); +} + +TEST_F(TizenBufferManagerTest, Import) { + EXPECT_CALL(GetTBM(), BoImport(NotNull(), _)); + EXPECT_CALL(GetTBM(), BoUnRef(NotNull())); + + EXPECT_NE(nullptr, GetBufferManager().Import(kDefaultBufferHandle)); +} + +TEST_F(TizenBufferManagerTest, Import_WithNullBO) { + EXPECT_CALL(GetTBM(), BoImport(NotNull(), _)).WillOnce(Return(nullptr)); + + EXPECT_EQ(nullptr, GetBufferManager().Import(kDefaultBufferHandle)); +} + +TEST_F(TizenBufferManagerTest, GetScaleManipulator) { + EXPECT_NE(nullptr, GetBufferManager().GetScaleManipulator()); +} + +class TizenBufferManagerScalerTest : public ::testing::Test { + public: + virtual void SetUp() override { + MockTBM::instance = &GetTBM(); + ON_CALL(GetTBM(), GAScale(_, _)).WillByDefault(Return(true)); + } + virtual void TearDown() override {} + + protected: + MockTBM::Fake& GetTBM() { return fake_tbm_; } + const VideoPlaneManipulator& GetScaler() { + return *bufmgr_.GetScaleManipulator(); + } + + private: + MockTBM::Fake fake_tbm_; + TizenBufferManager bufmgr_; +}; + +TEST_F(TizenBufferManagerScalerTest, Do) { + EXPECT_CALL( + GetTBM(), + GAScale( + NotNull(), + AllOf( + Field(&GraphicsGAScaleInfo::ga_mode, GRAPHICS_GA_SCALE_MODE), + Field(&GraphicsGAScaleInfo::rop_mode, GRAPHICS_GA_ROP_COPY), + Field(&GraphicsGAScaleInfo::ga_op_type, GRAPHICS_GA_SCALE), + Field(&GraphicsGAScaleInfo::pre_alphamode, 0), + Field(&GraphicsGAScaleInfo::ca_value, 0), + Field(&GraphicsGAScaleInfo::rop_on_off, 0), + Field(&GraphicsGAScaleInfo::src_handle, + kYComponentSrcVMInfo.handle), + Field(&GraphicsGAScaleInfo::src_hbytesize, + kYComponentSrcVMInfo.linesize), + Field( + &GraphicsGAScaleInfo::src_rect, + AllOf( + Field(&GraphicsRectInfo::x, kYComponentSrcVMInfo.rect.x), + Field(&GraphicsRectInfo::y, kYComponentSrcVMInfo.rect.y), + Field(&GraphicsRectInfo::w, kYComponentSrcVMInfo.rect.w), + Field(&GraphicsRectInfo::h, + kYComponentSrcVMInfo.rect.h))), + Field(&GraphicsGAScaleInfo::dst_handle, + kYComponentDestVMInfo.handle), + Field(&GraphicsGAScaleInfo::dst_hbytesize, + kYComponentDestVMInfo.linesize), + Field( + &GraphicsGAScaleInfo::dst_rect, + AllOf( + Field(&GraphicsRectInfo::x, kYComponentDestVMInfo.rect.x), + Field(&GraphicsRectInfo::y, kYComponentDestVMInfo.rect.y), + Field(&GraphicsRectInfo::w, kYComponentDestVMInfo.rect.w), + Field(&GraphicsRectInfo::h, + kYComponentDestVMInfo.rect.h)))))); + + EXPECT_TRUE(GetScaler().Do(kYComponentSrcVMInfo, kYComponentDestVMInfo)); +} + +TEST_F(TizenBufferManagerScalerTest, Do_WithDifferentComponent) { + EXPECT_FALSE(GetScaler().Do(kYComponentSrcVMInfo, kUVComponentDestVMInfo)); +} +} // namespace plusplayer_ut diff --git a/ut/src/mixer/ut_tizenbufferobj.cpp b/ut/src/mixer/ut_tizenbufferobj.cpp new file mode 100755 index 0000000..5eb1cd9 --- /dev/null +++ b/ut/src/mixer/ut_tizenbufferobj.cpp @@ -0,0 +1,129 @@ +#include +#include + +#include + +#include "mixer/constant.h" +#include "mixer/tizen/tizenbufferobj.h" + +using ::testing::_; +using ::testing::Return; + +namespace { +using namespace plusplayer; + +struct MockTBM { + public: + class Fake { + public: + MOCK_METHOD1(BoRef, BufferDefaultType(BufferDefaultType)); + MOCK_METHOD1(BoUnRef, void(BufferDefaultType)); + MOCK_METHOD1(BoSize, int(BufferDefaultType)); + MOCK_METHOD2(BoGetHandle, BufferUnionHandleType(BufferDefaultType, int)); + MOCK_METHOD1(BoExport, BufferKeyType(BufferDefaultType)); + }; + + static Fake* instance; + + static BufferDefaultType BoRef(BufferDefaultType bo) { + return instance->BoRef(bo); + } + + static void BoUnRef(BufferDefaultType bo) { instance->BoUnRef(bo); } + + static int BoSize(BufferDefaultType bo) { return instance->BoSize(bo); } + + static BufferUnionHandleType BoGetHandle(BufferDefaultType bo, int device) { + return instance->BoGetHandle(bo, device); + } + static BufferKeyType BoExport(BufferDefaultType bo) { + return instance->BoExport(bo); + } +}; + +MockTBM::Fake* MockTBM::instance = nullptr; +} // namespace + +namespace plusplayer_ut { + +using TizenBufferObject = BufferObjectWithType; + +class InvalidBufferObjectWithTypeTest : public ::testing::Test { + public: + explicit InvalidBufferObjectWithTypeTest() : bufferobj_(nullptr) {} + virtual ~InvalidBufferObjectWithTypeTest() = default; + virtual void SetUp() override {} + virtual void TearDown() override {} + + protected: + TizenBufferObject& GetBufferObject() { return bufferobj_; } + + private: + TizenBufferObject bufferobj_; +}; + +TEST_F(InvalidBufferObjectWithTypeTest, IsValid) { + EXPECT_FALSE(GetBufferObject().IsValid()); +} + +TEST_F(InvalidBufferObjectWithTypeTest, GetBufferHandle) { + EXPECT_EQ(plusplayer::kInvalidBufferHandle, + GetBufferObject().GetBufferHandle()); +} + +TEST_F(InvalidBufferObjectWithTypeTest, Export) { + EXPECT_EQ(plusplayer::kInvalidBufferKey, GetBufferObject().Export()); +} + +TEST_F(InvalidBufferObjectWithTypeTest, GetSize) { + EXPECT_EQ(plusplayer::kInvalidBufferSize, GetBufferObject().GetSize()); +} + +class BufferObjectWithTypeTest : public ::testing::Test { + public: + virtual void SetUp() override { + MockTBM::instance = &GetTBM(); + + ON_CALL(GetTBM(), BoRef(_)).WillByDefault(Return(kDefaultBuffer)); + ON_CALL(GetTBM(), BoSize(_)) + .WillByDefault(Return(kExpectedDefaultByteSize)); + ON_CALL(GetTBM(), BoGetHandle(_, _)) + .WillByDefault(Return(kDefaultMappedHandle)); + ON_CALL(GetTBM(), BoExport(_)).WillByDefault(Return(kDefaultBufferKey)); + + EXPECT_CALL(GetTBM(), BoRef(_)).Times(1); + EXPECT_CALL(GetTBM(), BoUnRef(_)).Times(1); + + bufferobj_ = std::unique_ptr( + new TizenBufferObject(kDefaultBuffer)); + } + virtual void TearDown() override {} + + protected: + MockTBM::Fake& GetTBM() { return fake_tbm_; } + TizenBufferObject& GetBufferObject() { return *bufferobj_; } + + private: + MockTBM::Fake fake_tbm_; + std::unique_ptr bufferobj_; +}; + +TEST_F(BufferObjectWithTypeTest, IsValid) { + EXPECT_TRUE(GetBufferObject().IsValid()); +} + +TEST_F(BufferObjectWithTypeTest, GetBufferHandle) { + EXPECT_CALL(GetTBM(), BoGetHandle(_, _)).Times(1); + EXPECT_EQ(kDefaultBufferHandle, GetBufferObject().GetBufferHandle()); +} + +TEST_F(BufferObjectWithTypeTest, Export) { + EXPECT_CALL(GetTBM(), BoExport(_)).Times(1); + EXPECT_EQ(kDefaultBufferKey, GetBufferObject().Export()); +} + +TEST_F(BufferObjectWithTypeTest, GetSize) { + EXPECT_CALL(GetTBM(), BoSize(_)).Times(1); + EXPECT_EQ(kExpectedDefaultByteSize, GetBufferObject().GetSize()); +} +} // namespace plusplayer_ut \ No newline at end of file diff --git a/ut/src/mixer/ut_videoplane.cpp b/ut/src/mixer/ut_videoplane.cpp new file mode 100755 index 0000000..ea22fab --- /dev/null +++ b/ut/src/mixer/ut_videoplane.cpp @@ -0,0 +1,212 @@ +#include +#include + +#include + +#include "mixer/constant.h" +#include "mixer/matcher.h" +#include "mixer/mock/mock_bufferobject.h" +#include "mixer/mock/movable.h" +#include "mixer/mock/moveobj_wrapper.h" +#include "mixer/videoplane.h" + +using ::testing::AllOf; +using ::testing::AnyNumber; +using ::testing::Field; +using ::testing::Return; + +namespace plusplayer_ut { + +template +class DefaultVideoPlaneOptionWithBufferObject { + public: + using BufferObjectPtr = typename PlaneType::BufferObjectPtr; + virtual ~DefaultVideoPlaneOptionWithBufferObject() = default; + + protected: + virtual void Init_() { + ON_CALL(GetBufferObject(), GetBufferHandle()) + .WillByDefault(Return(kDefaultBufferHandle)); + ON_CALL(GetBufferObject(), Export()) + .WillByDefault(Return(kDefaultBufferKey)); + ON_CALL(GetBufferObject(), GetSize()) + .WillByDefault(Return(kDefaultWidth * kDefaultHeight)); + + EXPECT_CALL(GetBufferObject(), GetBufferHandle()).Times(AnyNumber()); + EXPECT_CALL(GetBufferObject(), GetSize()).Times(AnyNumber()); + + ASSERT_TRUE(y_plane_->IsValid()); + } + + protected: + PlaneType& GetPlane() { return *y_plane_.get(); } + MockBufferObject& GetBufferObject() { return bufferobj_.Get(); } + MockBufferObject* MoveBufferObject() { return bufferobj_.Move(); } + + private: + MoveObjectWrapper bufferobj_{new MockBufferObject()}; + std::unique_ptr y_plane_{new PlaneType( + BufferObjectPtr(MoveBufferObject()), kDefaultWidth, kDefaultHeight)}; +}; + +class YComponentVideoPlaneTest + : public ::testing::Test, + public DefaultVideoPlaneOptionWithBufferObject { + public: + virtual void SetUp() override { Init_(); } + virtual void TearDown() override {} +}; + +TEST_F(YComponentVideoPlaneTest, GetVideoPlaneManipulableInfo) { + EXPECT_THAT( + GetPlane().GetVideoPlaneManipulableInfo(), + AllOf(Field(&VideoPlaneManipulableInfo::component, + PlaneComponent::kYComponent), + Field(&VideoPlaneManipulableInfo::handle, kDefaultBufferHandle), + Field(&VideoPlaneManipulableInfo::linesize, kDefaultWidth), + Field(&VideoPlaneManipulableInfo::rect, + IsSameGeometry( + GetGeometry(0, 0, kDefaultWidth, kDefaultHeight))))); +} + +TEST_F(YComponentVideoPlaneTest, GetVideoPlaneManipulableInfo_AfterCrop) { + GetPlane().SetCropArea(GetCropArea(0.1, 0.1, 0.9, 0.9)); + + EXPECT_THAT( + GetPlane().GetVideoPlaneManipulableInfo(), + AllOf(Field(&VideoPlaneManipulableInfo::component, + PlaneComponent::kYComponent), + Field(&VideoPlaneManipulableInfo::handle, kDefaultBufferHandle), + Field(&VideoPlaneManipulableInfo::linesize, kDefaultWidth), + Field(&VideoPlaneManipulableInfo::rect, + IsSameGeometry(GetGeometry( + kDefaultWidth * 0.1, kDefaultHeight * 0.1, + kDefaultWidth * 0.9, kDefaultHeight * 0.9))))); +} + +class UVComponentVideoPlaneTest + : public ::testing::Test, + public DefaultVideoPlaneOptionWithBufferObject { + public: + virtual void SetUp() override { + Init_(); + ON_CALL(GetBufferObject(), GetSize()) + .WillByDefault(Return(kDefaultWidth * kDefaultHeight / 2)); + } + virtual void TearDown() override {} +}; + +TEST_F(UVComponentVideoPlaneTest, GetVideoPlaneManipulableInfo) { + EXPECT_THAT( + GetPlane().GetVideoPlaneManipulableInfo(), + AllOf(Field(&VideoPlaneManipulableInfo::component, + PlaneComponent::kUVComponent), + Field(&VideoPlaneManipulableInfo::handle, kDefaultBufferHandle), + Field(&VideoPlaneManipulableInfo::linesize, kDefaultWidth), + Field(&VideoPlaneManipulableInfo::rect, + IsSameGeometry(GetGeometry(0, 0, kDefaultWidth / 2, + kDefaultHeight / 2))))); +} + +TEST_F(UVComponentVideoPlaneTest, GetVideoPlaneManipulableInfo_AfterCrop) { + GetPlane().SetCropArea(GetCropArea(0.1, 0.1, 0.9, 0.9)); + + EXPECT_THAT( + GetPlane().GetVideoPlaneManipulableInfo(), + AllOf(Field(&VideoPlaneManipulableInfo::component, + PlaneComponent::kUVComponent), + Field(&VideoPlaneManipulableInfo::handle, kDefaultBufferHandle), + Field(&VideoPlaneManipulableInfo::linesize, kDefaultWidth), + Field(&VideoPlaneManipulableInfo::rect, + IsSameGeometry(GetGeometry( + kDefaultWidth / 2 * 0.1, kDefaultHeight / 2 * 0.1, + kDefaultWidth / 2 * 0.9, kDefaultHeight / 2 * 0.9))))); +} + +class YUVComponentVideoPlaneTest : public ::testing::Test { + public: + virtual void SetUp() override { + ON_CALL(GetBufferObject(), GetBufferHandle()) + .WillByDefault(Return(kDefaultBufferHandle)); + ON_CALL(GetBufferObject(), Export()) + .WillByDefault(Return(kDefaultBufferKey)); + ON_CALL(GetBufferObject(), GetSize()) + .WillByDefault(Return(kDefaultWidth * kDefaultHeight * 1.5)); + + EXPECT_CALL(GetBufferObject(), GetBufferHandle()).Times(AnyNumber()); + EXPECT_CALL(GetBufferObject(), GetSize()).Times(AnyNumber()); + + ASSERT_TRUE(GetYPlane().IsValid()); + ASSERT_TRUE(GetUVPlane().IsValid()); + } + virtual void TearDown() override {} + + protected: + YComponentVideoPlaneWithSharedMemory& GetYPlane() { return *y_plane_.get(); } + UVComponentVideoPlaneWithSharedMemory& GetUVPlane() { + return *uv_plane_.get(); + } + MockBufferObject& GetBufferObject() { + return dynamic_cast(*bufferobj_.get()); + } + + private: + std::shared_ptr bufferobj_{new MockBufferObject()}; + std::unique_ptr y_plane_{ + new YComponentVideoPlaneWithSharedMemory(bufferobj_, kDefaultWidth, + kDefaultHeight)}; + std::unique_ptr uv_plane_{ + new UVComponentVideoPlaneWithSharedMemory(bufferobj_, kDefaultWidth, + kDefaultHeight)}; +}; + +TEST_F(YUVComponentVideoPlaneTest, GetVideoPlaneManipulableInfo) { + EXPECT_THAT( + GetYPlane().GetVideoPlaneManipulableInfo(), + AllOf(Field(&VideoPlaneManipulableInfo::component, + PlaneComponent::kYComponent), + Field(&VideoPlaneManipulableInfo::handle, kDefaultBufferHandle), + Field(&VideoPlaneManipulableInfo::linesize, kDefaultWidth), + Field(&VideoPlaneManipulableInfo::rect, + IsSameGeometry( + GetGeometry(0, 0, kDefaultWidth, kDefaultHeight))))); + EXPECT_THAT( + GetUVPlane().GetVideoPlaneManipulableInfo(), + AllOf( + Field(&VideoPlaneManipulableInfo::component, + PlaneComponent::kUVComponent), + Field(&VideoPlaneManipulableInfo::handle, kDefaultBufferHandle), + Field(&VideoPlaneManipulableInfo::linesize, kDefaultWidth), + Field(&VideoPlaneManipulableInfo::rect, + IsSameGeometry(GetGeometry(0, kDefaultHeight, kDefaultWidth / 2, + kDefaultHeight / 2))))); +} + +TEST_F(YUVComponentVideoPlaneTest, GetVideoPlaneManipulableInfo_AfterCrop) { + GetYPlane().SetCropArea(GetCropArea(0.1, 0.1, 0.9, 0.9)); + GetUVPlane().SetCropArea(GetCropArea(0.1, 0.1, 0.9, 0.9)); + + EXPECT_THAT( + GetYPlane().GetVideoPlaneManipulableInfo(), + AllOf(Field(&VideoPlaneManipulableInfo::component, + PlaneComponent::kYComponent), + Field(&VideoPlaneManipulableInfo::handle, kDefaultBufferHandle), + Field(&VideoPlaneManipulableInfo::linesize, kDefaultWidth), + Field(&VideoPlaneManipulableInfo::rect, + IsSameGeometry(GetGeometry( + kDefaultWidth * 0.1, kDefaultHeight * 0.1, + kDefaultWidth * 0.9, kDefaultHeight * 0.9))))); + EXPECT_THAT( + GetUVPlane().GetVideoPlaneManipulableInfo(), + AllOf(Field(&VideoPlaneManipulableInfo::component, + PlaneComponent::kUVComponent), + Field(&VideoPlaneManipulableInfo::handle, kDefaultBufferHandle), + Field(&VideoPlaneManipulableInfo::linesize, kDefaultWidth), + Field(&VideoPlaneManipulableInfo::rect, + IsSameGeometry(GetGeometry( + kDefaultWidth / 2 * 0.1, + kDefaultHeight / 2 * 0.1 + kDefaultHeight, + kDefaultWidth / 2 * 0.9, kDefaultHeight / 2 * 0.9))))); +} + +} // namespace plusplayer_ut \ No newline at end of file diff --git a/ut/src/plusplayer/imagesimilarity.cpp b/ut/src/plusplayer/imagesimilarity.cpp new file mode 100755 index 0000000..7cf4ff8 --- /dev/null +++ b/ut/src/plusplayer/imagesimilarity.cpp @@ -0,0 +1,123 @@ + +#include +#include // for standard I/O +#include // for strings +#include // for controlling float print precision +#include // string to number conversion + +#include // Basic OpenCV structures (cv::Mat, Scalar) +#include // Gaussian Blur +#include // OpenCV window I/O + +#include "ut/include/plusplayer/imagesimilarity.h" + +using namespace std; +using namespace cv; + + +double getPSNR(const Mat& I1, const Mat& I2) +{ + Mat s1; + absdiff(I1, I2, s1); // |I1 - I2| + s1.convertTo(s1, CV_32F); // cannot make a square on 8 bits + s1 = s1.mul(s1); // |I1 - I2|^2 + + Scalar s = sum(s1); // sum elements per channel + + double sse = s.val[0] + s.val[1] + s.val[2]; // sum channels + + if( sse <= 1e-10) { // for small values return zero + return 0; + } + else { + double mse = sse / (double)(I1.channels() * I1.total()); + double psnr = 10.0 * log10((255 * 255) / mse); + return psnr; + } +} + +Scalar getMSSIM( const Mat& i1, const Mat& i2) +{ + const double C1 = 6.5025, C2 = 58.5225; + + int d = CV_32F; + + Mat I1, I2; + i1.convertTo(I1, d); // cannot calculate on one byte large values + i2.convertTo(I2, d); + + Mat I2_2 = I2.mul(I2); // I2^2 + Mat I1_2 = I1.mul(I1); // I1^2 + Mat I1_I2 = I1.mul(I2); // I1 * I2 + + + Mat mu1, mu2; // PRELIMINARY COMPUTING + GaussianBlur(I1, mu1, Size(11, 11), 1.5); + GaussianBlur(I2, mu2, Size(11, 11), 1.5); + + Mat mu1_2 = mu1.mul(mu1); + Mat mu2_2 = mu2.mul(mu2); + Mat mu1_mu2 = mu1.mul(mu2); + + Mat sigma1_2, sigma2_2, sigma12; + + GaussianBlur(I1_2, sigma1_2, Size(11, 11), 1.5); + sigma1_2 -= mu1_2; + + GaussianBlur(I2_2, sigma2_2, Size(11, 11), 1.5); + sigma2_2 -= mu2_2; + + GaussianBlur(I1_I2, sigma12, Size(11, 11), 1.5); + sigma12 -= mu1_mu2; + + ///////////////////////////////// FORMULA //////////////////////////////// + Mat t1, t2, t3; + + t1 = 2 * mu1_mu2 + C1; + t2 = 2 * sigma12 + C2; + t3 = t1.mul(t2); // t3 = ((2*mu1_mu2 + C1).*(2*sigma12 + C2)) + + t1 = mu1_2 + mu2_2 + C1; + t2 = sigma1_2 + sigma2_2 + C2; + t1 = t1.mul(t2); // t1 =((mu1_2 + mu2_2 + C1).*(sigma1_2 + sigma2_2 + C2)) + + Mat ssim_map; + divide(t3, t1, ssim_map); // ssim_map = t3./t1; + + Scalar mssim = mean(ssim_map); // mssim = average of ssim map + return mssim; +} + +double ImageSimilarity::CompareImagesWithPSNR(char *reference_image, char *comparison_image) +{ + const string sourceReference(reference_image); + const string sourceCompareWith(comparison_image); + + Mat frameReference = imread(sourceReference); + Mat frameUnderTest = imread(sourceCompareWith); + + if (frameReference.cols != frameUnderTest.cols || frameReference.rows != frameUnderTest.rows) { + return -1; + } + + double PSNR_Value = getPSNR(frameReference,frameUnderTest); + return PSNR_Value; +} + +double ImageSimilarity::CompareImagesWithMSSIM(char *reference_image, char *comparison_image) +{ + const string sourceReference(reference_image); + const string sourceCompareWith(comparison_image); + + Mat frameReference = frameReference = imread(sourceReference); + Mat frameUnderTest = frameUnderTest = imread(sourceCompareWith); + + if (frameReference.cols != frameUnderTest.cols || frameReference.rows != frameUnderTest.rows) { + return -1; + } + + Scalar mssimV = getMSSIM(frameReference, frameUnderTest); + + double normalized_mean_SSIM = (mssimV.val[2] + mssimV.val[1] + mssimV.val[0])*100/3; + return normalized_mean_SSIM; +} \ No newline at end of file diff --git a/ut/src/plusplayer/utility.cpp b/ut/src/plusplayer/utility.cpp new file mode 100755 index 0000000..ee9fd01 --- /dev/null +++ b/ut/src/plusplayer/utility.cpp @@ -0,0 +1,491 @@ +#include +#include +#include +#include +#include +#include +#include + +//#include "ut/include/plusplayer/tclist.h" +#include "ut/include/plusplayer/utility.h" +#include "ut/include/esplusplayer/eseventlistener.hpp" +#include "ut/include/esplusplayer/esreader.hpp" + +#include +#include +#include +#include +#include "ivideocapture.hpp" +#include "capi-video-capture.h" +#include "iaudio-control.hpp" +#include "diagnosis-audio-control.hpp" + + +using namespace plusplayer; +//using namespace tc; +using UserData = void*; + +namespace utils { +using util_ptr = std::unique_ptr; +static util_ptr ptr = nullptr; + +Utility& Utility::Instance() { + if (ptr.get() == nullptr) ptr.reset(new Utility()); + return *(ptr.get()); +} + +void Utility::Kill() { ptr.reset(); } + +void Utility::ThreadSleep(long ms) { + std::this_thread::sleep_for(std::chrono::milliseconds(ms)); +} + +Utility::Utility() { + appwindow_.reset(new plusplayer_ut::AppWindow(0, 0, 1920, 1080)); + audioControl = IAudioControl::getInstance(); + audioDiagnoser = DiagnosisAudioControl::getInstance(); +} + +Utility::~Utility() {} + +const char* Utility::GetCurrentTestName(void) { + return ::testing::UnitTest::GetInstance()->current_test_info()->name(); +} + +#ifndef IS_AUDIO_PRODUCT + +#if 0 +plusplayer::PlusPlayer::Ptr Utility::GetOpenedMixPlusPlayer(std::string& uri, + Mixer* mixer, + Geometry& roi) { + auto player = plusplayer::PlusPlayer::Create(); + EXPECT_TRUE(player->Open(uri.c_str())); + EXPECT_TRUE(player->SetDisplay(DisplayType::kMixer, mixer)); + EXPECT_TRUE(player->SetDisplayRoi(roi)); + return player; +} + +plusplayer::PlusPlayer::Ptr Utility::GetPreparedMixPlusPlayer(std::string& uri, + Mixer* mixer, + Geometry& roi) { + auto player = this->GetOpenedMixPlusPlayer(uri, mixer, roi); + EXPECT_TRUE(player->Prepare()); + return player; +} + +plusplayer::PlusPlayer::Ptr Utility::GetStartedMixPlusPlayer(std::string& uri, + Mixer* mixer, + Geometry& roi) { + auto player = this->GetPreparedMixPlusPlayer(uri, mixer, roi); + EXPECT_TRUE(player->Start()); + return player; +} +#endif + +esplusplayer_handle Utility::GetOpenedMixESPP(mixer_handle mixer, + Geometry& roi) { + esplusplayer_handle player = esplusplayer_create(); + EXPECT_EQ(esplusplayer_open(player), ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ( + esplusplayer_set_display(player, ESPLUSPLAYER_DISPLAY_TYPE_MIXER, mixer), + ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_set_display_roi(player, roi.x, roi.y, roi.w, roi.h), + ESPLUSPLAYER_ERROR_TYPE_NONE); + return player; +} + +esplusplayer_handle Utility::GetPreparedMixESPP(std::string& uri, + mixer_handle mixer, + Geometry& roi) { + esplusplayer_handle player = this->GetOpenedMixESPP(mixer, roi); + + if (!PrepareESPP(player, uri)) return nullptr; + return player; +} + +esplusplayer_handle Utility::GetStartedMixESPP(std::string& uri, + mixer_handle mixer, + Geometry& roi) { + esplusplayer_handle player = this->GetPreparedMixESPP(uri, mixer, roi); + + if (esplusplayer_start(player) == ESPLUSPLAYER_ERROR_TYPE_NONE) + return player; + else + printf("esplusplayer_start failed\n"); + return nullptr; +} +#endif + +esplusplayer_handle Utility::GetOpenedESPP(Geometry& roi) { + esplusplayer_handle player = esplusplayer_create(); + EXPECT_EQ(esplusplayer_open(player), ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_set_display(player, ESPLUSPLAYER_DISPLAY_TYPE_OVERLAY, + this->GetWindow()), + ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ( + esplusplayer_set_display_mode(player, ESPLUSPLAYER_DISPLAY_MODE_DST_ROI), + ESPLUSPLAYER_ERROR_TYPE_NONE); + EXPECT_EQ(esplusplayer_set_display_roi(player, roi.x, roi.y, roi.w, roi.h), + ESPLUSPLAYER_ERROR_TYPE_NONE); + return player; +} + +esplusplayer_handle Utility::GetPreparedESPP(std::string& uri, Geometry& roi) { + esplusplayer_handle player = this->GetOpenedESPP(roi); + + if (!PrepareESPP(player, uri)) return nullptr; + return player; +} + +esplusplayer_handle Utility::GetStartedESPP(std::string& uri, Geometry& roi) { + esplusplayer_handle player = this->GetPreparedESPP(uri, roi); + + if (esplusplayer_start(player) == ESPLUSPLAYER_ERROR_TYPE_NONE) + return player; + else + printf("esplusplayer_start failed\n"); + return nullptr; +} + +bool Utility::PrepareESPP(esplusplayer_handle player, std::string& uri, + EsType type) { + if (!player) return false; + + EsStreamReader* video_reader = nullptr; + EsStreamReader* audio_reader = nullptr; + + if (static_cast(type) & EsType::kVideo) { + video_reader = + new EsStreamReader(uri + "video_00/", ESPLUSPLAYER_STREAM_TYPE_VIDEO); + EXPECT_TRUE(video_reader->SetStreamInfo(player)); + } + if (static_cast(type) & EsType::kAudio) { + audio_reader = + new EsStreamReader(uri + "audio_00/", ESPLUSPLAYER_STREAM_TYPE_AUDIO); + EXPECT_TRUE(audio_reader->SetStreamInfo(player)); + } + EsPlayerEventCallback* callback = + new EsPlayerEventCallback(player, video_reader, audio_reader); + callback->SetCallback(); + + bool ret = false; + if (esplusplayer_prepare_async(player) != ESPLUSPLAYER_ERROR_TYPE_NONE) + printf("esplusplayer_prepare_async failed\n"); + else + ret = callback->WaitForPrepareDone(); + + delete callback; + if (video_reader != nullptr) delete video_reader; + if (audio_reader != nullptr) delete audio_reader; + printf("PrepareESPP done\n"); + return ret; +} + +void Utility::FeedingEsPacket(esplusplayer_handle player, + const esplusplayer_stream_type type, + const std::string& uri) { + EsStreamReader* reader; + auto feeding_task_fn = [this, &player, &reader]() { + esplusplayer_es_packet pkt; + while (true) { + memset(&pkt, 0, sizeof(esplusplayer_es_packet)); + if (!reader->ReadNextPacket(pkt)) break; + esplusplayer_submit_packet(player, &pkt); + delete []pkt.buffer; + } + }; + if (type == ESPLUSPLAYER_STREAM_TYPE_VIDEO) { + reader = + new EsStreamReader(uri + "video_00/", ESPLUSPLAYER_STREAM_TYPE_VIDEO); + } else { + reader = + new EsStreamReader(uri + "audio_00/", ESPLUSPLAYER_STREAM_TYPE_AUDIO); + } + auto feeding_task = std::thread(feeding_task_fn); + if (feeding_task.joinable()) feeding_task.join(); +} + +void Utility::DestroyESPP(esplusplayer_handle player) { + if (!player) return; + esplusplayer_destroy(player); + player = nullptr; +} + +evas_h Utility::GetWindow() const { return appwindow_->GetWindow().obj; } + +unsigned int Gcd(unsigned int u, unsigned int v) { + int shift = 0; + + /* GCD(0,v) == v; GCD(u,0) == u, GCD(0,0) == 0 */ + + if (u == 0) { + return v; + } + if (v == 0) { + return u; + } + + /* Let shift := lg K, where K is the greatest power of 2 + dividing both u and v. */ + for (shift = 0; ((u | v) & 1) == 0; ++shift) { + u >>= 1; + v >>= 1; + } + + while ((u & 1) == 0) { + u >>= 1; + } + + /* From here on, u is always odd. */ + do { + /* remove all factors of 2 in v -- they are not common */ + /* note: v is not zero, so while will terminate */ + while ((v & 1) == 0) { + v >>= 1; + /* Loop X */ + } + /* Now u and v are both odd. Swap if necessary so u <= v, + then set v = v - u (which is even). For bignums, the + swapping is just pointer movement, and the subtraction + can be done in-place. */ + if (u > v) { + unsigned int t = v; + v = u; + u = t; + } // Swap u and v. + v = v - u; // Here v >= u. + } while (v != 0); + + /* restore common factors of 2 */ + return u << shift; +} + +void ResizeCopy(unsigned int m_width, unsigned int m_height, + unsigned int m_rwidth, unsigned int m_rheight, + char* c_ptr, char* y_ptr, unsigned char* dest, + unsigned int color_format) { + unsigned int omit = 1; + unsigned int x = 0; + unsigned int y = 0; + unsigned char resize = 1; + + if (m_width == m_rwidth) { + resize = 0; + } else { + omit = Gcd(m_width, m_rwidth); + omit = m_width / omit; + } + printf("m_width %u, m_rwidth %u, omit %u\n", m_width, m_rwidth, omit); + + // long int inc = 3 * m_rwidth * m_rheight; + long int inc = 0; + + for (y = 0; y < m_height; y++) { + if ((y + 1) % omit || !resize) { + for (x = 0; x < m_width; x += sizeof(unsigned int)) { + + unsigned int Yplain = + *((unsigned int*)((void*)(y_ptr + y * m_width + x))); + unsigned int Cplain = + *((unsigned int*)((void*)(c_ptr + y * m_width + x))); + + switch (color_format) { + case SECVIDEO_CAPTURE_COLOR_YUV444: // 444 + break; + case SECVIDEO_CAPTURE_COLOR_YUV422: // 422 + Cplain = *((unsigned int*)((void*)(c_ptr + y * m_width + x))); + break; + case SECVIDEO_CAPTURE_COLOR_YUV420: // 420 + Cplain = *((unsigned int*)((void*)(c_ptr + (y / 2) * m_width + x))); + break; + default: + break; + } + if ((x + 4) % omit || !resize) { + dest[inc++] = (Yplain) & 0xFF; + dest[inc++] = Cplain & 0xFF; + dest[inc++] = (Cplain >> 8) & 0xFF; + } + if ((x + 3) % omit || !resize) { + dest[inc++] = (Yplain >> 8) & 0xFF; + dest[inc++] = (Cplain) & 0xFF; + dest[inc++] = (Cplain >> 8) & 0xFF; + } + + if ((x + 2) % omit || !resize) { + dest[inc++] = (Yplain >> 16) & 0xFF; + dest[inc++] = (Cplain >> 16) & 0xFF; + dest[inc++] = (Cplain >> 24) & 0xFF; + } + + if ((x + 1) % omit || !resize) { + dest[inc++] = (Yplain >> 24) & 0xFF; + dest[inc++] = (Cplain >> 16) & 0xFF; + dest[inc++] = (Cplain >> 24) & 0xFF; + } + } + } + } +} + +int Utility::CaptureYUV(int capture_width, int capture_height, + char* ybuff, char* cbuff, int ysize, + int csize, std::string result_file_path) { + // screen capture + IVideoCapture* VCObjptr = IVideoCapture::getInstance(); + + IVideoCapture::InputParams input_params; + + input_params.capture_w = capture_width; + input_params.capture_h = capture_height; + + IVideoCapture::OutputParams output_params; + output_params.p_y_addr = ybuff; + output_params.p_c_addr = cbuff; + output_params.size_y = ysize; + output_params.size_c = csize; + + sleep(1); + + VCObjptr->getVideoPostYUV(input_params, output_params); + + // write + FILE* fexpect = NULL; + + while (output_params.p_y_addr) { + fexpect = fopen(result_file_path.c_str(), "wb"); + if (fexpect == NULL) { + LOGE("can't open the file"); + break; + } + fwrite(output_params.p_y_addr, 1, ysize, fexpect); + fclose(fexpect); + fexpect = NULL; + output_params.p_y_addr = NULL; + } + + return 0; +} +///TODO:: Modify the API to return the pointer instead of writing it as an image file. +int Utility::CaptureJPG(const int capture_width, const int capture_height, + char* ybuff, char* cbuff, const int ysize, + const int csize, std::string img_file_path) { + IVideoCapture* VCObjptr = IVideoCapture::getInstance(); + int output_color_format = 0; + + IVideoCapture::InputParams input_params; + input_params.capture_w = capture_width; + input_params.capture_h = capture_height; + + IVideoCapture::OutputParams output_params; + output_params.p_y_addr = ybuff; + output_params.p_c_addr = cbuff; + output_params.size_y = ysize; + output_params.size_c = csize; + + sleep(1); + + VCObjptr->getVideoPostYUV(input_params, output_params); + output_color_format = static_cast(output_params.color_format); + + sleep(1); + + unsigned char* rawImage; + rawImage = (unsigned char*)malloc(sizeof(unsigned char) * 3 * capture_width * + capture_height); + + ResizeCopy(capture_width, capture_height, capture_width, capture_height, + cbuff, ybuff, rawImage, output_color_format); + + struct jpeg_compress_struct cinfo; + struct jpeg_error_mgr jerr; + FILE* outfile = NULL; /* target file */ + + JSAMPROW row_pointer[1]; /* pointer to JSAMPLE row[s] */ + int row_stride; /* physical row width in image buffer */ + + cinfo.err = jpeg_std_error(&jerr); + jpeg_create_compress(&cinfo); + + outfile = fopen(img_file_path.c_str(), "wb"); + if (outfile == NULL) { + LOGE("can't open the file"); + if (rawImage != NULL) free(rawImage); + return -1; + } + jpeg_stdio_dest(&cinfo, outfile); + + cinfo.image_width = capture_width; + cinfo.image_height = capture_height; + cinfo.input_components = 3; /* # of color components per pixel */ + cinfo.in_color_space = JCS_YCbCr; + jpeg_set_defaults(&cinfo); + jpeg_set_quality(&cinfo, 70, TRUE); + jpeg_start_compress(&cinfo, TRUE); + + row_stride = capture_width * 3; /* JSAMPLEs per row in image_buffer */ + + while (cinfo.next_scanline < cinfo.image_height) { + row_pointer[0] = &((rawImage)[cinfo.next_scanline * row_stride]); + (void)jpeg_write_scanlines(&cinfo, row_pointer, 1); + } + + jpeg_finish_compress(&cinfo); + jpeg_destroy_compress(&cinfo); + if (rawImage != NULL) free(rawImage); + + if (outfile == NULL) { + LOGE("Fail to open file"); + return -1; + } else { + fclose(outfile); + } + return 0; +} + +int Utility::CheckYUV(int x, int y) { + + /* screen capture */ + IVideoCapture* VCObjptr = IVideoCapture::getInstance(); + + int CAPTURE_WIDTH = 640; + int CAPTURE_HEIGHT = 360; + int CAPTURE_BUF_SIZE = CAPTURE_WIDTH * CAPTURE_HEIGHT; + char ybuff[CAPTURE_BUF_SIZE] = {0}; + char cbuff[CAPTURE_BUF_SIZE] = {0}; + + IVideoCapture::InputParams input_params; + input_params.capture_w = 640; + input_params.capture_h = 360; + + IVideoCapture::OutputParams output_params; + output_params.p_y_addr = ybuff; + output_params.p_c_addr = cbuff; + output_params.size_y = CAPTURE_BUF_SIZE; + output_params.size_c = CAPTURE_BUF_SIZE; + + sleep(1); + + VCObjptr->getVideoPostYUV(input_params, output_params); + + /* check YUV value. As the capture size (640 x 360) is mapped with the full screen (1920 x 1080), the position should be resized. */ + int new_X = x / 3; + int new_Y = y / 3; + int position = CAPTURE_WIDTH * (new_Y - 1) + new_X; + + LOGE("Y value : %d", (int)ybuff[position]); + return (int)ybuff[position]; +} + +bool Utility::IsAudioDisconnected() { + TZTVAudioSource src = AUDIO_SOURCE_MAX; + EXPECT_EQ(audioControl->getMainOutSourceSelect(&src), 0); + return (src != AUDIO_MULTIMEDIA_DEC0); +} + +bool Utility::IsAudioMute() { + long audioMute = 0; + EXPECT_EQ(audioDiagnoser->Diagnosis_GetBoolean(0, "main out mute", &audioMute), 0); + return (audioMute != 0); +} +} // namespace utils diff --git a/ut/src/ut_espacket.cpp b/ut/src/ut_espacket.cpp new file mode 100755 index 0000000..b5e5f33 --- /dev/null +++ b/ut/src/ut_espacket.cpp @@ -0,0 +1,104 @@ +// +// @ Copyright [2018] +// + +#include "gtest/gtest.h" +#include "gmock/gmock.h" + +#include "plusplayer/espacket.h" + +namespace pp = plusplayer; + +namespace { +void MakeDummyMatroskaColor(pp::MatroskaColor& color_info) { + color_info.matrix_coefficients = 1; + color_info.bits_per_channel = 1; + color_info.chroma_subsampling_horizontal = 1; + color_info.chroma_subsampling_vertical = 1; + color_info.cb_subsampling_horizontal = 1; + color_info.cb_subsampling_vertical = 1; + color_info.chroma_siting_horizontal = 1; + color_info.chroma_siting_vertical = 1; + color_info.range = 1; + color_info.transfer_characteristics = 1; + color_info.primaries = 1; + color_info.max_cll = 1; + color_info.max_fall = 1; + color_info.metadata.primary_r_chromaticity_x = 0.5; + color_info.metadata.primary_r_chromaticity_y = 0.5; + color_info.metadata.primary_g_chromaticity_x = 0.5; + color_info.metadata.primary_g_chromaticity_y = 0.5; + color_info.metadata.primary_b_chromaticity_x = 0.5; + color_info.metadata.primary_b_chromaticity_y = 0.5; + color_info.metadata.white_point_chromaticity_x = 0.5; + color_info.metadata.white_point_chromaticity_y = 0.5; + color_info.metadata.luminance_max = 0.5; + color_info.metadata.luminance_min = 0.5; +} +bool IsSameMatroskaColor(const pp::MatroskaColor& color_info1, + const pp::MatroskaColor& color_info2) { + return color_info1.matrix_coefficients == color_info2.matrix_coefficients && + color_info1.bits_per_channel == color_info2.bits_per_channel && + color_info1.chroma_subsampling_horizontal == + color_info2.chroma_subsampling_horizontal && + color_info1.chroma_subsampling_vertical == + color_info2.chroma_subsampling_vertical && + color_info1.cb_subsampling_horizontal == + color_info2.cb_subsampling_horizontal && + color_info1.cb_subsampling_vertical == + color_info2.cb_subsampling_vertical && + color_info1.chroma_siting_horizontal == + color_info2.chroma_siting_horizontal && + color_info1.chroma_siting_vertical == + color_info2.chroma_siting_vertical && + color_info1.range == color_info2.range && + color_info1.transfer_characteristics == + color_info2.transfer_characteristics && + color_info1.primaries == color_info2.primaries && + color_info1.max_cll == color_info2.max_cll && + color_info1.max_fall == color_info2.max_fall && + color_info1.metadata.primary_r_chromaticity_x == + color_info2.metadata.primary_r_chromaticity_x && + color_info1.metadata.primary_r_chromaticity_y == + color_info2.metadata.primary_r_chromaticity_y && + color_info1.metadata.primary_g_chromaticity_x == + color_info2.metadata.primary_g_chromaticity_x && + color_info1.metadata.primary_g_chromaticity_y == + color_info2.metadata.primary_g_chromaticity_y && + color_info1.metadata.primary_b_chromaticity_x == + color_info2.metadata.primary_b_chromaticity_x && + color_info1.metadata.primary_b_chromaticity_y == + color_info2.metadata.primary_b_chromaticity_y && + color_info1.metadata.white_point_chromaticity_x == + color_info2.metadata.white_point_chromaticity_x && + color_info1.metadata.white_point_chromaticity_y == + color_info2.metadata.white_point_chromaticity_y && + color_info1.metadata.luminance_max == + color_info2.metadata.luminance_max && + color_info1.metadata.luminance_min == + color_info2.metadata.luminance_min; +} +} // namespace + +TEST(EsPacket, DISABLED_MatroskaColor_EmptyData) { + auto espacket = pp::EsPacket::Create(); + ASSERT_FALSE(espacket->HasMatroskaColorInfo()); +} + +TEST(EsPacket, DISABLED_MatroskaColor_HasData) { + auto espacket = pp::EsPacket::Create(); + pp::MatroskaColor dummy_color_info; + ::MakeDummyMatroskaColor(dummy_color_info); + espacket->SetMatroskaColorInfo(dummy_color_info); + ASSERT_TRUE(espacket->HasMatroskaColorInfo()); +} + +TEST(EsPacket, DISABLED_MatroskaColor_GetData) { + auto espacket = pp::EsPacket::Create(); + pp::MatroskaColor dummy_color_info; + ::MakeDummyMatroskaColor(dummy_color_info); + espacket->SetMatroskaColorInfo(dummy_color_info); + ASSERT_TRUE(espacket->HasMatroskaColorInfo()); + auto saved_data = espacket->GetMatroskaColorInfo(); + ASSERT_TRUE(::IsSameMatroskaColor(saved_data, dummy_color_info)); +} diff --git a/ut/src/ut_esplayer.cpp b/ut/src/ut_esplayer.cpp new file mode 100755 index 0000000..f68cba0 --- /dev/null +++ b/ut/src/ut_esplayer.cpp @@ -0,0 +1,510 @@ +// +// @ Copyright [2018] +// + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "plusplayer/esplusplayer.h" +#include "ut/include/streamreader.hpp" + +namespace es { +namespace utils { +pp::EsPacketPtr MakeEsPacketFromPacket(const es::PacketPtr &pkt, + pp::StreamType type) { + pp::EsPacketPtr buffer = nullptr; + if (pkt == nullptr) { + buffer = es::Packet::MakeEosPacket(type); + } else { + buffer = pkt->MakeEsPacket(type); + } + return std::move(buffer); +} +void MakeDummyMatroskaColor(pp::MatroskaColor &color_info) { + color_info.matrix_coefficients = 1; + color_info.bits_per_channel = 2; + color_info.chroma_subsampling_horizontal = 3; + color_info.chroma_subsampling_vertical = 4; + color_info.cb_subsampling_horizontal = 3; + color_info.cb_subsampling_vertical = 4; + color_info.chroma_siting_horizontal = 3; + color_info.chroma_siting_vertical = 4; + color_info.range = 1; + color_info.transfer_characteristics = 1; + color_info.primaries = 1; + color_info.max_cll = 1; + color_info.max_fall = 1; + color_info.metadata.primary_r_chromaticity_x = 0.1; + color_info.metadata.primary_r_chromaticity_y = 0.2; + color_info.metadata.primary_g_chromaticity_x = 0.3; + color_info.metadata.primary_g_chromaticity_y = 0.4; + color_info.metadata.primary_b_chromaticity_x = 0.5; + color_info.metadata.primary_b_chromaticity_y = 0.6; + color_info.metadata.white_point_chromaticity_x = 0.7; + color_info.metadata.white_point_chromaticity_y = 0.8; + color_info.metadata.luminance_max = 0.9; + color_info.metadata.luminance_min = 1.0; +} +} // namespace utils +} // namespace es + +class EsPlayerMockEventListener : public pp::EsEventListener { + public: + MOCK_METHOD2_T(OnError, void(const pp::ErrorType, UserData userdata)); + MOCK_METHOD1_T(OnResourceConflicted, void(UserData userdata)); + MOCK_METHOD1_T(OnSeekDone, void(UserData userdata)); + MOCK_METHOD1_T(OnEos, void(UserData userdata)); + MOCK_METHOD2_T(OnPrepareDone, void(bool, UserData userdata)); + MOCK_METHOD5_T(OnBufferStatus, + void(const pp::StreamType &, const pp::BufferStatus &, + const uint64_t, const uint64_t, UserData userdata)); + MOCK_METHOD2_T(OnReadyToPrepare, + void(const pp::StreamType &, UserData userdata)); + MOCK_METHOD3_T(OnReadyToSeek, void(const pp::StreamType &, const uint64_t, + UserData userdata)); + + void Bind(std::shared_ptr &&eventlistener) { + using ::testing::_; + using ::testing::Invoke; + eventlistener_ = eventlistener; + ON_CALL(*this, OnError(_, _)) + .WillByDefault( + Invoke(eventlistener_.get(), &pp::EsEventListener::OnError)); + ON_CALL(*this, OnResourceConflicted(_)) + .WillByDefault(Invoke(eventlistener_.get(), + &pp::EsEventListener::OnResourceConflicted)); + ON_CALL(*this, OnSeekDone(_)) + .WillByDefault( + Invoke(eventlistener_.get(), &pp::EsEventListener::OnSeekDone)); + ON_CALL(*this, OnEos(_)) + .WillByDefault( + Invoke(eventlistener_.get(), &pp::EsEventListener::OnEos)); + ON_CALL(*this, OnPrepareDone(_, _)) + .WillByDefault( + Invoke(eventlistener_.get(), &pp::EsEventListener::OnPrepareDone)); + ON_CALL(*this, OnBufferStatus(_, _, _, _, _)) + .WillByDefault( + Invoke(eventlistener_.get(), &pp::EsEventListener::OnBufferStatus)); + ON_CALL(*this, OnReadyToPrepare(_, _)) + .WillByDefault(Invoke(eventlistener_.get(), + &pp::EsEventListener::OnReadyToPrepare)); + ON_CALL(*this, OnReadyToSeek(_, _, _)) + .WillByDefault( + Invoke(eventlistener_.get(), &pp::EsEventListener::OnReadyToSeek)); + } + + private: + std::shared_ptr eventlistener_; +}; + +class EsPlayerTest : public ::testing::Test { + protected: + static void SetUpTestCase() { + env_ = new Environment(); + ESPacketDownloader::Init(); + } + + static void TearDownTestCase() { + if (env_) { + delete env_; + env_ = nullptr; + } + } + + void SetUp() override { + gst_init_check(nullptr, nullptr, nullptr); + submit_stopped_ = false; + player_ = GetCreatePlayer(); + + mock_eventlistener_ = std::make_shared(); + fake_eventlistener_ = std::make_shared(this); + mock_eventlistener_->Bind( + std::dynamic_pointer_cast(fake_eventlistener_)); + + player_->RegisterListener(mock_eventlistener_.get(), nullptr); + + v_streamreader_ = es::StreamReader::Create( + "/welcome_movie/video_00/", pp::kTrackTypeVideo); + a_streamreader_ = es::StreamReader::Create( + "/welcome_movie/audio_00/", pp::kTrackTypeAudio); + } + + void TearDown() override { + ClosePlayer(); + if (video_task_.valid()) { + video_task_.wait(); + } + if (audio_task_.valid()) { + audio_task_.wait(); + } + mock_eventlistener_.reset(); + fake_eventlistener_.reset(); + v_streamreader_.reset(); + a_streamreader_.reset(); + is_tzdata_ = false; + has_matroska_color_info = false; + submit_stopped_ = false; + } + + pp::EsPlusPlayer::Ptr GetCreatePlayer() { + auto esplayer = pp::EsPlusPlayer::Create(); + esplayer->Open(); + esplayer->SetDisplay(pp::DisplayType::kOverlay, env_->Window()); + return std::move(esplayer); + } + + void ClosePlayer() { + if (player_) { + player_->Close(); + player_.reset(); + } + } + + pp::VideoStreamPtr GetVideoStream(const pp::Track &track) { + auto video_stream = pp::VideoStream::Create(); + video_stream->SetMimeType(pp::VideoMimeType::kHEVC); + video_stream->SetMaxWidth(track.maxwidth); + video_stream->SetMaxHeight(track.maxheight); + video_stream->SetWidth(track.width); + video_stream->SetHeight(track.height); + video_stream->SetFramerate(track.framerate_num, track.framerate_den); + video_stream->SetCodecData(track.codec_data, track.codec_data_len); + return std::move(video_stream); + } + + void SetVideoStream() { + auto videoinfo = + v_streamreader_ + ->GetMediaInfo>(); + auto videoextradata = v_streamreader_->GetExtraData(); + auto videotrack = videoinfo.GetTrack(); + videotrack.codec_data = videoextradata->data; + videotrack.codec_data_len = videoextradata->size; + + auto video_stream = GetVideoStream(videotrack); + ASSERT_TRUE(player_->SetStream(video_stream)); + } + + pp::AudioStreamPtr GetAudioStream(const pp::Track &track) { + auto audio_stream = pp::AudioStream::Create(); + if (!track.mimetype.compare("audio/mpeg")) + audio_stream->SetMimeType(pp::AudioMimeType::kAAC); + else + audio_stream->SetMimeType(pp::AudioMimeType::kAC3); + audio_stream->SetSamplerate(track.sample_rate); + audio_stream->SetChannels(track.channels); + return std::move(audio_stream); + } + + void SetAudioStream() { + auto audioinfo = + a_streamreader_ + ->GetMediaInfo>(); + auto audiotrack = audioinfo.GetTrack(); + + auto audio_stream = GetAudioStream(audiotrack); + ASSERT_TRUE(player_->SetStream(audio_stream)); + } + + void SetMatroskaColorInfo() { has_matroska_color_info = true; } + + void SetTrustZoneData() { + ASSERT_TRUE(player_->SetSubmitDataType(pp::SubmitDataType::kTrustZoneData)); + is_tzdata_ = true; + } + + private: + class EsPlayerFakeEventListener : public pp::EsEventListener { + public: + explicit EsPlayerFakeEventListener(EsPlayerTest *handler) + : handler_(handler) { + assert(handler); + } + + void OnError(const pp::ErrorType &err_code, UserData userdata) override { + std::cout << "OnError" << std::endl; + } + void OnResourceConflicted(UserData userdata) override { + std::cout << "OnResourceConflicted" << std::endl; + } + void OnSeekDone(UserData userdata) override { + std::cout << "OnSeekDone" << std::endl; + } + void OnEos(UserData userdata) override { + std::unique_lock lk(eos_m_); + std::cout << "OnEos" << std::endl; + eos_ = true; + lk.unlock(); + eos_cv_.notify_all(); + ASSERT_TRUE(handler_->player_->Stop()); + } + void OnPrepareDone(bool ret, UserData userdata) override { + std::cout << "OnPrepareDone" << std::endl; + ASSERT_TRUE(handler_->player_->Start()); + } + void OnBufferStatus(const pp::StreamType &type, + const pp::BufferStatus &status, + const uint64_t byte_size, const uint64_t time_size, + UserData userdata) override { + auto buffer_status = + status == pp::BufferStatus::kUnderrun ? "underrun" : "overrun"; + std::cout << "OnBufferStatus " << buffer_status << std::endl; + } + void OnReadyToPrepare(const pp::StreamType &type, + UserData userdata) override { + std::cout << "OnReadyToPrepare" << std::endl; + + auto a_streaming_task_fn = [this]( + es::StreamReader::Ptr &streamreader, bool is_tzdata) { + while (true) { + auto pkt = streamreader->ReadNextPacket(); + auto buffer = es::utils::MakeEsPacketFromPacket( + pkt, static_cast(streamreader->GetTrackType())); + if (is_tzdata) + SubmitTzEsPacket(buffer); + else + SubmitEsPacket(buffer); + if (pkt == nullptr) break; + } + }; + auto v_streaming_task_fn = [this]( + es::StreamReader::Ptr &streamreader, bool is_tzdata, + bool has_color_info) { + while (true) { + auto pkt = streamreader->ReadNextPacket(); + auto buffer = es::utils::MakeEsPacketFromPacket( + pkt, static_cast(streamreader->GetTrackType())); + if (has_color_info && buffer->GetPts() == 0) { + pp::MatroskaColor color_info; + es::utils::MakeDummyMatroskaColor(color_info); + buffer->SetMatroskaColorInfo(color_info); + } + if (is_tzdata) + SubmitTzEsPacket(buffer); + else + SubmitEsPacket(buffer); + if (pkt == nullptr) break; + } + }; + if (type == pp::StreamType::kVideo) { + handler_->video_task_ = + std::async(std::launch::async, v_streaming_task_fn, + std::ref(handler_->v_streamreader_), + handler_->is_tzdata_, handler_->has_matroska_color_info); + } else if (type == pp::StreamType::kAudio) { + handler_->audio_task_ = std::async( + std::launch::async, a_streaming_task_fn, + std::ref(handler_->a_streamreader_), handler_->is_tzdata_); + } + } + void OnReadyToSeek(const pp::StreamType &type, const uint64_t offset, + UserData userdata) override { + std::cout << "OnReadyToSeek" << std::endl; + } + + void WaitForEos() { + std::unique_lock lk(eos_m_); + eos_cv_.wait_for(lk, std::chrono::minutes(1), + [this]() -> bool { return eos_; }); + eos_ = false; + lk.unlock(); + } + + void SubmitEsPacket(const pp::EsPacketPtr &packet) { + using PacketSubmitStatus = pp::PacketSubmitStatus; + PacketSubmitStatus status = PacketSubmitStatus::kNotPrepared; + while (status != PacketSubmitStatus::kSuccess) { + if (handler_->submit_stopped_ == true) break; + status = handler_->player_->SubmitPacket(packet); + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + } + } + + void SubmitTzEsPacket(const pp::EsPacketPtr &packet) { + using PacketSubmitStatus = pp::PacketSubmitStatus; + PacketSubmitStatus status = PacketSubmitStatus::kNotPrepared; + while (status != PacketSubmitStatus::kSuccess) { + if (handler_->submit_stopped_ == true) break; + status = handler_->player_->SubmitTrustZonePacket(packet); + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + } + } + + private: + bool eos_ = false; + std::mutex eos_m_; + std::condition_variable eos_cv_; + EsPlayerTest *handler_{nullptr}; + }; + + public: + static Environment *env_; + std::atomic submit_stopped_; + std::shared_ptr player_; + std::shared_ptr mock_eventlistener_; + std::shared_ptr fake_eventlistener_; + es::StreamReader::Ptr v_streamreader_; + es::StreamReader::Ptr a_streamreader_; + std::future video_task_; + std::future audio_task_; + bool has_matroska_color_info = false; + bool is_tzdata_ = false; +}; + +Environment *EsPlayerTest::env_ = nullptr; + +TEST_F(EsPlayerTest, Play) { + using ::testing::_; + using ::testing::AtLeast; + + EXPECT_CALL(*mock_eventlistener_, OnReadyToPrepare(_, _)).Times(AtLeast(1)); + EXPECT_CALL(*mock_eventlistener_, OnPrepareDone(true, _)); + EXPECT_CALL(*mock_eventlistener_, OnEos(_)).Times(1); + + SetVideoStream(); + SetAudioStream(); + + ASSERT_TRUE(player_->PrepareAsync()); + + fake_eventlistener_->WaitForEos(); +} + +TEST_F(EsPlayerTest, PlayWithDrm) { + using ::testing::_; + using ::testing::AtLeast; + + EXPECT_CALL(*mock_eventlistener_, OnReadyToPrepare(_, _)).Times(AtLeast(1)); + EXPECT_CALL(*mock_eventlistener_, OnPrepareDone(true, _)); + EXPECT_CALL(*mock_eventlistener_, OnEos(_)).Times(1); + + SetVideoStream(); + SetAudioStream(); + SetTrustZoneData(); + + ASSERT_TRUE(player_->PrepareAsync()); + + fake_eventlistener_->WaitForEos(); +} + +TEST_F(EsPlayerTest, PlayWithMatroskaColor) { + using ::testing::_; + using ::testing::AtLeast; + + EXPECT_CALL(*mock_eventlistener_, OnReadyToPrepare(_, _)).Times(AtLeast(1)); + EXPECT_CALL(*mock_eventlistener_, OnPrepareDone(true, _)); + + SetVideoStream(); + SetAudioStream(); + + SetMatroskaColorInfo(); + + ASSERT_TRUE(player_->PrepareAsync()); + + std::this_thread::sleep_for(std::chrono::seconds(5)); + + ASSERT_TRUE(player_->Stop()); + submit_stopped_ = true; +} + +TEST_F(EsPlayerTest, PlayWithDrmAndMatroskaColor) { + using ::testing::_; + using ::testing::AtLeast; + + EXPECT_CALL(*mock_eventlistener_, OnReadyToPrepare(_, _)).Times(AtLeast(1)); + EXPECT_CALL(*mock_eventlistener_, OnPrepareDone(true, _)); + + SetVideoStream(); + SetAudioStream(); + + SetMatroskaColorInfo(); + SetTrustZoneData(); + + ASSERT_TRUE(player_->PrepareAsync()); + + std::this_thread::sleep_for(std::chrono::seconds(5)); + + ASSERT_TRUE(player_->Stop()); + submit_stopped_ = true; +} + +TEST_F(EsPlayerTest, Get_Adaptive_Info) { + using ::testing::_; + using ::testing::AtLeast; + + SetVideoStream(); + SetAudioStream(); + + EXPECT_CALL(*mock_eventlistener_, OnReadyToPrepare(_, _)).Times(AtLeast(1)); + EXPECT_CALL(*mock_eventlistener_, OnPrepareDone(true, _)); + + ASSERT_TRUE(player_->PrepareAsync()); + + std::this_thread::sleep_for(std::chrono::seconds(5)); + + uint64_t *pvalue = new uint64_t; + ASSERT_TRUE(player_->GetAdaptiveInfo( + (void *)pvalue, pp::PlayerAdaptiveInfo::kVideoDroppedFrames)); + std::cout << "Dropped frames: " << *pvalue << std::endl; + + ASSERT_TRUE(player_->Stop()); + submit_stopped_ = true; +} + +TEST_F(EsPlayerTest, SetGetVolume) { + using ::testing::_; + using ::testing::AtLeast; + + SetVideoStream(); + SetAudioStream(); + + EXPECT_CALL(*mock_eventlistener_, OnReadyToPrepare(_, _)).Times(AtLeast(1)); + EXPECT_CALL(*mock_eventlistener_, OnPrepareDone(true, _)); + + const int kVolume = 80; + ASSERT_TRUE(player_->SetVolume(kVolume)); + + ASSERT_TRUE(player_->PrepareAsync()); + + std::this_thread::sleep_for(std::chrono::seconds(5)); + int volume = 0; + ASSERT_TRUE(player_->GetVolume(&volume)); + std::cout << "volume: " << volume << std::endl; + ASSERT_EQ(volume, kVolume); + + ASSERT_TRUE(player_->Stop()); + submit_stopped_ = true; +} + +TEST_F(EsPlayerTest, VideoActivateDeactivate) { + using ::testing::_; + using ::testing::AtLeast; + + SetVideoStream(); + SetAudioStream(); + + EXPECT_CALL(*mock_eventlistener_, OnReadyToPrepare(_, _)).Times(AtLeast(1)); + EXPECT_CALL(*mock_eventlistener_, OnPrepareDone(true, _)); + + const int kVolume = 80; + ASSERT_TRUE(player_->SetVolume(kVolume)); + + ASSERT_TRUE(player_->PrepareAsync()); + + std::this_thread::sleep_for(std::chrono::seconds(5)); + EXPECT_TRUE(player_->Deactivate(pp::StreamType::kVideo)); + // SetVideoStream(); + EXPECT_TRUE(player_->Activate(pp::StreamType::kVideo)); + std::this_thread::sleep_for(std::chrono::seconds(15)); + + ASSERT_TRUE(player_->Stop()); + submit_stopped_ = true; +} diff --git a/ut/src/ut_esplayer2.cpp b/ut/src/ut_esplayer2.cpp new file mode 100755 index 0000000..54c414f --- /dev/null +++ b/ut/src/ut_esplayer2.cpp @@ -0,0 +1,775 @@ +// +// @ Copyright [2017] +// + +#include + +#include +#include + +#include "gmock/gmock.h" +#include "gst/gst.h" +#include "gtest/gtest.h" + +#include "core/decoderinputbuffer.h" +#include "core/decoderinputbuffer_listener.h" +#include "core/track_util.h" +#include "core/utils/plusplayer_log.h" +#include "esplayer/esplayer.h" +#include "plusplayer/esplusplayer.h" +#include "tracksource/tracksource.h" +#include "ut/include/appwindow.h" + +using namespace plusplayer; + +static constexpr int kMaxSizeOfQueue = 3; +static constexpr int kTrackTypes[] = {kTrackTypeAudio, kTrackTypeVideo, + kTrackTypeSubtitle}; + +class Feeder2 : public DecoderInputBufferListener { + public: + Feeder2() noexcept {} + ~Feeder2() { + LOG_ENTER; + std::lock_guard lock(state_m_); + if (state_ != State::kActivated) { + LOG_INFO("Already stopped. just destroy feeder"); + return; + } + stop_ = true; + for (auto type : kTrackTypes) { + feed_buf_[type].buffer_cv.notify_one(); + if (feed_buf_[type].task.valid()) feed_buf_[type].task.wait(); + } + state_ = State::kNone; + player_ = nullptr; + LOG_LEAVE; + return; + } + + void OnRecv(DecoderInputBufferPtr inbuffer) { +#if 1 // Convert Raw Buffer + StreamType type = static_cast(inbuffer->GetType()); + GstBuffer *gstbuffer = static_cast(inbuffer->Release()); + uint64_t pts = GST_BUFFER_PTS(gstbuffer) / 1000000; + uint64_t duration = GST_BUFFER_DURATION(gstbuffer) / 1000000; + GstMapInfo info; + + gst_buffer_map(gstbuffer, &info, GST_MAP_READ); + // LOG_INFO("type [%d] pts [%lld] duration [%lld] size [%d]", type, pts, + // duration, info.size); + + std::shared_ptr data(new char[info.size], + std::default_delete()); + for (gsize i = 0; i < info.size; i++) { + data.get()[i] = info.data[i]; + } + auto inputbuffer = EsPacket::Create(type, data, info.size, pts, duration); + gst_buffer_unmap(gstbuffer, &info); + + Push_(std::move(inputbuffer)); +#else + Push_(std::move(inbuffer)); +#endif + } + + bool Start(plusplayer::EsPlusPlayer *player) { + LOG_ENTER; + assert(player); + std::lock_guard lock(state_m_); + if (state_ == State::kActivated) { + LOG_INFO("do nothing, task already activated, call stop if you need"); + return false; + } + player_ = player; + stop_ = false; + for (auto type : kTrackTypes) { + feed_buf_[type].type = static_cast(type); + feed_buf_[type].task = std::async(std::launch::async, &Feeder2::Task_, + this, &feed_buf_[type]); + feed_buf_[type].activated = true; + } + state_ = State::kActivated; + LOG_LEAVE; + return true; + } + + bool Stop() { + LOG_ENTER; + std::lock_guard lock(state_m_); + if (state_ != State::kActivated) { + LOG_INFO("Already stopped. just destroy feeder"); + return true; + } + stop_ = true; + for (auto type : kTrackTypes) { + feed_buf_[type].buffer_cv.notify_one(); + if (feed_buf_[type].task.valid()) feed_buf_[type].task.wait(); + } + state_ = State::kNone; + player_ = nullptr; + LOG_LEAVE; + return true; + } + + bool SetEos() { + LOG_ENTER; + for (auto type : kTrackTypes) { + auto inbuffer = EsPacket::CreateEos(static_cast(type)); + Push_(std::move(inbuffer)); + } + LOG_LEAVE; + return true; + } + + bool Flush(TrackType type) { + LOG_ENTER; + if (type >= kTrackTypeMax) { + return false; + } + std::lock_guard lock(feed_buf_[type].buffer_m); + feed_buf_[type].buffer_cv.notify_all(); + // bool ret = + // decoderinputbuffer_util::FlushQueue(feed_buf_[type].inbufferqueue); + bool ret = true; + while (!feed_buf_[type].inbufferqueue.empty()) { + feed_buf_[type].inbufferqueue.pop(); + } + LOG_LEAVE; + return ret; + } + + private: + bool Push_(EsPacketPtr inbuffer) { + TrackType type = static_cast(inbuffer->GetType()); + + if (type >= kTrackTypeMax) { + LOG_INFO("invalid type , failed to push"); + return false; + } + if (stop_) { + // LOG_INFO("stopped, failed to push"); + return false; + } + { + std::unique_lock lock(feed_buf_[type].buffer_m); + if (feed_buf_[type].activated) { + feed_buf_[type].inbufferqueue.push(std::move(inbuffer)); + feed_buf_[type].buffer_cv.notify_one(); + } + if (feed_buf_[type].inbufferqueue.size() > kMaxSizeOfQueue) { + feed_buf_[type].buffer_cv.wait(lock); + } + } + return true; + } + + private: + struct FeedBuffer { + bool activated = false; + TrackType type = kTrackTypeMax; + std::mutex buffer_m; + std::condition_variable buffer_cv; + std::queue inbufferqueue; + std::future task; + }; + + void Task_(FeedBuffer *feed_buf) { + LOG_INFO("ENTER , TASK type[%d]", feed_buf->type); + if (!feed_buf) { + assert(0 && "feed_buf is null, can't create feeder task"); + return; + } + LOG_INFO("FeederTask is Created , TaskID type[%d]", feed_buf->type); + while (!stop_) { + std::unique_lock lock(feed_buf->buffer_m); + if (feed_buf->inbufferqueue.empty()) { + feed_buf->buffer_cv.wait(lock); + } else { + if (player_->SubmitPacket(feed_buf->inbufferqueue.front()) != + PacketSubmitStatus::kSuccess) { + lock.unlock(); + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + } else { + feed_buf->inbufferqueue.pop(); + lock.unlock(); + feed_buf->buffer_cv.notify_one(); + } + } + } + std::lock_guard lock(feed_buf->buffer_m); + feed_buf->activated = false; + // decoderinputbuffer_util::FlushQueue(feed_buf->inbufferqueue); + while (!feed_buf->inbufferqueue.empty()) { + feed_buf->inbufferqueue.pop(); + } + LOG_INFO("LEAVE , TASK TYPE[%d]", feed_buf->type); + } + + enum class State { + kNone, + kActivated, + }; + + private: + plusplayer::EsPlusPlayer *player_ = nullptr; + State state_ = State::kNone; + bool stop_ = false; + std::mutex state_m_; + FeedBuffer feed_buf_[kTrackTypeMax]; +}; + +class EsPlayerMockEventListener1 : public EsEventListener { + public: + MOCK_METHOD2_T(OnError, void(const ErrorType, UserData userdata)); + MOCK_METHOD1_T(OnResourceConflicted, void(UserData userdata)); + MOCK_METHOD1_T(OnSeekDone, void(UserData userdata)); + MOCK_METHOD1_T(OnEos, void(UserData userdata)); + MOCK_METHOD2_T(OnPrepareDone, void(bool, UserData userdata)); + MOCK_METHOD5_T(OnBufferStatus, + void(const StreamType &, const BufferStatus &, const uint64_t, + const uint64_t, UserData userdata)); + MOCK_METHOD2_T(OnReadyToPrepare, void(const StreamType &, UserData userdata)); + MOCK_METHOD3_T(OnReadyToSeek, + void(const StreamType &, const uint64_t, UserData userdata)); + + void Bind(std::shared_ptr &&eventlistener) { + using ::testing::_; + using ::testing::Invoke; + eventlistener_ = eventlistener; + ON_CALL(*this, OnError(_, _)) + .WillByDefault(Invoke(eventlistener_.get(), &EsEventListener::OnError)); + ON_CALL(*this, OnResourceConflicted(_)) + .WillByDefault(Invoke(eventlistener_.get(), + &EsEventListener::OnResourceConflicted)); + ON_CALL(*this, OnSeekDone(_)) + .WillByDefault( + Invoke(eventlistener_.get(), &EsEventListener::OnSeekDone)); + ON_CALL(*this, OnEos(_)) + .WillByDefault(Invoke(eventlistener_.get(), &EsEventListener::OnEos)); + ON_CALL(*this, OnPrepareDone(_, _)) + .WillByDefault( + Invoke(eventlistener_.get(), &EsEventListener::OnPrepareDone)); + ON_CALL(*this, OnBufferStatus(_, _, _, _, _)) + .WillByDefault( + Invoke(eventlistener_.get(), &EsEventListener::OnBufferStatus)); + ON_CALL(*this, OnReadyToPrepare(_, _)) + .WillByDefault( + Invoke(eventlistener_.get(), &EsEventListener::OnReadyToPrepare)); + ON_CALL(*this, OnReadyToSeek(_, _, _)) + .WillByDefault( + Invoke(eventlistener_.get(), &EsEventListener::OnReadyToSeek)); + } + + private: + std::shared_ptr eventlistener_; +}; + +class EsPlayerFakeEventListener1 : public EsEventListener { + public: + EsPlayerFakeEventListener1() + : eos_(false), ready_audio_data_(false), ready_video_data_(false) {} + + void OnError(const ErrorType &err_code, UserData userdata) override { + std::cout << "OnError" << std::endl; + } + void OnResourceConflicted(UserData userdata) override { + std::cout << "OnResourceConflicted" << std::endl; + } + void OnSeekDone(UserData userdata) override { + std::cout << "OnSeekDone" << std::endl; + } + void OnEos(UserData userdata) override { + std::unique_lock lk(eos_m_); + std::cout << "OnEos" << std::endl; + eos_ = true; + lk.unlock(); + eos_cv_.notify_all(); + } + void OnPrepareDone(bool ret, UserData userdata) override { + std::cout << "OnPrepareDone" << std::endl; + } + void OnBufferStatus(const StreamType &type, const BufferStatus &status, + const uint64_t byte_size, const uint64_t time_size, + UserData userdata) override { + auto buffer_status = + status == BufferStatus::kUnderrun ? "underrun" : "overrun"; + std::cout << "OnBufferStatus " << buffer_status << std::endl; + } + void OnReadyToPrepare(const StreamType &type, UserData userdata) override { + std::cout << "OnReadyToPrepare" << std::endl; + std::unique_lock lk(data_m_); + if (type == StreamType::kAudio) + ready_audio_data_ = true; + else if (type == StreamType::kVideo) + ready_video_data_ = true; + lk.unlock(); + data_cv_.notify_all(); + } + void OnReadyToSeek(const StreamType &type, const uint64_t offset, + UserData userdata) override { + std::cout << "OnReadyToSeek" << std::endl; + std::unique_lock lk(data_m_); + if (type == StreamType::kAudio) + ready_audio_data_ = true; + else if (type == StreamType::kVideo) + ready_video_data_ = true; + lk.unlock(); + data_cv_.notify_all(); + } + void WaitAllStreamData() { + std::unique_lock lk(data_m_); + std::cout << "WaitAllStreamData start" << std::endl; + data_cv_.wait_for(lk, std::chrono::seconds(1), [this]() -> bool { + return ready_audio_data_ && ready_video_data_; + }); + ready_audio_data_ = false; + ready_video_data_ = false; + std::cout << "WaitAllStreamData stop" << std::endl; + lk.unlock(); + } + void WaitAudioStreamData() { + std::unique_lock lk(data_m_); + std::cout << "WaitAudioStreamData start" << std::endl; + data_cv_.wait_for(lk, std::chrono::seconds(1), + [this]() -> bool { return ready_audio_data_; }); + std::cout << "WaitAudioStreamData stop" << std::endl; + lk.unlock(); + } + void WaitVideoStreamData() { + std::unique_lock lk(data_m_); + std::cout << "WaitVideoStreamData start" << std::endl; + data_cv_.wait_for(lk, std::chrono::seconds(1), + [this]() -> bool { return ready_audio_data_; }); + std::cout << "WaitVideoStreamData stop" << std::endl; + lk.unlock(); + } + void WaitForEos() { + std::cout << "WaitForEos start" << std::endl; + std::unique_lock lk(eos_m_); + eos_cv_.wait_for(lk, std::chrono::minutes(1), + [this]() -> bool { return eos_; }); + eos_ = false; + std::cout << "WaitForEos stop" << std::endl; + lk.unlock(); + } + + private: + bool eos_; + std::mutex eos_m_; + std::condition_variable eos_cv_; + bool ready_audio_data_; + bool ready_video_data_; + std::mutex data_m_; + std::condition_variable data_cv_; +}; + +class EsPlayerTest2 : public ::testing::Test { + public: + EsPlayerTest2() {} + + void SetUp() override { + gst_init_check(nullptr, nullptr, nullptr); + std::string url( + "http://devimages.apple.com.edgekey.net/streaming/examples/bipbop_16x9/" + "bipbop_16x9_variant.m3u8"); + TypeFinder typefinder(url); + StreamingProperty property; + if (!typefinder.Probe()) { + return; + } + tracksource_ = TrackSource::CreateCompositor(); + tracksource_->AddSource(&typefinder, property); + if (!tracksource_->Prepare()) { + LOG_ERROR("tracksource prepare was failed"); + return; + } + feeder_.reset(new Feeder2()); + tracksource_->RegisterListener(feeder_.get()); + } + + void TearDown() override { + feeder_.reset(); + tracksource_.reset(); + } + + std::shared_ptr GetFeeder2() { return feeder_; } + std::shared_ptr GetTrackSource() { return tracksource_; } + + private: + std::shared_ptr feeder_; + std::shared_ptr tracksource_; +}; + +VideoStreamPtr GetVideoStream() { + auto video_stream = VideoStream::Create(); + video_stream->SetMimeType(VideoMimeType::kH264); + video_stream->SetWidth(640); + video_stream->SetHeight(352); + video_stream->SetFramerate(30, 1); + return std::move(video_stream); +} + +AudioStreamPtr GetAudioStream() { + auto audio_stream = AudioStream::Create(); + audio_stream->SetMimeType(AudioMimeType::kAAC); + audio_stream->SetSamplerate(44100); + audio_stream->SetChannels(2); + return std::move(audio_stream); +} + +AudioStreamPtr GetAudioStream2() { + auto audio_stream = AudioStream::Create(); + audio_stream->SetMimeType(AudioMimeType::kAAC); + audio_stream->SetSamplerate(22050); + audio_stream->SetChannels(2); + return std::move(audio_stream); +} + +TEST_F(EsPlayerTest2, Play) { + using ::testing::_; + using ::testing::AtLeast; + + auto mock_eventlistener = std::make_shared(); + auto fake_eventlistener = std::make_shared(); + mock_eventlistener->Bind( + std::dynamic_pointer_cast(fake_eventlistener)); + + auto feeder = GetFeeder2(); + auto tracksource = GetTrackSource(); + + auto esplayer = EsPlusPlayer::Create(); + esplayer->Open(); + + std::unique_ptr appwindow( + new plusplayer_ut::AppWindow(0, 0, 1920, 1080)); + assert(appwindow.get()); +#if ECORE_WAYLAND_DISPLAY_TEST + esplayer->SetDisplay(DisplayType::kOverlay, appwindow->GetEcoreWL2Window(), 0, + 0, 1920, 1080); +#else + Evas_Object *obj = appwindow->GetWindow().obj; + esplayer->SetDisplay(plusplayer::DisplayType::kOverlay, obj); +#endif + + esplayer->RegisterListener(mock_eventlistener.get(), nullptr); + EXPECT_CALL(*mock_eventlistener, OnReadyToPrepare(_, _)).Times(AtLeast(1)); + EXPECT_CALL(*mock_eventlistener, OnPrepareDone(true, _)); + + auto feeding_task_fn = [this, &esplayer]() { + auto feeder = GetFeeder2(); + auto tracksource = GetTrackSource(); + // Feeder must be started before TrackSource::Start to prevent loss of + // packets. + feeder->Start(esplayer.get()); + tracksource->Start(); + + std::this_thread::sleep_for(std::chrono::seconds(10)); + }; + + esplayer->SetStream(GetVideoStream()); + esplayer->SetStream(GetAudioStream()); + esplayer->PrepareAsync(); + + fake_eventlistener->WaitAllStreamData(); + auto feeding_task = std::thread(feeding_task_fn); + + esplayer->Start(); + + feeding_task.join(); + + esplayer->Stop(); + esplayer->Close(); + + feeder->Stop(); + tracksource->Stop(); +} + +TEST_F(EsPlayerTest2, Seek) { + using ::testing::_; + using ::testing::AtLeast; + + auto mock_eventlistener = std::make_shared(); + auto fake_eventlistener = std::make_shared(); + mock_eventlistener->Bind( + std::dynamic_pointer_cast(fake_eventlistener)); + + std::unique_ptr appwindow( + new plusplayer_ut::AppWindow(0, 0, 1920, 1080)); + assert(appwindow.get()); + Evas_Object *obj = appwindow->GetWindow().obj; + + auto feeder = GetFeeder2(); + auto tracksource = GetTrackSource(); + auto esplayer = EsPlusPlayer::Create(); + + esplayer->Open(); + + esplayer->RegisterListener(mock_eventlistener.get(), nullptr); + EXPECT_CALL(*mock_eventlistener, OnReadyToPrepare(_, _)).Times(AtLeast(1)); + EXPECT_CALL(*mock_eventlistener, OnPrepareDone(true, _)); + + auto feeding_task_fn = [this, &esplayer]() { + auto feeder = GetFeeder2(); + auto tracksource = GetTrackSource(); + // Feeder must be started before TrackSource::Start to prevent loss of + // packets. + feeder->Start(esplayer.get()); + tracksource->Start(); + + std::this_thread::sleep_for(std::chrono::seconds(10)); + }; + + esplayer->SetDisplay(plusplayer::DisplayType::kOverlay, obj); + esplayer->SetStream(GetVideoStream()); + esplayer->SetStream(GetAudioStream()); + esplayer->PrepareAsync(); + + fake_eventlistener->WaitAllStreamData(); + auto feeding_task = std::thread(feeding_task_fn); + + esplayer->Start(); + + feeding_task.join(); + + feeder->Stop(); + tracksource->Pause(); + tracksource->Seek(5000); + + EXPECT_CALL(*mock_eventlistener, OnReadyToSeek(_, _, _)).Times(AtLeast(1)); + EXPECT_CALL(*mock_eventlistener, OnSeekDone(_)); + + esplayer->Seek(5000); + + fake_eventlistener->WaitAllStreamData(); + auto seek_feeding_task = std::thread(feeding_task_fn); + + seek_feeding_task.join(); + + esplayer->Stop(); + esplayer->Close(); + feeder->Stop(); + tracksource->Stop(); +} + +TEST_F(EsPlayerTest2, DISABLED_SetPlaybackRate) { + using ::testing::_; + using ::testing::AtLeast; + + auto mock_eventlistener = std::make_shared(); + auto fake_eventlistener = std::make_shared(); + mock_eventlistener->Bind( + std::dynamic_pointer_cast(fake_eventlistener)); + + std::unique_ptr appwindow( + new plusplayer_ut::AppWindow(0, 0, 1920, 1080)); + assert(appwindow.get()); + Evas_Object *obj = appwindow->GetWindow().obj; + + auto feeder = GetFeeder2(); + auto tracksource = GetTrackSource(); + auto esplayer = EsPlusPlayer::Create(); + + esplayer->Open(); + + esplayer->RegisterListener(mock_eventlistener.get(), nullptr); + EXPECT_CALL(*mock_eventlistener, OnReadyToPrepare(_, _)).Times(AtLeast(1)); + EXPECT_CALL(*mock_eventlistener, OnPrepareDone(true, _)); + + auto feeding_task_fn = [this, &esplayer]() { + auto feeder = GetFeeder2(); + auto tracksource = GetTrackSource(); + // Feeder must be started before TrackSource::Start to prevent loss of + // packets. + feeder->Start(esplayer.get()); + tracksource->Start(); + + std::this_thread::sleep_for(std::chrono::seconds(10)); + }; + + esplayer->SetDisplay(plusplayer::DisplayType::kOverlay, obj); + esplayer->SetStream(GetVideoStream()); + esplayer->SetStream(GetAudioStream()); + esplayer->PrepareAsync(); + + fake_eventlistener->WaitAllStreamData(); + auto feeding_task = std::thread(feeding_task_fn); + + esplayer->Start(); + + feeding_task.join(); + + feeder->Stop(); + tracksource->Pause(); + + double playback_rate = 1.5; + uint64_t time_millisecond = 0; + esplayer->GetPlayingTime(&time_millisecond); + + esplayer->SetPlaybackRate(playback_rate, true); + tracksource->Seek(time_millisecond, playback_rate); + + auto seek_feeding_task = std::thread(feeding_task_fn); + seek_feeding_task.join(); + + esplayer->Stop(); + esplayer->Close(); + feeder->Stop(); + tracksource->Stop(); +} + +TEST_F(EsPlayerTest2, A_DeactivateActivate) { + using ::testing::_; + using ::testing::AtLeast; + + auto mock_eventlistener = std::make_shared(); + auto fake_eventlistener = std::make_shared(); + mock_eventlistener->Bind( + std::dynamic_pointer_cast(fake_eventlistener)); + + std::unique_ptr appwindow( + new plusplayer_ut::AppWindow(0, 0, 1920, 1080)); + assert(appwindow.get()); + Evas_Object *obj = appwindow->GetWindow().obj; + + auto feeder = GetFeeder2(); + auto tracksource = GetTrackSource(); + auto esplayer = EsPlusPlayer::Create(); + + esplayer->Open(); + + esplayer->RegisterListener(mock_eventlistener.get(), nullptr); + EXPECT_CALL(*mock_eventlistener, OnReadyToPrepare(_, _)).Times(AtLeast(1)); + EXPECT_CALL(*mock_eventlistener, OnPrepareDone(true, _)); + + auto feeding_task_fn = [this, &esplayer]() { + auto feeder = GetFeeder2(); + auto tracksource = GetTrackSource(); + // Feeder must be started before TrackSource::Start to prevent loss of + // packets. + feeder->Start(esplayer.get()); + tracksource->Start(); + }; + uint64_t cur_pos_msec = 0; + esplayer->SetDisplay(plusplayer::DisplayType::kOverlay, obj); + tracksource->SelectTrack(kTrackTypeAudio, 0, 0); + esplayer->SetStream(GetVideoStream()); + esplayer->SetStream(GetAudioStream()); + esplayer->PrepareAsync(); + + fake_eventlistener->WaitAllStreamData(); + auto feeding_task = std::thread(feeding_task_fn); + + esplayer->Start(); + feeding_task.join(); + std::this_thread::sleep_for(std::chrono::seconds(3)); + + auto track_list = tracksource->GetTrackInfo(); + Track activated_track; + track_util::GetActiveTrack(track_list, kTrackTypeAudio, &activated_track); + if (activated_track.index == 0) { + printf("the index[%d] is already activated", 0); + } + esplayer->Deactivate(static_cast(kTrackTypeAudio)); + esplayer->GetPlayingTime(&cur_pos_msec); + + feeder->Flush(kTrackTypeAudio); + feeder->Stop(); + tracksource->Pause(); + tracksource->SelectTrack(kTrackTypeAudio, 1, cur_pos_msec); + tracksource->Seek(cur_pos_msec); + printf("activate tracktype : %d index : %d playingtime : %llu ms ", + static_cast(kTrackTypeAudio), 0, cur_pos_msec); + track_list = tracksource->GetTrackInfo(); + if (!track_util::GetActiveTrack(track_list, kTrackTypeAudio, + &activated_track)) { + LOG_ERROR("Can not find active track with [%d] index", 1); + return; + } + esplayer->SetStream(GetAudioStream2()); + esplayer->Activate(static_cast(kTrackTypeAudio)); + esplayer->Seek(cur_pos_msec); + fake_eventlistener->WaitAllStreamData(); + auto new_feeding_task = std::thread(feeding_task_fn); + + new_feeding_task.join(); + std::this_thread::sleep_for(std::chrono::seconds(3)); + esplayer->Stop(); + esplayer->Close(); + feeder->Stop(); + tracksource->Stop(); +} + +TEST_F(EsPlayerTest2, VideoPeekSeek) { + using ::testing::_; + using ::testing::AtLeast; + + auto mock_eventlistener = std::make_shared(); + auto fake_eventlistener = std::make_shared(); + mock_eventlistener->Bind( + std::dynamic_pointer_cast(fake_eventlistener)); + + std::unique_ptr appwindow( + new plusplayer_ut::AppWindow(0, 0, 1920, 1080)); + assert(appwindow.get()); + Evas_Object *obj = appwindow->GetWindow().obj; + + auto feeder = GetFeeder2(); + auto tracksource = GetTrackSource(); + auto esplayer = EsPlusPlayer::Create(); + + esplayer->Open(); + + esplayer->RegisterListener(mock_eventlistener.get(), nullptr); + EXPECT_CALL(*mock_eventlistener, OnReadyToPrepare(_, _)).Times(AtLeast(1)); + EXPECT_CALL(*mock_eventlistener, OnPrepareDone(true, _)); + + auto feeding_task_fn = [this, &esplayer]() { + auto feeder = GetFeeder2(); + auto tracksource = GetTrackSource(); + // Feeder must be started before TrackSource::Start to prevent loss of + // packets. + feeder->Start(esplayer.get()); + tracksource->Start(); + }; + + esplayer->SetDisplay(plusplayer::DisplayType::kOverlay, obj); + esplayer->SetStream(GetVideoStream()); + esplayer->SetStream(GetAudioStream()); + esplayer->SetVideoFramePeekMode(); + esplayer->PrepareAsync(); + + fake_eventlistener->WaitAllStreamData(); + auto feeding_task = std::thread(feeding_task_fn); + + esplayer->Start(); + + std::this_thread::sleep_for(std::chrono::seconds(10)); + + feeding_task.join(); + + feeder->Stop(); + tracksource->Pause(); + tracksource->Seek(0); + + EXPECT_CALL(*mock_eventlistener, OnReadyToSeek(_, _, _)).Times(AtLeast(1)); + EXPECT_CALL(*mock_eventlistener, OnSeekDone(_)); + + esplayer->Pause(); + esplayer->Seek(0); + fake_eventlistener->WaitAllStreamData(); + auto seek_feeding_task = std::thread(feeding_task_fn); + std::this_thread::sleep_for(std::chrono::seconds(5)); + std::cout <<" Render Video and wait 5 sec ~"<< std::endl; + esplayer->RenderVideoFrame(); + std::this_thread::sleep_for(std::chrono::seconds(5)); + std::cout <<" Resume ~"<< std::endl; + esplayer->Resume(); + std::this_thread::sleep_for(std::chrono::seconds(5)); + seek_feeding_task.join(); + + esplayer->Stop(); + esplayer->Close(); + feeder->Stop(); + tracksource->Stop(); +} \ No newline at end of file diff --git a/ut/src/ut_esplayer_trackrenderer.cpp b/ut/src/ut_esplayer_trackrenderer.cpp new file mode 100755 index 0000000..6c91b2c --- /dev/null +++ b/ut/src/ut_esplayer_trackrenderer.cpp @@ -0,0 +1,266 @@ +// +// @ Copyright [2018] +// + +#include +#include + +#include "gtest/gtest.h" +#include "gmock/gmock.h" + +#include "trackrenderer/trackrenderer.h" +#include "ut/include/streamreader.hpp" + +namespace es { + +namespace utils { +pp::trackrenderer::DecoderInputBufferPtr MakeBufferFromPacket( + const es::PacketPtr &pkt, pp::trackrenderer::TrackType type) { + pp::trackrenderer::DecoderInputBufferPtr buffer = nullptr; + if (pkt == nullptr) { + buffer = es::Packet::MakeEosBuffer(type); + } else { + buffer = pkt->MakeDecoderInputBuffer(type); + } + return std::move(buffer); +} +void SubmitDecoderInputBuffer(const pp::trackrenderer::TrackRenderer::Ptr &trackrenderer, + const pp::trackrenderer::DecoderInputBufferPtr &buffer) { + using SubmitStatus = pp::trackrenderer::SubmitStatus; + SubmitStatus status = SubmitStatus::kNotPrepared; + while (status == SubmitStatus::kNotPrepared || + status == SubmitStatus::kFull || status == SubmitStatus::kHold) { + trackrenderer->SubmitPacket(buffer, &status); + } +} +} // namespace utils +} // namespace es + +class TrackRendererMockEventListener : public pp::trackrenderer::TrackRenderer::EventListener { + public: + MOCK_METHOD1_T(OnError, void(const pp::trackrenderer::ErrorType)); + MOCK_METHOD0_T(OnResourceConflicted, void()); + MOCK_METHOD0_T(OnSeekDone, void()); + MOCK_METHOD0_T(OnEos, void()); + MOCK_METHOD4_T(OnDrmInitData, void(int *, unsigned int, unsigned char *, + pp::trackrenderer::TrackType)); + MOCK_METHOD2_T(OnBufferStatus, void(const pp::trackrenderer::TrackType &, + const pp::trackrenderer::BufferStatus &)); + MOCK_METHOD2_T(OnSeekData, + void(const pp::trackrenderer::TrackType &, const uint64_t)); + + void Bind(std::shared_ptr + &&eventlistener) { + using ::testing::_; + using ::testing::Invoke; + eventlistener_ = eventlistener; + ON_CALL(*this, OnError(_)) + .WillByDefault( + Invoke(eventlistener_.get(), + &pp::trackrenderer::TrackRenderer::EventListener::OnError)); + ON_CALL(*this, OnResourceConflicted()) + .WillByDefault(Invoke(eventlistener_.get(), + &pp::trackrenderer::TrackRenderer::EventListener:: + OnResourceConflicted)); + ON_CALL(*this, OnSeekDone()) + .WillByDefault(Invoke( + eventlistener_.get(), + &pp::trackrenderer::TrackRenderer::EventListener::OnSeekDone)); + ON_CALL(*this, OnEos()) + .WillByDefault( + Invoke(eventlistener_.get(), + &pp::trackrenderer::TrackRenderer::EventListener::OnEos)); + ON_CALL(*this, OnDrmInitData(_, _, _, _)) + .WillByDefault(Invoke( + eventlistener_.get(), + &pp::trackrenderer::TrackRenderer::EventListener::OnDrmInitData)); + ON_CALL(*this, OnBufferStatus(_, _)) + .WillByDefault(Invoke( + eventlistener_.get(), + &pp::trackrenderer::TrackRenderer::EventListener::OnBufferStatus)); + ON_CALL(*this, OnSeekData(_, _)) + .WillByDefault(Invoke( + eventlistener_.get(), + &pp::trackrenderer::TrackRenderer::EventListener::OnSeekData)); + } + + private: + std::shared_ptr + eventlistener_; +}; + +class EsPlayerTrackRendererTest : public ::testing::Test { + protected: + static void SetUpTestCase() { + env_ = new Environment(); + ESPacketDownloader::Init(); + } + static void TearDownTestCase() { + if (env_) { + delete env_; + env_ = nullptr; + } + } + + void SetUp() override { gst_init_check(nullptr, nullptr, nullptr); } + void TearDown() override {} + + public: + static Environment *env_; +}; + +Environment *EsPlayerTrackRendererTest::env_ = nullptr; + +TEST_F(EsPlayerTrackRendererTest, DISABLED_DefaultPlaybackOnPushMode) { + class TrackRendererFakeEventListener + : public pp::trackrenderer::TrackRenderer::EventListener {}; + TrackRendererFakeEventListener eventlistener; + + auto v_streamreader = es::StreamReader::Create( + "/welcome_movie/video_00/", pp::trackrenderer::kTrackTypeVideo); + auto videoinfo = v_streamreader->GetMediaInfo< + es::VideoInfo>(); + auto videoextradata = v_streamreader->GetExtraData(); + auto videotrack = videoinfo.GetTrack(); + videotrack.codec_data = videoextradata->data; + videotrack.codec_data_len = videoextradata->size; + + auto a_streamreader = es::StreamReader::Create( + "/welcome_movie/audio_01/", pp::trackrenderer::kTrackTypeAudio); + auto audioinfo = a_streamreader->GetMediaInfo< + es::AudioInfo>(); + auto audiotrack = audioinfo.GetTrack(); + + auto trackrenderer = pp::trackrenderer::TrackRenderer::Create(); + ASSERT_TRUE( + trackrenderer->SetDisplay(pp::trackrenderer::DisplayType::kOverlay, env_->Window())); + trackrenderer->RegisterListener(&eventlistener); + + auto tracks = {videotrack, audiotrack}; + ASSERT_TRUE(trackrenderer->SetTrack(tracks)); + + auto streaming_task_fn = [&trackrenderer](es::StreamReader::Ptr &streamreader) { + while (true) { + auto pkt = streamreader->ReadNextPacket(); + auto buffer = + es::utils::MakeBufferFromPacket(pkt, streamreader->GetTrackType()); + es::utils::SubmitDecoderInputBuffer(trackrenderer, buffer); + if (pkt == nullptr) break; + } + }; + + auto video_task = std::thread(streaming_task_fn, std::ref(v_streamreader)); + auto audio_task = std::thread(streaming_task_fn, std::ref(a_streamreader)); + + ASSERT_TRUE(trackrenderer->Prepare()); + ASSERT_TRUE(trackrenderer->Start()); + + video_task.join(); + audio_task.join(); + + ASSERT_TRUE(trackrenderer->Stop()); +} + +TEST_F(EsPlayerTrackRendererTest, DISABLED_Reuse) { + class TrackRendererFakeEventListener + : public pp::trackrenderer::TrackRenderer::EventListener {}; + TrackRendererFakeEventListener eventlistener; + auto trackrenderer = pp::trackrenderer::TrackRenderer::Create(); + trackrenderer->RegisterListener(&eventlistener); // only once + + auto streaming_task_fn = + [&trackrenderer]( + es::StreamReader::Ptr &streamreader) { + while (true) { + auto pkt = streamreader->ReadNextPacket(); + auto buffer = es::utils::MakeBufferFromPacket( + pkt, streamreader->GetTrackType()); + es::utils::SubmitDecoderInputBuffer(trackrenderer, buffer); + if (pkt == nullptr) break; + } + }; + + // first playback + { + auto v_streamreader = + es::StreamReader::Create( + "/welcome_movie/video_00/", pp::trackrenderer::kTrackTypeVideo); + auto videoinfo = v_streamreader->GetMediaInfo>(); + auto videoextradata = v_streamreader->GetExtraData(); + auto videotrack = videoinfo.GetTrack(); + videotrack.codec_data = videoextradata->data; + videotrack.codec_data_len = videoextradata->size; + + auto a_streamreader = + es::StreamReader::Create( + "/welcome_movie/audio_01/", pp::trackrenderer::kTrackTypeAudio); + auto audioinfo = a_streamreader->GetMediaInfo>(); + auto audiotrack = audioinfo.GetTrack(); + + auto tracks = {videotrack, audiotrack}; + ASSERT_TRUE(trackrenderer->SetTrack(tracks)); + + ASSERT_TRUE(trackrenderer->SetDisplay( + pp::trackrenderer::DisplayType::kOverlay, env_->Window())); + + auto video_task = std::thread(streaming_task_fn, std::ref(v_streamreader)); + auto audio_task = std::thread(streaming_task_fn, std::ref(a_streamreader)); + + ASSERT_TRUE(trackrenderer->Prepare()); + ASSERT_TRUE(trackrenderer->Start()); + + ASSERT_TRUE(trackrenderer->SetAudioMute(true)); + ASSERT_TRUE( + trackrenderer->SetDisplayMode(pp::trackrenderer::DisplayMode::kDstRoi)); + pp::trackrenderer::Geometry roi; + roi.x = 100; + roi.y = 100; + roi.w = 640; + roi.h = 480; + ASSERT_TRUE(trackrenderer->SetDisplayRoi(roi)); + + video_task.join(); + audio_task.join(); + + ASSERT_TRUE(trackrenderer->Stop()); + } + + // reuse + { + auto v_streamreader = + es::StreamReader::Create( + "/welcome_movie/video_00/", pp::trackrenderer::kTrackTypeVideo); + auto videoinfo = v_streamreader->GetMediaInfo>(); + auto videoextradata = v_streamreader->GetExtraData(); + auto videotrack = videoinfo.GetTrack(); + videotrack.codec_data = videoextradata->data; + videotrack.codec_data_len = videoextradata->size; + + auto a_streamreader = + es::StreamReader::Create( + "/welcome_movie/audio_01/", pp::trackrenderer::kTrackTypeAudio); + auto audioinfo = a_streamreader->GetMediaInfo>(); + auto audiotrack = audioinfo.GetTrack(); + + auto tracks = {videotrack, audiotrack}; + ASSERT_TRUE(trackrenderer->SetTrack(tracks)); + + ASSERT_TRUE(trackrenderer->SetDisplay( + pp::trackrenderer::DisplayType::kOverlay, env_->Window())); + + auto video_task = std::thread(streaming_task_fn, std::ref(v_streamreader)); + auto audio_task = std::thread(streaming_task_fn, std::ref(a_streamreader)); + + ASSERT_TRUE(trackrenderer->Prepare()); + ASSERT_TRUE(trackrenderer->Start()); + + video_task.join(); + audio_task.join(); + + ASSERT_TRUE(trackrenderer->Stop()); + } +} diff --git a/ut/src/ut_main.cpp b/ut/src/ut_main.cpp new file mode 100755 index 0000000..a852b81 --- /dev/null +++ b/ut/src/ut_main.cpp @@ -0,0 +1,18 @@ +// +// @ Copyright [2017] +// + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +int main(int argc, char *argv[]) { + + ::testing::InitGoogleTest(&argc, argv); + ::testing::InitGoogleMock(&argc, argv); + + auto ret = -1; + ret = RUN_ALL_TESTS(); + + + return ret; +} diff --git a/ut/src/ut_miscellaneous.cpp b/ut/src/ut_miscellaneous.cpp new file mode 100755 index 0000000..b2eeae3 --- /dev/null +++ b/ut/src/ut_miscellaneous.cpp @@ -0,0 +1,133 @@ +// +// @ Copyright [2017] +// + +#include + +#include +#include // to test "BoostScopeExit" + +#include "gtest/gtest.h" + +#include "core/utils/scope_exit.h" // to test "ScopeExit" +#include "trackrenderer/core/pipeline.hpp" // to test "UnlinkElements" + +using namespace plusplayer; + +TEST(MiscTest, DISABLED_BoostScopeExit) { + std::string input; + std::cout << "input string:" << std::endl; + std::getline(std::cin , input); + + if(input == "exit_now") { + std::cout << "early return" << std::endl; + return; + } + + BOOST_SCOPE_EXIT(&input) { + input = "string updated"; + std::cout << "Boost Scopt Exit : string(" << input << ")" << std::endl; + } BOOST_SCOPE_EXIT_END + + if(input == "1") { + std::cout << "input is 1" << std::endl; + return; + } else { + std::cout << "input is not 1" << std::endl; + } +} + +enum class TestState { + kNone, + kGood, + kBad +}; + +static void ControlDropRate(TestState* state) { + std::cout << __FUNCTION__ << std::endl; + *state = TestState::kGood; + return; +} + +TEST(MiscTest, DISABLED_BoostScopeExit2) { + TestState state = TestState::kNone; + BOOST_SCOPE_EXIT(&state) { + std::cout << static_cast(state) << std::endl; + ASSERT_EQ(state , TestState::kGood); + } BOOST_SCOPE_EXIT_END + return ControlDropRate(&state); +} + +TEST(MiscTest, DISABLED_BoostScopeExitSequence) { + std::string input1{"1st scopt exit requested"}; + BOOST_SCOPE_EXIT(&input1) { + std::cout << input1 << std::endl; + } BOOST_SCOPE_EXIT_END + + std::string input2{"2nd scopt exit requested"}; + BOOST_SCOPE_EXIT(&input2) { + std::cout << input2 << std::endl; + } BOOST_SCOPE_EXIT_END + + std::string input3{"3rd scopt exit requested"}; + BOOST_SCOPE_EXIT(&input3) { + std::cout << input3 << std::endl; + } BOOST_SCOPE_EXIT_END +} + +// TODO(js4716.chun) : don't use this yet...use boost_scope_exit +TEST(MiscTest, DISABLED_ScopeExit) { + std::string input; + std::cout << "input string:" << std::endl; + std::getline(std::cin, input); + + if (input == "exit_now") { + std::cout << "early return" << std::endl; + return; + } + + auto x = utils::ScopeExit([&input]() { + std::cout << "lambda in" << std::endl; + input = "string updated"; + std::cout << "Scopt Exit : string(" << input << ")" << std::endl; + }); + + if (input == "1") { + std::cout << "input is 1 (" << input << ")" << std::endl; + return; + } else { + std::cout << "input is not 1 (" << input << ")" << std::endl; + } +} + +TEST(MiscTest, DISABLED_BoostAnyCast) { + const float test1 = 3.14; + std::cout << "test1: [" << test1 << "]" << std::endl; + + // + // Failed Cases + // + boost::any value1 = 3.14; + const float* casted_val1 = boost::any_cast(&value1); + if(!casted_val1) { // if casting failed + std::cout << "casted_val1: casting failed" << std::endl; + } else { + std::cout << "casted_val1: [" << *casted_val1 << "]" << std::endl; + } + try { + const float casted_val2 = + boost::any_cast(value1); // Exception thrown. + std::cout << "casted_val2: [" << casted_val2 << "]" << std::endl; + } catch(...) { + std::cout << "casted_val2: casting failed" << std::endl; + } + + // + // Success Cases + // + boost::any value2 = float{3.14}; + const float casted_val3 = boost::any_cast(value2); // Success + std::cout << "casted_val3: [" << casted_val3 << "]" << std::endl; + const float* casted_val4 = boost::any_cast(&value2); // Success + std::cout << "casted_val4: [" << *casted_val4 << "]" << std::endl; +} diff --git a/ut/src/ut_streamreader.cpp b/ut/src/ut_streamreader.cpp new file mode 100755 index 0000000..b61156c --- /dev/null +++ b/ut/src/ut_streamreader.cpp @@ -0,0 +1,130 @@ +// +// @ Copyright [2018] +// + +#include "gtest/gtest.h" +#include "gmock/gmock.h" + +#include "ut/include/streamreader.hpp" +#include "plusplayer/track.h" + +class StreamReaderTest : public ::testing::Test { + protected: + static void SetUpTestCase() { ESPacketDownloader::Init(); } + static void TearDownTestCase() {} + + void SetUp() override {} + void TearDown() override {} + + public: + static Environment *env_; +}; + +TEST_F(StreamReaderTest, DISABLED_Create) { + ASSERT_NO_THROW({ + es::StreamReader::Create("/welcome_movie/video_00/", + pp::kTrackTypeVideo); + es::StreamReader::Create("/welcome_movie/audio_00/", + pp::kTrackTypeAudio); + }); +} + +TEST_F(StreamReaderTest, DISABLED_ThrowOnCreate) { + ASSERT_ANY_THROW({ + es::StreamReader::Create("foo/bar/", pp::kTrackTypeVideo); + }); +} + +TEST_F(StreamReaderTest, DISABLED_GetVideoExtraData) { + auto v_streamreader = es::StreamReader::Create( + "/welcome_movie/video_00/", pp::kTrackTypeVideo); + ASSERT_NO_THROW({ v_streamreader->GetExtraData(); }); +} + +TEST_F(StreamReaderTest, DISABLED_GetAudioExtraData) { + auto a_streamreader = es::StreamReader::Create( + "/welcome_movie/audio_00/", pp::kTrackTypeAudio); + ASSERT_NO_THROW({ a_streamreader->GetExtraData(); }); +} + +TEST_F(StreamReaderTest, DISABLED_GetPtsFromFirstVideoPacket) { + auto v_streamreader = es::StreamReader::Create( + "/welcome_movie/video_00/", pp::kTrackTypeVideo); + auto firstpkt = v_streamreader->ReadNextPacket(); + ASSERT_EQ(1, firstpkt->pts); +} + +TEST_F(StreamReaderTest, DISABLED_GetPtsFromTwoVideoPacket) { + auto v_streamreader = es::StreamReader::Create( + "/welcome_movie/video_00/", pp::kTrackTypeVideo); + auto firstpkt = v_streamreader->ReadNextPacket(); + ASSERT_EQ(1, firstpkt->pts); + + auto secondpkt = v_streamreader->ReadNextPacket(); + ASSERT_EQ(16666668, secondpkt->pts); +} + +TEST_F(StreamReaderTest, DISABLED_GetPtsFromFirstAudioPacket) { + auto a_streamreader = es::StreamReader::Create( + "/welcome_movie/audio_00/", pp::kTrackTypeAudio); + auto firstpkt = a_streamreader->ReadNextPacket(); + ASSERT_EQ(1, firstpkt->pts); +} + +TEST_F(StreamReaderTest, DISABLED_GetPtsFromTwoAudioPacket) { + auto a_streamreader = es::StreamReader::Create( + "/welcome_movie/audio_00/", pp::kTrackTypeAudio); + auto firstpkt = a_streamreader->ReadNextPacket(); + ASSERT_EQ(1, firstpkt->pts); + + auto secondpkt = a_streamreader->ReadNextPacket(); + ASSERT_EQ(21333334, secondpkt->pts); +} + +TEST_F(StreamReaderTest, DISABLED_CheckVideoEOF) { + auto v_streamreader = es::StreamReader::Create( + "/welcome_movie/video_00/", pp::kTrackTypeVideo); + es::PacketPtr pkt = nullptr; + while (1) { + pkt = v_streamreader->ReadNextPacket(); + if (pkt == nullptr) break; + ASSERT_NE(nullptr, pkt); + } + ASSERT_EQ(nullptr, pkt); +} + +TEST_F(StreamReaderTest, DISABLED_CheckAudioEOF) { + auto a_streamreader = es::StreamReader::Create( + "/welcome_movie/audio_00/", pp::kTrackTypeAudio); + es::PacketPtr pkt = nullptr; + while (1) { + pkt = a_streamreader->ReadNextPacket(); + if (pkt == nullptr) break; + ASSERT_NE(nullptr, pkt); + } + ASSERT_EQ(nullptr, pkt); +} + +TEST_F(StreamReaderTest, DISABLED_CheckVideoInfo) { + auto v_streamreader = es::StreamReader::Create( + "/welcome_movie/video_00/", pp::kTrackTypeVideo); + auto videoinfo = + v_streamreader->GetMediaInfo>(); + ASSERT_EQ("video/x-h265", videoinfo.mimetype); + ASSERT_EQ(3840, videoinfo.width); + ASSERT_EQ(2160, videoinfo.height); + ASSERT_EQ(3840, videoinfo.maxwidth); + ASSERT_EQ(2160, videoinfo.maxheight); + ASSERT_EQ(60, videoinfo.framerate_num); + ASSERT_EQ(1, videoinfo.framerate_den); +} + +TEST_F(StreamReaderTest, DISABLED_CheckAudioInfo) { + auto a_streamreader = es::StreamReader::Create( + "/welcome_movie/audio_00/", pp::kTrackTypeAudio); + auto audioinfo = a_streamreader->GetMediaInfo< + es::AudioInfo>(); + ASSERT_EQ("audio/mpeg", audioinfo.mimetype); + ASSERT_EQ(48000, audioinfo.samplerate); + ASSERT_EQ(2, audioinfo.channels); +} diff --git a/ut/src/ut_trackrendereradapter.cpp b/ut/src/ut_trackrendereradapter.cpp new file mode 100755 index 0000000..12f76ce --- /dev/null +++ b/ut/src/ut_trackrendereradapter.cpp @@ -0,0 +1,410 @@ +// +// @ Copyright [2017] +// + +#include "gst/gst.h" +#include "gtest/gtest.h" + +#include "Ecore.h" +#include "Elementary.h" +#include "glib-object.h" + +#include "core/track_util.h" +#include "core/trackrendereradapter.h" +#include "core/trackrendereradapter_utils.h" +#include "plusplayer/track.h" +#include "trackrenderer/core/decoderinputbuffer.h" +#include "trackrenderer/core/track_util.h" +#include "trackrenderer/trackrenderer_capi_utils.h" +#include "trackrenderer_capi/iniproperty.h" +#include "trackrenderer_capi/track.h" +#include "trackrenderer_capi/trackrenderer_capi.h" +#include "ut/include/appwindow.h" + +using namespace plusplayer; + +class TrackRendererAdapterTest : public ::testing::Test { + public: + TrackRendererAdapterTest() {} + void SetUp() override { + // basic , clean stream + std::string url = ""; + trackrenderer_adapter_ = TrackRendererAdapter::Create(); + ASSERT_TRUE(trackrenderer_adapter_.get()); + } + std::shared_ptr GetAdapter() { + return trackrenderer_adapter_; + } + std::shared_ptr GetAppWindow() { + return appwindow_; + } + + private: + std::shared_ptr trackrenderer_adapter_; + std::shared_ptr appwindow_; +}; +constexpr int kTrackRendererMaxStreamNumber = 3; + +Track SetVideoInfo_(std::string mimetype, TrackType type, + const char* codec_data, int w, int h, int framerate_num, + int framerate_den, bool activate) { + Track trackinfo; + trackinfo.mimetype = mimetype; + trackinfo.type = type; + trackinfo.codec_data = std::make_shared(*codec_data); + trackinfo.width = w; + trackinfo.height = h; + trackinfo.framerate_num = framerate_num; + trackinfo.framerate_den = framerate_den; + trackinfo.active = activate; + return trackinfo; +} + +Track SetAudioInfo_(std::string mimetype, int samplerate, int sampleformat, + int channels, int version) { + Track trackinfo; + trackinfo.mimetype = mimetype; + trackinfo.sample_rate = samplerate; + trackinfo.sample_format = sampleformat; + trackinfo.channels = channels; + trackinfo.version = version; + trackinfo.active = true; + return trackinfo; +} + +TEST_F(TrackRendererAdapterTest, DISABLED_Start) { + // auto adapter = GetAdapter(); + // ASSERT_TRUE(adapter->Start()); +} + +TEST_F(TrackRendererAdapterTest, DISABLED_Stop) { + // auto adapter = GetAdapter(); + // ASSERT_TRUE(adapter->Stop()); +} + +TEST_F(TrackRendererAdapterTest, DISABLED_Prepare) { + // auto adapter = GetAdapter(); + // ASSERT_TRUE(adapter->Prepare()); +} + +TEST_F(TrackRendererAdapterTest, DISABLED_Pause) { + // auto adapter = GetAdapter(); + // ASSERT_TRUE(adapter->Pause()); +} + +TEST_F(TrackRendererAdapterTest, DISABLED_Resume) { + // auto adapter = GetAdapter(); + // ASSERT_TRUE(adapter->Resume()); +} + +std::vector MakeTrackInfo() { + std::vector trackvector; + const char* ffmpeg = "ffmpeg"; + Track trackvideoinfo = + SetVideoInfo_("video/x-h264", TrackType::kTrackTypeVideo, ffmpeg, 640, + 352, 30, 1, true); + Track trackaudioinfo = SetAudioInfo_("audio/mpeg", 44100, 0, 2, 4); + trackvector.push_back(trackvideoinfo); + trackvector.push_back(trackaudioinfo); + return trackvector; +} + +TEST_F(TrackRendererAdapterTest, DISABLED_SetTrack) { + auto adapter = GetAdapter(); + std::vector input_vector = MakeTrackInfo(); + int size = input_vector.size(); + if (size <= 0 || size > kTrackRendererMaxStreamNumber) return; + + TrackRendererTrack trackrenderer_tracks[size]; + int index = 0; + for (const auto& track : input_vector) { + adapter_utils::MakeTrackRendererTrack(&trackrenderer_tracks[index], track); + index++; + } + // ASSERT + // + + auto output_vector = + trackrenderer::capi_utils::MakeTrack(trackrenderer_tracks, size); + + plusplayer::track_util::ShowTrackInfo(input_vector); + plusplayer::trackrenderer::track_util::ShowTrackInfo(output_vector); + + index = 0; + for (const auto& input : input_vector) { + auto output = output_vector.at(index); + ASSERT_EQ(input.index, output.index); + ASSERT_EQ(input.id, output.id); + ASSERT_EQ(input.mimetype, output.mimetype); + ASSERT_EQ(input.streamtype, output.streamtype); + ASSERT_EQ(input.type, output.type); + ASSERT_EQ(input.codec_data_len, output.codec_data_len); + ASSERT_EQ(0, memcmp(input.codec_data.get(), output.codec_data.get(), + output.codec_data_len)); + ASSERT_EQ(input.width, output.width); + ASSERT_EQ(input.height, output.height); + ASSERT_EQ(input.maxwidth, output.maxwidth); + ASSERT_EQ(input.maxheight, output.maxheight); + ASSERT_EQ(input.framerate_num, output.framerate_num); + ASSERT_EQ(input.framerate_den, output.framerate_den); + ASSERT_EQ(input.sample_rate, output.sample_rate); + ASSERT_EQ(input.sample_format, output.sample_format); + ASSERT_EQ(input.channels, output.channels); + ASSERT_EQ(input.version, output.version); + ASSERT_EQ(input.layer, output.layer); + ASSERT_EQ(input.bits_per_sample, output.bits_per_sample); + ASSERT_EQ(input.block_align, output.block_align); + ASSERT_EQ(input.bitrate, output.bitrate); + ASSERT_EQ(input.endianness, output.endianness); + ASSERT_EQ(input.is_signed, output.is_signed); + ASSERT_EQ(input.active, output.active); + ASSERT_EQ(input.use_swdecoder, output.use_swdecoder); + ASSERT_EQ(input.language_code, output.language_code); + ASSERT_EQ(input.subtitle_format, output.subtitle_format); + index++; + } + ASSERT_TRUE(adapter->SetTrack(input_vector)); +} + +std::map MakeIniProperties() { + std::map properties; + std::string key = "use_new_hls_mpegts_demuxer"; + properties[key] = true; + key = "use_new_dash_tiny_demuxer"; + properties[key] = true; + key = "use_new_http_demuxer"; + properties[key] = true; + key = "generate_dot"; + properties[key] = true; + return properties; +} + +TEST_F(TrackRendererAdapterTest, DISABLED_SetIniProperty) { + std::map input = MakeIniProperties(); + const int size = input.size(); + if (size <= 0) return; + + TrackRendererIniProperty trackrenderer_iniproperty[size]; + int index = 0; + for (const auto& pair : input) { + trackrenderer_iniproperty[index].key = pair.first.c_str(); + trackrenderer_iniproperty[index].value = pair.second; + index++; + } + + std::map output = + trackrenderer::capi_utils::MakeIniProperty(trackrenderer_iniproperty, size); + ASSERT_EQ(input, output); +} + +TEST_F(TrackRendererAdapterTest, DISABLED_Seek) { + // auto adapter = GetAdapter(); + // uint64_t time_millisecond = 30; + // double playback_rate = 2.5; + // ASSERT_TRUE(adapter->Seek(time_millisecond, playback_rate)); +} + +TEST_F(TrackRendererAdapterTest, DISABLED_GetPlayingTime) { + // auto adapter = GetAdapter(); + // uint64_t time_millisecond = 0; + // ASSERT_TRUE(adapter->GetPlayingTime(&time_millisecond)); +} + +TEST_F(TrackRendererAdapterTest, DISABLED_Deactivate) { + // auto adapter = GetAdapter(); + // TrackType input = TrackType::kTrackTypeVideo; + // TrackRendererTrackType type = + // adapter_utils::ConvertToTrackRendererTrackType(input); TrackType output = + // plusplayer::trackrenderer::capi_utils::ConvertToTrackType(type); ASSERT_EQ(input, + // output); ASSERT_TRUE(adapter->Deactivate(input)); +} + +TEST_F(TrackRendererAdapterTest, DISABLED_Activate) { + const char* ffmpeg = "ffmpeg"; + Track input = SetVideoInfo_("video/x-h264", TrackType::kTrackTypeVideo, + ffmpeg, 640, 352, 30, 1, true); + TrackRendererTrack trackrenderer_track; + + adapter_utils::MakeTrackRendererTrack(&trackrenderer_track, input); + auto output_vector = trackrenderer::capi_utils::MakeTrack(&trackrenderer_track, 1); + + auto output = output_vector.front(); + + ASSERT_EQ(input.index, output.index); + ASSERT_EQ(input.id, output.id); + ASSERT_EQ(input.mimetype, output.mimetype); + ASSERT_EQ(input.streamtype, output.streamtype); + ASSERT_EQ(input.type, output.type); + ASSERT_EQ(input.codec_data_len, output.codec_data_len); + ASSERT_EQ(0, memcmp(input.codec_data.get(), output.codec_data.get(), + output.codec_data_len)); + ASSERT_EQ(input.width, output.width); + ASSERT_EQ(input.height, output.height); + ASSERT_EQ(input.maxwidth, output.maxwidth); + ASSERT_EQ(input.maxheight, output.maxheight); + ASSERT_EQ(input.framerate_num, output.framerate_num); + ASSERT_EQ(input.framerate_den, output.framerate_den); + ASSERT_EQ(input.sample_rate, output.sample_rate); + ASSERT_EQ(input.sample_format, output.sample_format); + ASSERT_EQ(input.channels, output.channels); + ASSERT_EQ(input.version, output.version); + ASSERT_EQ(input.layer, output.layer); + ASSERT_EQ(input.bits_per_sample, output.bits_per_sample); + ASSERT_EQ(input.block_align, output.block_align); + ASSERT_EQ(input.bitrate, output.bitrate); + ASSERT_EQ(input.endianness, output.endianness); + ASSERT_EQ(input.is_signed, output.is_signed); + ASSERT_EQ(input.active, output.active); + ASSERT_EQ(input.use_swdecoder, output.use_swdecoder); + ASSERT_EQ(input.language_code, output.language_code); + ASSERT_EQ(input.subtitle_format, output.subtitle_format); +} + +TEST_F(TrackRendererAdapterTest, DISABLED_SubmitPacket) { + auto input = DecoderInputBuffer::Create( + static_cast(kTrackTypeVideo), kInvalidTrackIndex, nullptr); + + TrackRendererDecoderInputBuffer trackrenderer_decoderinputbuffer{ + adapter_utils::ConvertToTrackRendererTrackType(input->GetType()), + input->GetIndex(), const_cast(input->Get())}; + + auto output = trackrenderer::DecoderInputBuffer::Create( + trackrenderer::capi_utils::ConvertToTrackType( + trackrenderer_decoderinputbuffer.type), + trackrenderer_decoderinputbuffer.index, + trackrenderer_decoderinputbuffer.buffer); + + ASSERT_EQ((*input).GetType(), (*output).GetType()); + ASSERT_EQ((*input).GetIndex(), (*output).GetIndex()); + ASSERT_EQ((*input).Get(), (*output).Get()); +} + +TEST_F(TrackRendererAdapterTest, DISABLED_SetDrm) { + const drm::Property input; + TrackRendererDrmProperty trackrenderer_drm_property; + + adapter_utils::MakeTrackRendererDrmProperty(&trackrenderer_drm_property, + input); + + trackrenderer::drm::Property output; + trackrenderer::capi_utils::MakeDrmProperty(&output, trackrenderer_drm_property); + ASSERT_EQ(input.type, output.type); + ASSERT_EQ(input.handle, output.handle); + ASSERT_EQ(input.external_decryption, output.external_decryption); + ASSERT_EQ(input.license_acquired_cb, output.license_acquired_cb); + ASSERT_EQ(input.license_acquired_userdata, output.license_acquired_userdata); +} +TEST_F(TrackRendererAdapterTest, DISABLED_SetMatroskaColorInfo) {} + +TEST_F(TrackRendererAdapterTest, DISABLED_DrmLicenseAcquiredDone) { + TrackType input = TrackType::kTrackTypeAudio; + auto type = + adapter_utils::ConvertToTrackRendererTrackType( + input); + + trackrenderer::TrackType output = trackrenderer::capi_utils::ConvertToTrackType(type); + ASSERT_EQ(input, output); +} + +TEST_F(TrackRendererAdapterTest, DISABLED_SetDisplayMode) { + auto adapter = GetAdapter(); + const DisplayMode input = DisplayMode::kOriginSize; + TrackRendererDisplayMode mode = + adapter_utils::ConvertToTrackRendererDisplayMode(input); + trackrenderer::DisplayMode output = trackrenderer::capi_utils::ConvertToDisplayMode(mode); + + ASSERT_EQ(input, output); + ASSERT_TRUE(adapter->SetDisplayMode(input)); +} + +TEST_F(TrackRendererAdapterTest, DISABLED_SetDisplay2) { + auto adapter = GetAdapter(); + auto appwindow = GetAppWindow(); + appwindow.reset(new plusplayer_ut::AppWindow(0, 0, 1920, 1080)); + + const DisplayType input = DisplayType::kOverlay; + + TrackRendererDisplayType type = + adapter_utils::ConvertToTrackRendererDisplayType(input); + trackrenderer::DisplayType output = trackrenderer::capi_utils::ConvertToDisplayType(type); + + ASSERT_EQ(input, output); + ASSERT_TRUE(adapter->SetDisplay(input, appwindow->GetWindow().obj)); +} + +TEST_F(TrackRendererAdapterTest, DISABLED_SetDisplay3) {} + +TEST_F(TrackRendererAdapterTest, DISABLED_SetDisplayRoi) { + auto adapter = GetAdapter(); + Geometry input; + input.x = 1; + input.y = 1; + input.w = 1; + input.h = 1; + TrackRendererGeometry geometry = {0, 0, 1920, 1080}; + adapter_utils::MakeTrackRendererGeometry(&geometry, input); + + trackrenderer::Geometry output; + trackrenderer::capi_utils::MakeGeometry(&output, geometry); + ASSERT_EQ(input.x, output.x); + ASSERT_EQ(input.y, output.y); + ASSERT_EQ(input.w, output.w); + ASSERT_EQ(input.h, output.h); + ASSERT_TRUE(adapter->SetDisplayRoi(input)); +} + +TEST_F(TrackRendererAdapterTest, DISABLED_SetDisplayVisible) { + auto adapter = GetAdapter(); + bool is_visible = false; + ASSERT_TRUE(adapter->SetDisplayVisible(is_visible)); +} + +TEST_F(TrackRendererAdapterTest, DISABLED_SetAudioMute) { + // auto adapter = GetAdapter(); + // bool is_mute = false; + // ASSERT_TRUE(adapter->SetAudioMute(is_mute)); +} + +TEST_F(TrackRendererAdapterTest, DISABLED_SetVideoStillMode) { + StillMode input = StillMode::kOn; + TrackRendererStillMode still_mode = + adapter_utils::ConvertToTrackRendererStillMode(input); + + trackrenderer::StillMode output = trackrenderer::capi_utils::ConvertToStillMode(still_mode); + ASSERT_EQ(input, output); // TODO(js4716.chun) : wrong condition. fix it +} + +TEST_F(TrackRendererAdapterTest, DISABLED_ErrorCb) { + trackrenderer::ErrorType input = ErrorType::kInvalidOperation; + TrackRendererErrorType error_type = + trackrenderer::capi_utils::ConvertToTrackRendererErrorType(input); + ErrorType output = adapter_utils::ConvertToErrorType(error_type); + ASSERT_EQ(input, output); // TODO(js4716.chun) : wrong condition. fix it +} + +template +bool IsSameValue(const boost::any& v1, const boost::any& v2) { + if (v1.type() != v2.type()) return false; + return (boost::any_cast(v1) == boost::any_cast(v2)); +} + +TEST_F(TrackRendererAdapterTest, DISABLED_SubtitleDataCb) { + SubtitleType input_type = SubtitleType::kPicture; + TrackRendererSubtitleType type = + trackrenderer::capi_utils::ConvertToTrackRendererSubtitleType(input_type); + SubtitleType output_type = adapter_utils::ConvertToSubtitleType(type); + ASSERT_EQ(input_type, output_type); +} + +TEST_F(TrackRendererAdapterTest, DISABLED_DrmInitDataCb) { + TrackRendererTrackType input = kTrackRendererTrackTypeSubtitle; + auto type = trackrenderer::capi_utils::ConvertToTrackType(input); + TrackRendererTrackType output = + trackrenderer::capi_utils::ConvertToTrackRendererTrackType(type); + ASSERT_EQ(input, output); +} +/* +TEST_F(TrackRendererAdapterTest, DISABLED_GetDisplay){} +TEST_F(TrackRendererAdapterTest, DISABLED_RegisterListener){} +TEST_F(TrackRendererAdapterTest, DISABLED_ClosedCaptionDataCb_){} +*/