From: Haesu Gwon Date: Thu, 7 Apr 2022 05:20:47 +0000 (+0900) Subject: Add new APIs for media editing framework X-Git-Tag: accepted/tizen/unified/20220419.142239^0 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=refs%2Fchanges%2F30%2F273530%2F35;p=platform%2Fcore%2Fapi%2Fmediaeditor.git Add new APIs for media editing framework It supports for - selecting the period of media clip and placing it to the output timeline - adding audio, video effect - saving editorial cut information and loading it to edit continuously Change-Id: I5dd4b05dc7fd4023c95f2edc0717d03206b90d61 --- diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..d651608 --- /dev/null +++ b/AUTHORS @@ -0,0 +1 @@ +Haesu Gwon diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..89e8b59 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,107 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 2.8.12) +SET(fw_name "capi-media-editor") + +PROJECT(${fw_name}) + +SET(CMAKE_INSTALL_PREFIX /usr) +SET(PREFIX ${CMAKE_INSTALL_PREFIX}) + +SET(INC_DIR include) +INCLUDE_DIRECTORIES(${INC_DIR}) + +SET(dependents "glib-2.0 gio-2.0 dlog iniparser capi-base-common mm-common mm-display-interface capi-system-info\ + gstreamer-1.0 gstreamer-plugins-base-1.0 gstreamer-pbutils-1.0 gst-editing-services-1.0") +SET(pc_dependents "capi-base-common") + +IF(NOT TIZEN_PROFILE_TV) + SET(dependents "${dependents} mm-resource-manager") +ELSE() + ADD_DEFINITIONS("-DTIZEN_TV") +ENDIF() + +INCLUDE(FindPkgConfig) +pkg_check_modules(${fw_name} REQUIRED ${dependents}) +FOREACH(flag ${${fw_name}_CFLAGS}) + SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} ${flag}") +ENDFOREACH(flag) + +SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${EXTRA_CFLAGS} -fPIC -Wall -Werror -Wextra -Wno-array-bounds -Wno-empty-body -Wno-ignored-qualifiers -Wno-unused-parameter -Wshadow -Wwrite-strings -Wswitch-default -Wno-unused-but-set-parameter -Wno-unused-but-set-variable") +SET(CMAKE_C_FLAGS_DEBUG "-O0 -g") + +ADD_DEFINITIONS("-DPREFIX=\"${CMAKE_INSTALL_PREFIX}\"") +ADD_DEFINITIONS("-DTIZEN_DEBUG -DPRINT_CLIP_DEBUG_INFO -DPRINT_LAYER_DEBUG_INFO") + +SET(CMAKE_EXE_LINKER_FLAGS "-Wl,--as-needed -Wl,--rpath=${LIB_INSTALL_DIR}") + +AUX_SOURCE_DIRECTORY (src MAIN_SRC) + +LIST (APPEND SOURCES + ${MAIN_SRC} +) +IF(TIZEN_PROFILE_TV) + LIST (REMOVE_ITEM SOURCES src/media_editor_resource.c) +ENDIF() + +ADD_LIBRARY(${fw_name} SHARED ${SOURCES}) + +TARGET_LINK_LIBRARIES(${fw_name} ${${fw_name}_LDFLAGS}) + +SET_TARGET_PROPERTIES(${fw_name} + PROPERTIES + VERSION ${FULLVER} + SOVERSION ${MAJORVER} + CLEAN_DIRECT_OUTPUT 1 +) + +INSTALL(TARGETS ${fw_name} DESTINATION ${LIB_INSTALL_DIR}) +INSTALL( + DIRECTORY ${INC_DIR}/ DESTINATION include/media + FILES_MATCHING + PATTERN "*_private.h" EXCLUDE + PATTERN "${INC_DIR}/*.h" +) + +SET(PC_NAME ${fw_name}) +SET(PC_REQUIRED ${pc_dependents}) +SET(PC_LDFLAGS -l${fw_name}) + +CONFIGURE_FILE( + ${fw_name}.pc.in + ${CMAKE_CURRENT_SOURCE_DIR}/${fw_name}.pc + @ONLY +) +INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${fw_name}.pc DESTINATION ${LIB_INSTALL_DIR}/pkgconfig) + +ADD_SUBDIRECTORY(test) +ADD_SUBDIRECTORY(gtest) + +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) diff --git a/LICENSE.APLv2 b/LICENSE.APLv2 new file mode 100644 index 0000000..696ca6f --- /dev/null +++ b/LICENSE.APLv2 @@ -0,0 +1,206 @@ +Copyright (c) 2000 - 2022 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/NOTICE b/NOTICE new file mode 100644 index 0000000..0e0f016 --- /dev/null +++ b/NOTICE @@ -0,0 +1,3 @@ +Copyright (c) Samsung Electronics Co., Ltd. All rights reserved. +Except as noted, this software is licensed under Apache License, Version 2. +Please, see the LICENSE.APLv2 file for Apache License terms and conditions. diff --git a/capi-media-editor-tool.manifest b/capi-media-editor-tool.manifest new file mode 100644 index 0000000..86dbb26 --- /dev/null +++ b/capi-media-editor-tool.manifest @@ -0,0 +1,5 @@ + + + + + diff --git a/capi-media-editor.manifest b/capi-media-editor.manifest new file mode 100644 index 0000000..86dbb26 --- /dev/null +++ b/capi-media-editor.manifest @@ -0,0 +1,5 @@ + + + + + diff --git a/capi-media-editor.pc.in b/capi-media-editor.pc.in new file mode 100644 index 0000000..1508489 --- /dev/null +++ b/capi-media-editor.pc.in @@ -0,0 +1,15 @@ + +# Package Information for pkg-config + +prefix=@PREFIX@ +exec_prefix=/usr +libdir=@LIB_INSTALL_DIR@ +includedir=/usr/include/media + +Name: @PC_NAME@ +Description: @PACKAGE_DESCRIPTION@ +Version: @VERSION@ +Requires: @PC_REQUIRED@ +Libs: -L${libdir} @PC_LDFLAGS@ +Cflags: -I${includedir} + diff --git a/doc/media_editor_doc.h b/doc/media_editor_doc.h new file mode 100644 index 0000000..c92dbdd --- /dev/null +++ b/doc/media_editor_doc.h @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an AS IS BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#ifndef __TIZEN_MEDIA_EDITOR_DOC_H__ +#define __TIZEN_MEDIA_EDITOR_DOC_H__ + +/** + * @file media_editor_doc.h + * @brief This file contains high level documentation for the media editor API. + */ + +/** + * @ingroup CAPI_MEDIA_FRAMEWORK + * @defgroup CAPI_MEDIA_EDITOR_MODULE Mediaeditor + * @brief The @ref CAPI_MEDIA_EDITOR_MODULE API provides functions for editing media clip and creating output media file. + * + * @section CAPI_MEDIA_EDITOR_MODULE_HEADER Required Header + * \#include + * + * @section CAPI_MEDIA_EDITOR_MODULE_OVERVIEW Overview + * The Mediaeditor API allows application developers to edit media clip. + * + * The Mediaeditor API set allows you to: + * - add/remove/split/group/ungroup the media clip + * - add/remove/move layer + * - add/remove audio, video effect to media clip + * + * @subsection CAPI_MEDIA_EDITOR_LIFE_CYCLE_STATE_TRANSITIONS State Transitions + *
+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
FUNCTIONPRE-STATEPOST-STATESYNC TYPE
mediaeditor_create() NONE IDLE SYNC
mediaeditor_destroy() IDLE/RENDERING/PREVIEW NONE SYNC
mediaeditor_start_preview() IDLE PREVIEW ASYNC
mediaeditor_start_render() IDLE RENDERING ASYNC
+ * + * (*) The transition from the RENDERING state to the IDLE state will be processed after finishing the rendering. \n + * + * @subsection CAPI_MEDIA_EDITOR_LIFE_CYCLE_CALLBACK_OPERATIONS Callback(Event) Operations + * The callback mechanism is used to notify the application about significant mediaeditor events. + *
+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
REGISTERUNREGISTERCALLBACKDESCRIPTION
mediaeditor_set_state_changed_cb()mediaeditor_unset_state_changed_cb()mediaeditor_state_changed_cb()This callback is used to notify that the mediaeditor state has changed
mediaeditor_set_error_cb()mediaeditor_unset_error_cb()mediaeditor_error_cb()This callback is used to notify that an error has occurred
+ * + * @section CAPI_MEDIA_EDITOR_MODULE_FEATURE Related Feature + * This API is related with the following feature:\n + * - %http://tizen.org/feature/display\n + * + * It is recommended to design feature related codes in your application for reliability.\n + * You can check if a device supports the related features for this API by using @ref CAPI_SYSTEM_SYSTEM_INFO_MODULE, thereby controlling the procedure of your application.\n + * To ensure your application is only running on the device with specific features, please define the features in your manifest file using the manifest editor in the SDK.\n + * More details on featuring your application can be found from Feature Element. + * + */ + +/** + * @ingroup CAPI_MEDIA_EDITOR_MODULE + * @defgroup CAPI_MEDIA_EDITOR_LAYER_MODULE Layer + * @brief The @ref CAPI_MEDIA_EDITOR_LAYER_MODULE API provides functions to manage layers. + * + * @section CAPI_MEDIA_EDITOR_LAYER_MODULE_HEADER Required Header + * \#include + * + * @section CAPI_MEDIA_EDITOR_LAYER_MODULE_OVERVIEW Overview + * The Mediaeditor Layer API allows you to: + * - add/remove layer to internal timeline + * - activate/deactivate layer which is already added + * - move layer + * +*/ + +/** + * @ingroup CAPI_MEDIA_EDITOR_MODULE + * @defgroup CAPI_MEDIA_EDITOR_CLIP_MODULE Clip + * @brief The @ref CAPI_MEDIA_EDITOR_CLIP_MODULE API provides functions to manage clips. + * + * @section CAPI_MEDIA_EDITOR_CLIP_MODULE_HEADER Required Header + * \#include + * + * @section CAPI_MEDIA_EDITOR_CLIP_MODULE_OVERVIEW Overview + * The Mediaeditor Clip API allows you to: + * - add/remove media clips to internal timeline + * - split media clips with user given time position + * - group/ungroup media clips + * - get/set clip start, duration, resolution, volume + * +*/ + +/** + * @ingroup CAPI_MEDIA_EDITOR_MODULE + * @defgroup CAPI_MEDIA_EDITOR_EFFECT_MODULE Effect + * @brief The @ref CAPI_MEDIA_EDITOR_EFFECT_MODULE API provides functions to manage layers. + * + * @section CAPI_MEDIA_EDITOR_EFFECT_MODULE_HEADER Required Header + * \#include + * + * @section CAPI_MEDIA_EDITOR_EFFECT_MODULE_OVERVIEW Overview + * The Mediaeditor Effect API allows you to: + * - add/remove effect + * - add transition effect where clips are overlapped + * +*/ + +/** + * @ingroup CAPI_MEDIA_EDITOR_MODULE + * @defgroup CAPI_MEDIA_EDITOR_PROJECT_MODULE Project + * @brief The @ref CAPI_MEDIA_EDITOR_PROJECT_MODULE API provides functions to manage project. + * + * @section CAPI_MEDIA_EDITOR_PROJECT_MODULE_HEADER Required Header + * \#include + * + * @section CAPI_MEDIA_EDITOR_PROJECT_MODULE_OVERVIEW Overview + * The Mediaeditor Project API allows you to: + * - create/save editorial cut information as project file + * - load editorial cut information from project file and user can edit clip again + * +*/ + +#endif /* __TIZEN_MEDIA_EDITOR_DOC_H__ */ diff --git a/gtest/CMakeLists.txt b/gtest/CMakeLists.txt new file mode 100644 index 0000000..e9bf56a --- /dev/null +++ b/gtest/CMakeLists.txt @@ -0,0 +1,73 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 2.8.12) +SET(fw_name "mediaeditor_ut") + +SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin) +SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib) + +SET(${fw_name}_CXXFLAGS "-Wall -pthread -fPIE -Wl,-z,relro -fstack-protector -fno-delete-null-pointer-checks -DEFL_BETA_API_SUPPORT") + +SET(${fw_name}_LDFLAGS) + +SET(ADD_LIBS + "capi-media-editor" +) + +SET(dependents + "glib-2.0 gstreamer-base-1.0 gstreamer-app-1.0 dlog" + "boost" + "appcore-efl elementary ecore evas ecore-wl2" +) + +find_package(GTest REQUIRED) +set(GTEST_LIBRARY gtest) + +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}") +SET(EXTRA_CXXFLAGS "${EXTRA_CXXFLAGS} ${flag}") +ENDFOREACH(flag) +FOREACH(flag ${${fw_name}_CXXFLAGS}) +SET(EXTRA_CXXFLAGS "${EXTRA_CXXFLAGS} ${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}/src + ${PROJECT_SOURCE_DIR}/include + ${PROJECT_SOURCE_DIR} +) + +SET(UT_SRC + ut_clip.cpp + ut_composition.cpp + ut_effect.cpp + ut_layer.cpp + ut_main.cpp + ut_project.cpp +) + +ADD_EXECUTABLE(${fw_name} ${UT_SRC}) + +LINK_DIRECTORIES(${LIB_INSTALL_DIR}) + +TARGET_LINK_LIBRARIES(${fw_name} + ${GTEST_LIBRARY} + ${CMAKE_THREAD_LIBS_INIT} + ${ADD_LIBS} + ${${fw_name}_LDFLAGS} + "-pie" +) + +INSTALL( + TARGETS ${fw_name} + DESTINATION bin +) diff --git a/gtest/testbase.hpp b/gtest/testbase.hpp new file mode 100644 index 0000000..f0cf8a3 --- /dev/null +++ b/gtest/testbase.hpp @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gtest/gtest.h" +#include + +#undef LOG_TAG +#define LOG_TAG "MEDIAEDITOR_UT" + +class MediaeditorTestBase : public ::testing::Test { +public: + MediaeditorTestBase() : _editor(nullptr) {} + + void SetUp() override { + LOGD("Enter"); + + int ret = mediaeditor_create(&_editor); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + LOGD("Leave"); + } + + void TearDown() override { + LOGD("Enter"); + + if (_editor) { + int ret = mediaeditor_destroy(_editor); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + } + + LOGD("Leave"); + } + + mediaeditor_h GetHandle() { return _editor; } + const std::string& GetBgmPath() { return _bgmPath; } + const std::string& GetMp4Path(int idx) { return _mp4Ppath[idx]; } + const std::string& GetProjectPath(int idx) { return _projectPath[idx]; } + +private: + mediaeditor_h _editor; + + const std::string _basePath = "/tmp/"; + const std::string _bgmPath = _basePath + "bg.mp3"; + const std::vector _mp4Ppath = { + _basePath + "test1.mp4", + _basePath + "test2.mp4", + _basePath + "test3.mp4", + _basePath + "test4.mp4", + _basePath + "test5.mp4" + }; + const std::vector _projectPath = { + _basePath + "test1.xges", + _basePath + "test2.xges" + }; +}; diff --git a/gtest/ut_clip.cpp b/gtest/ut_clip.cpp new file mode 100644 index 0000000..ed78ed9 --- /dev/null +++ b/gtest/ut_clip.cpp @@ -0,0 +1,382 @@ +/* + * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "testbase.hpp" + +static GMainLoop *mainloop; + +class MediaeditorClipTest : public MediaeditorTestBase { +public: + MediaeditorClipTest() {} + + static void RenderCompletedCb(void *user_data) + { + LOGD("Enter"); + if (mainloop) + g_main_loop_quit(mainloop); + } + + static void ErrorCb(mediaeditor_error_e error, mediaeditor_state_e state, void *user_data) + { + LOGD("Enter. error[%d] state[%d]", error, state); + if (mainloop) + g_main_loop_quit(mainloop); + } + + static void WaitForAsync() + { + mainloop = g_main_loop_new(NULL, TRUE); + + LOGD("Wait render completed cb"); + g_main_loop_run(mainloop); + + g_main_loop_unref(mainloop); + mainloop = NULL; + } +}; + +TEST_F(MediaeditorClipTest, DISABLED_clip_create) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + unsigned int layer_id = 0; + unsigned int layer_priority = 0; + unsigned int clip_id = 0; + unsigned int start = 0; + unsigned int duration = 100; + unsigned int in_point = 0; + + ret = mediaeditor_add_layer(GetHandle(), &layer_id, &layer_priority); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + ret = mediaeditor_add_clip(GetHandle(), GetMp4Path(0).c_str(), layer_id, start, duration, in_point, &clip_id); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); +} + +TEST_F(MediaeditorClipTest, DISABLED_clip_delete) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + unsigned int layer_id = 0; + unsigned int layer_priority = 0; + unsigned int clip_id = 0; + unsigned int start = 0; + unsigned int duration = 100; + unsigned int in_point = 0; + + ret = mediaeditor_add_layer(GetHandle(), &layer_id, &layer_priority); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + ret = mediaeditor_add_clip(GetHandle(), GetMp4Path(0).c_str(), layer_id, start, duration, in_point, &clip_id); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + ret = mediaeditor_remove_clip(GetHandle(), clip_id); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); +} + +TEST_F(MediaeditorClipTest, DISABLED_clip_split) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + unsigned int layer_id = 0; + unsigned int layer_priority = 0; + unsigned int clip_id = 0; + unsigned int new_clip_id = 0; + unsigned int start = 0; + unsigned int duration = 100; + unsigned int in_point = 0; + + ret = mediaeditor_add_layer(GetHandle(), &layer_id, &layer_priority); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + ret = mediaeditor_add_clip(GetHandle(), GetMp4Path(0).c_str(), layer_id, start, duration, in_point, &clip_id); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + ret = mediaeditor_split_clip(GetHandle(), clip_id, (start + duration) / 2, &new_clip_id); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); +} + +TEST_F(MediaeditorClipTest, DISABLED_clip_group_ungroup) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + unsigned int layer_id = 0; + unsigned int layer_priority = 0; + unsigned int src_ids[5] = { 0 }; + unsigned int *dst_ids = NULL; + unsigned int group_id = 0; + unsigned int ungroup_size = 0; + unsigned int start = 0; + unsigned int duration = 100; + + ret = mediaeditor_add_layer(GetHandle(), &layer_id, &layer_priority); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + for (int i = 0; i < 5; i++) { + ret = mediaeditor_add_clip(GetHandle(), GetMp4Path(0).c_str(), layer_id, start, duration, i * 100, &src_ids[i]); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + LOGD("clip id : %d", src_ids[i]); + + start += duration; + } + + for (int i = 0 ; i < 4 ; i++) + src_ids[i] += 1; + + ret = mediaeditor_group_clip(GetHandle(), src_ids, 4, &group_id); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + LOGD("grouped id : %d", group_id); + + ret = mediaeditor_ungroup_clip(GetHandle(), group_id, &dst_ids, &ungroup_size); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + for (int i = 0 ; i < (int)ungroup_size ; i++) { + LOGD("src_ids[%d], ungrouped_ids[%d]", src_ids[i], dst_ids[i]); + ASSERT_EQ(src_ids[i], dst_ids[i]); + } +} + +TEST_F(MediaeditorClipTest, DISABLED_clip_move) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + unsigned int layer_id = 0; + unsigned int layer_priority = 0; + unsigned int clip_id = 0; + unsigned int start = 0; + unsigned int duration = 100; + unsigned int in_point = 0; + + ret = mediaeditor_add_layer(GetHandle(), &layer_id, &layer_priority); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + ret = mediaeditor_add_clip(GetHandle(), GetMp4Path(0).c_str(), layer_id, start, duration, in_point, &clip_id); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + LOGD("clip id : %d", clip_id); + + ret = mediaeditor_move_clip_layer(GetHandle(), clip_id, 0); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); +} + +TEST_F(MediaeditorClipTest, DISABLED_clip_set_duration) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + unsigned int layer_id = 0; + unsigned int layer_priority = 0; + unsigned int clip_id = 0; + unsigned int start = 0; + unsigned int duration = 100; + unsigned int in_point = 0; + unsigned int set_duration = 200; + unsigned int get_duration = 0; + + ret = mediaeditor_add_layer(GetHandle(), &layer_id, &layer_priority); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + ret = mediaeditor_add_clip(GetHandle(), GetMp4Path(0).c_str(), layer_id, start, duration, in_point, &clip_id); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + LOGD("original duration : %d", duration); + + ret = mediaeditor_set_clip_duration(GetHandle(), clip_id, set_duration); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + LOGD("set_duration : %d", set_duration); + + ret = mediaeditor_get_clip_duration(GetHandle(), clip_id, &get_duration); + ASSERT_EQ(set_duration, get_duration); + LOGD("get_duration : %d", get_duration); +} + +TEST_F(MediaeditorClipTest, DISABLED_clip_set_get_resolution) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + unsigned int layer_id = 0; + unsigned int layer_priority = 0; + unsigned int clip_id = 0; + unsigned int start = 0; + unsigned int duration = 100; + unsigned int in_point = 0; + unsigned int width = 0; + unsigned int height = 0; + + ret = mediaeditor_add_layer(GetHandle(), &layer_id, &layer_priority); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + ret = mediaeditor_add_clip(GetHandle(), GetMp4Path(0).c_str(), layer_id, start, duration, in_point, &clip_id); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + ret = mediaeditor_get_clip_resolution(GetHandle(), clip_id, &width, &height); + LOGD("original resolution : %dx%d", width, height); + + width = 640; + height = 360; + ret = mediaeditor_set_clip_resolution(GetHandle(), clip_id, width, height); + LOGD("set resolution : %dx%d", width, height); + + ret = mediaeditor_get_clip_resolution(GetHandle(), clip_id, &width, &height); + LOGD("get resolution : %dx%d", width, height); +} + +TEST_F(MediaeditorClipTest, DISABLED_audio_clip_set_get_resolution) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + unsigned int layer_id = 0; + unsigned int layer_priority = 0; + unsigned int clip_id = 0; + unsigned int start = 0; + unsigned int duration = 100; + unsigned int in_point = 0; + unsigned int width = 0; + unsigned int height = 0; + + ret = mediaeditor_add_layer(GetHandle(), &layer_id, &layer_priority); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + ret = mediaeditor_add_clip(GetHandle(), GetBgmPath().c_str(), layer_id, start, duration, in_point, &clip_id); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + ret = mediaeditor_get_clip_resolution(GetHandle(), clip_id, &width, &height); + LOGD("Audio clip doesn't have resolution information. Should return error."); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_INVALID_OPERATION); +} + +TEST_F(MediaeditorClipTest, DISABLED_volume) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + unsigned int layer_id = 0; + unsigned int layer_priority = 0; + unsigned int clip_id = 0; + unsigned int start = 0; + unsigned int duration = 100; + unsigned int in_point = 0; + double volume = 0.0; + + ret = mediaeditor_add_layer(GetHandle(), &layer_id, &layer_priority); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + ret = mediaeditor_add_clip(GetHandle(), GetMp4Path(0).c_str(), layer_id, start, duration, in_point, &clip_id); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + ret = mediaeditor_get_clip_volume(GetHandle(), clip_id, &volume); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + ASSERT_TRUE(volume == 1.0); + + ret = mediaeditor_set_clip_volume(GetHandle(), clip_id, 2.0); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + ret = mediaeditor_get_clip_volume(GetHandle(), clip_id, &volume); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + ASSERT_TRUE(volume == 2.0); + LOGD("get volume [%f]", volume); +} + +TEST_F(MediaeditorClipTest, DISABLED_transition) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + unsigned int layer_id = 0; + unsigned int layer_priority = 0; + unsigned int clip_id = 0; + unsigned int start = 0; + unsigned int duration = 100; + unsigned int in_point = 0; + + ret = mediaeditor_add_layer(GetHandle(), &layer_id, &layer_priority); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + ret = mediaeditor_add_clip(GetHandle(), GetMp4Path(0).c_str(), layer_id, start, duration, in_point, &clip_id); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + ret = mediaeditor_add_clip(GetHandle(), GetMp4Path(1).c_str(), layer_id, start + (duration / 2), duration, in_point, &clip_id); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + ret = mediaeditor_add_transition(GetHandle(), MEDIAEDITOR_TRANSITION_TYPE_CROSSFADE, layer_id, start + (duration / 2), duration / 2); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); +} + +TEST_F(MediaeditorClipTest, DISABLED_transition_n) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + unsigned int layer_id = 0; + unsigned int layer_priority = 0; + unsigned int clip_id = 0; + unsigned int start = 0; + unsigned int duration = 1000; + unsigned int in_point = 0; + + ret = mediaeditor_add_layer(GetHandle(), &layer_id, &layer_priority); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + ret = mediaeditor_set_error_cb(GetHandle(), ErrorCb, GetHandle()); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + /* Test for no overlapped area */ + ret = mediaeditor_add_clip(GetHandle(), GetMp4Path(0).c_str(), layer_id, start, duration, in_point, &clip_id); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + ret = mediaeditor_add_clip(GetHandle(), GetMp4Path(1).c_str(), layer_id, start + (duration * 2), duration, in_point, &clip_id); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + ret = mediaeditor_add_transition(GetHandle(), MEDIAEDITOR_TRANSITION_TYPE_CROSSFADE, layer_id, start + duration, duration / 2); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + ret = mediaeditor_start_render(GetHandle(), "/tmp/out_trans.ogv", RenderCompletedCb, GetHandle()); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + WaitForAsync(); +} + +#ifdef TIZEN_MEDIAEDITOR_TEXTOVERLAY_SUPPORT +TEST_F(MediaeditorClipTest, DISABLED_text_overlay) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + unsigned int layer_id = 0; + unsigned int layer_priority = 0; + unsigned int clip_id1 = 0; + unsigned int clip_id2 = 0; + unsigned int start = 0; + unsigned int duration = 100; + unsigned int in_point = 0; + + ret = mediaeditor_add_layer(GetHandle(), &layer_id, &layer_priority); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + ret = mediaeditor_add_clip(GetHandle(), GetMp4Path(0).c_str(), layer_id, start, duration, in_point, &clip_id1); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + ret = mediaeditor_add_text_overlay(GetHandle(), "Text Overlay", "sans-serif 13", start, duration, 0xFFFFFFFF, 0.5, 0.8, &clip_id2); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); +} + +TEST_F(MediaeditorClipTest, DISABLED_title) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + unsigned int layer_id1 = 0; + unsigned int layer_id2 = 0; + unsigned int layer_priority = 0; + unsigned int layer_priority2 = 0; + unsigned int clip_id1 = 0; + unsigned int clip_id2 = 0; + unsigned int start = 0; + unsigned int duration = 100; + unsigned int in_point = 0; + + ret = mediaeditor_add_layer(GetHandle(), &layer_id1, &layer_priority); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + ret = mediaeditor_add_layer(GetHandle(), &layer_id2, &layer_priority2); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + ret = mediaeditor_add_clip(GetHandle(), GetMp4Path(0).c_str(), layer_id2, start, duration, in_point, &clip_id1); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + ret = mediaeditor_add_title(GetHandle(), "Title", "sans-serif 20", start, duration, 0x80FFFF00, 0.5, 0.8, &clip_id2); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); +} +#endif diff --git a/gtest/ut_composition.cpp b/gtest/ut_composition.cpp new file mode 100644 index 0000000..75924ec --- /dev/null +++ b/gtest/ut_composition.cpp @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "testbase.hpp" + +enum LAYER +{ + VIDEO_LAYER, + BGM_LAYER +}; + +enum EDITING_ELEMENT +{ + VIDEO_ELEMENT, + TRANSITION_ELEMENT, + BGM_ELEMENT, +#ifdef TIZEN_MEDIAEDITOR_TEXTOVERLAY_SUPPORT + TEXTOVERLAY_ELEMENT, +#endif + EFFECT_ELEMENT, + ELEMENT_MAX +}; + +static GMainLoop *mainloop; + +class MediaeditorCompositionTest : public MediaeditorTestBase { +public: + MediaeditorCompositionTest() {} + + static void RenderCompletedCb(void *user_data) + { + LOGD("Enter"); + if (mainloop) + g_main_loop_quit(mainloop); + } + + static void WaitForAsync() + { + mainloop = g_main_loop_new(NULL, TRUE); + + LOGD("Wait render completed cb"); + g_main_loop_run(mainloop); + + g_main_loop_unref(mainloop); + mainloop = NULL; + } +}; + +TEST_F(MediaeditorCompositionTest, DISABLED_mp4_bgm_transition_text) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + const int num_of_clip = 2; + const int num_of_layer = 2; + unsigned int layer_id[num_of_layer] = { 0 }; + unsigned int layer_priority = 0; + unsigned int clip_id = 0; + unsigned int start = 0; + unsigned int total = 0; + + bool editing_element[ELEMENT_MAX] = { + true, // video + true, // transition + true, // bgm +#ifdef TIZEN_MEDIAEDITOR_TEXTOVERLAY_SUPPORT + false, // textoverlay +#endif + true // effect + }; + + unsigned int duration[num_of_clip] = { + G_TIME_SPAN_MILLISECOND * 3, + G_TIME_SPAN_MILLISECOND * 3 + }; + unsigned int duration_bgm = G_TIME_SPAN_MILLISECOND * 3; + + bool is_transition[num_of_clip] = { + true, + false // The last is always false + }; + unsigned int transition_duration = G_TIME_SPAN_MILLISECOND * 2; + + // Create layer + for (int i = 0 ; i < num_of_layer ; i++) { + ret = mediaeditor_add_layer(GetHandle(), &layer_id[i], &layer_priority); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + } + + // Add clips + for (int i = 0 ; i < num_of_clip ; i++) { + ret = mediaeditor_add_clip(GetHandle(), GetMp4Path(i).c_str(), layer_id[VIDEO_LAYER], start, duration[i], 0, &clip_id); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + start += duration[i]; + + if (editing_element[TRANSITION_ELEMENT] && is_transition[i]) + { + ASSERT_TRUE(start > transition_duration); + start -= transition_duration; + + ret = mediaeditor_add_transition(GetHandle(), MEDIAEDITOR_TRANSITION_TYPE_CROSSFADE, layer_id[VIDEO_LAYER], start, transition_duration); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + } + + total = start; + } + + LOGI("total = %d", total); + + // Add BGM + if (editing_element[BGM_ELEMENT]) { + start = 0; + for (int i = 0, step = (int)(duration_bgm / G_TIME_SPAN_MILLISECOND) ; i < (int)(total / G_TIME_SPAN_MILLISECOND) ; i += step) { + if (start + duration_bgm > total) + { + duration_bgm = total - start; + } + + ret = mediaeditor_add_clip(GetHandle(), GetBgmPath().c_str(), layer_id[BGM_LAYER], start, duration_bgm, G_TIME_SPAN_MILLISECOND * 0, &clip_id); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + ret = mediaeditor_set_clip_volume(GetHandle(), clip_id, 0.3); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + start += duration_bgm; + } + } + +#ifdef TIZEN_MEDIAEDITOR_TEXTOVERLAY_SUPPORT + // Add textoverlay + if (editing_element[TEXTOVERLAY_ELEMENT]) { + ret = mediaeditor_add_text_overlay(GetHandle(), "Editing test", "sans-serif 30", 0, total, 0xFFFFFF00, 0.5, 0.9, &clip_id); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + ret = mediaeditor_set_text_overlay_outline(GetHandle(), clip_id, false); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + ret = mediaeditor_set_text_overlay_shadow(GetHandle(), clip_id, true); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + } +#endif + + // Add effect + ret = mediaeditor_add_effect(GetHandle(), MEDIAEDITOR_EFFECT_VIDEO_TYPE_AGINGTV, layer_id[VIDEO_LAYER], + G_TIME_SPAN_MILLISECOND * 0, duration[0], &clip_id); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + ret = mediaeditor_start_render(GetHandle(), "/tmp/out_bgm_trans.ogv", RenderCompletedCb, GetHandle()); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + WaitForAsync(); +} + +TEST_F(MediaeditorCompositionTest, DISABLED_mp4_concat_many) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + unsigned int layer_id = 0; + unsigned int layer_priority = 0; + unsigned int clip_id = 0; + + ret = mediaeditor_add_layer(GetHandle(), &layer_id, &layer_priority); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + for (int i = 0 ; i < (int)(G_TIME_SPAN_MILLISECOND * 10) ; i += 100) { + LOGD("offset : %d", i); + ret = mediaeditor_add_clip(GetHandle(), GetMp4Path(0).c_str(), layer_id, i, 100, G_TIME_SPAN_MILLISECOND * 20 - i, &clip_id); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + } + + ret = mediaeditor_start_render(GetHandle(), "/tmp/out_concat_many.ogv", RenderCompletedCb, GetHandle()); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + WaitForAsync(); +} diff --git a/gtest/ut_effect.cpp b/gtest/ut_effect.cpp new file mode 100644 index 0000000..28b6cc8 --- /dev/null +++ b/gtest/ut_effect.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "testbase.hpp" + +class MediaeditorEffectTest : public MediaeditorTestBase { +public: + MediaeditorEffectTest() {} +}; + +TEST_F(MediaeditorEffectTest, DISABLED_effect_add_remove) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + unsigned int layer_id = 0; + unsigned int layer_priority = 0; + unsigned int path_clip_id = 0; + unsigned int effect_clip_id = 0; + unsigned int start = 0; + unsigned int duration = 100; + unsigned int in_point = 0; + + ret = mediaeditor_add_layer(GetHandle(), &layer_id, &layer_priority); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + ret = mediaeditor_add_clip(GetHandle(), GetMp4Path(0).c_str(), layer_id, start, duration, in_point, &path_clip_id); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + ret = mediaeditor_add_effect(GetHandle(), MEDIAEDITOR_EFFECT_VIDEO_TYPE_AGINGTV, layer_id, start, duration, &effect_clip_id); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + ret = mediaeditor_remove_effect(GetHandle(), effect_clip_id); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); +} + +TEST_F(MediaeditorEffectTest, DISABLED_audio_fade_in_out) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + unsigned int layer_id = 0; + unsigned int layer_priority = 0; + unsigned int clip_id = 0; + unsigned int start = 0; + unsigned int duration = 400; + unsigned int in_point = 0; + + ret = mediaeditor_add_layer(GetHandle(), &layer_id, &layer_priority); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + ret = mediaeditor_add_clip(GetHandle(), GetMp4Path(0).c_str(), layer_id, start, duration, in_point, &clip_id); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + ret = mediaeditor_add_effect(GetHandle(), MEDIAEDITOR_EFFECT_AUDIO_TYPE_FADE_IN, 0, start, duration / 2, &clip_id); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + ret = mediaeditor_add_effect(GetHandle(), MEDIAEDITOR_EFFECT_AUDIO_TYPE_FADE_OUT, 0, start + (duration / 2), duration / 2, &clip_id); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); +} diff --git a/gtest/ut_layer.cpp b/gtest/ut_layer.cpp new file mode 100644 index 0000000..65c3609 --- /dev/null +++ b/gtest/ut_layer.cpp @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "testbase.hpp" + +class MediaeditorLayerTest : public MediaeditorTestBase { +public: + MediaeditorLayerTest() {} + + static void LayerPriorityChangedCb(mediaeditor_layer_info_s *layer_info, unsigned int size, void *user_data) + { + for (unsigned int i = 0 ; i < size ; i++) + LOGD("layer id[%d], priority[%d]", layer_info[i].id, layer_info[i].priority); + } +}; + +TEST_F(MediaeditorLayerTest, DISABLED_layer_add) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + unsigned int layer_id = 0; + unsigned int layer_priority[2] = { 0 }; + + for (int i = 0 ; i < 2 ; i++) { + ret = mediaeditor_add_layer(GetHandle(), &layer_id, &layer_priority[i]); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + ASSERT_EQ(layer_priority[i], i); + } +} + +TEST_F(MediaeditorLayerTest, DISABLED_layer_remove) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + unsigned int layer_id[4] = { 0 }; + unsigned int layer_priority; + + ret = mediaeditor_set_layer_priority_changed_cb(GetHandle(), LayerPriorityChangedCb, NULL); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + for (int i = 0 ; i < 4 ; i++) { + ret = mediaeditor_add_layer(GetHandle(), &layer_id[i], &layer_priority); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + } + + ret = mediaeditor_remove_layer(GetHandle(), layer_id[0]); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + ret = mediaeditor_remove_layer(GetHandle(), layer_id[1]); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); +} + +TEST_F(MediaeditorLayerTest, DISABLED_layer_remove_n) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + + ret = mediaeditor_remove_layer(GetHandle(), 0); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_INVALID_PARAMETER); + + ret = mediaeditor_remove_layer(GetHandle(), 10); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_INVALID_PARAMETER); +} + +TEST_F(MediaeditorLayerTest, DISABLED_layer_move) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + unsigned int layer_id[4] = { 0 }; + unsigned int layer_priority = 0; + + ret = mediaeditor_set_layer_priority_changed_cb(GetHandle(), LayerPriorityChangedCb, NULL); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + for (int i = 0 ; i < 4 ; i++) { + ret = mediaeditor_add_layer(GetHandle(), &layer_id[i], &layer_priority); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + } + + ret = mediaeditor_move_layer(GetHandle(), layer_id[0], 3); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + ret = mediaeditor_move_layer(GetHandle(), layer_id[0], 2); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); +} + +TEST_F(MediaeditorLayerTest, DISABLED_layer_move_to_bottom) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + unsigned int layer_id[3] = { 0 }; + unsigned int layer_priority = 0; + + for (int i = 0 ; i < 3 ; i++) { + ret = mediaeditor_add_layer(GetHandle(), &layer_id[i], &layer_priority); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + } + + ret = mediaeditor_get_layer_lowest_priority(GetHandle(), &layer_priority); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + ret = mediaeditor_move_layer(GetHandle(), layer_id[0], layer_priority); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); +} + +TEST_F(MediaeditorLayerTest, DISABLED_layer_activate_deactivate) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + unsigned int layer_id = 0; + unsigned int layer_priority = 0; + + ret = mediaeditor_add_layer(GetHandle(), &layer_id, &layer_priority); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + ret = mediaeditor_activate_layer(GetHandle(), layer_id); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + ret = mediaeditor_deactivate_layer(GetHandle(), layer_id); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + ret = mediaeditor_remove_layer(GetHandle(), layer_id); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + ret = mediaeditor_activate_layer(GetHandle(), layer_id); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_INVALID_PARAMETER); + + ret = mediaeditor_deactivate_layer(GetHandle(), layer_id); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_INVALID_PARAMETER); +} diff --git a/gtest/ut_main.cpp b/gtest/ut_main.cpp new file mode 100644 index 0000000..9a19657 --- /dev/null +++ b/gtest/ut_main.cpp @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "gtest/gtest.h" + +int main(int argc, char *argv[]) { + ::testing::InitGoogleTest(&argc, argv); + + auto ret = -1; + ret = RUN_ALL_TESTS(); + + return ret; +} diff --git a/gtest/ut_project.cpp b/gtest/ut_project.cpp new file mode 100644 index 0000000..69d8e79 --- /dev/null +++ b/gtest/ut_project.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "testbase.hpp" + +static GMainLoop *mainloop; + +class MediaeditorProjectTest : public MediaeditorTestBase { +public: + MediaeditorProjectTest() {} + + static void TimelineLoadedCb(void *user_data) + { + LOGD("Enter"); + if (mainloop) + g_main_loop_quit(mainloop); + } + + static void WaitForAsync() + { + mainloop = g_main_loop_new(NULL, TRUE); + + LOGD("Wait timeline loaded cb"); + g_main_loop_run(mainloop); + + g_main_loop_unref(mainloop); + mainloop = NULL; + } +}; + +TEST_F(MediaeditorProjectTest, DISABLED_project_create_save) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + unsigned int layer_id = 0; + unsigned int layer_priority = 0; + unsigned int clip_id = 0; + unsigned int start = 0; + unsigned int duration = 100; + unsigned int in_point = 0; + + ret = mediaeditor_create_project(GetHandle(), GetProjectPath(0).c_str()); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + ret = mediaeditor_add_layer(GetHandle(), &layer_id, &layer_priority); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + ret = mediaeditor_add_clip(GetHandle(), GetMp4Path(0).c_str(), layer_id, start, duration, in_point, &clip_id); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + ret = mediaeditor_add_effect(GetHandle(), MEDIAEDITOR_EFFECT_VIDEO_TYPE_AGINGTV, layer_id, start, duration, &clip_id); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + ret = mediaeditor_save_project(GetHandle()); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); +} + +TEST_F(MediaeditorProjectTest, DISABLED_project_load_save) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + + ret = mediaeditor_load_project(GetHandle(), GetProjectPath(1).c_str(), TimelineLoadedCb, GetHandle()); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); + + WaitForAsync(); + + ret = mediaeditor_save_project(GetHandle()); + ASSERT_TRUE(ret == MEDIAEDITOR_ERROR_NONE); +} diff --git a/include/media_editor.h b/include/media_editor.h new file mode 100644 index 0000000..721579a --- /dev/null +++ b/include/media_editor.h @@ -0,0 +1,1226 @@ +/* + * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __TIZEN_MEDIA_EDITOR_H__ +#define __TIZEN_MEDIA_EDITOR_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @file media_editor.h + * @brief This file contains the media editor API, related structures and enumerations. + * @since_tizen 7.0 + */ + +/** + * @addtogroup CAPI_MEDIA_EDITOR_MODULE + * @{ + */ + +#ifndef TIZEN_ERROR_MEDIA_EDITOR +#define TIZEN_ERROR_MEDIA_EDITOR -0x030D0000 /* It'll be removed at end of ACR process */ +#endif + +/** + * @brief Enumeration for the error codes of media editor. + * @since_tizen 7.0 + */ +typedef enum { + MEDIAEDITOR_ERROR_NONE = TIZEN_ERROR_NONE, /**< Successful */ + MEDIAEDITOR_ERROR_NOT_SUPPORTED = TIZEN_ERROR_NOT_SUPPORTED, /**< Not supported */ + MEDIAEDITOR_ERROR_INVALID_OPERATION = TIZEN_ERROR_INVALID_OPERATION, /**< Internal error */ + MEDIAEDITOR_ERROR_INVALID_PARAMETER = TIZEN_ERROR_INVALID_PARAMETER, /**< Invalid parameter */ + MEDIAEDITOR_ERROR_PERMISSION_DENIED = TIZEN_ERROR_PERMISSION_DENIED, /**< The access to the resources can not be granted*/ + MEDIAEDITOR_ERROR_INVALID_STATE = TIZEN_ERROR_MEDIA_EDITOR | 0x01, /**< Invalid state */ + MEDIAEDITOR_ERROR_RESOURCE_CONFLICT = TIZEN_ERROR_MEDIA_EDITOR | 0x02, /**< Blocked by resource conflicted */ + MEDIAEDITOR_ERROR_RESOURCE_FAILED = TIZEN_ERROR_MEDIA_EDITOR | 0x03, /**< Blocked by resource failed */ +} mediaeditor_error_e; + +/** + * @brief Enumeration for the media editor state. + * @since_tizen 7.0 + */ +typedef enum { + MEDIAEDITOR_STATE_IDLE, /**< Created, but not started to render */ + MEDIAEDITOR_STATE_RENDERING, /**< Start to rendering and saving */ + MEDIAEDITOR_STATE_PREVIEW /**< Start to preview without saving */ +} mediaeditor_state_e; + +/** + * @brief Enumeration for the media editor display type. + * @since_tizen 7.0 + */ +typedef enum { + MEDIAEDITOR_DISPLAY_TYPE_OVERLAY, /**< Overlay surface display */ + MEDIAEDITOR_DISPLAY_TYPE_EVAS, /**< Evas object surface display */ + MEDIAEDITOR_DISPLAY_TYPE_ECORE, /**< Ecore object surface display */ + MEDIAEDITOR_DISPLAY_TYPE_NONE /**< This disposes of buffers */ +} mediaeditor_display_type_e; + +/** + * @} + */ + +/** + * @addtogroup CAPI_MEDIA_EDITOR_EFFECT_MODULE + * @{ + */ + +/** + * @brief Enumeration for the transition type. + * @since_tizen 7.0 + */ +typedef enum { + MEDIAEDITOR_TRANSITION_TYPE_NONE = 0, /**< Transition none */ + MEDIAEDITOR_TRANSITION_TYPE_BAR_WIPE_LR = 1, /**< A bar moves from left to right */ + MEDIAEDITOR_TRANSITION_TYPE_BAR_WIPE_TB = 2, /**< A bar moves from top to bottom */ + MEDIAEDITOR_TRANSITION_TYPE_BOX_WIPE_TL = 3, /**< A box expands from the upper-left corner to the lower-right corner */ + MEDIAEDITOR_TRANSITION_TYPE_BOX_WIPE_TR = 4, /**< A box expands from the upper-right corner to the lower-left corner */ + MEDIAEDITOR_TRANSITION_TYPE_BOX_WIPE_BR = 5, /**< A box expands from the lower-right corner to the upper-left corner */ + MEDIAEDITOR_TRANSITION_TYPE_BOX_WIPE_BL = 6, /**< A box expands from the lower-left corner to the upper-right corner */ + MEDIAEDITOR_TRANSITION_TYPE_FOUR_BOX_WIPE_CI = 7, /**< A box shape expands from each of the four corners toward the center */ + MEDIAEDITOR_TRANSITION_TYPE_FOUR_BOX_WIPE_CO = 8, /**< A box shape expands from the center of each quadrant toward the corners of each quadrant */ + MEDIAEDITOR_TRANSITION_TYPE_BARNDOOR_V = 21, /**< A central, vertical line splits and expands toward the left and right edges */ + MEDIAEDITOR_TRANSITION_TYPE_BARNDOOR_H = 22, /**< A central, horizontal line splits and expands toward the top and bottom edges */ + MEDIAEDITOR_TRANSITION_TYPE_BOX_WIPE_TC = 23, /**< A box expands from the top edge's midpoint to the bottom corners */ + MEDIAEDITOR_TRANSITION_TYPE_BOX_WIPE_RC = 24, /**< A box expands from the right edge's midpoint to the left corners */ + MEDIAEDITOR_TRANSITION_TYPE_BOX_WIPE_BC = 25, /**< A box expands from the bottom edge's midpoint to the top corners */ + MEDIAEDITOR_TRANSITION_TYPE_BOX_WIPE_LC = 26, /**< A box expands from the left edge's midpoint to the right corners */ + MEDIAEDITOR_TRANSITION_TYPE_DIAGONAL_TL = 41, /**< A diagonal line moves from the upper-left corner to the lower-right corner */ + MEDIAEDITOR_TRANSITION_TYPE_DIAGONAL_TR = 42, /**< A diagonal line moves from the upper right corner to the lower-left corner */ + MEDIAEDITOR_TRANSITION_TYPE_BOWTIE_V = 43, /**< Two wedge shapes slide in from the top and bottom edges toward the center */ + MEDIAEDITOR_TRANSITION_TYPE_BOWTIE_H = 44, /**< Two wedge shapes slide in from the left and right edges toward the center */ + MEDIAEDITOR_TRANSITION_TYPE_BARNDOOR_DBL = 45, /**< A diagonal line from the lower-left to upper-right corners splits and expands toward the opposite corners */ + MEDIAEDITOR_TRANSITION_TYPE_BARNDOOR_DTL = 46, /**< A diagonal line from upper-left to lower-right corners splits and expands toward the opposite corners */ + MEDIAEDITOR_TRANSITION_TYPE_MISC_DIAGONAL_DBD = 47, /**< Four wedge shapes split from the center and retract toward the four edges */ + MEDIAEDITOR_TRANSITION_TYPE_MISC_DIAGONAL_DD = 48, /**< A diamond connecting the four edge midpoints simultaneously contracts toward the center and expands toward the edges */ + MEDIAEDITOR_TRANSITION_TYPE_VEE_D = 61, /**< A wedge shape moves from top to bottom */ + MEDIAEDITOR_TRANSITION_TYPE_VEE_L = 62, /**< A wedge shape moves from right to left */ + MEDIAEDITOR_TRANSITION_TYPE_VEE_U = 63, /**< A wedge shape moves from bottom to top */ + MEDIAEDITOR_TRANSITION_TYPE_VEE_R = 64, /**< A wedge shape moves from left to right */ + MEDIAEDITOR_TRANSITION_TYPE_BARNVEE_D = 65, /**< A 'V' shape extending from the bottom edge's midpoint to the opposite corners contracts toward the center and expands toward the edges */ + MEDIAEDITOR_TRANSITION_TYPE_BARNVEE_L = 66, /**< A 'V' shape extending from the left edge's midpoint to the opposite corners contracts toward the center and expands toward the edges */ + MEDIAEDITOR_TRANSITION_TYPE_BARNVEE_U = 67, /**< A 'V' shape extending from the top edge's midpoint to the opposite corners contracts toward the center and expands toward the edges */ + MEDIAEDITOR_TRANSITION_TYPE_BARNVEE_R = 68, /**< A 'V' shape extending from the right edge's midpoint to the opposite corners contracts toward the center and expands toward the edges */ + MEDIAEDITOR_TRANSITION_TYPE_IRIS_RECT = 101, /**< A rectangle expands from the center. */ + MEDIAEDITOR_TRANSITION_TYPE_CLOCK_CW12 = 201, /**< A radial hand sweeps clockwise from the twelve o'clock position */ + MEDIAEDITOR_TRANSITION_TYPE_CLOCK_CW3 = 202, /**< A radial hand sweeps clockwise from the three o'clock position */ + MEDIAEDITOR_TRANSITION_TYPE_CLOCK_CW6 = 203, /**< A radial hand sweeps clockwise from the six o'clock position */ + MEDIAEDITOR_TRANSITION_TYPE_CLOCK_CW9 = 204, /**< A radial hand sweeps clockwise from the nine o'clock position */ + MEDIAEDITOR_TRANSITION_TYPE_PINWHEEL_TBV = 205, /**< Two radial hands sweep clockwise from the twelve and six o'clock positions */ + MEDIAEDITOR_TRANSITION_TYPE_PINWHEEL_TBH = 206, /**< Two radial hands sweep clockwise from the nine and three o'clock positions */ + MEDIAEDITOR_TRANSITION_TYPE_PINWHEEL_FB = 207, /**< Four radial hands sweep clockwise */ + MEDIAEDITOR_TRANSITION_TYPE_FAN_CT = 211, /**< A fan unfolds from the top edge, the fan axis at the center */ + MEDIAEDITOR_TRANSITION_TYPE_FAN_CR = 212, /**< A fan unfolds from the right edge, the fan axis at the center */ + MEDIAEDITOR_TRANSITION_TYPE_DOUBLEFAN_FOV = 213, /**< Two fans, their axes at the center, unfold from the top and bottom */ + MEDIAEDITOR_TRANSITION_TYPE_DOUBLEFAN_FOH = 214, /**< Two fans, their axes at the center, unfold from the left and right */ + MEDIAEDITOR_TRANSITION_TYPE_SINGLESWEEP_CWT = 221, /**< A radial hand sweeps clockwise from the top edge's midpoint */ + MEDIAEDITOR_TRANSITION_TYPE_SINGLESWEEP_CWR = 222, /**< A radial hand sweeps clockwise from the right edge's midpoint */ + MEDIAEDITOR_TRANSITION_TYPE_SINGLESWEEP_CWB = 223, /**< A radial hand sweeps clockwise from the bottom edge's midpoint */ + MEDIAEDITOR_TRANSITION_TYPE_SINGLESWEEP_CWL = 224, /**< A radial hand sweeps clockwise from the left edge's midpoint */ + MEDIAEDITOR_TRANSITION_TYPE_DOUBLESWEEP_PV = 225, /**< Two radial hands sweep clockwise and counter-clockwise from the top and bottom edges' midpoints */ + MEDIAEDITOR_TRANSITION_TYPE_DOUBLESWEEP_PD = 226, /**< Two radial hands sweep clockwise and counter-clockwise from the left and right edges' midpoints */ + MEDIAEDITOR_TRANSITION_TYPE_DOUBLESWEEP_OV = 227, /**< Two radial hands attached at the top and bottom edges' midpoints sweep from right to left */ + MEDIAEDITOR_TRANSITION_TYPE_DOUBLESWEEP_OH = 228, /**< Two radial hands attached at the left and right edges' midpoints sweep from top to bottom */ + MEDIAEDITOR_TRANSITION_TYPE_FAN_T = 231, /**< A fan unfolds from the bottom, the fan axis at the top edge's midpoint */ + MEDIAEDITOR_TRANSITION_TYPE_FAN_R = 232, /**< A fan unfolds from the left, the fan axis at the right edge's midpoint */ + MEDIAEDITOR_TRANSITION_TYPE_FAN_B = 233, /**< A fan unfolds from the top, the fan axis at the bottom edge's midpoint */ + MEDIAEDITOR_TRANSITION_TYPE_FAN_L = 234, /**< A fan unfolds from the right, the fan axis at the left edge's midpoint */ + MEDIAEDITOR_TRANSITION_TYPE_DOUBLEFAN_FIV = 235, /**< Two fans, their axes at the top and bottom, unfold from the center */ + MEDIAEDITOR_TRANSITION_TYPE_DOUBLEFAN_FIH = 236, /**< Two fans, their axes at the left and right, unfold from the center */ + MEDIAEDITOR_TRANSITION_TYPE_SINGLESWEEP_CWTL = 241, /**< A radial hand sweeps clockwise from the upper-left corner */ + MEDIAEDITOR_TRANSITION_TYPE_SINGLESWEEP_CCWBL = 242, /**< A radial hand sweeps counter-clockwise from the lower-left corner */ + MEDIAEDITOR_TRANSITION_TYPE_SINGLESWEEP_CWBR = 243, /**< A radial hand sweeps clockwise from the lower-right corner */ + MEDIAEDITOR_TRANSITION_TYPE_SINGLESWEEP_CCWTR = 244, /**< A radial hand sweeps counter-clockwise from the upper-right corner */ + MEDIAEDITOR_TRANSITION_TYPE_DOUBLESWEEP_PDTL = 245, /**< Two radial hands attached at the upper-left and lower-right corners sweep down and up */ + MEDIAEDITOR_TRANSITION_TYPE_DOUBLESWEEP_PDBL = 246, /**< Two radial hands attached at the lower-left and upper-right corners sweep down and up */ + MEDIAEDITOR_TRANSITION_TYPE_SALOONDOOR_T = 251, /**< Two radial hands attached at the upper-left and upper-right corners sweep down */ + MEDIAEDITOR_TRANSITION_TYPE_SALOONDOOR_L = 252, /**< Two radial hands attached at the upper-left and lower-left corners sweep to the right */ + MEDIAEDITOR_TRANSITION_TYPE_SALOONDOOR_B = 253, /**< Two radial hands attached at the lower-left and lower-right corners sweep up */ + MEDIAEDITOR_TRANSITION_TYPE_SALOONDOOR_R = 254, /**< Two radial hands attached at the upper-right and lower-right corners sweep to the left */ + MEDIAEDITOR_TRANSITION_TYPE_WINDSHIELD_R = 261, /**< Two radial hands attached at the midpoints of the top and bottom halves sweep from right to left */ + MEDIAEDITOR_TRANSITION_TYPE_WINDSHIELD_U = 262, /**< Two radial hands attached at the midpoints of the left and right halves sweep from top to bottom */ + MEDIAEDITOR_TRANSITION_TYPE_WINDSHIELD_V = 263, /**< Two sets of radial hands attached at the midpoints of the top and bottom halves sweep from top to bottom and bottom to top */ + MEDIAEDITOR_TRANSITION_TYPE_WINDSHIELD_H = 264, /**< Two sets of radial hands attached at the midpoints of the left and right halves sweep from left to right and right to left */ + MEDIAEDITOR_TRANSITION_TYPE_CROSSFADE = 512 /**< Crossfade */ +} mediaeditor_transition_type_e; + +/** + * @brief Enumeration for the effect type. + * @since_tizen 7.0 + * @remarks #MEDIAEDITOR_EFFECT_AUDIO_TYPE_FADE_IN and #MEDIAEDITOR_EFFECT_AUDIO_TYPE_FADE_OUT Can not be rolled back * + */ +typedef enum { + MEDIAEDITOR_EFFECT_TYPE_NONE, /**< None */ + MEDIAEDITOR_EFFECT_VIDEO_TYPE_EDGETV, /**< Applies edge detect on video */ + MEDIAEDITOR_EFFECT_VIDEO_TYPE_AGINGTV, /**< Adds age to video input using scratches and dust */ + MEDIAEDITOR_EFFECT_VIDEO_TYPE_DICETV, /**< Dices the screen up into many small squares */ + MEDIAEDITOR_EFFECT_VIDEO_TYPE_WARPTV, /**< Realtime goo'ing of the video input */ + MEDIAEDITOR_EFFECT_VIDEO_TYPE_SHAGADELICTV, /**< Makes images shagadelic */ + MEDIAEDITOR_EFFECT_VIDEO_TYPE_VERTIGOTV, /**< A loopback alpha blending effector with rotating and scaling */ + MEDIAEDITOR_EFFECT_VIDEO_TYPE_REVTV, /**< A video waveform monitor for each line of video processed */ + MEDIAEDITOR_EFFECT_VIDEO_TYPE_QUARKTV, /**< Motion dissolver */ + MEDIAEDITOR_EFFECT_VIDEO_TYPE_OPTV, /**< Optical art meets real-time video effect */ + MEDIAEDITOR_EFFECT_VIDEO_TYPE_RADIOACTV, /**< Detects a difference from previous frame and blurs it */ + MEDIAEDITOR_EFFECT_VIDEO_TYPE_STREAKTV, /**< Makes after images of moving objects */ + MEDIAEDITOR_EFFECT_VIDEO_TYPE_RIPPLETV, /**< Makes ripple mark effect on the video input */ + MEDIAEDITOR_EFFECT_AUDIO_TYPE_FADE_IN, /**< Audio fade in */ + MEDIAEDITOR_EFFECT_AUDIO_TYPE_FADE_OUT, /**< Audio fade out */ + MEDIAEDITOR_EFFECT_AUDIO_TYPE_ECHO /**< Adds an echo or reverb effect to an audio stream */ +} mediaeditor_effect_type_e; + +/** + * @} + */ + +/** + * @addtogroup CAPI_MEDIA_EDITOR_MODULE + * @{ + */ + + +/** + * @brief The media editor handle. + * @since_tizen 7.0 + */ +typedef void *mediaeditor_h; + +/** + * @brief The media editor display handle type. + * @since_tizen 7.0 + */ +typedef void *mediaeditor_display_h; + +/** + * @brief Called when an error occurs. + * @details The following error codes can be received:\n + * #MEDIAEDITOR_ERROR_INVALID_OPERATION\n + * #MEDIAEDITOR_ERROR_INVALID_STATE\n + * #MEDIAEDITOR_ERROR_RESOURCE_CONFLICT\n + * #MEDIAEDITOR_ERROR_RESOURCE_FAILED + * + * @since_tizen 7.0 + * @param[in] error The error code + * @param[in] state The state when error was occurred + * @param[in] user_data The user data passed from the callback registration function + * @see mediaeditor_set_error_cb() + * @see mediaeditor_unset_error_cb() + */ +typedef void (*mediaeditor_error_cb)(mediaeditor_error_e error, mediaeditor_state_e state, void *user_data); + +/** + * @brief Called when the state of media editor is changed. + * @since_tizen 7.0 + * @param[in] previous The previous state + * @param[in] current The current state + * @param[in] user_data The user data passed from the callback registration function + * @see mediaeditor_set_state_changed_cb() + * @see mediaeditor_unset_state_changed_cb() + */ +typedef void (*mediaeditor_state_changed_cb)(mediaeditor_state_e previous, mediaeditor_state_e current, void *user_data); + +/** + * @brief Called when rendering output is completed. + * @since_tizen 7.0 + * @param[in] user_data The user data passed from the callback registration function + * @see mediaeditor_start_render() + */ +typedef void (*mediaeditor_render_completed_cb)(void *user_data); + +/** + * @brief Creates a new media editor handle. + * @since_tizen 7.0 + * @remarks The @a editor must be released using mediaeditor_destroy().\n + * The timeline which is composed of a set of layers and clips will be created. + * @param[out] editor A newly returned handle to the media editor + * @return @c 0 on success, otherwise a negative error value + * @retval #MEDIAEDITOR_ERROR_NONE Successful + * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter + * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation + * @retval #MEDIAEDITOR_ERROR_RESOURCE_FAILED Resource manager initialization error + * @post @a editor state will be #MEDIAEDITOR_STATE_IDLE. + * + * @see mediaeditor_destroy() + */ +int mediaeditor_create(mediaeditor_h *editor); + +/** + * @brief Destroys a media editor handle and releases all its resources. + * @since_tizen 7.0 + * @param[in] editor The media editor handle + * @return @c 0 on success, otherwise a negative error value + * @retval #MEDIAEDITOR_ERROR_NONE Successful + * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter + * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation + * @retval #MEDIAEDITOR_ERROR_RESOURCE_FAILED Resource manager deinitialization error + * + * @see mediaeditor_create() + */ +int mediaeditor_destroy(mediaeditor_h editor); + +/** + * @brief Sets a display for preview. + * @since_tizen 7.0 + * @param[in] editor The media editor handle + * @param[in] type The display type + * @param[in] display The display handle + * @return @c 0 on success, otherwise a negative error value + * @retval #MEDIAEDITOR_ERROR_NONE Successful + * @retval #MEDIAEDITOR_ERROR_NOT_SUPPORTED The feature is not supported + * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter + * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation + * @retval #MEDIAEDITOR_ERROR_INVALID_STATE Invalid state + * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE. + * + * @see mediaeditor_start_preview() + */ +int mediaeditor_set_display(mediaeditor_h editor, mediaeditor_display_type_e type, mediaeditor_display_h display); + +/** + * @brief Gets the current state. + * @since_tizen 7.0 + * @param[in] editor The media editor handle + * @param[out] state The media editor state + * @return @c 0 on success, otherwise a negative error value + * @retval #MEDIAEDITOR_ERROR_NONE Successful + * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter + * + * @see #mediaeditor_state_e + */ +int mediaeditor_get_state(mediaeditor_h editor, mediaeditor_state_e *state); + +/** + * @brief Starts to render with given path, asynchronously. + * @since_tizen 7.0 + * @privlevel public + * @privilege %http://tizen.org/privilege/mediastorage + * @privilege %http://tizen.org/privilege/externalstorage + * @remarks If you want to access only internal storage by using this function, you should add privilege %http://tizen.org/privilege/mediastorage.\n + * Or if you want to access only external storage by using this function, you should add privilege %http://tizen.org/privilege/externalstorage.\n + * If you want to access both storage, you must add both privileges. + * @param[in] editor The media editor handle + * @param[in] path The path to save rendered output + * @param[in] callback The callback function to register + * @param[in] user_data The user data to be passed to the callback function + * @return @c 0 on success, otherwise a negative error value + * @retval #MEDIAEDITOR_ERROR_NONE Successful + * @retval #MEDIAEDITOR_ERROR_PERMISSION_DENIED Permission denied + * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter + * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation + * @retval #MEDIAEDITOR_ERROR_INVALID_STATE Invalid state + * @pre User must add clip by calling mediaeditor_add_clip(). + * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE. + * @post @a editor state will be #MEDIAEDITOR_STATE_RENDERING. + * @post mediaeditor_render_completed_cb() will be invoked. + * @see mediaeditor_add_clip() + * @see mediaeditor_cancel_render() + */ +int mediaeditor_start_render(mediaeditor_h editor, const char *path, mediaeditor_render_completed_cb callback, void *user_data); + +/** + * @brief Cancels rendering. + * @since_tizen 7.0 + * @param[in] editor The media editor handle + * @return @c 0 on success, otherwise a negative error value + * @retval #MEDIAEDITOR_ERROR_NONE Successful + * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter + * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation + * @retval #MEDIAEDITOR_ERROR_INVALID_STATE Invalid state + * @pre @a editor state must be set to #MEDIAEDITOR_STATE_RENDERING. + * @post @a editor state will be #MEDIAEDITOR_STATE_IDLE. + * @see mediaeditor_start_render() + */ +int mediaeditor_cancel_render(mediaeditor_h editor); + +/** + * @brief Starts to preview. + * @since_tizen 7.0 + * @param[in] editor The media editor handle + * @return @c 0 on success, otherwise a negative error value + * @retval #MEDIAEDITOR_ERROR_NONE Successful + * @retval #MEDIAEDITOR_ERROR_NOT_SUPPORTED The feature is not supported + * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter + * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation + * @retval #MEDIAEDITOR_ERROR_INVALID_STATE Invalid state + * @pre The display handle must be set by calling mediaeditor_set_display() + * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE. + * @post @a editor state will be #MEDIAEDITOR_STATE_PREVIEW. + * @see mediaeditor_set_display() + */ +int mediaeditor_start_preview(mediaeditor_h editor); + +/** + * @brief Stops to preview. + * @since_tizen 7.0 + * @param[in] editor The media editor handle + * @return @c 0 on success, otherwise a negative error value + * @retval #MEDIAEDITOR_ERROR_NONE Successful + * @retval #MEDIAEDITOR_ERROR_NOT_SUPPORTED The feature is not supported + * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter + * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation + * @retval #MEDIAEDITOR_ERROR_INVALID_STATE Invalid state + * @pre @a editor state must be set to #MEDIAEDITOR_STATE_PREVIEW. + * @post @a editor state will be #MEDIAEDITOR_STATE_IDLE. + * @see mediaeditor_start_preview() + */ +int mediaeditor_stop_preview(mediaeditor_h editor); + +/** + * @brief Sets a callback function to be invoked when an asynchronous operation error occurs. + * @since_tizen 7.0 + * @param[in] editor The media editor handle + * @param[in] callback Callback function pointer + * @param[in] user_data The user data to be passed to the callback function + * @return @c 0 on success, + * otherwise a negative error value + * @retval #MEDIAEDITOR_ERROR_NONE Successful + * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter + * @retval #MEDIAEDITOR_ERROR_INVALID_STATE Invalid state + * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE. + * @post mediaeditor_error_cb() will be invoked. + * @see mediaeditor_unset_error_cb() + * @see mediaeditor_error_cb() + */ +int mediaeditor_set_error_cb(mediaeditor_h editor, mediaeditor_error_cb callback, void *user_data); + +/** + * @brief Unsets an error callback function. + * @since_tizen 7.0 + * @param[in] editor The media editor handle + * @return @c 0 on success, + * otherwise a negative error value + * @retval #MEDIAEDITOR_ERROR_NONE Successful + * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter + * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation + * @retval #MEDIAEDITOR_ERROR_INVALID_STATE Invalid state + * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE. + * @see mediaeditor_set_error_cb() + */ +int mediaeditor_unset_error_cb(mediaeditor_h editor); + +/** + * @brief Sets a callback function to be invoked when the media editor state is changed. + * @since_tizen 7.0 + * @param[in] editor The media editor handle + * @param[in] callback Callback function pointer + * @param[in] user_data The user data to be passed to the callback function + * @return @c 0 on success, + * otherwise a negative error value + * @retval #MEDIAEDITOR_ERROR_NONE Successful + * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter + * @retval #MEDIAEDITOR_ERROR_INVALID_STATE Invalid state + * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE. + * @post mediaeditor_state_changed_cb() will be invoked. + * @see mediaeditor_unset_state_changed_cb() + * @see mediaeditor_state_changed_cb() + */ +int mediaeditor_set_state_changed_cb(mediaeditor_h editor, mediaeditor_state_changed_cb callback, void *user_data); + +/** + * @brief Unsets a state changed callback function. + * @since_tizen 7.0 + * @param[in] editor The media editor handle + * @return @c 0 on success, + * otherwise a negative error value + * @retval #MEDIAEDITOR_ERROR_NONE Successful + * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter + * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation + * @retval #MEDIAEDITOR_ERROR_INVALID_STATE Invalid state + * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE. + * @see mediaeditor_set_state_changed_cb() + */ +int mediaeditor_unset_state_changed_cb(mediaeditor_h editor); + +/** + * @} + */ + +/** + * @addtogroup CAPI_MEDIA_EDITOR_LAYER_MODULE + * @{ + */ + +/** + * @brief The structure type of the layer information. + * @since_tizen 7.0 + */ +typedef struct { + unsigned int id; /**< The layer ID */ + unsigned int priority; /**< The layer priority */ +} mediaeditor_layer_info_s; + +/** + * @brief Called when the priority of layers is changed. + * @since_tizen 7.0 + * @remarks @a layer_info should be released using free(). + * @param[in] layer_infos The layer information including layer id and its priority. It's array. + * @param[in] size The number of layer_infos array + * @param[in] user_data The user data passed from the callback registration function + * @see mediaeditor_set_layer_priority_changed_cb() + * @see mediaeditor_unset_layer_priority_changed_cb() + */ +typedef void (*mediaeditor_layer_priority_changed_cb)(mediaeditor_layer_info_s *layer_infos, unsigned int size, void *user_data); + +/** + * @brief Adds a layer to timeline. + * @since_tizen 7.0 + * @remarks Layers are responsible for ordering of contained clips. The order is determined by layers' priorities.\n + * The layers are stacked in a hierarchical structure.\n + * e.g. If we have 3 layers, it will have the following hierarchy.\n + *
+ *          Top    : layer ID 1, layer priority 0 (The highest priority)
+ *                   layer ID 2, layer priority 1
+ *          Bottom : layer ID 3, layer priority 2 (The lowest priority)
+ * 
+ * Priorities are always a continuous sequence, with no numbers missing in-between.\n + * For example, priorities 0, 1, 3, 4 are not possible.\n + * But, layer Id could be a discontinuous sequence. Please refer to examples of mediaeditor_remove_layer().\n + * The @a layer_priority of newly added layer will be lowest priority. + * @param[in] editor The media editor handle + * @param[out] layer_id The layer ID. It'll be used when you want to control this layer. + * @param[out] layer_priority The layer priority represents the hierarchical ordering of contained clips. + * @return @c 0 on success, otherwise a negative error value + * @retval #MEDIAEDITOR_ERROR_NONE Successful + * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter + * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation + * @retval #MEDIAEDITOR_ERROR_INVALID_STATE Invalid state + * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE. + * @see mediaeditor_remove_layer() + * @see mediaeditor_move_layer() + * @see mediaeditor_activate_layer() + * @see mediaeditor_deactivate_layer() + */ +int mediaeditor_add_layer(mediaeditor_h editor, unsigned int *layer_id, unsigned int *layer_priority); + +/** + * @brief Removes a layer from timeline. + * @since_tizen 7.0 + * @remarks The other layer's layer ID is not changed after removing @a layer_id.\n + * If @a layer_id is not bottom layer, layer priority will be rearranged.\n + * e.g. There're 3 layers like the followings.\n + * \n + * case 1 : remove layer ID 3.\n + * The layer ID 3 is removed and other layers' priorities are not rearranged.\n + *
+ *                   | top    : layer ID 1, layer priority 0 (including 1 clips)
+ *          Before   |          layer ID 2, layer priority 1 (including 3 clips)
+ *          removing | bottom : layer ID 3, layer priority 2 (including 5 clips)
+ *          --------------------------------------------------------------------
+ *          After    | top    : layer ID 1, layer priority 0 (including 1 clips)
+ *          removing | bottom : layer ID 2, layer priority 1 (including 3 clips)
+ * 
+ * \n + * case 2 : remove layer ID 1.\n + *
+ *                   | top    : layer ID 1, layer priority 0 (including 1 clips)
+ *          Before   |          layer ID 2, layer priority 1 (including 3 clips)
+ *          removing | bottom : layer ID 3, layer priority 2 (including 5 clips)
+ *          --------------------------------------------------------------------
+ *          After    | top    : layer ID 2, layer priority 0 (including 3 clips)
+ *          removing | bottom : layer ID 3, layer priority 1 (including 5 clips)
+ * 
+ * \n + * * case 3 : remove layer ID 2 and add a new layer.\n + *
+ *                   | top    : layer ID 1, layer priority 0 (including 1 clips)
+ *          Before   |          layer ID 2, layer priority 1 (including 3 clips)
+ *          removing | bottom : layer ID 3, layer priority 2 (including 5 clips)
+ *          --------------------------------------------------------------------
+ *          After    | top    : layer ID 1, layer priority 0 (including 1 clips)
+ *          removing | bottom : layer ID 3, layer priority 1 (including 5 clips)
+ *          --------------------------------------------------------------------
+ *          After    | top    : layer ID 1, layer priority 0 (including 1 clips)
+ *          adding   |        : layer ID 3, layer priority 1 (including 5 clips)
+ *          a layer  | bottom : layer ID 4, layer priority 2 (including 7 clips)
+ * 
+ * @param[in] editor The media editor handle + * @param[in] layer_id The layer ID + * @return @c 0 on success, otherwise a negative error value + * @retval #MEDIAEDITOR_ERROR_NONE Successful + * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter + * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation + * @retval #MEDIAEDITOR_ERROR_INVALID_STATE Invalid state + * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE. + * @post mediaeditor_layer_priority_changed_cb() will be invoked if it's set. + * @see mediaeditor_add_layer() + * @see mediaeditor_set_layer_priority_changed_cb() + * @see mediaeditor_unset_layer_priority_changed_cb() + */ +int mediaeditor_remove_layer(mediaeditor_h editor, unsigned int layer_id); + +/** + * @brief Moves a @a layer_id layer to @a layer_priority position. + * @since_tizen 7.0 + * @remarks e.g. There're 3 layers.\n + * case 1 : move layer ID 1 to layer priority 0.\n + * Nothing happens.\n + *
+ *                 | top    : layer ID 1, layer priority 0 (including 1 clips)
+ *          Before |          layer ID 2, layer priority 1 (including 3 clips)
+ *          moving | bottom : layer ID 3, layer priority 2 (including 5 clips)
+ *          ------------------------------------------------------------------
+ *                 | top    : layer ID 1, layer priority 0 (including 1 clips)
+ *          After  |          layer ID 2, layer priority 1 (including 3 clips)
+ *          moving | bottom : layer ID 3, layer priority 2 (including 5 clips)
+ * 
+ * \n + * case 2 : move layer ID 3 to layer priority 1.\n + *
+ *                 | top    : layer ID 1, layer priority 0 (including 1 clips)
+ *          Before |          layer ID 2, layer priority 1 (including 3 clips)
+ *          moving | bottom : layer ID 3, layer priority 2 (including 5 clips)
+ *          ------------------------------------------------------------------
+ *                 | top    : layer ID 1, layer priority 0 (including 1 clips)
+ *          After  |          layer ID 3, layer priority 1 (including 5 clips)
+ *          moving | bottom : layer ID 2, layer priority 2 (including 3 clips)
+ * 
+ * \n + * case 3 : move layer ID 1 to layer priority 3.\n + * (Currently, there's no priority 3 layer.)\n + *
+ *                 | top    : layer ID 1, layer priority 0 (including 1 clips)
+ *          Before |          layer ID 2, layer priority 1 (including 3 clips)
+ *          moving | bottom : layer ID 3, layer priority 2 (including 5 clips)
+ *          ------------------------------------------------------------------
+ *                 | top    : layer ID 2, layer priority 0 (including 3 clips)
+ *          After  |          layer ID 3, layer priority 1 (including 5 clips)
+ *          moving | bottom : layer ID 1, layer priority 2 (including 1 clips)
+ * 
+ * \n + * case 4 : move layer ID 1 to layer priority 5.\n + * (Currently, there's no priority 5 layer and it's not continuous sequence.)\n + *
+ *                 | top    : layer ID 1, layer priority 0 (including 1 clips)
+ *          Before |          layer ID 2, layer priority 1 (including 3 clips)
+ *          moving | bottom : layer ID 3, layer priority 2 (including 5 clips)
+ *          ------------------------------------------------------------------
+ *                 | top    : layer ID 2, layer priority 0 (including 3 clips)
+ *          After  |          layer ID 3, layer priority 1 (including 5 clips)
+ *          moving | bottom : layer ID 1, layer priority 2 (including 1 clips)
+ * 
+ * \n + * If you can move layer to the lowest priority position, you can get the lowest priority using mediaeditor_get_layer_lowest_priority() + * @param[in] editor The media editor handle + * @param[in] layer_id The layer ID + * @param[in] layer_priority The target layer priority that @a layer_id is moved + * @return @c 0 on success, otherwise a negative error value + * @retval #MEDIAEDITOR_ERROR_NONE Successful + * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter + * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation + * @retval #MEDIAEDITOR_ERROR_INVALID_STATE Invalid state + * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE. + * @post mediaeditor_layer_priority_changed_cb() will be invoked if it's set. + * @see mediaeditor_add_layer() + * @see mediaeditor_get_layer_lowest_priority() + * @see mediaeditor_set_layer_priority_changed_cb() + * @see mediaeditor_unset_layer_priority_changed_cb() + */ +int mediaeditor_move_layer(mediaeditor_h editor, unsigned int layer_id, unsigned int layer_priority); + +/** + * @brief Activates given layer on timeline. The layer will be included when it's rendered. + * @since_tizen 7.0 + * @remarks Note that the newly created layer will be activated by default. + * @param[in] editor The media editor handle + * @param[in] layer_id The layer ID + * @return @c 0 on success, otherwise a negative error value + * @retval #MEDIAEDITOR_ERROR_NONE Successful + * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter + * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation + * @retval #MEDIAEDITOR_ERROR_INVALID_STATE Invalid state + * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE. + * @see mediaeditor_deactivate_layer() + */ +int mediaeditor_activate_layer(mediaeditor_h editor, unsigned int layer_id); + +/** + * @brief Deactivates given layer on timeline. + * @since_tizen 7.0 + * @remarks The layer is not removed actually but just excluded when it's rendered. + * @param[in] editor The media editor handle + * @param[in] layer_id The layer ID + * @return @c 0 on success, otherwise a negative error value + * @retval #MEDIAEDITOR_ERROR_NONE Successful + * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter + * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation + * @retval #MEDIAEDITOR_ERROR_INVALID_STATE Invalid state + * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE. + * @see mediaeditor_activate_layer() + */ +int mediaeditor_deactivate_layer(mediaeditor_h editor, unsigned int layer_id); + +/** + * @brief Gets the priority of @a layer_id layer. + * @since_tizen 7.0 + * @param[in] editor The media editor handle + * @param[in] layer_id The layer ID + * @param[out] layer_priority The layer priority + * @return @c 0 on success, otherwise a negative error value + * @retval #MEDIAEDITOR_ERROR_NONE Successful + * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter + * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation + * @see mediaeditor_add_layer() + * @see mediaeditor_get_layer_lowest_priority() + * @see mediaeditor_get_layer_id() + */ +int mediaeditor_get_layer_priority(mediaeditor_h editor, unsigned int layer_id, unsigned int *layer_priority); + +/** + * @brief Gets the lowest priority of all layers. + * @since_tizen 7.0 + * @param[in] editor The media editor handle + * @param[out] layer_priority The lowest layer priority + * @return @c 0 on success, otherwise a negative error value + * @retval #MEDIAEDITOR_ERROR_NONE Successful + * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter + * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation + * @see mediaeditor_add_layer() + * @see mediaeditor_get_layer_priority() + * @see mediaeditor_get_layer_id() + */ +int mediaeditor_get_layer_lowest_priority(mediaeditor_h editor, unsigned int *layer_priority); + +/** + * @brief Gets the layer ID of @a layer_priority layer. + * @since_tizen 7.0 + * @param[in] editor The media editor handle + * @param[in] layer_priority The layer priority + * @param[out] layer_id The layer ID + * @return @c 0 on success, otherwise a negative error value + * @retval #MEDIAEDITOR_ERROR_NONE Successful + * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter + * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation + * @see mediaeditor_add_layer() + * @see mediaeditor_get_layer_priority() + * @see mediaeditor_get_layer_lowest_priority() + */ +int mediaeditor_get_layer_id(mediaeditor_h editor, unsigned int layer_priority, unsigned int *layer_id); + +/** + * @brief Sets a callback function to be invoked when a layer priority is changed. + * @since_tizen 7.0 + * @param[in] editor The media editor handle + * @param[in] callback Callback function pointer + * @param[in] user_data The user data to be passed to the callback function + * @return @c 0 on success, + * otherwise a negative error value + * @retval #MEDIAEDITOR_ERROR_NONE Successful + * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter + * @retval #MEDIAEDITOR_ERROR_INVALID_STATE Invalid state + * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE. + * @post mediaeditor_layer_priority_changed_cb() will be invoked. + * @see mediaeditor_unset_layer_priority_changed_cb() + * @see mediaeditor_layer_priority_changed_cb() + */ +int mediaeditor_set_layer_priority_changed_cb(mediaeditor_h editor, mediaeditor_layer_priority_changed_cb callback, void *user_data); + +/** + * @brief Unsets a layer priority changed callback function. + * @since_tizen 7.0 + * @param[in] editor The media editor handle + * @return @c 0 on success, + * otherwise a negative error value + * @retval #MEDIAEDITOR_ERROR_NONE Successful + * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter + * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation + * @retval #MEDIAEDITOR_ERROR_INVALID_STATE Invalid state + * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE. + * @see mediaeditor_set_layer_priority_changed_cb() + */ +int mediaeditor_unset_layer_priority_changed_cb(mediaeditor_h editor); + +/** + * @} + */ + +/** + * @addtogroup CAPI_MEDIA_EDITOR_CLIP_MODULE + * @{ + */ + +/** + * @brief Adds a clip to timeline. + * @since_tizen 7.0 + * @privlevel public + * @privilege %http://tizen.org/privilege/mediastorage + * @privilege %http://tizen.org/privilege/externalstorage + * @remarks If you want to access only internal storage by using this function, you should add privilege %http://tizen.org/privilege/mediastorage.\n + * Or if you want to access only external storage by using this function, you should add privilege %http://tizen.org/privilege/externalstorage.\n + * If you want to access both storage, you must add both privileges. + * @param[in] editor The media editor handle + * @param[in] path The content location to add + * @param[in] layer_id The layer ID to add clip + * @param[in] start The starting position of @a path clip which is placed in timeline (in milliseconds)\n + * If this is less than 0, clip will be added to the end of layer. i.e. it will be set to layer's duration + * @param[in] duration The duration that the clip is in effect for in the timeline (in milliseconds)\n + * It should be lesser than or equal to clip's duration. If not, error will be occurred in runtime.\n + * The clip should have enough internal content greater than @a duration. + * @param[in] in_point The initial offset of @a path clip to use internally when outputting content (in milliseconds) + * @param[out] clip_id The ID of added clip. It'll be used when you want to delete this clip. + * @return @c 0 on success, otherwise a negative error value + * @retval #MEDIAEDITOR_ERROR_NONE Successful + * @retval #MEDIAEDITOR_ERROR_PERMISSION_DENIED Permission denied + * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter + * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation + * @retval #MEDIAEDITOR_ERROR_INVALID_STATE Invalid state + * @pre The layer must be added by calling mediaeditor_add_layer(). + * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE. + * @see mediaeditor_add_layer() + * @see mediaeditor_remove_clip() + */ +int mediaeditor_add_clip(mediaeditor_h editor, const char *path, unsigned int layer_id, + int start, unsigned int duration, unsigned int in_point, unsigned int *clip_id); + +/** + * @brief Removes a clip from timeline. + * @since_tizen 7.0 + * @param[in] editor The media editor handle + * @param[in] clip_id The clip ID that will be removed. + * @return @c 0 on success, otherwise a negative error value + * @retval #MEDIAEDITOR_ERROR_NONE Successful + * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter + * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation + * @retval #MEDIAEDITOR_ERROR_INVALID_STATE Invalid state + * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE. + * @see mediaeditor_add_clip() + */ +int mediaeditor_remove_clip(mediaeditor_h editor, unsigned int clip_id); + +/** + * @brief Splits a clip. + * @since_tizen 7.0 + * @remarks After splitting the clip into two clips, the source clip's duration will be changed. + * @param[in] editor The media editor handle + * @param[in] src_clip_id The current clip ID that will be split. + * @param[in] position The time position at which the source clip will be split. (in milliseconds) + * @param[out] new_clip_id The newly created clip ID. + * @return @c 0 on success, otherwise a negative error value + * @retval #MEDIAEDITOR_ERROR_NONE Successful + * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter + * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation + * @retval #MEDIAEDITOR_ERROR_INVALID_STATE Invalid state + * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE. + * @see mediaeditor_add_clip() + */ +int mediaeditor_split_clip(mediaeditor_h editor, unsigned int src_clip_id, unsigned int position, + unsigned int *new_clip_id); + +/** + * @brief Groups clips. + * @since_tizen 7.0 + * @param[in] editor The media editor handle + * @param[in] clip_ids The clip IDs to be grouped. Array. + * @param[in] size The number of clips + * @param[out] group_id The grouped clip ID. + * @return @c 0 on success, otherwise a negative error value + * @retval #MEDIAEDITOR_ERROR_NONE Successful + * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter + * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation + * @retval #MEDIAEDITOR_ERROR_INVALID_STATE Invalid state + * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE. + * @see mediaeditor_add_clip() + * @see mediaeditor_ungroup_clip() + */ +int mediaeditor_group_clip(mediaeditor_h editor, unsigned int *clip_ids, unsigned int size, unsigned int *group_id); + +/** + * @brief Ungroups a clip. + * @since_tizen 7.0 + * @remarks Layer priorities of ungrouped clips are the same as before grouping.\n + * If there's no matched @a group_id, #MEDIAEDITOR_ERROR_INVALID_PARAMETER will be returned.\n + * @a clip_ids should be released using free(). + * @param[in] editor The media editor handle + * @param[in] group_id The grouped clip ID + * @param[out] clip_ids The ungrouped clip IDs. Array + * @param[out] size The size of ungrouped clips + * @return @c 0 on success, otherwise a negative error value + * @retval #MEDIAEDITOR_ERROR_NONE Successful + * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter + * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation + * @retval #MEDIAEDITOR_ERROR_INVALID_STATE Invalid state + * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE. + * @see mediaeditor_group_clip() + */ +int mediaeditor_ungroup_clip(mediaeditor_h editor, unsigned int group_id, unsigned int **clip_ids, unsigned int *size); + +/** + * @brief Moves a clip to different layer. + * @since_tizen 7.0 + * @param[in] editor The media editor handle + * @param[in] clip_id The clip ID that will be moved + * @param[in] layer_priority The destination layer priority + * @return @c 0 on success, otherwise a negative error value + * @retval #MEDIAEDITOR_ERROR_NONE Successful + * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter + * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation + * @retval #MEDIAEDITOR_ERROR_INVALID_STATE Invalid state + * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE. + * @see mediaeditor_add_clip() + */ +int mediaeditor_move_clip_layer(mediaeditor_h editor, unsigned int clip_id, unsigned int layer_priority); + +/** + * @brief Gets the start position of clip. + * @since_tizen 7.0 + * @param[in] editor The media editor handle + * @param[in] clip_id The clip ID + * @param[out] start The current start position of clip (in milliseconds) + * @return @c 0 on success, otherwise a negative error value + * @retval #MEDIAEDITOR_ERROR_NONE Successful + * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter + * @see mediaeditor_add_clip() + * @see mediaeditor_set_clip_start() + * @see mediaeditor_get_clip_duration() + * @see mediaeditor_set_clip_duration() + * @see mediaeditor_get_clip_in_point() + * @see mediaeditor_set_clip_in_point() + */ +int mediaeditor_get_clip_start(mediaeditor_h editor, unsigned int clip_id, unsigned int *start); + +/** + * @brief Sets the start position of clip. + * @since_tizen 7.0 + * @param[in] editor The media editor handle + * @param[in] clip_id The clip ID that will be changed + * @param[in] start The new start position (in milliseconds) + * @return @c 0 on success, otherwise a negative error value + * @retval #MEDIAEDITOR_ERROR_NONE Successful + * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter + * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation + * @retval #MEDIAEDITOR_ERROR_INVALID_STATE Invalid state + * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE. + * @see mediaeditor_add_clip() + * @see mediaeditor_get_clip_start() + * @see mediaeditor_get_clip_duration() + * @see mediaeditor_set_clip_duration() + * @see mediaeditor_get_clip_in_point() + * @see mediaeditor_set_clip_in_point() + */ +int mediaeditor_set_clip_start(mediaeditor_h editor, unsigned int clip_id, unsigned int start); + +/** + * @brief Gets the duration of clip. + * @since_tizen 7.0 + * @param[in] editor The media editor handle + * @param[in] clip_id The clip ID + * @param[out] duration The current time duration of clip (in milliseconds) + * @return @c 0 on success, otherwise a negative error value + * @retval #MEDIAEDITOR_ERROR_NONE Successful + * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter + * @see mediaeditor_add_clip() + * @see mediaeditor_get_clip_start() + * @see mediaeditor_set_clip_start() + * @see mediaeditor_set_clip_duration() + * @see mediaeditor_get_clip_in_point() + * @see mediaeditor_set_clip_in_point() + */ +int mediaeditor_get_clip_duration(mediaeditor_h editor, unsigned int clip_id, unsigned int *duration); + +/** + * @brief Sets the duration of clip. + * @since_tizen 7.0 + * @param[in] editor The media editor handle + * @param[in] clip_id The clip ID that will be changed + * @param[in] duration The new time duration (in milliseconds) + * @return @c 0 on success, otherwise a negative error value + * @retval #MEDIAEDITOR_ERROR_NONE Successful + * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter + * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation + * @retval #MEDIAEDITOR_ERROR_INVALID_STATE Invalid state + * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE. + * @see mediaeditor_add_clip() + * @see mediaeditor_get_clip_start() + * @see mediaeditor_set_clip_start() + * @see mediaeditor_get_clip_duration() + * @see mediaeditor_get_clip_in_point() + * @see mediaeditor_set_clip_in_point() + */ +int mediaeditor_set_clip_duration(mediaeditor_h editor, unsigned int clip_id, unsigned int duration); + +/** + * @brief Gets the offset of clip. + * @since_tizen 7.0 + * @param[in] editor The media editor handle + * @param[in] clip_id The clip ID that will be changed + * @param[out] in_point The offset of clip to use internally when outputting content (in milliseconds) + * @return @c 0 on success, otherwise a negative error value + * @retval #MEDIAEDITOR_ERROR_NONE Successful + * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter + * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation + * @see mediaeditor_add_clip() + * @see mediaeditor_get_clip_start() + * @see mediaeditor_set_clip_start() + * @see mediaeditor_get_clip_duration() + * @see mediaeditor_set_clip_duration() + * @see mediaeditor_set_clip_in_point() + */ +int mediaeditor_get_clip_in_point(mediaeditor_h editor, unsigned int clip_id, unsigned int *in_point); + +/** + * @brief Sets the offset of clip. + * @since_tizen 7.0 + * @param[in] editor The media editor handle + * @param[in] clip_id The clip ID + * @param[in] in_point The offset of clip to use internally when outputting content (in milliseconds) + * @return @c 0 on success, otherwise a negative error value + * @retval #MEDIAEDITOR_ERROR_NONE Successful + * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter + * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation + * @retval #MEDIAEDITOR_ERROR_INVALID_STATE Invalid state + * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE. + * @see mediaeditor_add_clip() + * @see mediaeditor_get_clip_start() + * @see mediaeditor_set_clip_start() + * @see mediaeditor_get_clip_duration() + * @see mediaeditor_set_clip_duration() + * @see mediaeditor_get_clip_in_point() + */ +int mediaeditor_set_clip_in_point(mediaeditor_h editor, unsigned int clip_id, unsigned int in_point); + +/** + * @brief Gets the resolution of clip. + * @since_tizen 7.0 + * @remarks If the clip doesn't have video, #MEDIAEDITOR_ERROR_INVALID_OPERATION will be returned. + * @param[in] editor The media editor handle + * @param[in] clip_id The clip ID + * @param[out] width The width + * @param[out] height The height + * @return @c 0 on success, otherwise a negative error value + * @retval #MEDIAEDITOR_ERROR_NONE Successful + * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter + * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation + * @see mediaeditor_add_clip() + * @see mediaeditor_set_clip_resolution() + */ +int mediaeditor_get_clip_resolution(mediaeditor_h editor, unsigned int clip_id, unsigned int *width, unsigned int *height); + +/** + * @brief Sets the resolution of clip. + * @since_tizen 7.0 + * @remarks If the clip doesn't have video, #MEDIAEDITOR_ERROR_INVALID_OPERATION will be returned. + * @param[in] editor The media editor handle + * @param[in] clip_id The clip ID + * @param[in] width The width + * @param[in] height The height + * @return @c 0 on success, otherwise a negative error value + * @retval #MEDIAEDITOR_ERROR_NONE Successful + * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter + * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation + * @retval #MEDIAEDITOR_ERROR_INVALID_STATE Invalid state + * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE. + * @see mediaeditor_add_clip() + * @see mediaeditor_get_clip_resolution() + */ +int mediaeditor_set_clip_resolution(mediaeditor_h editor, unsigned int clip_id, unsigned int width, unsigned int height); + +/** + * @brief Gets the volume of clip. + * @since_tizen 7.0 + * @remarks If the clip doesn't have audio, #MEDIAEDITOR_ERROR_INVALID_OPERATION will be returned. + * @param[in] editor The media editor handle + * @param[in] clip_id The clip ID + * @param[out] volume The current audio volume (0.0 ~ 10.0) + * The default value is 1.0 (100%) + * @return @c 0 on success, otherwise a negative error value + * @retval #MEDIAEDITOR_ERROR_NONE Successful + * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter + * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation + * @see mediaeditor_add_clip() + * @see mediaeditor_set_clip_volume() + */ +int mediaeditor_get_clip_volume(mediaeditor_h editor, unsigned int clip_id, double *volume); + +/** + * @brief Sets the volume of clip. + * @since_tizen 7.0 + * @remarks If the clip doesn't have audio, #MEDIAEDITOR_ERROR_INVALID_OPERATION will be returned. + * @param[in] editor The media editor handle + * @param[in] clip_id The clip ID + * @param[in] volume The new audio volume (0.0 ~ 10.0)\n + * @return @c 0 on success, otherwise a negative error value + * @retval #MEDIAEDITOR_ERROR_NONE Successful + * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter + * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation + * @retval #MEDIAEDITOR_ERROR_INVALID_STATE Invalid state + * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE. + * @see mediaeditor_add_clip() + * @see mediaeditor_get_clip_volume() + */ +int mediaeditor_set_clip_volume(mediaeditor_h editor, unsigned int clip_id, double volume); + +/** + * @} + */ + +/** + * @addtogroup CAPI_MEDIA_EDITOR_EFFECT_MODULE + * @{ + */ + +/** + * @brief Adds transition effect to the overlapped clip section. + * @since_tizen 7.0 + * @remarks Note that clips should be already overlapped. If not, error will be occurred.\n + * Only one transition effect can be applied in the overlapped section.\n + * For each overlapped section, this function should be called to apply transition effect. + * @param[in] editor The media editor handle + * @param[in] type The transition type + * @param[in] layer_id The layer ID + * @param[in] start The start position of overlapped clip area to be applied transition effect + * @param[in] duration The duration of overlapped clip area to be applied transition effect + * @return @c 0 on success, otherwise a negative error value + * @retval #MEDIAEDITOR_ERROR_NONE Successful + * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter + * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation + * @retval #MEDIAEDITOR_ERROR_INVALID_STATE Invalid state + * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE. + * @see mediaeditor_add_layer() + * @see mediaeditor_add_clip() + */ +int mediaeditor_add_transition(mediaeditor_h editor, mediaeditor_transition_type_e type, unsigned int layer_id, + unsigned int start, unsigned int duration); + +/** + * @brief Adds a new effect. + * @since_tizen 7.0 + * @remarks #MEDIAEDITOR_EFFECT_AUDIO_TYPE_FADE_IN and #MEDIAEDITOR_EFFECT_AUDIO_TYPE_FADE_OUT can not be rolled back using + * mediaeditor_remove_effect(). + * @param[in] editor The media editor handle + * @param[in] type The effect type + * @param[in] layer_id The layer priority + * @param[in] start The starting position of effect which is placed in timeline (in milliseconds) + * @param[in] duration The duration of effect (in milliseconds) + * @param[out] effect_id The effect ID + * @return @c 0 on success, otherwise a negative error value + * @retval #MEDIAEDITOR_ERROR_NONE Successful + * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter + * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation + * @retval #MEDIAEDITOR_ERROR_INVALID_STATE Invalid state + * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE. + * @see mediaeditor_remove_effect() + */ +int mediaeditor_add_effect(mediaeditor_h editor, mediaeditor_effect_type_e type, unsigned int layer_id, + unsigned int start, unsigned int duration, unsigned int *effect_id); + +/** + * @brief Removes an effect from timeline. + * @since_tizen 7.0 + * @param[in] editor The media editor handle + * @param[in] effect_id The effect ID + * @return @c 0 on success, otherwise a negative error value + * @retval #MEDIAEDITOR_ERROR_NONE Successful + * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter + * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation + * @retval #MEDIAEDITOR_ERROR_INVALID_STATE Invalid state + * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE. + * @see mediaeditor_add_effect() + */ +int mediaeditor_remove_effect(mediaeditor_h editor, unsigned int effect_id); + +/** + * @} + */ + +/** + * @addtogroup CAPI_MEDIA_EDITOR_PROJECT_MODULE + * @{ + */ + +/** + * @brief Called when a timeline of the project is loaded. + * @since_tizen 7.0 + * @param[in] user_data The user data passed from the callback registration function + * @see mediaeditor_load_project() + */ +typedef void (*mediaeditor_project_loaded_cb)(void *user_data); + +/** + * @brief Creates a project to @a path. + * @since_tizen 7.0 + * @privlevel public + * @privilege %http://tizen.org/privilege/mediastorage + * @privilege %http://tizen.org/privilege/externalstorage + * @remarks If you want to access only internal storage by using this function, you should add privilege %http://tizen.org/privilege/mediastorage.\n + * Or if you want to access only external storage by using this function, you should add privilege %http://tizen.org/privilege/externalstorage.\n + * If you want to access both storage, you must add both privileges. + * @param[in] editor The media editor handle + * @param[in] path The path to create project + * @return @c 0 on success, otherwise a negative error value + * @retval #MEDIAEDITOR_ERROR_NONE Successful + * @retval #MEDIAEDITOR_ERROR_PERMISSION_DENIED The access to the resources can not be granted + * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter + * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation + * @retval #MEDIAEDITOR_ERROR_INVALID_STATE Invalid state + * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE. + * @see mediaeditor_save_project() + */ +int mediaeditor_create_project(mediaeditor_h editor, const char *path); + +/** + * @brief Loads a project from @a path, asynchronously. + * @since_tizen 7.0 + * @privlevel public + * @privilege %http://tizen.org/privilege/mediastorage + * @privilege %http://tizen.org/privilege/externalstorage + * @remarks If you want to access only internal storage by using this function, you should add privilege %http://tizen.org/privilege/mediastorage.\n + * Or if you want to access only external storage by using this function, you should add privilege %http://tizen.org/privilege/externalstorage.\n + * If you want to access both storage, you must add both privileges. + * @param[in] editor The media editor handle + * @param[in] path The path to the saved project + * @param[in] callback The callback function to register + * @param[in] user_data The user data to be passed to the callback function + * @return @c 0 on success, otherwise a negative error value + * @retval #MEDIAEDITOR_ERROR_NONE Successful + * @retval #MEDIAEDITOR_ERROR_PERMISSION_DENIED The access to the resources can not be granted + * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter + * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation + * @retval #MEDIAEDITOR_ERROR_INVALID_STATE Invalid state + * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE. + * @post mediaeditor_project_loaded_cb() will be invoked. + * @see mediaeditor_save_project() + */ +int mediaeditor_load_project(mediaeditor_h editor, const char *path, mediaeditor_project_loaded_cb callback, void *user_data); + +/** + * @brief Saves the current editing information. + * @since_tizen 7.0 + * @remarks The project will be saved to the project's path, which was set during creation or loading. + * @param[in] editor The media editor handle + * @return @c 0 on success, otherwise a negative error value + * @retval #MEDIAEDITOR_ERROR_NONE Successful + * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter + * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation + * @retval #MEDIAEDITOR_ERROR_INVALID_STATE Invalid state + * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE. + * @pre The project must be created or loaded. + * @see mediaeditor_create_project() + * @see mediaeditor_load_project() + */ +int mediaeditor_save_project(mediaeditor_h editor); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* __TIZEN_MEDIA_EDITOR_H__ */ diff --git a/include/media_editor_internal.h b/include/media_editor_internal.h new file mode 100644 index 0000000..7bef4d5 --- /dev/null +++ b/include/media_editor_internal.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __TIZEN_MEDIA_EDITOR_INTERNAL_H__ +#define __TIZEN_MEDIA_EDITOR_INTERNAL_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @internal + * @brief Sets the ecore wayland video display. + * @since_tizen 7.0 + * @remarks This function must be called in main thread of the application. + * Otherwise, it will return #MEDIAEDITOR_ERROR_INVALID_OPERATION by internal restriction. + * To avoid #MEDIAEDITOR_ERROR_INVALID_OPERATION in sub thread, ecore_thread_main_loop_begin() and + * ecore_thread_main_loop_end() can be used, but deadlock can occur if the main thread is busy. + * So, it's not recommended to use them. + * @param[in] editor The handle to the mediaeditor + * @param[in] ecore_wl_window The ecore wayland window handle + * @return @c 0 on success, + * otherwise a negative error value + * @retval #MEDIAEDITOR_ERROR_NONE Successful + * @retval #MEDIAEDITOR_ERROR_INVALID_PARAMETER Invalid parameter + * @retval #MEDIAEDITOR_ERROR_INVALID_STATE Invalid state + * @retval #MEDIAEDITOR_ERROR_INVALID_OPERATION Invalid operation + * @pre @a editor state must be set to #MEDIAEDITOR_STATE_IDLE. + * @see mediaeditor_start_preview() + * @see ecore_thread_main_loop_begin() + * @see ecore_thread_main_loop_end() + */ +int mediaeditor_set_ecore_wl_display(mediaeditor_h editor, void *ecore_wl_window); + +#ifdef __cplusplus +} +#endif + +#endif /* __TIZEN_MEDIA_EDITOR_INTERNAL_H__ */ diff --git a/include/media_editor_private.h b/include/media_editor_private.h new file mode 100644 index 0000000..ddb4cda --- /dev/null +++ b/include/media_editor_private.h @@ -0,0 +1,370 @@ +/* + * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __TIZEN_MEDIA_EDITOR_PRIVATE_H__ +#define __TIZEN_MEDIA_EDITOR_PRIVATE_H__ + +#include +#include + +#include +#include + +#include +#include +#ifndef TIZEN_TV +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef LOG_TAG +#undef LOG_TAG +#endif + +#define LOG_TAG "TIZEN_N_MEDIAEDITOR" + +#define FONT_COLOR_RESET "\033[0m" +#define FONT_COLOR_RED "\033[31m" +#define FONT_COLOR_GREEN "\033[32m" +#define FONT_COLOR_YELLOW "\033[33m" +#define FONT_COLOR_PURPLE "\033[35m" + +#define LOG_DEBUG(fmt, arg...) \ +do { \ + LOGD(FONT_COLOR_RESET""fmt""FONT_COLOR_RESET, ##arg); \ +} while (0) + +#define LOG_INFO(fmt, arg...) \ +do { \ + LOGI(FONT_COLOR_GREEN""fmt""FONT_COLOR_RESET, ##arg); \ +} while (0) + +#define LOG_WARNING(fmt, arg...) \ +do { \ + LOGW(FONT_COLOR_YELLOW""fmt""FONT_COLOR_RESET, ##arg); \ +} while (0) + +#define LOG_ERROR(fmt, arg...) \ +do { \ + LOGE(FONT_COLOR_RED""fmt""FONT_COLOR_RESET, ##arg); \ +} while (0) + +#define LOG_ERROR_IF_REACHED(fmt, arg...) \ +do { \ + LOGE(FONT_COLOR_RED"should not be reached here. "fmt""FONT_COLOR_RESET, ##arg); \ +} while (0) + +#define LOG_DEBUG_ENTER() \ +do { \ + LOGD(FONT_COLOR_PURPLE""FONT_COLOR_RESET); \ +} while (0) + +#define LOG_DEBUG_LEAVE() \ +do { \ + LOGD(FONT_COLOR_PURPLE""FONT_COLOR_RESET); \ +} while (0) + +#define LOG_WARNING_IF_CALLBACK_EXISTS(x_callback) \ +do { \ + if (x_callback.callback) \ + LOG_WARNING("previous callback[%p] user_data[%p]", x_callback.callback, x_callback.user_data); \ +} while (0) + +#define RET_IF(expr, fmt, arg...) \ +do { \ + if ((expr)) { \ + LOG_ERROR(""fmt"", ##arg); \ + return; \ + } \ +} while (0) + +#define RET_VAL_IF(expr, val, fmt, arg...) \ +do { \ + if ((expr)) { \ + LOG_ERROR(""fmt"", ##arg); \ + return (val);\ + } \ +} while (0) + +#define RET_WITH_UNLOCK_IF(expr, mutex, fmt, arg...) \ +do { \ + if ((expr)) { \ + LOG_ERROR(""fmt"", ##arg); \ + g_mutex_unlock(mutex); \ + return; \ + } \ +} while (0) + +#define RET_VAL_WITH_UNLOCK_IF(expr, val, mutex, fmt, arg...) \ +do { \ + if ((expr)) { \ + LOG_ERROR(""fmt"", ##arg); \ + g_mutex_unlock(mutex); \ + return (val);\ + } \ +} while (0) + +#define RET_ERR_IF_FEATURE_IS_NOT_SUPPORTED(x_feature) \ +do { \ + int fret = _check_feature(x_feature); \ + if (fret != MEDIAEDITOR_ERROR_NONE) return fret; \ +} while (0) + +#define SAFE_FREE(x) { if (x) { free(x); x = NULL; } } +#define SAFE_G_FREE(x) { if (x) { g_free(x); x = NULL; } } + +#define GENERATE_DOT(x_mediaeditor, x_fmt, x_arg...) \ +do { \ + gchar *dot_name; \ + if (!x_mediaeditor->ini.general.generate_dot) break; \ + dot_name = g_strdup_printf(""x_fmt"", x_arg); \ + _generate_dot(x_mediaeditor, dot_name); \ + g_free(dot_name); \ +} while (0) + +#define URI_TO_PATH_OFFSET 7 + +#define MILLI_TO_NANO(x) ((guint64)((x) * G_GUINT64_CONSTANT(1000000))) +#define NANO_TO_MILLI(x) ((unsigned int)((x) / G_GUINT64_CONSTANT(1000000))) + +#define CONTAINER_TYPE_OGG "ogg" +#define CONTAINER_TYPE_MPEG "mpeg" +#define AUDIO_ENCODER_TYPE_VORBIS "vorbis" +#define AUDIO_ENCODER_TYPE_MPEG "mpeg" +#define VIDEO_ENCODER_TYPE_THEORA "theora" +#define VIDEO_ENCODER_TYPE_MPEG "mpeg" + +#define CONTAINER_MIME_TYPE_OGG "application/ogg" +#define CONTAINER_MIME_TYPE_MPEG "video/quicktime,variant=iso" +#define AUDIO_ENCODER_MIME_TYPE_VORBIS "audio/x-vorbis" +#define AUDIO_ENCODER_MIME_TYPE_MPEG "audio/mpeg,channels=2,rate=44100,mpegversion=4,stream-format=raw,base-profile=lc" +#define VIDEO_ENCODER_MIME_TYPE_THEORA "video/x-theora" +#define VIDEO_ENCODER_MIME_TYPE_MPEG "video/mpeg,mpegversion=4,chroma-site=raw" + +#define MEDIAEDITOR_CLIP(x) (((mediaeditor_clip_s *)((x)->data))->clip) +#define MEDIAEDITOR_GROUP(x) (((mediaeditor_group_s *)((x)->data))->group) + +#ifndef TIZEN_TV +#define RESOURCE_TYPE_MAX MM_RESOURCE_MANAGER_RES_TYPE_VIDEO_ENCODER + 1 + +typedef struct _mediaeditor_resource_s { + mm_resource_manager_h mgr; + mm_resource_manager_res_h res[RESOURCE_TYPE_MAX]; + gboolean need_to_acquire[RESOURCE_TYPE_MAX]; + gboolean release_cb_is_calling; +} mediaeditor_resource_s; +#endif + +typedef struct _mediaeditor_callbacks_s { + void *callback; + void *user_data; +} mediaeditor_callbacks_s; + +typedef struct _mediaeditor_gst_s { + GESPipeline *pipeline; + GESTimeline *timeline; + GESProject *project; + + GstBus *bus; +} mediaeditor_gst_s; + +typedef struct _ini_item_general_s { + bool generate_dot; + const char *dot_path; + gchar **gst_args; +} ini_item_general_s; + +typedef struct _ini_item_encoder_s { + const char *container; + const char *audio_hw_encoder; + const char *video_hw_encoder; +} ini_item_encoder_s; + +typedef struct _ini_item_decoder_s { + const char *audio_hw_decoder; + const char *video_hw_decoder; +} ini_item_decoder_s; + +typedef struct _ini_item_resource_acquisition_s { + bool video_encoder; + bool video_decoder; +} ini_item_resource_acquisition_s; + +typedef struct _mediaeditor_ini_s { + dictionary *dict; + ini_item_general_s general; + ini_item_encoder_s encoder; + ini_item_decoder_s decoder; + ini_item_resource_acquisition_s resource_acquisition; +} mediaeditor_ini_s; + +typedef struct _mediaeditor_display { + GMutex mutex; + + int type; + int overlay_surface_id; + + mediaeditor_display_h object; // The window object to display + mm_display_interface_h mm_display; +} mediaeditor_display_s; + +typedef struct _mediaeditor_effect_s { + GESEffect *effect; + unsigned int id; +} mediaeditor_effect_s; + +typedef struct _mediaeditor_layer_s { + GESLayer *layer; + unsigned int id; +} mediaeditor_layer_s; + +typedef struct _mediaeditor_clip_s { + GESClip *clip; + unsigned int id; + mediaeditor_effect_type_e effect_type; +} mediaeditor_clip_s; + +typedef struct _mediaeditor_group_s { + GESGroup *group; + unsigned int id; +} mediaeditor_group_s; + +typedef enum _idle_cb_type_e { + IDLE_CB_TYPE_STATE, + IDLE_CB_TYPE_ERROR, + IDLE_CB_TYPE_NUM +} idle_cb_type_e; + +typedef struct _mediaeditor_s { + GMutex mutex; + GMutex event_src_mutex; + GCond cond; + + mediaeditor_gst_s gst; + + mediaeditor_state_e state; + mediaeditor_state_e pend_state; + + mediaeditor_callbacks_s error_cb; + mediaeditor_callbacks_s state_changed_cb; + mediaeditor_callbacks_s layer_priority_changed_cb; + mediaeditor_callbacks_s render_completed_cb; + mediaeditor_callbacks_s project_loaded_cb; + + guint idle_cb_event_source_ids[IDLE_CB_TYPE_NUM]; + + mediaeditor_ini_s ini; + +#ifndef TIZEN_TV + mediaeditor_resource_s resource; +#endif + mediaeditor_display_s *display; + + GList *layers; + GList *clips; + GList *groups; + GSList *audio_fade_effects; + + unsigned int layer_id; + unsigned int clip_id; + unsigned int group_id; +} mediaeditor_s; + +/* media_editor_private */ +void _invoke_error_cb(mediaeditor_s *editor, mediaeditor_error_e error); +void _post_error_cb_in_idle(mediaeditor_s *editor, mediaeditor_error_e error); +void _remove_remained_event_sources(mediaeditor_s *editor); +int _gst_init(mediaeditor_s *editor); +int _gst_pipeline_set_state(mediaeditor_s *editor, GstState state); +void _gst_pipeline_destroy(mediaeditor_s *editor); +int _mediaeditor_stop(mediaeditor_s *editor); +int _mediaeditor_create_pipeline(mediaeditor_s *editor); +int _mediaeditor_start_render(mediaeditor_s *editor, const char *path); +int _mediaeditor_cancel_render(mediaeditor_s *editor); +int _mediaeditor_start_preview(mediaeditor_s *editor); +int _mediaeditor_stop_preview(mediaeditor_s *editor); +int _check_privilege(const char *path, bool file_exist, int mode); +int _check_feature(const char *feature); + +#ifndef TIZEN_TV +/* media_editor_resource */ +int _create_resource_manager(mediaeditor_s *editor); +int _acquire_resource_for_type(mediaeditor_s *editor, mm_resource_manager_res_type_e type); +int _release_all_resources(mediaeditor_s *editor); +int _destroy_resource_manager(mediaeditor_s *editor); +#endif + +/* media_editor_layer */ +int _mediaeditor_get_layer(mediaeditor_s *editor, unsigned int layer_id, GESLayer **layer); +int _mediaeditor_add_layer(mediaeditor_s *editor, unsigned int *layer_id, unsigned int *layer_priority); +int _mediaeditor_remove_layer(mediaeditor_s *editor, unsigned int layer_id); +int _mediaeditor_move_layer(mediaeditor_s *editor, unsigned int layer_id, unsigned int layer_priority); +int _mediaeditor_activate_layer(mediaeditor_s *editor, unsigned int layer_id); +int _mediaeditor_deactivate_layer(mediaeditor_s *editor, unsigned int layer_id); +int _mediaeditor_get_layer_priority(mediaeditor_s *editor, unsigned int layer_id, unsigned int *layer_priority); +int _mediaeditor_get_layer_lowest_priority(mediaeditor_s *editor, unsigned int *layer_priority); +int _mediaeditor_get_layer_id(mediaeditor_s *editor, unsigned int layer_priority, unsigned int *layer_id); + +/* media_editor_clip */ +mediaeditor_clip_s * _mediaeditor_find_clip_data(GList *lists, unsigned int id); +int _mediaeditor_create_clip_common(mediaeditor_s *editor, GESClip *clip, mediaeditor_effect_type_e effect_type, unsigned int *clip_id); +int _mediaeditor_add_clip(mediaeditor_s *editor, const char *path, unsigned int layer_id, + int start, unsigned int duration, unsigned int in_point, unsigned int *clip_id); +int _mediaeditor_remove_clip(mediaeditor_s *editor, unsigned int clip_id); +int _mediaeditor_split_clip(mediaeditor_s *editor, unsigned int src_clip_id, unsigned int position, unsigned int *new_clip_id); +int _mediaeditor_group_clip(mediaeditor_s *editor, unsigned int *clip_ids, unsigned int size, unsigned int *group_id); +int _mediaeditor_ungroup_clip(mediaeditor_s *editor, unsigned int group_id, unsigned int **clip_ids, unsigned int *size); +int _mediaeditor_move_clip_layer(mediaeditor_s *editor, unsigned int clip_id, unsigned int layer_id); +int _mediaeditor_get_clip_start(mediaeditor_s *editor, unsigned int clip_id, unsigned int *start); +int _mediaeditor_set_clip_start(mediaeditor_s *editor, unsigned int clip_id, unsigned int start); +int _mediaeditor_get_clip_duration(mediaeditor_s *editor, unsigned int clip_id, unsigned int *duration); +int _mediaeditor_set_clip_duration(mediaeditor_s *editor, unsigned int clip_id, unsigned int duration); +int _mediaeditor_get_clip_in_point(mediaeditor_s *editor, unsigned int clip_id, unsigned int *in_point); +int _mediaeditor_set_clip_in_point(mediaeditor_s *editor, unsigned int clip_id, unsigned int in_point); +int _mediaeditor_get_clip_resolution(mediaeditor_s *editor, unsigned int clip_id, unsigned int *width, unsigned int *height); +int _mediaeditor_set_clip_resolution(mediaeditor_s *editor, unsigned int clip_id, unsigned int width, unsigned int height); +int _mediaeditor_get_clip_volume(mediaeditor_s *editor, unsigned int clip_id, double *volume); +int _mediaeditor_set_clip_volume(mediaeditor_s *editor, unsigned int clip_id, double volume); + +/* media_editor_display */ +int _mediaeditor_set_display(mediaeditor_h editor, mediaeditor_display_type_e type, mediaeditor_display_h object); +void _mediaeditor_release_display(mediaeditor_display_s *display); + +/* media_editor_effect */ +int _mediaeditor_add_transition(mediaeditor_s *editor, mediaeditor_transition_type_e type, unsigned int layer_id, + unsigned int start, unsigned int duration); +int _mediaeditor_add_effect(mediaeditor_s *editor, mediaeditor_effect_type_e type, unsigned int layer_id, + unsigned int start, unsigned int duration, unsigned int *effect_id); +int _mediaeditor_remove_effect(mediaeditor_s *editor, unsigned int effect_id); + +/* media_editor_project */ +int _mediaeditor_create_project(mediaeditor_s *editor, const char *path); +int _mediaeditor_load_project(mediaeditor_s *editor, const char *path); +int _mediaeditor_save_project(mediaeditor_s *editor, const gchar *uri); + +/* media_editor_ini */ +int _load_ini(mediaeditor_s *editor); +void _unload_ini(mediaeditor_s *editor); +bool _is_resource_required(mediaeditor_ini_s *ini); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __TIZEN_MEDIA_EDITOR_PRIVATE_H__ */ diff --git a/packaging/capi-media-editor.spec b/packaging/capi-media-editor.spec new file mode 100644 index 0000000..b5a47dc --- /dev/null +++ b/packaging/capi-media-editor.spec @@ -0,0 +1,137 @@ +Name: capi-media-editor +Summary: A Tizen Media Editor API +Version: 0.0.1 +Release: 0 +Group: Multimedia/API +License: Apache-2.0 +Source0: %{name}-%{version}.tar.gz +BuildRequires: cmake +BuildRequires: pkgconfig(dlog) +BuildRequires: pkgconfig(glib-2.0) +BuildRequires: pkgconfig(gio-2.0) +BuildRequires: pkgconfig(mm-common) +BuildRequires: pkgconfig(mm-display-interface) +BuildRequires: pkgconfig(capi-base-common) +BuildRequires: pkgconfig(capi-media-tool) +BuildRequires: pkgconfig(appcore-efl) +BuildRequires: pkgconfig(elementary) +BuildRequires: pkgconfig(ecore) +BuildRequires: pkgconfig(evas) +BuildRequires: pkgconfig(gstreamer-1.0) +BuildRequires: pkgconfig(gstreamer-plugins-base-1.0) +BuildRequires: pkgconfig(gstreamer-pbutils-1.0) +BuildRequires: pkgconfig(gst-editing-services-1.0) +BuildRequires: pkgconfig(boost) +BuildRequires: pkgconfig(ecore-wl2) +BuildRequires: pkgconfig(wayland-client) +BuildRequires: pkgconfig(tizen-extension-client) +BuildRequires: pkgconfig(iniparser) +BuildRequires: pkgconfig(capi-system-info) +BuildRequires: gtest-devel +BuildRequires: gtest +Requires(post): /sbin/ldconfig +Requires(postun): /sbin/ldconfig +%if "%{tizen_profile_name}" != "tv" +BuildRequires: pkgconfig(mm-resource-manager) +%endif + +%description +A Media Editor library in Tizen Native API. + +%package devel +Summary: A Media Editor API (Development) +Requires: %{name} = %{version}-%{release} + +%description devel +Development related files for a Media Editor library in Tizen Native API. + +%package tool +Summary: A Media Editor API testsuite with gtest +Requires: %{name} = %{version}-%{release} +BuildRequires: gtest-devel +BuildRequires: gtest + +%description tool +Tizen Native Media Editor API testsuite with gtest. + +%files tool +%defattr(-,root,root,-) +%{_bindir}/mediaeditor_ut + +%if 0%{?gcov:1} +%package gcov +Summary: Line Coverage of Media Editor library in Tizen Native API +Group: Development/Multimedia + +%description gcov +Collection of files related to Line Coverage. It is teseted as gcov for a Media Editor library in Tizen native API +%endif + +%prep +%setup -q + +%build +export CFLAGS+=" -DSYSCONFDIR=\\\"%{_hal_sysconfdir}\\\"" +export CXXFLAGS+=" -DSYSCONFDIR=\\\"%{_hal_sysconfdir}\\\"" + +%if 0%{?sec_build_binary_debug_enable} +export CFLAGS+=" -DTIZEN_DEBUG_ENABLE" +%endif + +%if 0%{?gcov:1} +export CFLAGS+=" -fprofile-arcs -ftest-coverage" +export CXXFLAGS+=" -fprofile-arcs -ftest-coverage" +export LDFLAGS+=" -lgcov" +%endif + +MAJORVER=`echo %{version} | awk 'BEGIN {FS="."}{print $1}'` +%cmake . -DCMAKE_INSTALL_PREFIX=%{_prefix} -DFULLVER=%{version} -DMAJORVER=${MAJORVER} \ +%if "%{tizen_profile_name}" == "tv" +-DTIZEN_PROFILE_TV=on +%else +-DTIZEN_PROFILE_TV=off +%endif + +make %{?jobs:-j%jobs} + +%if 0%{?gcov:1} +mkdir -p gcov-obj +find . -name '*.gcno' -exec cp '{}' gcov-obj ';' +%endif + +%install +rm -rf %{buildroot} +mkdir -p %{buildroot}%{_bindir} +cp test/media_editor_test %{buildroot}%{_bindir} + +%make_install + +%if 0%{?gcov:1} +mkdir -p %{buildroot}%{_datadir}/gcov/obj +install -m 0644 gcov-obj/* %{buildroot}%{_datadir}/gcov/obj +%endif + +%post -p /sbin/ldconfig + +%postun -p /sbin/ldconfig + +%files +%manifest capi-media-editor.manifest +%license LICENSE.APLv2 +%{_libdir}/libcapi-media-editor.so.* + +%files devel +%{_includedir}/media/media_editor.h +%{_includedir}/media/media_editor_internal.h +%{_libdir}/pkgconfig/*.pc +%{_libdir}/libcapi-media-editor.so + +%files tool +%manifest capi-media-editor-tool.manifest +%license LICENSE.APLv2 +%{_bindir}/* + +%if 0%{?gcov:1} +%files gcov +%{_datadir}/gcov/obj/* +%endif diff --git a/src/media_editor.c b/src/media_editor.c new file mode 100644 index 0000000..fb1e61c --- /dev/null +++ b/src/media_editor.c @@ -0,0 +1,1099 @@ +/* + * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include "media_editor.h" +#include "media_editor_private.h" + +#define _MEDIAEDITOR_FEATURE_DISPLAY "http://tizen.org/feature/display" + +int mediaeditor_create(mediaeditor_h *editor) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + mediaeditor_s *_editor = NULL; + g_autoptr(GMutexLocker) locker = NULL; + + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + LOG_DEBUG("Enter [%p]", editor); + + _editor = g_new0(mediaeditor_s, 1); + + g_mutex_init(&_editor->mutex); + locker = g_mutex_locker_new(&_editor->mutex); + + g_mutex_init(&_editor->event_src_mutex); + g_cond_init(&_editor->cond); + + _load_ini(_editor); + + if (_is_resource_required(&_editor->ini)) { +#ifndef TIZEN_TV + if ((ret = _create_resource_manager(_editor)) != MEDIAEDITOR_ERROR_NONE) + goto error; +#else + LOG_WARNING("no resource manager integration yet"); +#endif + } + + if ((ret = _gst_init(_editor)) != MEDIAEDITOR_ERROR_NONE) + goto error; + + if ((ret = _mediaeditor_create_pipeline(_editor)) != MEDIAEDITOR_ERROR_NONE) + goto error; + + _editor->state = MEDIAEDITOR_STATE_IDLE; + + _editor->layer_id = 1; + _editor->clip_id = 1; + _editor->group_id = 1; + + *editor = _editor; + + LOG_INFO("media editor[%p] is created", *editor); + + return MEDIAEDITOR_ERROR_NONE; + +error: + _unload_ini(_editor); + g_free(_editor); + + return ret; +} + +int mediaeditor_destroy(mediaeditor_h editor) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + g_autoptr(GMutexLocker) locker = NULL; + mediaeditor_s *_editor = (mediaeditor_s *)editor; + + RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + locker = g_mutex_locker_new(&_editor->mutex); + + LOG_DEBUG("Enter [%p]", _editor); + + _remove_remained_event_sources(_editor); + + ret = _gst_pipeline_set_state(_editor, GST_STATE_NULL); + RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to set gst state"); + + if (_editor->display) + _mediaeditor_release_display(_editor->display); + + _gst_pipeline_destroy(_editor); + +#ifndef TIZEN_TV + if (_destroy_resource_manager(_editor) != MEDIAEDITOR_ERROR_NONE) + LOG_ERROR("failed to destroy editor[%p]", _editor); +#else + LOG_WARNING("no resource manager integration yet"); +#endif + + _unload_ini(_editor); + + ges_deinit(); + + g_cond_clear(&_editor->cond); + g_mutex_clear(&_editor->event_src_mutex); + + g_clear_pointer(&locker, g_mutex_locker_free); + g_mutex_clear(&_editor->mutex); + + LOG_INFO("media editor[%p] is destroyed", _editor); + + SAFE_G_FREE(_editor); + + LOG_INFO("Leave"); + + return MEDIAEDITOR_ERROR_NONE; +} + +int mediaeditor_set_display(mediaeditor_h editor, mediaeditor_display_type_e type, mediaeditor_display_h display) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + g_autoptr(GMutexLocker) locker = NULL; + mediaeditor_s *_editor = (mediaeditor_s *)editor; + + RET_ERR_IF_FEATURE_IS_NOT_SUPPORTED(_MEDIAEDITOR_FEATURE_DISPLAY); + RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + RET_VAL_IF(display == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "display is NULL"); + RET_VAL_IF(type < MEDIAEDITOR_DISPLAY_TYPE_OVERLAY || type > MEDIAEDITOR_DISPLAY_TYPE_NONE, + MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid display type"); + + locker = g_mutex_locker_new(&_editor->mutex); + + LOG_DEBUG("Enter [%p]", editor); + + RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE, + "the state should be IDLE"); + + ret = _mediaeditor_set_display(_editor, type, display); + + LOG_DEBUG("Leave [%p]", editor); + + return ret; +} + +int mediaeditor_get_state(mediaeditor_h editor, mediaeditor_state_e *state) +{ + mediaeditor_s *_editor = (mediaeditor_s *)editor; + + RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + LOG_DEBUG("Enter [%p]", editor); + + g_mutex_lock(&_editor->mutex); + + *state = _editor->state; + + g_mutex_unlock(&_editor->mutex); + + LOG_DEBUG("Leave [%p]", editor); + + return MEDIAEDITOR_ERROR_NONE; +} + +int mediaeditor_start_render(mediaeditor_h editor, const char* path, mediaeditor_render_completed_cb callback, void *user_data) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + g_autoptr(GMutexLocker) locker = NULL; + mediaeditor_s *_editor = (mediaeditor_s *)editor; + + RET_VAL_IF(path == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "path is NULL"); + ret = _check_privilege(path, false, R_OK | W_OK); + RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to check privilege"); + RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + RET_VAL_IF(callback == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "callback is NULL"); + + locker = g_mutex_locker_new(&_editor->mutex); + + LOG_DEBUG("Enter [%p]", editor); + + RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE, + "the state should be IDLE"); + + _editor->render_completed_cb.callback = callback; + _editor->render_completed_cb.user_data = user_data; + + ret = _mediaeditor_start_render(_editor, path); + + LOG_DEBUG("Leave [%p]", editor); + + return ret; +} + +int mediaeditor_cancel_render(mediaeditor_h editor) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + g_autoptr(GMutexLocker) locker = NULL; + mediaeditor_s *_editor = (mediaeditor_s *)editor; + + RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + locker = g_mutex_locker_new(&_editor->mutex); + + LOG_DEBUG("Enter [%p]", editor); + + RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_RENDERING, MEDIAEDITOR_ERROR_INVALID_STATE, + "the state should be RENDERING"); + + ret = _mediaeditor_cancel_render(_editor); + + LOG_DEBUG("Leave [%p]", editor); + + return ret; +} + +int mediaeditor_start_preview(mediaeditor_h editor) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + g_autoptr(GMutexLocker) locker = NULL; + mediaeditor_s *_editor = (mediaeditor_s *)editor; + + RET_ERR_IF_FEATURE_IS_NOT_SUPPORTED(_MEDIAEDITOR_FEATURE_DISPLAY); + RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + locker = g_mutex_locker_new(&_editor->mutex); + + LOG_DEBUG("Enter [%p]", editor); + + RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE, + "the state should be IDLE"); + + ret = _mediaeditor_start_preview(_editor); + + LOG_DEBUG("Leave [%p]", editor); + + return ret; +} + +int mediaeditor_stop_preview(mediaeditor_h editor) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + g_autoptr(GMutexLocker) locker = NULL; + mediaeditor_s *_editor = (mediaeditor_s *)editor; + + RET_ERR_IF_FEATURE_IS_NOT_SUPPORTED(_MEDIAEDITOR_FEATURE_DISPLAY); + RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + locker = g_mutex_locker_new(&_editor->mutex); + + LOG_DEBUG("Enter [%p], state [%d], pend_state[%d]", editor, _editor->state, _editor->pend_state); + + RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_PREVIEW, MEDIAEDITOR_ERROR_INVALID_STATE, + "the state should be PREVIEW"); + + ret = _mediaeditor_stop_preview(_editor); + + LOG_DEBUG("Leave [%p]", editor); + + return ret; +} + +int mediaeditor_add_layer(mediaeditor_h editor, unsigned int *layer_id, unsigned int *layer_priority) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + g_autoptr(GMutexLocker) locker = NULL; + mediaeditor_s *_editor = (mediaeditor_s *)editor; + + RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + RET_VAL_IF(layer_id == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "layer_id is NULL"); + RET_VAL_IF(layer_priority == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "layer_priority is NULL"); + + locker = g_mutex_locker_new(&_editor->mutex); + + LOG_DEBUG("Enter [%p]", editor); + + RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE, + "the state should be IDLE"); + + ret = _mediaeditor_add_layer(_editor, layer_id, layer_priority); + + LOG_DEBUG("Leave [%p]", editor); + + return ret; +} + +int mediaeditor_remove_layer(mediaeditor_h editor, unsigned int layer_id) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + g_autoptr(GMutexLocker) locker = NULL; + mediaeditor_s *_editor = (mediaeditor_s *)editor; + + RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + locker = g_mutex_locker_new(&_editor->mutex); + + LOG_DEBUG("Enter [%p]", editor); + + RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE, + "the state should be IDLE"); + + ret = _mediaeditor_remove_layer(_editor, layer_id); + + LOG_DEBUG("Leave [%p]", editor); + + return ret; +} + +int mediaeditor_move_layer(mediaeditor_h editor, unsigned int layer_id, unsigned int layer_priority) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + g_autoptr(GMutexLocker) locker = NULL; + mediaeditor_s *_editor = (mediaeditor_s *)editor; + + RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + locker = g_mutex_locker_new(&_editor->mutex); + + LOG_DEBUG("Enter [%p]", editor); + + RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE, + "the state should be IDLE"); + + ret = _mediaeditor_move_layer(_editor, layer_id, layer_priority); + + LOG_DEBUG("Leave [%p]", editor); + + return ret; +} + +int mediaeditor_activate_layer(mediaeditor_h editor, unsigned int layer_id) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + g_autoptr(GMutexLocker) locker = NULL; + mediaeditor_s *_editor = (mediaeditor_s *)editor; + + RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + locker = g_mutex_locker_new(&_editor->mutex); + + LOG_DEBUG("Enter [%p]", editor); + + RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE, + "the state should be IDLE"); + + ret = _mediaeditor_activate_layer(_editor, layer_id); + + LOG_DEBUG("Leave [%p]", editor); + + return ret; +} + +int mediaeditor_deactivate_layer(mediaeditor_h editor, unsigned int layer_id) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + g_autoptr(GMutexLocker) locker = NULL; + mediaeditor_s *_editor = (mediaeditor_s *)editor; + + RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + locker = g_mutex_locker_new(&_editor->mutex); + + LOG_DEBUG("Enter [%p]", editor); + + RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE, + "the state should be IDLE"); + + ret = _mediaeditor_deactivate_layer(_editor, layer_id); + + LOG_DEBUG("Leave [%p]", editor); + + return ret; +} + +int mediaeditor_get_layer_priority(mediaeditor_h editor, unsigned int layer_id, unsigned int *layer_priority) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + mediaeditor_s *_editor = (mediaeditor_s *)editor; + + RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + RET_VAL_IF(layer_priority == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "layer_priority is NULL"); + + g_mutex_lock(&_editor->mutex); + + LOG_DEBUG("Enter [%p]", editor); + + ret = _mediaeditor_get_layer_priority(_editor, layer_id, layer_priority); + + g_mutex_unlock(&_editor->mutex); + + LOG_DEBUG("Leave [%p]", editor); + + return ret; +} + +int mediaeditor_get_layer_lowest_priority(mediaeditor_h editor, unsigned int *layer_priority) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + mediaeditor_s *_editor = (mediaeditor_s *)editor; + + RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + RET_VAL_IF(layer_priority == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "layer_priority is NULL"); + + g_mutex_lock(&_editor->mutex); + + LOG_DEBUG("Enter [%p]", editor); + + ret = _mediaeditor_get_layer_lowest_priority(_editor, layer_priority); + + g_mutex_unlock(&_editor->mutex); + + LOG_DEBUG("Leave [%p]", editor); + + return ret; +} + +int mediaeditor_get_layer_id(mediaeditor_h editor, unsigned int layer_priority, unsigned int *layer_id) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + mediaeditor_s *_editor = (mediaeditor_s *)editor; + + RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + RET_VAL_IF(layer_id == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "layer_id is NULL"); + + g_mutex_lock(&_editor->mutex); + + LOG_DEBUG("Enter [%p]", editor); + + ret = _mediaeditor_get_layer_id(_editor, layer_priority, layer_id); + + g_mutex_unlock(&_editor->mutex); + + LOG_DEBUG("Leave [%p]", editor); + + return ret; +} + +int mediaeditor_add_clip(mediaeditor_h editor, const char *path, unsigned int layer_id, + int start, unsigned int duration, unsigned int in_point, unsigned int *clip_id) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + g_autoptr(GMutexLocker) locker = NULL; + mediaeditor_s *_editor = (mediaeditor_s *)editor; + + RET_VAL_IF(path == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "path is NULL"); + ret = _check_privilege(path, true, R_OK); + RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to check privilege"); + RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + locker = g_mutex_locker_new(&_editor->mutex); + + LOG_DEBUG("Enter [%p]", editor); + + RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE, + "the state should be IDLE"); + + ret = _mediaeditor_add_clip(_editor, path, layer_id, start, duration, in_point, clip_id); + + LOG_DEBUG("Leave [%p]", editor); + + return ret; +} + +int mediaeditor_remove_clip(mediaeditor_h editor, unsigned int clip_id) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + g_autoptr(GMutexLocker) locker = NULL; + mediaeditor_s *_editor = (mediaeditor_s *)editor; + + RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + locker = g_mutex_locker_new(&_editor->mutex); + + LOG_DEBUG("Enter [%p]", editor); + + RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE, + "the state should be IDLE"); + + ret = _mediaeditor_remove_clip(_editor, clip_id); + + LOG_DEBUG("Leave [%p]", editor); + + return ret; +} + +int mediaeditor_split_clip(mediaeditor_h editor, unsigned int src_clip_id, unsigned int position, + unsigned int *new_clip_id) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + g_autoptr(GMutexLocker) locker = NULL; + mediaeditor_s *_editor = (mediaeditor_s *)editor; + + RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + locker = g_mutex_locker_new(&_editor->mutex); + + LOG_DEBUG("Enter [%p]", editor); + + RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE, + "the state should be IDLE"); + + ret = _mediaeditor_split_clip(_editor, src_clip_id, position, new_clip_id); + + LOG_DEBUG("Leave [%p]", editor); + + return ret; +} + +int mediaeditor_group_clip(mediaeditor_h editor, unsigned int *clip_ids, unsigned int size, unsigned int *group_id) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + g_autoptr(GMutexLocker) locker = NULL; + mediaeditor_s *_editor = (mediaeditor_s *)editor; + + RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + locker = g_mutex_locker_new(&_editor->mutex); + + LOG_DEBUG("Enter [%p]", editor); + + RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE, + "the state should be IDLE"); + + ret = _mediaeditor_group_clip(_editor, clip_ids, size, group_id); + + LOG_DEBUG("Leave [%p]", editor); + + return ret; +} + +int mediaeditor_ungroup_clip(mediaeditor_h editor, unsigned int group_id, unsigned int **clip_ids, unsigned int *size) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + g_autoptr(GMutexLocker) locker = NULL; + mediaeditor_s *_editor = (mediaeditor_s *)editor; + + RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + locker = g_mutex_locker_new(&_editor->mutex); + + LOG_DEBUG("Enter [%p]", editor); + + RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE, + "the state should be IDLE"); + + ret = _mediaeditor_ungroup_clip(_editor, group_id, clip_ids, size); + + LOG_DEBUG("Leave [%p]", editor); + + return ret; +} + +int mediaeditor_move_clip_layer(mediaeditor_h editor, unsigned int clip_id, unsigned int layer_priority) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + g_autoptr(GMutexLocker) locker = NULL; + mediaeditor_s *_editor = (mediaeditor_s *)editor; + + RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + locker = g_mutex_locker_new(&_editor->mutex); + + LOG_DEBUG("Enter [%p]", editor); + + RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE, + "the state should be IDLE"); + + ret = _mediaeditor_move_clip_layer(_editor, clip_id, layer_priority); + + LOG_DEBUG("Leave [%p]", editor); + + return ret; +} + +int mediaeditor_get_clip_start(mediaeditor_h editor, unsigned int clip_id, unsigned int *start) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + mediaeditor_s *_editor = (mediaeditor_s *)editor; + + RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + RET_VAL_IF(start == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "start is NULL"); + + LOG_DEBUG("Enter [%p]", editor); + + g_mutex_lock(&_editor->mutex); + + ret = _mediaeditor_get_clip_start(_editor, clip_id, start); + + g_mutex_unlock(&_editor->mutex); + + LOG_DEBUG("Leave [%p]", editor); + + return ret; +} + +int mediaeditor_set_clip_start(mediaeditor_h editor, unsigned int id, unsigned int start) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + g_autoptr(GMutexLocker) locker = NULL; + mediaeditor_s *_editor = (mediaeditor_s *)editor; + + RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + locker = g_mutex_locker_new(&_editor->mutex); + + LOG_DEBUG("Enter [%p]", editor); + + RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE, + "the state should be IDLE"); + + ret = _mediaeditor_set_clip_start(_editor, id, start); + + LOG_DEBUG("Leave [%p]", editor); + + return ret; +} + +int mediaeditor_get_clip_duration(mediaeditor_h editor, unsigned int id, unsigned int *duration) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + mediaeditor_s *_editor = (mediaeditor_s *)editor; + + RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + RET_VAL_IF(duration == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "duration is NULL"); + + LOG_DEBUG("Enter [%p]", editor); + + g_mutex_lock(&_editor->mutex); + + ret = _mediaeditor_get_clip_duration(_editor, id, duration); + + g_mutex_unlock(&_editor->mutex); + + LOG_DEBUG("Leave [%p]", editor); + + return ret; +} + +int mediaeditor_set_clip_duration(mediaeditor_h editor, unsigned int id, unsigned int duration) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + g_autoptr(GMutexLocker) locker = NULL; + mediaeditor_s *_editor = (mediaeditor_s *)editor; + + RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + locker = g_mutex_locker_new(&_editor->mutex); + + LOG_DEBUG("Enter [%p]", editor); + + RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE, + "the state should be IDLE"); + + ret = _mediaeditor_set_clip_duration(_editor, id, duration); + + LOG_DEBUG("Leave [%p]", editor); + + return ret; +} + +int mediaeditor_get_clip_in_point(mediaeditor_h editor, unsigned int id, unsigned int *in_point) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + mediaeditor_s *_editor = (mediaeditor_s *)editor; + + RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + RET_VAL_IF(in_point == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "in_point is NULL"); + + LOG_DEBUG("Enter [%p]", editor); + + g_mutex_lock(&_editor->mutex); + + ret = _mediaeditor_get_clip_in_point(_editor, id, in_point); + + g_mutex_unlock(&_editor->mutex); + + LOG_DEBUG("Leave [%p]", editor); + + return ret; +} + +int mediaeditor_set_clip_in_point(mediaeditor_h editor, unsigned int id, unsigned int in_point) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + g_autoptr(GMutexLocker) locker = NULL; + mediaeditor_s *_editor = (mediaeditor_s *)editor; + + RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + locker = g_mutex_locker_new(&_editor->mutex); + + LOG_DEBUG("Enter [%p]", editor); + + RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE, + "the state should be IDLE"); + + ret = _mediaeditor_set_clip_in_point(_editor, id, in_point); + + LOG_DEBUG("Leave [%p]", editor); + + return ret; +} + +int mediaeditor_get_clip_resolution(mediaeditor_h editor, unsigned int id, unsigned int *width, unsigned int *height) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + mediaeditor_s *_editor = (mediaeditor_s *)editor; + + RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + RET_VAL_IF(width == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "width is NULL"); + RET_VAL_IF(height == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "height is NULL"); + + LOG_DEBUG("Enter [%p]", editor); + + g_mutex_lock(&_editor->mutex); + + ret = _mediaeditor_get_clip_resolution(_editor, id, width, height); + + g_mutex_unlock(&_editor->mutex); + + LOG_DEBUG("Leave [%p]", editor); + + return ret; +} + +int mediaeditor_set_clip_resolution(mediaeditor_h editor, unsigned int id, unsigned int width, unsigned int height) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + g_autoptr(GMutexLocker) locker = NULL; + mediaeditor_s *_editor = (mediaeditor_s *)editor; + + RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + locker = g_mutex_locker_new(&_editor->mutex); + + LOG_DEBUG("Enter [%p]", editor); + + RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE, + "the state should be IDLE"); + + ret = _mediaeditor_set_clip_resolution(_editor, id, width, height); + + LOG_DEBUG("Leave [%p]", editor); + + return ret; +} + +int mediaeditor_get_clip_volume(mediaeditor_h editor, unsigned int id, double *volume) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + mediaeditor_s *_editor = (mediaeditor_s *)editor; + + RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + RET_VAL_IF(volume == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "volume is NULL"); + + LOG_DEBUG("Enter [%p]", editor); + + g_mutex_lock(&_editor->mutex); + + ret = _mediaeditor_get_clip_volume(_editor, id, volume); + + g_mutex_unlock(&_editor->mutex); + + LOG_DEBUG("Leave [%p]", editor); + + return ret; +} + +int mediaeditor_set_clip_volume(mediaeditor_h editor, unsigned int id, double volume) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + g_autoptr(GMutexLocker) locker = NULL; + mediaeditor_s *_editor = (mediaeditor_s *)editor; + + RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + RET_VAL_IF(volume < 0.0 || volume > 10.0, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "volume is out of range"); + + locker = g_mutex_locker_new(&_editor->mutex); + + LOG_DEBUG("Enter [%p]", editor); + + RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE, + "the state should be IDLE"); + + ret = _mediaeditor_set_clip_volume(_editor, id, volume); + + LOG_DEBUG("Leave [%p]", editor); + + return ret; +} + +int mediaeditor_add_transition(mediaeditor_h editor, mediaeditor_transition_type_e type, unsigned int layer_id, + unsigned int start, unsigned int duration) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + g_autoptr(GMutexLocker) locker = NULL; + mediaeditor_s *_editor = (mediaeditor_s *)editor; + + RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + RET_VAL_IF(type < MEDIAEDITOR_TRANSITION_TYPE_NONE || type > MEDIAEDITOR_TRANSITION_TYPE_CROSSFADE, + MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid transition type"); + + locker = g_mutex_locker_new(&_editor->mutex); + + LOG_DEBUG("Enter [%p]", editor); + + RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE, + "the state should be IDLE"); + + ret = _mediaeditor_add_transition(_editor, type, layer_id, start, duration); + + LOG_DEBUG("Leave [%p]", editor); + + return ret; +} + +int mediaeditor_add_effect(mediaeditor_h editor, mediaeditor_effect_type_e type, unsigned int layer_id, + unsigned int start, unsigned int duration, unsigned int *effect_id) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + g_autoptr(GMutexLocker) locker = NULL; + mediaeditor_s *_editor = (mediaeditor_s *)editor; + + RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + RET_VAL_IF(type <= MEDIAEDITOR_EFFECT_TYPE_NONE || type > MEDIAEDITOR_EFFECT_AUDIO_TYPE_ECHO, + MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid effect type"); + + locker = g_mutex_locker_new(&_editor->mutex); + + LOG_DEBUG("Enter [%p]", editor); + + RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE, + "the state should be IDLE"); + + ret = _mediaeditor_add_effect(_editor, type, layer_id, start, duration, effect_id); + + LOG_DEBUG("Leave [%p]", editor); + + return ret; +} + +int mediaeditor_remove_effect(mediaeditor_h editor, unsigned int effect_id) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + g_autoptr(GMutexLocker) locker = NULL; + mediaeditor_s *_editor = (mediaeditor_s *)editor; + + RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + locker = g_mutex_locker_new(&_editor->mutex); + + LOG_DEBUG("Enter [%p]", editor); + + RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE, + "the state should be IDLE"); + + ret = _mediaeditor_remove_effect(_editor, effect_id); + + LOG_DEBUG("Leave [%p]", editor); + + return ret; +} + +int mediaeditor_create_project(mediaeditor_h editor, const char *path) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + g_autoptr(GMutexLocker) locker = NULL; + mediaeditor_s *_editor = (mediaeditor_s *)editor; + + RET_VAL_IF(path == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "path is NULL"); + ret = _check_privilege(path, false, R_OK | W_OK); + RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to check privilege"); + RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + locker = g_mutex_locker_new(&_editor->mutex); + + LOG_DEBUG("Enter [%p]", editor); + + RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE, + "the state should be IDLE"); + + ret = _mediaeditor_create_project(_editor, path); + + LOG_DEBUG("Leave [%p]", editor); + + return ret; +} + +int mediaeditor_load_project(mediaeditor_h editor, const char *path, mediaeditor_project_loaded_cb callback, void *user_data) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + g_autoptr(GMutexLocker) locker = NULL; + mediaeditor_s *_editor = (mediaeditor_s *)editor; + + RET_VAL_IF(path == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "path is NULL"); + ret = _check_privilege(path, true, R_OK); + RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to check privilege"); + RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + RET_VAL_IF(callback == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "callback is NULL"); + + locker = g_mutex_locker_new(&_editor->mutex); + + LOG_DEBUG("Enter [%p]", editor); + + RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE, + "the state should be IDLE"); + + _editor->project_loaded_cb.callback = callback; + _editor->project_loaded_cb.user_data = user_data; + + ret = _mediaeditor_load_project(_editor, path); + + LOG_DEBUG("Leave [%p]", editor); + + return ret; +} + +int mediaeditor_save_project(mediaeditor_h editor) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + g_autoptr(GMutexLocker) locker = NULL; + mediaeditor_s *_editor = (mediaeditor_s *)editor; + g_autofree gchar *uri = NULL; + + RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + RET_VAL_IF(_editor->gst.project == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "project is not loaded or created"); + + uri = ges_project_get_uri(_editor->gst.project); + RET_VAL_IF(uri == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to get uri from project"); + ret = _check_privilege(uri + URI_TO_PATH_OFFSET, false, W_OK); + RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to check privilege"); + + locker = g_mutex_locker_new(&_editor->mutex); + + LOG_DEBUG("Enter [%p]", editor); + + RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE, + "the state should be IDLE"); + + ret = _mediaeditor_save_project(_editor, uri); + + LOG_DEBUG("Leave [%p]", editor); + + return ret; +} + +int mediaeditor_set_error_cb(mediaeditor_h editor, mediaeditor_error_cb callback, void *user_data) +{ + g_autoptr(GMutexLocker) locker = NULL; + mediaeditor_s *_editor = (mediaeditor_s *)editor; + + RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + RET_VAL_IF(callback == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "callback is NULL"); + + locker = g_mutex_locker_new(&_editor->mutex); + + LOG_DEBUG("Enter [%p]", editor); + + RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE, + "the state should be IDLE"); + + _editor->error_cb.callback = callback; + _editor->error_cb.user_data = user_data; + + LOG_INFO("callback[%p] user_data[%p]", callback, user_data); + + LOG_DEBUG("Leave [%p]", editor); + + return MEDIAEDITOR_ERROR_NONE; +} + +int mediaeditor_unset_error_cb(mediaeditor_h editor) +{ + g_autoptr(GMutexLocker) locker = NULL; + mediaeditor_s *_editor = (mediaeditor_s *)editor; + + RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + locker = g_mutex_locker_new(&_editor->mutex); + + LOG_DEBUG("Enter [%p]", editor); + + RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE, + "the state should be IDLE"); + RET_VAL_IF(_editor->error_cb.callback == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, + "callback was not set"); + + _editor->error_cb.callback = NULL; + _editor->error_cb.user_data = NULL; + + LOG_DEBUG("Leave [%p]", editor); + + return MEDIAEDITOR_ERROR_NONE; +} + +int mediaeditor_set_state_changed_cb(mediaeditor_h editor, mediaeditor_state_changed_cb callback, void *user_data) +{ + g_autoptr(GMutexLocker) locker = NULL; + mediaeditor_s *_editor = (mediaeditor_s *)editor; + + RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + RET_VAL_IF(callback == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "callback is NULL"); + + locker = g_mutex_locker_new(&_editor->mutex); + + LOG_DEBUG("Enter [%p]", editor); + + RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE, + "the state should be IDLE"); + + _editor->state_changed_cb.callback = callback; + _editor->state_changed_cb.user_data = user_data; + + LOG_INFO("callback[%p] user_data[%p]", callback, user_data); + + LOG_DEBUG("Leave [%p]", editor); + + return MEDIAEDITOR_ERROR_NONE; +} + +int mediaeditor_unset_state_changed_cb(mediaeditor_h editor) +{ + g_autoptr(GMutexLocker) locker = NULL; + mediaeditor_s *_editor = (mediaeditor_s *)editor; + + RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + locker = g_mutex_locker_new(&_editor->mutex); + + LOG_DEBUG("Enter [%p]", editor); + + RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE, + "the state should be IDLE"); + RET_VAL_IF(_editor->state_changed_cb.callback == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, + "callback was not set"); + + _editor->state_changed_cb.callback = NULL; + _editor->state_changed_cb.user_data = NULL; + + LOG_DEBUG("Leave [%p]", editor); + + return MEDIAEDITOR_ERROR_NONE; +} + +int mediaeditor_set_layer_priority_changed_cb(mediaeditor_h editor, mediaeditor_layer_priority_changed_cb callback, void *user_data) +{ + g_autoptr(GMutexLocker) locker = NULL; + mediaeditor_s *_editor = (mediaeditor_s *)editor; + + RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + RET_VAL_IF(callback == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "callback is NULL"); + + locker = g_mutex_locker_new(&_editor->mutex); + + LOG_DEBUG("Enter [%p]", editor); + + RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE, + "the state should be IDLE"); + + _editor->layer_priority_changed_cb.callback = callback; + _editor->layer_priority_changed_cb.user_data = user_data; + + LOG_INFO("callback[%p] user_data[%p]", callback, user_data); + + LOG_DEBUG("Leave [%p]", editor); + + return MEDIAEDITOR_ERROR_NONE; +} + +int mediaeditor_unset_layer_priority_changed_cb(mediaeditor_h editor) +{ + g_autoptr(GMutexLocker) locker = NULL; + mediaeditor_s *_editor = (mediaeditor_s *)editor; + + RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + locker = g_mutex_locker_new(&_editor->mutex); + + LOG_DEBUG("Enter [%p]", editor); + + RET_VAL_IF(_editor->state != MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE, + "the state should be IDLE"); + RET_VAL_IF(_editor->layer_priority_changed_cb.callback == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, + "callback was not set"); + + _editor->layer_priority_changed_cb.callback = NULL; + _editor->layer_priority_changed_cb.user_data = NULL; + + LOG_DEBUG("Leave [%p]", editor); + + return MEDIAEDITOR_ERROR_NONE; +} \ No newline at end of file diff --git a/src/media_editor_clip.c b/src/media_editor_clip.c new file mode 100644 index 0000000..14c3319 --- /dev/null +++ b/src/media_editor_clip.c @@ -0,0 +1,609 @@ +/* + * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +static void __print_clip_data(mediaeditor_clip_s *clip_data) +{ +#ifdef PRINT_CLIP_DEBUG_INFO + RET_IF(clip_data == NULL, "clip_data is null"); + LOG_DEBUG("clip_data[%p], clip[%p], id[%d], effect_type[%d]", clip_data, clip_data->clip, clip_data->id, (int)clip_data->effect_type); +#endif +} + +static mediaeditor_clip_s *__mediaeditor_create_clip_data(GESClip *clip, mediaeditor_effect_type_e effect_type, unsigned int id) +{ + mediaeditor_clip_s *data = NULL; + + data = g_new0(mediaeditor_clip_s, 1); + + data->clip = clip; + data->effect_type = effect_type; + data->id = id; + + return data; +} + +static mediaeditor_group_s *__mediaeditor_create_group_data(GESGroup *group, unsigned int id) +{ + mediaeditor_group_s *data = NULL; + + data = g_new0(mediaeditor_group_s, 1); + + data->group = group; + data->id = id; + + return data; +} + +static gint __mediaeditor_comparefunc_find_clip(gconstpointer a, gconstpointer b) +{ + mediaeditor_clip_s *clip_data = (mediaeditor_clip_s *)a; + unsigned int *id = (unsigned int *)b; + + if (clip_data == NULL || clip_data->id != *id) + return -1; + + LOG_DEBUG("clip_data->id[%d], *id[%d]", clip_data->id,*id); + + return 0; +} + +static GList *__mediaeditor_find_clip_list(GList *lists, unsigned int id) +{ + GList *list = NULL; + + list = g_list_find_custom(lists, (gconstpointer)&id, __mediaeditor_comparefunc_find_clip); + RET_VAL_IF(list == NULL, NULL, "failed to find matched clip"); + + LOG_DEBUG("list->data(clip)[%p]", list->data); + + return list; +} + +mediaeditor_clip_s *_mediaeditor_find_clip_data(GList *lists, unsigned int id) +{ + GList *list = NULL; + + list = __mediaeditor_find_clip_list(lists, id); + RET_VAL_IF(list == NULL, NULL, "failed to find clip list"); + + __print_clip_data(list->data); + + return list->data; +} + +static GESClip *__mediaeditor_find_clip(GList *lists, unsigned int id) +{ + mediaeditor_clip_s *clip_data = NULL; + + clip_data = _mediaeditor_find_clip_data(lists, id); + RET_VAL_IF(clip_data == NULL, NULL, "failed to find clip data"); + + return clip_data->clip; +} + +/* this method find/return effect and don't remove it from list */ +static GList *__mediaeditor_peek_clips(GList *lists, unsigned int *ids, unsigned int size) +{ + GList *list_new = NULL; + GESClip *clip = NULL; + + for (unsigned int i = 0 ; i < size ; i++) { + if ((clip = __mediaeditor_find_clip(lists, ids[i]))) + list_new = g_list_append(list_new, clip); + } + + return list_new; +} + +static void __mediaeditor_find_id(mediaeditor_s *editor, GList *lists, unsigned int *ids) +{ + int i = 0; + GList *sl = NULL; + GList *dl = NULL; + mediaeditor_clip_s *clip_data = NULL; + + for (sl = lists ; sl ; sl = sl->next, i++) { + for (dl = editor->clips ; dl ; dl = dl->next) { + clip_data = dl->data; + + if (clip_data->clip == (GESClip *)sl->data) { + ids[i] = clip_data->id; + LOG_DEBUG("found clip : [%p]", clip_data->clip); + break; + } + } + } +} + +static gint __mediaeditor_comparefunc_find_group(gconstpointer a, gconstpointer b) +{ + mediaeditor_group_s *group_data = (mediaeditor_group_s *)a; + unsigned int *id = (unsigned int *)b; + + if (group_data == NULL || group_data->id != *id) + return -1; + + return 0; +} + +static GList *__mediaeditor_find_group_list(GList *lists, unsigned int id) +{ + GList *list = NULL; + + list = g_list_find_custom(lists, &id, __mediaeditor_comparefunc_find_group); + RET_VAL_IF(list == NULL, NULL, "failed to find matched group"); + + LOG_DEBUG("list-data(clip)[%p]", list->data); + + return list; +} + +static mediaeditor_group_s *__mediaeditor_find_group_data(GList *lists, unsigned int id) +{ + GList *list = NULL; + + list = __mediaeditor_find_group_list(lists, id); + RET_VAL_IF(list == NULL, NULL, "failed to find group list"); + + return list->data; +} + +static GESGroup *__mediaeditor_find_group(GList *lists, unsigned int id) +{ + mediaeditor_group_s *group_data = NULL; + + group_data = __mediaeditor_find_group_data(lists, id); + RET_VAL_IF(group_data == NULL, NULL, "failed to find group data"); + + return group_data->group; +} + +static bool __has_audio_track(GESClip *clip) +{ + RET_VAL_IF(clip == NULL, false, "clip is NULL"); + + if (ges_clip_find_track_element(clip, NULL, GES_TYPE_AUDIO_SOURCE) != NULL) { + LOG_DEBUG("The clip have audio"); + return true; + } + + return false; +} + +static bool __has_video_track(GESClip *clip) +{ + RET_VAL_IF(clip == NULL, false, "clip is NULL"); + + if (ges_clip_find_track_element(clip, NULL, GES_TYPE_VIDEO_SOURCE) != NULL) { + LOG_DEBUG("The clip have video"); + return true; + } + + return false; +} + +static void __mediaeditor_add_child_properties_resolution(GESClip *clip) +{ + GValue val = G_VALUE_INIT; + + g_value_init(&val, G_TYPE_INT); + + if (!ges_timeline_element_get_child_property(GES_TIMELINE_ELEMENT(clip), "width", &val)) { + GObjectClass *eklass = G_OBJECT_GET_CLASS(clip); + + if (ges_timeline_element_add_child_property(GES_TIMELINE_ELEMENT(clip), g_object_class_find_property(eklass, "width"), G_OBJECT(clip))) + LOG_DEBUG("width property is added in URI clip"); + + if (ges_timeline_element_add_child_property(GES_TIMELINE_ELEMENT(clip), g_object_class_find_property(eklass, "height"), G_OBJECT(clip))) + LOG_DEBUG("height property is added in URI clip"); + } +} + +int _mediaeditor_create_clip_common(mediaeditor_s *editor, GESClip *clip, mediaeditor_effect_type_e effect_type, unsigned int *clip_id) +{ + mediaeditor_clip_s *clip_data = NULL; + + clip_data = __mediaeditor_create_clip_data(clip, effect_type, editor->clip_id); + RET_VAL_IF(clip_data == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "clip_data is null"); + + __print_clip_data(clip_data); + + editor->clips = g_list_append(editor->clips, clip_data); + RET_VAL_IF(editor->clips == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "list is null"); + + *clip_id = (editor->clip_id)++; + + return MEDIAEDITOR_ERROR_NONE; +} + +int _mediaeditor_add_clip(mediaeditor_s *editor, const char *path, unsigned int layer_id, + int start, unsigned int duration, unsigned int in_point, unsigned int *clip_id) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + GESClip *clip = NULL; + GESLayer *layer = NULL; + guint64 start_nsec = 0; + guint64 duration_nsec = 0; + guint64 in_point_nsec = 0; + g_autofree gchar *uri = NULL; + + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + RET_VAL_IF(path == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "path is NULL"); + RET_VAL_IF(clip_id == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "clip_id is NULL"); + + uri = gst_filename_to_uri(path, NULL); + RET_VAL_IF(uri == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "failed to get uri"); + LOGI("uri : [%s]", uri); + + clip = GES_CLIP(ges_uri_clip_new(uri)); + + start_nsec = MILLI_TO_NANO(start); + duration_nsec = MILLI_TO_NANO(duration); + in_point_nsec = MILLI_TO_NANO(in_point); + + LOG_INFO("uri[%s], layer_id[%d], clip[%p], start[%"G_GUINT64_FORMAT"], duration[%"G_GUINT64_FORMAT"]", + uri, layer_id, clip, start_nsec, duration_nsec); + ges_timeline_element_set_start(GES_TIMELINE_ELEMENT(clip), start_nsec); + ges_timeline_element_set_duration(GES_TIMELINE_ELEMENT(clip), duration_nsec); + ges_timeline_element_set_inpoint(GES_TIMELINE_ELEMENT(clip), in_point_nsec); + + ret = _mediaeditor_get_layer(editor, layer_id, &layer); + RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to get layer"); + + if (!ges_layer_add_clip(layer, clip)) { + LOG_ERROR("failed to add clip to layer"); + return MEDIAEDITOR_ERROR_INVALID_OPERATION; + } + + ret = _mediaeditor_create_clip_common(editor, clip, MEDIAEDITOR_EFFECT_TYPE_NONE, clip_id); + RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to create clip common"); + + return MEDIAEDITOR_ERROR_NONE; +} + +int _mediaeditor_remove_clip(mediaeditor_s *editor, unsigned int clip_id) +{ + GESClip *clip = NULL; + GESLayer *layer = NULL; + g_autofree mediaeditor_clip_s *clip_data = NULL; + + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + RET_VAL_IF(clip_id >= editor->clip_id, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid clip id"); + + clip_data = _mediaeditor_find_clip_data(editor->clips, clip_id); + RET_VAL_IF(clip_data == NULL || clip_data->clip == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid clip id"); + + clip = clip_data->clip; + layer = ges_clip_get_layer(clip); + RET_VAL_IF(layer == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to get layer"); + + if (!ges_layer_remove_clip(layer, clip)) { + LOG_ERROR("failed to remove clip from layer"); + return MEDIAEDITOR_ERROR_INVALID_OPERATION; + } + + editor->clips = g_list_remove(editor->clips, clip_data); + + return MEDIAEDITOR_ERROR_NONE; +} + +int _mediaeditor_split_clip(mediaeditor_s *editor, unsigned int src_clip_id, unsigned int position, + unsigned int *new_clip_id) +{ + GESClip *clip = NULL; + GESClip *clip_new = NULL; + mediaeditor_clip_s *clip_data_new = NULL; + guint64 position_nsec = 0; + + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + RET_VAL_IF(src_clip_id >= editor->clip_id, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid clip id"); + + clip = __mediaeditor_find_clip(editor->clips, src_clip_id); + RET_VAL_IF(clip == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid clip id"); + + position_nsec = MILLI_TO_NANO(position); + LOG_DEBUG("src_clip_id[%d], position[%"G_GUINT64_FORMAT"]", src_clip_id, position_nsec); + + clip_new = ges_clip_split(clip, position_nsec); + RET_VAL_IF(clip_new == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to split clip"); + + clip_data_new = __mediaeditor_create_clip_data(clip_new, MEDIAEDITOR_EFFECT_TYPE_NONE, editor->clip_id); + + /* add new clip to managed clip list. */ + editor->clips = g_list_append(editor->clips, clip_data_new); + *new_clip_id = (editor->clip_id)++; + + __print_clip_data(clip_data_new); + + return MEDIAEDITOR_ERROR_NONE; +} + +int _mediaeditor_group_clip(mediaeditor_s *editor, unsigned int *clip_ids, unsigned int size, unsigned int *group_id) +{ + GList *list = NULL; + GESGroup *group = NULL; + mediaeditor_group_s *group_data = NULL; + + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + for (unsigned int i = 0 ; i < size ; i++) + RET_VAL_IF(clip_ids[i] >= editor->clip_id, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid clip id"); + + list = __mediaeditor_peek_clips(editor->clips, clip_ids, size); + RET_VAL_IF(list == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to get clip from list"); + + group = GES_GROUP(ges_container_group(list)); + group_data = __mediaeditor_create_group_data(group, editor->group_id); + + editor->groups = g_list_append(editor->groups, group_data); + *group_id = (editor->group_id)++; + + g_list_free(list); + + return MEDIAEDITOR_ERROR_NONE; +} + +int _mediaeditor_ungroup_clip(mediaeditor_s *editor, unsigned int group_id, unsigned int **clip_ids, unsigned int *size) +{ + GESGroup *group = NULL; + GList *clips = NULL; + int number_of_clips = 0; + unsigned int *ids = NULL; + + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + RET_VAL_IF(group_id <= 0 || group_id >= editor->group_id, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid group id"); + + group = __mediaeditor_find_group(editor->groups, group_id); + RET_VAL_IF(group == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid group id"); + + clips = ges_container_ungroup(GES_CONTAINER(group), FALSE); + RET_VAL_IF(clips == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to ungroup"); + + number_of_clips = g_list_length(clips); + LOG_DEBUG("The ungrouped clips are %d", number_of_clips); + + ids = (unsigned int *)malloc(sizeof(unsigned int) * number_of_clips); + RET_VAL_IF(ids == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to alloc ids"); + + memset(ids, 0, sizeof(unsigned int) * number_of_clips); + + __mediaeditor_find_id(editor, clips, ids); + + *clip_ids = ids; + *size = number_of_clips; + + return MEDIAEDITOR_ERROR_NONE; +} + +int _mediaeditor_move_clip_layer(mediaeditor_s *editor, unsigned int clip_id, unsigned int layer_priority) +{ + GESLayer *layer = NULL; + GESClip *clip = NULL; + + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + RET_VAL_IF(clip_id >= editor->clip_id, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid clip id"); + + layer = ges_timeline_get_layer(editor->gst.timeline, layer_priority); + RET_VAL_IF(layer == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid layer id"); + + clip = __mediaeditor_find_clip(editor->clips, clip_id); + RET_VAL_IF(clip == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid clip id"); + + if (!ges_clip_move_to_layer(clip, layer)) { + LOG_ERROR("failed to move layer"); + return MEDIAEDITOR_ERROR_INVALID_OPERATION; + } + + return MEDIAEDITOR_ERROR_NONE; +} + +int _mediaeditor_get_clip_start(mediaeditor_s *editor, unsigned int clip_id, unsigned int *start) +{ + GESClip *clip = NULL; + + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + RET_VAL_IF(clip_id >= editor->clip_id, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid clip id"); + + clip = __mediaeditor_find_clip(editor->clips, clip_id); + RET_VAL_IF(clip == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid clip id"); + + *start = NANO_TO_MILLI(ges_timeline_element_get_start(GES_TIMELINE_ELEMENT(clip))); + + return MEDIAEDITOR_ERROR_NONE; +} + +int _mediaeditor_set_clip_start(mediaeditor_s *editor, unsigned int clip_id, unsigned int start) +{ + GESClip *clip = NULL; + guint64 start_nsec = 0; + + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + RET_VAL_IF(clip_id >= editor->clip_id, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid clip id"); + + clip = __mediaeditor_find_clip(editor->clips, clip_id); + RET_VAL_IF(clip == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid clip id"); + + start_nsec = MILLI_TO_NANO(start); + LOG_DEBUG("clip_id[%d], start[%"G_GUINT64_FORMAT"]", clip_id, start_nsec); + + if (!ges_timeline_element_set_start(GES_TIMELINE_ELEMENT(clip), start_nsec)) { + LOG_ERROR("failed to set start"); + return MEDIAEDITOR_ERROR_INVALID_OPERATION; + } + + return MEDIAEDITOR_ERROR_NONE; +} + +int _mediaeditor_get_clip_duration(mediaeditor_s *editor, unsigned int clip_id, unsigned int *duration) +{ + GESClip *clip = NULL; + + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + RET_VAL_IF(clip_id >= editor->clip_id, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid clip id"); + + clip = __mediaeditor_find_clip(editor->clips, clip_id); + RET_VAL_IF(clip == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid clip id"); + + *duration = NANO_TO_MILLI(ges_timeline_element_get_duration(GES_TIMELINE_ELEMENT(clip))); + + return MEDIAEDITOR_ERROR_NONE; +} + +int _mediaeditor_set_clip_duration(mediaeditor_s *editor, unsigned int clip_id, unsigned int duration) +{ + GESClip *clip = NULL; + guint64 duration_nsec = 0; + + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + RET_VAL_IF(clip_id >= editor->clip_id, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid clip id"); + + clip = __mediaeditor_find_clip(editor->clips, clip_id); + RET_VAL_IF(clip == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid clip id"); + + duration_nsec = MILLI_TO_NANO(duration); + LOG_DEBUG("clip_id[%d], duration[%"G_GUINT64_FORMAT"]", clip_id, duration_nsec); + + if (!ges_timeline_element_set_duration(GES_TIMELINE_ELEMENT(clip), duration_nsec)) { + LOG_ERROR("failed to set duration"); + return MEDIAEDITOR_ERROR_INVALID_OPERATION; + } + + return MEDIAEDITOR_ERROR_NONE; +} + +int _mediaeditor_get_clip_in_point(mediaeditor_s *editor, unsigned int clip_id, unsigned int *in_point) +{ + GESClip *clip = NULL; + + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + RET_VAL_IF(clip_id >= editor->clip_id, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid clip id"); + + clip = __mediaeditor_find_clip(editor->clips, clip_id); + RET_VAL_IF(clip == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid clip id"); + + *in_point = NANO_TO_MILLI(ges_timeline_element_get_inpoint(GES_TIMELINE_ELEMENT(clip))); + + return MEDIAEDITOR_ERROR_NONE; +} + +int _mediaeditor_set_clip_in_point(mediaeditor_s *editor, unsigned int clip_id, unsigned int in_point) +{ + GESClip *clip = NULL; + guint64 in_point_nsec = 0; + + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + RET_VAL_IF(clip_id >= editor->clip_id, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid clip id"); + + clip = __mediaeditor_find_clip(editor->clips, clip_id); + RET_VAL_IF(clip == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid clip id"); + + in_point_nsec = MILLI_TO_NANO(in_point); + LOG_DEBUG("clip_id[%d], in_point[%"G_GUINT64_FORMAT"]", clip_id, in_point_nsec); + + if (!ges_timeline_element_set_inpoint(GES_TIMELINE_ELEMENT(clip), in_point_nsec)) { + LOG_ERROR("failed to set in_point"); + return MEDIAEDITOR_ERROR_INVALID_OPERATION; + } + + return MEDIAEDITOR_ERROR_NONE; +} + +int _mediaeditor_get_clip_resolution(mediaeditor_s *editor, unsigned int clip_id, + unsigned int *width, unsigned int *height) +{ + GESClip *clip = NULL; + + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + RET_VAL_IF(clip_id >= editor->clip_id, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid id"); + + clip = __mediaeditor_find_clip(editor->clips, clip_id); + RET_VAL_IF(clip == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid clip id"); + RET_VAL_IF(!__has_video_track(clip), MEDIAEDITOR_ERROR_INVALID_OPERATION, "Not video clip"); + + __mediaeditor_add_child_properties_resolution(clip); + + ges_timeline_element_get_child_properties(GES_TIMELINE_ELEMENT(clip), "width", width, "height", height, NULL); + + LOGD("width:%d, height:%d", *width, *height); + + return MEDIAEDITOR_ERROR_NONE; +} + +int _mediaeditor_set_clip_resolution(mediaeditor_s *editor, unsigned int clip_id, + unsigned int width, unsigned int height) +{ + GESClip *clip = NULL; + + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + RET_VAL_IF(clip_id >= editor->clip_id, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid id"); + + clip = __mediaeditor_find_clip(editor->clips, clip_id); + RET_VAL_IF(clip == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid clip id"); + RET_VAL_IF(!__has_video_track(clip), MEDIAEDITOR_ERROR_INVALID_OPERATION, "Not video clip"); + + LOGD("width:%d, height:%d", width, height); + + __mediaeditor_add_child_properties_resolution(clip); + + ges_timeline_element_set_child_properties(GES_TIMELINE_ELEMENT(clip), "width", width, "height", height, NULL); + + return MEDIAEDITOR_ERROR_NONE; +} + +int _mediaeditor_get_clip_volume(mediaeditor_s *editor, unsigned int clip_id, double *volume) +{ + GESClip *clip = NULL; + GValue val = { 0 }; + + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + clip = __mediaeditor_find_clip(editor->clips, clip_id); + RET_VAL_IF(clip == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid clip id"); + RET_VAL_IF(!__has_audio_track(clip), MEDIAEDITOR_ERROR_INVALID_OPERATION, "Not audio clip"); + + g_value_init(&val, G_TYPE_DOUBLE); + if (!ges_timeline_element_get_child_property(GES_TIMELINE_ELEMENT(clip), "volume", &val)) { + LOG_ERROR("failed to get volume"); + return MEDIAEDITOR_ERROR_INVALID_OPERATION; + } + + *volume = g_value_get_double(&val); + + return MEDIAEDITOR_ERROR_NONE; +} + +int _mediaeditor_set_clip_volume(mediaeditor_s *editor, unsigned int clip_id, double volume) +{ + GESClip *clip = NULL; + GValue val = { 0 }; + + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + clip = __mediaeditor_find_clip(editor->clips, clip_id); + RET_VAL_IF(clip == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid clip id"); + RET_VAL_IF(!__has_audio_track(clip), MEDIAEDITOR_ERROR_INVALID_OPERATION, "Not audio clip"); + + g_value_init(&val, G_TYPE_DOUBLE); + g_value_set_double(&val, volume); + if (!ges_timeline_element_set_child_property(GES_TIMELINE_ELEMENT(clip), "volume", &val)) { + LOG_ERROR("failed to set volume"); + return MEDIAEDITOR_ERROR_INVALID_OPERATION; + } + + return MEDIAEDITOR_ERROR_NONE; +} diff --git a/src/media_editor_display.c b/src/media_editor_display.c new file mode 100644 index 0000000..d57219d --- /dev/null +++ b/src/media_editor_display.c @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "media_editor.h" +#include "media_editor_private.h" +#include + +static int __set_evas_display(mediaeditor_display_s *display) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + + RET_VAL_IF(display == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "display is NULL"); + + ret = mm_display_interface_set_display_mainloop_sync(display->mm_display, MM_DISPLAY_TYPE_EVAS, display->object, NULL); + RET_VAL_IF(ret != MM_ERROR_NONE, MEDIAEDITOR_ERROR_INVALID_OPERATION, + "failed to mm_display_interface_set_display_mainloop_sync()"); + + ret = mm_display_interface_evas_set_mode(display->mm_display, 0); + RET_VAL_IF(ret != MM_ERROR_NONE, MEDIAEDITOR_ERROR_INVALID_OPERATION, + "failed to mm_display_interface_evas_set_mode()"); + + ret = mm_display_interface_evas_set_rotation(display->mm_display, 0); + RET_VAL_IF(ret != MM_ERROR_NONE, MEDIAEDITOR_ERROR_INVALID_OPERATION, + "failed to mm_display_interface_evas_set_rotation()"); + + ret = mm_display_interface_evas_set_visible(display->mm_display, true); + RET_VAL_IF(ret != MM_ERROR_NONE, MEDIAEDITOR_ERROR_INVALID_OPERATION, + "failed to mm_display_interface_evas_set_visible()"); + + return MEDIAEDITOR_ERROR_NONE; +} + +static int __set_wl_sink(mediaeditor_s *_editor) +{ + GstElement *videosink = NULL; + + RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + RET_VAL_IF(_editor->gst.pipeline == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "pipeline is NULL"); + + videosink = gst_element_factory_make("tizenwlsink", NULL); + RET_VAL_IF(videosink == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to create element"); + + ges_pipeline_preview_set_video_sink(_editor->gst.pipeline, videosink); + + gst_video_overlay_set_wl_window_wl_surface_id(GST_VIDEO_OVERLAY(videosink), _editor->display->overlay_surface_id); + + return MEDIAEDITOR_ERROR_NONE; +} + +static int __set_overlay_display(mediaeditor_s *_editor, mediaeditor_display_s *display) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + mm_display_type_e type = MM_DISPLAY_TYPE_OVERLAY; + + RET_VAL_IF(display == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "display is NULL"); + + if (display->type == MEDIAEDITOR_DISPLAY_TYPE_ECORE) + type = MM_DISPLAY_TYPE_OVERLAY_EXT; + + ret = mm_display_interface_set_display_mainloop_sync(display->mm_display, type, display->object, &display->overlay_surface_id); + RET_VAL_IF(ret != MM_ERROR_NONE, MEDIAEDITOR_ERROR_INVALID_OPERATION, + "failed to mm_display_interface_set_display_mainloop_sync()"); + + ret = __set_wl_sink(_editor); + RET_VAL_IF(ret != MM_ERROR_NONE, ret, "failed to set wayland sink"); + + LOG_INFO("overlay_surface_id[%d]", display->overlay_surface_id); + + return MEDIAEDITOR_ERROR_NONE; +} + +static int __init_display(mediaeditor_display_s **display) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + + RET_VAL_IF(*display != NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, + "display is already initialized."); + + mediaeditor_display_s *_display = g_new0(mediaeditor_display_s, 1); + + g_mutex_init(&_display->mutex); + + if ((ret = mm_display_interface_init(&_display->mm_display)) != MM_ERROR_NONE) { + LOG_ERROR("failed to init display. (0x%x)", ret); + return MEDIAEDITOR_ERROR_INVALID_OPERATION; + } + + LOG_DEBUG("initialized display[%p], mm_display[%p]", _display, _display->mm_display); + + *display = _display; + + return MEDIAEDITOR_ERROR_NONE; +} + +int _mediaeditor_set_display(mediaeditor_h editor, mediaeditor_display_type_e type, mediaeditor_display_h object) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + g_autoptr(GMutexLocker) locker = NULL; + mediaeditor_s *_editor = (mediaeditor_s*)editor; + mediaeditor_display_s *_display = _editor->display; + + RET_VAL_IF(_editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + if (_display == NULL) { + if ((ret = __init_display(&_display)) != MEDIAEDITOR_ERROR_NONE) { + LOG_ERROR("failed to initialize display[0x%x]", ret); + return ret; + } + } + + LOG_DEBUG("_display[%p]", _display); + LOG_DEBUG("mutex[%p]", &_display->mutex); + + locker = g_mutex_locker_new(&_display->mutex); + + _display->type = type; + _display->object = object; + + LOG_DEBUG("display type : %d", type); + switch (type) { + case MEDIAEDITOR_DISPLAY_TYPE_OVERLAY: + ret = __set_overlay_display(_editor, _display); + break; + case MEDIAEDITOR_DISPLAY_TYPE_EVAS: + ret = __set_evas_display(_display); + break; + case MEDIAEDITOR_DISPLAY_TYPE_ECORE: + ret = __set_overlay_display(_editor, _display); + break; + default: + LOG_DEBUG("display type none"); + break; + } + + RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to set display[%d]", type); + + return MEDIAEDITOR_ERROR_NONE; +} + +void _mediaeditor_release_display(mediaeditor_display_s *display) +{ + RET_IF(display == NULL, "display is NULL"); + + g_mutex_lock(&display->mutex); + + if (display->mm_display) { + LOG_DEBUG("deinit display->mm_display[%p]", display->mm_display); + mm_display_interface_deinit(display->mm_display); + display->mm_display = NULL; + } + + g_mutex_unlock(&display->mutex); + g_mutex_clear(&display->mutex); + + LOG_DEBUG("free display[%p]", display); + + g_free(display); +} diff --git a/src/media_editor_effect.c b/src/media_editor_effect.c new file mode 100644 index 0000000..1f9f46b --- /dev/null +++ b/src/media_editor_effect.c @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "media_editor.h" +#include "media_editor_private.h" + +static gchar *__get_effect_name(mediaeditor_effect_type_e type) +{ + switch (type) { + case MEDIAEDITOR_EFFECT_VIDEO_TYPE_EDGETV: + return g_strdup("edgetv"); + case MEDIAEDITOR_EFFECT_VIDEO_TYPE_AGINGTV: + return g_strdup("agingtv"); + case MEDIAEDITOR_EFFECT_VIDEO_TYPE_DICETV: + return g_strdup("dicetv"); + case MEDIAEDITOR_EFFECT_VIDEO_TYPE_WARPTV: + return g_strdup("warptv"); + case MEDIAEDITOR_EFFECT_VIDEO_TYPE_SHAGADELICTV: + return g_strdup("shagadelictv"); + case MEDIAEDITOR_EFFECT_VIDEO_TYPE_VERTIGOTV: + return g_strdup("vertigotv"); + case MEDIAEDITOR_EFFECT_VIDEO_TYPE_REVTV: + return g_strdup("revtv"); + case MEDIAEDITOR_EFFECT_VIDEO_TYPE_QUARKTV: + return g_strdup("quarktv"); + case MEDIAEDITOR_EFFECT_VIDEO_TYPE_OPTV: + return g_strdup("optv"); + case MEDIAEDITOR_EFFECT_VIDEO_TYPE_RADIOACTV: + return g_strdup("radioactv"); + case MEDIAEDITOR_EFFECT_VIDEO_TYPE_STREAKTV: + return g_strdup("streaktv"); + case MEDIAEDITOR_EFFECT_VIDEO_TYPE_RIPPLETV: + return g_strdup("rippletv"); + case MEDIAEDITOR_EFFECT_AUDIO_TYPE_ECHO: + return g_strdup("audioecho"); + default: + LOG_ERROR("Not supported effect"); + break; + } + + return NULL; +} + +static GstTimedValue *__new_timed_value(GstClockTime time, gdouble val) +{ + GstTimedValue *tmval = g_new0(GstTimedValue, 1); + + tmval->value = val; + tmval->timestamp = time; + + return tmval; +} + +static int __keep_audio_fade_effect(mediaeditor_s *editor, unsigned int start, unsigned int duration, bool is_fade_in) +{ + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + if (is_fade_in) { + LOG_DEBUG("FADE IN, start[%"G_GUINT64_FORMAT"], end[%"G_GUINT64_FORMAT"]", + start * GST_MSECOND, (start + duration) * GST_MSECOND); + editor->audio_fade_effects = g_slist_prepend(editor->audio_fade_effects, __new_timed_value(start * GST_MSECOND, 0.0)); + editor->audio_fade_effects = g_slist_prepend(editor->audio_fade_effects, __new_timed_value((start + 100) * GST_MSECOND, 0.0)); + editor->audio_fade_effects = g_slist_prepend(editor->audio_fade_effects, __new_timed_value((start + duration) * GST_MSECOND, 1.0)); + } else { + LOG_DEBUG("FADE OUT, start[%"G_GUINT64_FORMAT"], end[%"G_GUINT64_FORMAT"]", + start * GST_MSECOND, (start + duration) * GST_MSECOND); + editor->audio_fade_effects = g_slist_prepend(editor->audio_fade_effects, __new_timed_value(start * GST_MSECOND, 1.0)); + editor->audio_fade_effects = g_slist_prepend(editor->audio_fade_effects, __new_timed_value((start + duration - 100) * GST_MSECOND, 0.0)); + editor->audio_fade_effects = g_slist_prepend(editor->audio_fade_effects, __new_timed_value((start + duration) * GST_MSECOND, 0.0)); + } + + RET_VAL_IF(editor->audio_fade_effects == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to prepend fade effect"); + + return MEDIAEDITOR_ERROR_NONE; +} + +int _mediaeditor_add_transition(mediaeditor_s *editor, mediaeditor_transition_type_e type, unsigned int layer_id, + unsigned int start, unsigned int duration) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + gboolean ges_ret = FALSE; + GESLayer *layer = NULL; + GESTransitionClip *transition_clip = NULL; + guint64 start_nsec = 0; + guint64 duration_nsec = 0; + + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + if (!(transition_clip = ges_transition_clip_new((GESVideoStandardTransitionType)type))) { + LOGE("Failed to create transition clip :%d", type); + return MEDIAEDITOR_ERROR_INVALID_OPERATION; + } + + start_nsec = MILLI_TO_NANO(start); + duration_nsec = MILLI_TO_NANO(duration); + + LOG_DEBUG("layer_id[%d], layer[%p], start[%"G_GUINT64_FORMAT"], duration[%"G_GUINT64_FORMAT"]", + layer_id, layer, start_nsec, duration_nsec); + + ges_ret = ges_timeline_element_set_start(GES_TIMELINE_ELEMENT(transition_clip), start_nsec); + RET_VAL_IF(!ges_ret, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to set start"); + ges_ret = ges_timeline_element_set_duration(GES_TIMELINE_ELEMENT(transition_clip), duration_nsec); + RET_VAL_IF(!ges_ret, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to set duration"); + + ret = _mediaeditor_get_layer(editor, layer_id, &layer); + RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to get layer"); + + ges_layer_add_clip(layer, GES_CLIP(transition_clip)); + + return MEDIAEDITOR_ERROR_NONE; +} + +static int __mediaeditor_create_effect_clip(mediaeditor_s *editor, mediaeditor_effect_type_e type, unsigned int layer_id, + unsigned int start, unsigned int duration, unsigned int *effect_id) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + gboolean ges_ret = FALSE; + GESClip *clip = NULL; + GESLayer *layer = NULL; + g_autofree gchar *effect_name = NULL; + guint64 start_nsec = 0; + guint64 duration_nsec = 0; + + effect_name = __get_effect_name(type); + RET_VAL_IF(effect_name == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to get effect name"); + + clip = GES_CLIP(ges_effect_clip_new(effect_name, NULL)); + RET_VAL_IF(clip == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to create effect clip"); + + start_nsec = MILLI_TO_NANO(start); + duration_nsec = MILLI_TO_NANO(duration); + + LOG_DEBUG("start[%"G_GUINT64_FORMAT"], duration[%"G_GUINT64_FORMAT"]", start_nsec, duration_nsec); + + ges_ret = ges_timeline_element_set_start(GES_TIMELINE_ELEMENT(clip), start_nsec); + RET_VAL_IF(!ges_ret, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to set start"); + ges_ret = ges_timeline_element_set_duration(GES_TIMELINE_ELEMENT(clip), duration_nsec); + RET_VAL_IF(!ges_ret, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to set duration"); + + ret = _mediaeditor_get_layer(editor, layer_id, &layer); + if (ret != MEDIAEDITOR_ERROR_NONE) { + LOG_ERROR("failed to get layer"); + g_object_unref(clip); + return ret; + } + + if (!ges_layer_add_clip(layer, clip)) { + LOG_ERROR("failed to add clip to layer"); + g_object_unref(clip); + return MEDIAEDITOR_ERROR_INVALID_OPERATION; + } + + LOG_INFO("add effect layer[%p] clip[%p]", layer, clip); + + ret = _mediaeditor_create_clip_common(editor, clip, type, effect_id); + if (ret != MEDIAEDITOR_ERROR_NONE) { + LOG_ERROR("failed to create clip common"); + g_object_unref(clip); + return ret; + } + + return MEDIAEDITOR_ERROR_NONE; +} + +static int __mediaeditor_create_effect(mediaeditor_s *editor, mediaeditor_effect_type_e type, + unsigned int start, unsigned int duration, unsigned int *effect_id) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + + switch (type) { + case MEDIAEDITOR_EFFECT_AUDIO_TYPE_FADE_IN: + ret = __keep_audio_fade_effect(editor, start, duration, true); + break; + case MEDIAEDITOR_EFFECT_AUDIO_TYPE_FADE_OUT: + ret = __keep_audio_fade_effect(editor, start, duration, false); + break; + default: + LOG_ERROR("There's no matched effect"); + ret = MEDIAEDITOR_ERROR_INVALID_PARAMETER; + break; + } + RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to apply audio effect"); + + ret = _mediaeditor_create_clip_common(editor, NULL, type, effect_id); + RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to create clip common"); + + return MEDIAEDITOR_ERROR_NONE; +} + +int _mediaeditor_add_effect(mediaeditor_s *editor, mediaeditor_effect_type_e type, unsigned int layer_id, + unsigned int start, unsigned int duration, unsigned int *effect_id) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + switch (type) { + case MEDIAEDITOR_EFFECT_AUDIO_TYPE_FADE_IN: + case MEDIAEDITOR_EFFECT_AUDIO_TYPE_FADE_OUT: + ret = __mediaeditor_create_effect(editor, type, start, duration, effect_id); + break; + default: + ret = __mediaeditor_create_effect_clip(editor, type, layer_id, start, duration, effect_id); + break; + } + RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to create effect"); + + return ret; +} + +int _mediaeditor_remove_effect(mediaeditor_s *editor, unsigned int effect_id) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + mediaeditor_clip_s *clip_data = NULL; + + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + clip_data = _mediaeditor_find_clip_data(editor->clips, effect_id); + RET_VAL_IF(clip_data == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid clip id"); + RET_VAL_IF(clip_data->effect_type == MEDIAEDITOR_EFFECT_TYPE_NONE, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "not effect clip id"); + + switch (clip_data->effect_type) { + case MEDIAEDITOR_EFFECT_AUDIO_TYPE_FADE_IN: + case MEDIAEDITOR_EFFECT_AUDIO_TYPE_FADE_OUT: + editor->clips = g_list_remove(editor->clips, clip_data); + free(clip_data); + break; + default: + ret = _mediaeditor_remove_clip(editor, effect_id); + break; + } + RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to remove effect"); + + return ret; +} diff --git a/src/media_editor_ini.c b/src/media_editor_ini.c new file mode 100644 index 0000000..c42becb --- /dev/null +++ b/src/media_editor_ini.c @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#define MEDIAEDITOR_INI_PATH SYSCONFDIR"/multimedia/mmfw_media_editor.ini" +#define DEFAULT_GENERATE_DOT false +#define DEFAULT_DOT_PATH "/tmp" + +/* categories */ +#define INI_CATEGORY_GENERAL "general" +#define INI_CATEGORY_ENCODER "encoder" +#define INI_CATEGORY_DECODER "decoder" +#define INI_CATEGORY_RESOURCE_ACQUISITION "resource acquisition" + +/* items for general */ +#define INI_ITEM_DOT_PATH "dot path" +#define INI_ITEM_DOT_GENERATE "generate dot" +#define INI_ITEM_GST_ARGS "gstreamer arguments" + +/* items for encoder */ +#define INI_ITEM_ENCODER_CONTAINER "container" +#define INI_ITEM_AUDIO_HW_ENCODER "audio hw encoder" +#define INI_ITEM_VIDEO_HW_ENCODER "video hw encoder" + +/* items for decoder */ +#define INI_ITEM_AUDIO_HW_DECODER "audio hw decoder" +#define INI_ITEM_VIDEO_HW_DECODER "video hw decoder" + +/* items for resource acquisition */ +#define INI_ITEM_RESOURCE_VIDEO_ENCODER "video encoder" +#define INI_ITEM_RESOURCE_VIDEO_DECODER "video decoder" + +#define DEFAULT_RESOURCE_VIDEO_ENCODER_REQUIRED false +#define DEFAULT_RESOURCE_VIDEO_DECODER_REQUIRED false + +#define DEFAULT_CONTAINER "ogg" +#define DEFAULT_AUDIO_ENCODER "vorbis" +#define DEFAULT_VIDEO_ENCODER "theora" + +typedef enum { + INI_ITEM_TYPE_BOOL, + INI_ITEM_TYPE_INT, + INI_ITEM_TYPE_STRING, + INI_ITEM_TYPE_STRINGS +} ini_item_type_e; + +static void __dump_item(const char *prefix_str, ini_item_type_e type, void *item) +{ + RET_IF(prefix_str == NULL, "prefix_str is NULL"); + + if (item == NULL) + return; + + switch (type) { + case INI_ITEM_TYPE_BOOL: + LOG_INFO("- %-19s = %s", prefix_str, *(bool*)item ? "yes" : "no"); + break; + case INI_ITEM_TYPE_INT: + LOG_INFO("- %-19s = %d", prefix_str, *(int*)item); + break; + case INI_ITEM_TYPE_STRING: + LOG_INFO("- %-19s = %s", prefix_str, (const char *)item); + break; + case INI_ITEM_TYPE_STRINGS: { + gchar *joined_str = g_strjoinv(" ", item); + LOG_INFO("- %-19s = %s", prefix_str, joined_str); + g_free(joined_str); + break; + } + default: + LOG_ERROR("not supported type[%d]", type); + break; + } +} + +static void __dump_ini(mediaeditor_ini_s *ini) +{ + RET_IF(ini == NULL, "ini is NULL"); + + LOG_INFO("[%s]", INI_CATEGORY_GENERAL); + __dump_item(INI_ITEM_DOT_GENERATE, INI_ITEM_TYPE_BOOL, &ini->general.generate_dot); + __dump_item(INI_ITEM_DOT_PATH, INI_ITEM_TYPE_STRING, (void *)ini->general.dot_path); + __dump_item(INI_ITEM_GST_ARGS, INI_ITEM_TYPE_STRINGS, ini->general.gst_args); + + LOG_INFO("[%s]", INI_CATEGORY_ENCODER); + __dump_item(INI_ITEM_ENCODER_CONTAINER, INI_ITEM_TYPE_STRING, (void *)ini->encoder.container); + __dump_item(INI_ITEM_AUDIO_HW_ENCODER, INI_ITEM_TYPE_STRING, (void *)ini->encoder.audio_hw_encoder); + __dump_item(INI_ITEM_VIDEO_HW_ENCODER, INI_ITEM_TYPE_STRING, (void *)ini->encoder.video_hw_encoder); + + LOG_INFO("[%s]", INI_CATEGORY_DECODER); + __dump_item(INI_ITEM_AUDIO_HW_DECODER, INI_ITEM_TYPE_STRING, (void *)ini->decoder.audio_hw_decoder); + __dump_item(INI_ITEM_VIDEO_HW_DECODER, INI_ITEM_TYPE_STRING, (void *)ini->decoder.video_hw_decoder); + + LOG_INFO("[%s]", INI_CATEGORY_RESOURCE_ACQUISITION); + __dump_item(INI_ITEM_RESOURCE_VIDEO_ENCODER, INI_ITEM_TYPE_STRING, (void *)ini->resource_acquisition.video_encoder); + __dump_item(INI_ITEM_RESOURCE_VIDEO_DECODER, INI_ITEM_TYPE_STRING, (void *)ini->resource_acquisition.video_decoder); +} + +static const char* __get_delimiter(const char *ini_path) +{ + const char *delimiter = ","; + + if (g_strrstr(ini_path, INI_ITEM_GST_ARGS)) + delimiter = "|"; + + return delimiter; +} + +static void __ini_read_list(dictionary *dict, const char *category, const char *item, gchar ***list) +{ + const char *str = NULL; + g_autofree gchar *path = NULL; + g_autofree gchar *strtmp = NULL; + + if (dict == NULL) + return; + + RET_IF(category == NULL, "category is NULL"); + RET_IF(item == NULL, "item is NULL"); + RET_IF(list == NULL, "list is NULL"); + + path = g_strconcat(category, ":", item, NULL); + str = iniparser_getstring(dict, path, NULL); + if (str && strlen(str) > 0) { + strtmp = g_strdup(str); + g_strstrip(strtmp); + *list = g_strsplit(strtmp, __get_delimiter((const char *)path), 10); + } + + LOG_DEBUG("[%s] %s", path, strtmp ? strtmp : "none"); +} + +static const char* __ini_get_string(dictionary *dict, const char *category, const char *item, const char *default_value) +{ + g_autofree gchar *path = NULL; + const char *ret_val = NULL; + + if (dict == NULL) + return default_value; + + RET_VAL_IF(category == NULL, default_value, "category is NULL"); + RET_VAL_IF(item == NULL, default_value, "item is NULL"); + + path = g_strconcat(category, ":", item, NULL); + ret_val = iniparser_getstring(dict, path, default_value); + + if (ret_val != NULL && strlen(ret_val) == 0) + return default_value; + + return ret_val; +} + +static bool __ini_get_boolean(dictionary *dict, const char *category, const char *item, bool default_value) +{ + g_autofree gchar *path = NULL; + bool ret_val = false; + + if (dict == NULL) + return default_value; + RET_VAL_IF(category == NULL, default_value, "category is NULL"); + RET_VAL_IF(item == NULL, default_value, "item is NULL"); + + path = g_strconcat(category, ":", item, NULL); + ret_val = (bool)iniparser_getboolean(dict, path, default_value); + + return ret_val; +} + +int _load_ini(mediaeditor_s *editor) +{ + LOG_DEBUG("Enter [%p]", editor); + mediaeditor_ini_s *ini = NULL; + + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + memset(&editor->ini, 0, sizeof(mediaeditor_ini_s)); + ini = &editor->ini; + + ini->dict = iniparser_load(MEDIAEDITOR_INI_PATH); + if (!ini->dict) + LOG_WARNING("could not open ini[%s], use default values", MEDIAEDITOR_INI_PATH); + + /* general */ + ini->general.generate_dot = __ini_get_boolean(ini->dict, INI_CATEGORY_GENERAL, INI_ITEM_DOT_GENERATE, DEFAULT_GENERATE_DOT); + ini->general.dot_path = __ini_get_string(ini->dict, INI_CATEGORY_GENERAL, INI_ITEM_DOT_PATH, DEFAULT_DOT_PATH); + if (ini->general.generate_dot) { + LOG_INFO("dot file will be stored in [%s]", ini->general.dot_path); + g_setenv("GST_DEBUG_DUMP_DOT_DIR", ini->general.dot_path, FALSE); + } + __ini_read_list(ini->dict, INI_CATEGORY_GENERAL, INI_ITEM_GST_ARGS, &ini->general.gst_args); + + /* encoder */ + ini->encoder.container = __ini_get_string(ini->dict, INI_CATEGORY_ENCODER, INI_ITEM_ENCODER_CONTAINER, DEFAULT_CONTAINER); + ini->encoder.audio_hw_encoder = __ini_get_string(ini->dict, INI_CATEGORY_ENCODER, INI_ITEM_AUDIO_HW_ENCODER, NULL); + ini->encoder.video_hw_encoder = __ini_get_string(ini->dict, INI_CATEGORY_ENCODER, INI_ITEM_VIDEO_HW_ENCODER, NULL); + + /* decoder */ + ini->decoder.audio_hw_decoder = __ini_get_string(ini->dict, INI_CATEGORY_DECODER, INI_ITEM_AUDIO_HW_DECODER, NULL); + ini->decoder.video_hw_decoder = __ini_get_string(ini->dict, INI_CATEGORY_DECODER, INI_ITEM_VIDEO_HW_DECODER, NULL); + + /* resource acqusition */ + ini->resource_acquisition.video_encoder = __ini_get_boolean(ini->dict, INI_CATEGORY_RESOURCE_ACQUISITION, + INI_ITEM_RESOURCE_VIDEO_ENCODER, DEFAULT_RESOURCE_VIDEO_ENCODER_REQUIRED); + ini->resource_acquisition.video_decoder = __ini_get_boolean(ini->dict, INI_CATEGORY_RESOURCE_ACQUISITION, + INI_ITEM_RESOURCE_VIDEO_DECODER, DEFAULT_RESOURCE_VIDEO_DECODER_REQUIRED); + + __dump_ini(ini); + + return MEDIAEDITOR_ERROR_NONE; +} + +void _unload_ini(mediaeditor_s *editor) +{ + LOG_DEBUG("Enter [%p]", editor); + + RET_IF(editor == NULL, "editor is NULL"); + RET_IF(editor->ini.dict == NULL, "ini.dict is NULL"); + + g_strfreev(editor->ini.general.gst_args); + editor->ini.general.gst_args = NULL; + + iniparser_freedict(editor->ini.dict); + LOG_DEBUG("ini instance[%p] is freed", editor->ini.dict); + editor->ini.dict = NULL; +} + +bool _is_resource_required(mediaeditor_ini_s *ini) +{ + RET_VAL_IF(ini == NULL, false, "ini is NULL"); + + return (ini->resource_acquisition.video_encoder || + ini->resource_acquisition.video_decoder); +} diff --git a/src/media_editor_layer.c b/src/media_editor_layer.c new file mode 100644 index 0000000..930791d --- /dev/null +++ b/src/media_editor_layer.c @@ -0,0 +1,436 @@ +/* + * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "media_editor.h" +#include "media_editor_private.h" +#include + +static void __print_layer_data(mediaeditor_layer_s *layer_data) +{ +#ifdef PRINT_LAYER_DEBUG_INFO + RET_IF(layer_data == NULL, "layer_data is null"); + LOG_DEBUG("layer_data[%p], layer[%p], id[%d]", layer_data, layer_data->layer, layer_data->id); +#endif +} + +static void __print_layer_priorities(GESTimeline *timeline) +{ +#ifdef PRINT_LAYER_DEBUG_INFO + guint priority = 0; + + RET_IF(timeline == NULL, "timeline is NULL"); + + for (GList *layer_iter = timeline->layers; layer_iter; layer_iter = layer_iter->next) { + GESLayer *layer = GES_LAYER(layer_iter->data); + + priority = ges_layer_get_priority(layer); + LOG_DEBUG("layer[%p] layer priority[%d]", layer, priority); + } +#endif +} + +static gint __mediaeditor_comparefunc_find_layer(gconstpointer a, gconstpointer b) +{ + mediaeditor_layer_s *layer_data = (mediaeditor_layer_s *)a; + unsigned int *id = (unsigned int *)b; + + if (layer_data == NULL || layer_data->id != *id) + return -1; + + LOG_DEBUG("layer id[%d], layer[%p]", *id, layer_data->layer); + + return 0; +} + +static mediaeditor_layer_s *__mediaeditor_find_layer_data(GList *lists, unsigned int id) +{ + GList *list = NULL; + + list = g_list_find_custom(lists, (gconstpointer)&id, __mediaeditor_comparefunc_find_layer); + RET_VAL_IF(list == NULL, NULL, "failed to find matched layer"); + + __print_layer_data(list->data); + + return list->data; +} + +static GESLayer *__mediaeditor_find_layer(GList *lists, unsigned int id) +{ + mediaeditor_layer_s *layer_data = NULL; + + layer_data = __mediaeditor_find_layer_data(lists, id); + RET_VAL_IF(layer_data == NULL, NULL, "failed to find layer data"); + + return layer_data->layer; +} + +static gint __mediaeditor_comparefunc_find_id(gconstpointer a, gconstpointer b) +{ + mediaeditor_layer_s *layer_data = (mediaeditor_layer_s *)a; + GESLayer *layer = (GESLayer *)b; + + if (layer_data == NULL || layer_data->layer != layer) + return -1; + + LOG_DEBUG("layer[%p], layer id[%d]", layer_data->layer, layer_data->id); + + return 0; +} + +static unsigned int __mediaeditor_find_layer_id(GList *lists, GESLayer *layer) +{ + mediaeditor_layer_s *layer_data = NULL; + GList *list = NULL; + + list = g_list_find_custom(lists, (gconstpointer)layer, __mediaeditor_comparefunc_find_id); + RET_VAL_IF(list == NULL, INT_MAX, "failed to find matched layer"); + + layer_data = (mediaeditor_layer_s *)list->data; + RET_VAL_IF(layer_data == NULL, INT_MAX, "failed to find layer"); + + return layer_data->id; +} + +static mediaeditor_layer_s * __mediaeditor_create_layer_data(GESLayer *layer, unsigned int id) +{ + mediaeditor_layer_s *data = NULL; + + data = g_new0(mediaeditor_layer_s, 1); + + data->layer = layer; + data->id = id; + + return data; +} + +static int __resync_layers(mediaeditor_s *editor, unsigned int layer_priority, bool *is_resynced) +{ + /* It should be done in GES but it's not curreltly. This is workaround. */ + int ret = MEDIAEDITOR_ERROR_NONE; + GESTimeline *timeline = editor->gst.timeline; + GESLayer *layer = NULL; + unsigned int layer_id = 0; + + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + RET_VAL_IF(is_resynced == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "is_resynced is NULL"); + *is_resynced = false; + + LOG_DEBUG("layer_priority[%d]", layer_priority); + + GList *list = ges_timeline_get_layers(editor->gst.timeline); + if (list == NULL) { + LOG_DEBUG("There's no layers to resync"); + return MEDIAEDITOR_ERROR_NONE; + } + + ret = _mediaeditor_get_layer_id(editor, layer_priority + 1, &layer_id); + RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret , "failed to get layer id"); + + ret = _mediaeditor_get_layer(editor, layer_id, &layer); + if (ret != MEDIAEDITOR_ERROR_NONE) { + LOG_DEBUG("There's nothing to resync"); + return MEDIAEDITOR_ERROR_NONE; + } + LOG_DEBUG("priority + 1[%d], layer id[%d], layer[%p]", layer_priority + 1, layer_id, layer); + + /* ges_timeline_move_layer() resync priority internally. So we don't need to move layer anymore */ + if (!ges_timeline_move_layer(timeline, layer, layer_priority)) { + LOG_DEBUG("failed to move layer"); + return MEDIAEDITOR_ERROR_INVALID_OPERATION; + } + + *is_resynced = true; + LOG_DEBUG("layer is resynced"); + + return MEDIAEDITOR_ERROR_NONE; +} + +static void __invoke_layer_priority_changed_cb(mediaeditor_s *editor, mediaeditor_layer_info_s *layer_info, unsigned int size) +{ + RET_IF(editor == NULL, "editor is NULL"); + + if (editor->layer_priority_changed_cb.callback) { + LOG_DEBUG(">>> callback[%p] user_data[%p]", editor->layer_priority_changed_cb.callback, editor->layer_priority_changed_cb.user_data); + ((mediaeditor_layer_priority_changed_cb)(editor->layer_priority_changed_cb.callback))(layer_info, size, editor->layer_priority_changed_cb.user_data); + LOG_DEBUG("<<< end of the callback"); + } +} + +static int __create_layer_info(mediaeditor_s *editor, mediaeditor_layer_info_s **layer_info, unsigned int *size) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + unsigned int lowest_priority = 0; + unsigned int layer_id = 0; + unsigned int number_of_layer = 0; + mediaeditor_layer_info_s *layer_info_tmp = NULL; + + ret = _mediaeditor_get_layer_lowest_priority(editor, &lowest_priority); + RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to get lowest layer priority"); + + number_of_layer = lowest_priority + 1; + layer_info_tmp = (mediaeditor_layer_info_s *)malloc(sizeof(mediaeditor_layer_info_s) * number_of_layer); + RET_VAL_IF(layer_info_tmp == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to alloc layer info"); + + memset(layer_info_tmp, 0, sizeof(mediaeditor_layer_info_s) * number_of_layer); + + for (unsigned int i = 0 ; i < number_of_layer ; i++) { + ret = _mediaeditor_get_layer_id(editor, i, &layer_id); + RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to get layer id"); + + layer_info_tmp[i].id = layer_id; + layer_info_tmp[i].priority = i; + } + + *layer_info = layer_info_tmp; + *size = number_of_layer; + + return MEDIAEDITOR_ERROR_NONE; +} + +static int __mediaeditor_create_layer_common(mediaeditor_s *editor, GESLayer *layer, unsigned int *layer_id) +{ + mediaeditor_layer_s *layer_data = NULL; + + layer_data = __mediaeditor_create_layer_data(layer, editor->layer_id); + RET_VAL_IF(layer_data == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "layer_data is null"); + + __print_layer_data(layer_data); + + editor->layers = g_list_append(editor->layers, layer_data); + RET_VAL_IF(editor->layers == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "list is null"); + + *layer_id = (editor->layer_id)++; + + return MEDIAEDITOR_ERROR_NONE; +} + +int _mediaeditor_get_layer(mediaeditor_s *editor, unsigned int layer_id, GESLayer **layer) +{ + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + RET_VAL_IF(layer == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "layer is NULL"); + + *layer = __mediaeditor_find_layer(editor->layers, layer_id); + RET_VAL_IF(*layer == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid layer id"); + + LOG_DEBUG("layer[%p]", *layer); + + return MEDIAEDITOR_ERROR_NONE; +} + +int _mediaeditor_add_layer(mediaeditor_s *editor, unsigned int *layer_id, unsigned int *layer_priority) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + GESLayer *layer = NULL; + + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + RET_VAL_IF(layer_id == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "layer_id is NULL"); + RET_VAL_IF(layer_priority == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "layer_priority is NULL"); + + layer = ges_timeline_append_layer(editor->gst.timeline); + RET_VAL_IF(layer == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to append layer"); + + *layer_priority = ges_layer_get_priority(layer); + + LOG_DEBUG("added layer[%p], layer_priority[%d]", layer, *layer_priority); + + ret = __mediaeditor_create_layer_common(editor, layer, layer_id); + RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to create layer common"); + + return MEDIAEDITOR_ERROR_NONE; +} + +int _mediaeditor_remove_layer(mediaeditor_s *editor, unsigned int layer_id) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + GESLayer *layer = NULL; + g_autofree mediaeditor_layer_s *layer_data = NULL; + unsigned int layer_priority = 0; + unsigned int number_of_layer = 0; + mediaeditor_layer_info_s *layer_info = NULL; + bool is_resynced = false; + + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + layer_data = __mediaeditor_find_layer_data(editor->layers, layer_id); + RET_VAL_IF(layer_data == NULL || layer_data->layer == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid layer id"); + layer = layer_data->layer; + + __print_layer_priorities(editor->gst.timeline); + + layer_priority = ges_layer_get_priority(layer); + LOG_DEBUG("layer_priority to remove [%d]", layer_priority); + + if (!ges_timeline_remove_layer(editor->gst.timeline, layer)) { + LOG_ERROR("failed to remove layer frome timeline"); + return MEDIAEDITOR_ERROR_INVALID_OPERATION; + } + + __print_layer_priorities(editor->gst.timeline); + + editor->layers = g_list_remove(editor->layers, layer_data); + + ret = __resync_layers(editor, layer_priority, &is_resynced); + RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to resync layer"); + + __print_layer_priorities(editor->gst.timeline); + + if (is_resynced) + { + ret = __create_layer_info(editor, &layer_info, &number_of_layer); + RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to create layer info"); + + __invoke_layer_priority_changed_cb(editor, layer_info, number_of_layer); + } + + return MEDIAEDITOR_ERROR_NONE; +} + +int _mediaeditor_move_layer(mediaeditor_s *editor, unsigned int layer_id, unsigned int layer_priority) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + GESLayer *layer = NULL; + gboolean result = FALSE; + unsigned int current_priority = 0; + unsigned int number_of_layer = 0; + mediaeditor_layer_info_s *layer_info = NULL; + + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + ret = _mediaeditor_get_layer(editor, layer_id, &layer); + RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to get layer"); + + ret = _mediaeditor_get_layer_priority(editor, layer_id, ¤t_priority); + RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "invalid layer id"); + + if (current_priority == layer_priority) { + LOG_DEBUG("Nothing to move"); + return MEDIAEDITOR_ERROR_NONE; + } + + LOG_DEBUG("move layer ID[%d] -> layer priority[%d]", layer_id, layer_priority); + + __print_layer_priorities(editor->gst.timeline); + + result = ges_timeline_move_layer(editor->gst.timeline, layer, layer_priority); + RET_VAL_IF(result == FALSE, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to move layer"); + + __print_layer_priorities(editor->gst.timeline); + + ret = __create_layer_info(editor, &layer_info, &number_of_layer); + RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to create layer info"); + + __invoke_layer_priority_changed_cb(editor, layer_info, number_of_layer); + + return MEDIAEDITOR_ERROR_NONE; +} + +int _mediaeditor_activate_layer(mediaeditor_s *editor, unsigned int layer_id) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + GESLayer *layer = NULL; + GList *tracks = NULL; + gboolean result = FALSE; + + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + tracks = ges_timeline_get_tracks(editor->gst.timeline); + RET_VAL_IF(tracks == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "tracks is NULL"); + + ret = _mediaeditor_get_layer(editor, layer_id, &layer); + RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to get layer"); + + result = ges_layer_set_active_for_tracks(layer, true, tracks); + RET_VAL_IF(result == FALSE, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to set active"); + + return MEDIAEDITOR_ERROR_NONE; +} + +int _mediaeditor_deactivate_layer(mediaeditor_s *editor, unsigned int layer_id) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + GESLayer *layer = NULL; + GList *tracks = NULL; + gboolean result = FALSE; + + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + tracks = ges_timeline_get_tracks(editor->gst.timeline); + RET_VAL_IF(tracks == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "tracks is NULL"); + + ret = _mediaeditor_get_layer(editor, layer_id, &layer); + RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to get layer"); + + result = ges_layer_set_active_for_tracks(layer, false, tracks); + RET_VAL_IF(result == FALSE, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to set active"); + + return MEDIAEDITOR_ERROR_NONE; +} + +int _mediaeditor_get_layer_priority(mediaeditor_s *editor, unsigned int layer_id, unsigned int *layer_priority) +{ + GESLayer *layer = NULL; + + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is null"); + RET_VAL_IF(layer_priority == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "layer_priority is null"); + + layer = __mediaeditor_find_layer(editor->layers, layer_id); + RET_VAL_IF(layer == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "failed to get layer"); + + *layer_priority = ges_layer_get_priority(layer); + + return MEDIAEDITOR_ERROR_NONE; +} + +int _mediaeditor_get_layer_lowest_priority(mediaeditor_s *editor, unsigned int *layer_priority) +{ + unsigned int priority_max = 0; + unsigned int priority_tmp = 0; + + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is null"); + RET_VAL_IF(layer_priority == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "layer_priority is null"); + RET_VAL_IF(editor->layers == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "there's no layer"); + + for (GList *layer_iter = editor->layers; layer_iter; layer_iter = layer_iter->next) { + mediaeditor_layer_s *layer_data = (mediaeditor_layer_s *)(layer_iter->data); + + priority_tmp = ges_layer_get_priority(layer_data->layer); + if (priority_tmp > priority_max) { + priority_max = priority_tmp; + } + } + + *layer_priority = priority_max; + + return MEDIAEDITOR_ERROR_NONE; +} + +int _mediaeditor_get_layer_id(mediaeditor_s *editor, unsigned int layer_priority, unsigned int *layer_id) +{ + GESLayer *layer = NULL; + + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is null"); + RET_VAL_IF(layer_id == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "layer_id is null"); + + LOG_DEBUG("layer priority[%d]", layer_priority); + + layer = ges_timeline_get_layer(editor->gst.timeline, layer_priority); + RET_VAL_IF(layer == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invlid layer_priority"); + + *layer_id = __mediaeditor_find_layer_id(editor->layers, layer); + RET_VAL_IF(*layer_id == INT_MAX, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invlid layer_priority"); + + return MEDIAEDITOR_ERROR_NONE; +} diff --git a/src/media_editor_private.c b/src/media_editor_private.c new file mode 100644 index 0000000..a6e06e7 --- /dev/null +++ b/src/media_editor_private.c @@ -0,0 +1,752 @@ +/* + * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "media_editor.h" +#include "media_editor_private.h" +#include + +#define DEFAULT_DOT_FILE_NAME_PREFIX "mediaeditor" + +static const char* __state_str[] = { + [MEDIAEDITOR_STATE_IDLE] = "IDLE", + [MEDIAEDITOR_STATE_RENDERING] = "RENDERING", + [MEDIAEDITOR_STATE_PREVIEW] = "PREVIEW" +}; + +typedef struct _idle_userdata { + mediaeditor_s *editor; + idle_cb_type_e type; + union { + mediaeditor_state_e state; + mediaeditor_error_e error; + } new; +} idle_userdata_s; + +static const char *__get_error_string(mediaeditor_error_e error) +{ + switch (error) { + case MEDIAEDITOR_ERROR_NONE: + return "NONE"; + case MEDIAEDITOR_ERROR_PERMISSION_DENIED: + return "PERMISSION_DENIED"; + case MEDIAEDITOR_ERROR_INVALID_PARAMETER: + return "INVALID_PARAMETER"; + case MEDIAEDITOR_ERROR_INVALID_OPERATION: + return "INVALID_OPERATION"; + case MEDIAEDITOR_ERROR_INVALID_STATE: + return "INVALID_STATE"; + case MEDIAEDITOR_ERROR_RESOURCE_FAILED: + return "RESOURCE_FAILED"; + case MEDIAEDITOR_ERROR_RESOURCE_CONFLICT: + return "RESOURCE_CONFLICT"; + default: + LOG_ERROR_IF_REACHED("invalid error(0x%x)", error); + return "(invalid error)"; + } +} + +static int __create_encoding_profile(mediaeditor_s *editor, const char* path) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + GstEncodingContainerProfile *profile = NULL; + GstCaps *container_caps = NULL; + GstCaps *audio_caps = NULL; + GstCaps *video_caps = NULL; + g_autofree gchar *container_mime_type = NULL; + g_autofree gchar *audio_mime_type = NULL; + g_autofree gchar *video_mime_type = NULL; + g_autofree gchar *uri = NULL; + + uri = gst_filename_to_uri(path, NULL); + RET_VAL_IF(uri == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "failed to get uri"); + LOGI("uri : [%s]", uri); + + LOG_INFO("Container : %s", editor->ini.encoder.container); + if (!strcmp(editor->ini.encoder.container, CONTAINER_TYPE_MPEG)) { + container_mime_type = g_strdup(CONTAINER_MIME_TYPE_MPEG); + audio_mime_type = g_strdup(AUDIO_ENCODER_MIME_TYPE_MPEG); + video_mime_type = g_strdup(VIDEO_ENCODER_MIME_TYPE_MPEG); + } else { + /* default is ogg */ + container_mime_type = g_strdup(CONTAINER_MIME_TYPE_OGG); + audio_mime_type = g_strdup(AUDIO_ENCODER_MIME_TYPE_VORBIS); + video_mime_type = g_strdup(VIDEO_ENCODER_MIME_TYPE_THEORA);; + } + + container_caps = gst_caps_from_string(container_mime_type); + RET_VAL_IF(container_caps == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to create caps"); + profile = gst_encoding_container_profile_new(NULL, NULL, container_caps, NULL); + if (profile == NULL) { + LOG_ERROR("failed to create profile"); + ret = MEDIAEDITOR_ERROR_INVALID_OPERATION; + goto error_container; + } + + audio_caps = gst_caps_from_string(audio_mime_type); + RET_VAL_IF(audio_caps == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to create audio_caps"); + if (!gst_encoding_container_profile_add_profile(profile, (GstEncodingProfile *)gst_encoding_audio_profile_new(audio_caps, NULL, NULL, 0))) { + LOG_ERROR("failed to add audio prifile"); + ret = MEDIAEDITOR_ERROR_INVALID_OPERATION; + g_object_unref(profile); + goto error_audio_caps; + } + + video_caps = gst_caps_from_string(video_mime_type); + RET_VAL_IF(video_caps == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to create video_caps"); + if (!gst_encoding_container_profile_add_profile(profile, (GstEncodingProfile *)gst_encoding_video_profile_new(video_caps, NULL, NULL, 0))) { + LOG_ERROR("failed to add audio prifile"); + ret = MEDIAEDITOR_ERROR_INVALID_OPERATION; + g_object_unref(profile); + goto error_video_caps; + } + + if (!ges_pipeline_set_render_settings(editor->gst.pipeline, uri, (GstEncodingProfile *)profile)) { + LOG_ERROR("failed to add audio prifile"); + ret = MEDIAEDITOR_ERROR_INVALID_OPERATION; + g_object_unref(profile); + } + +error_video_caps: + gst_caps_unref(video_caps); +error_audio_caps: + gst_caps_unref(audio_caps); +error_container: + gst_caps_unref(container_caps); + + return ret; +} + +static int __commit_audio_fade_effect(mediaeditor_s *editor) +{ + GESTrackElement *track_element = NULL; + GstControlSource *control_source = NULL; + + LOG_DEBUG("Enter"); + + if (!editor->audio_fade_effects) { + LOG_DEBUG("No audio fade effect"); + return MEDIAEDITOR_ERROR_NONE; + } + + GList * tracks = ges_timeline_get_tracks(editor->gst.timeline); + for (GList *tmp = tracks; tmp; tmp = tmp->next) { + GList *trackelements = ges_track_get_elements(GES_TRACK(tmp->data)); + + if (GES_IS_AUDIO_URI_SOURCE(trackelements->data)) { + track_element = GES_TRACK_ELEMENT(trackelements->data); + break; + } + + g_list_free_full(trackelements, gst_object_unref); + } + RET_VAL_IF(track_element == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to get track element"); + + control_source = GST_CONTROL_SOURCE(gst_interpolation_control_source_new()); + g_object_set(G_OBJECT(control_source), "mode", GST_INTERPOLATION_MODE_LINEAR, NULL); + + if (!gst_timed_value_control_source_set_from_list(GST_TIMED_VALUE_CONTROL_SOURCE(control_source), editor->audio_fade_effects)) { + LOG_ERROR("failed to set timed value control source"); + return MEDIAEDITOR_ERROR_INVALID_OPERATION; + } + + if (!ges_track_element_set_control_source(track_element, control_source, "volume", "direct-absolute")) { + LOG_ERROR("failed to set control source to track"); + return MEDIAEDITOR_ERROR_INVALID_OPERATION; + } + + LOG_DEBUG("Leave"); + + return MEDIAEDITOR_ERROR_NONE; +} + +static void _generate_dot(mediaeditor_s *editor, const gchar *name) +{ + g_autofree gchar *dot_name = NULL; + + RET_IF(editor == NULL, "editor is NULL"); + RET_IF(editor->gst.pipeline == NULL, "pipeline is NULL"); + + LOG_INFO("Enter"); + + if (!name) + dot_name = g_strdup(DEFAULT_DOT_FILE_NAME_PREFIX); + else + dot_name = g_strconcat(DEFAULT_DOT_FILE_NAME_PREFIX, ".", name, NULL); + + GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(editor->gst.pipeline), GST_DEBUG_GRAPH_SHOW_ALL, dot_name); + + LOG_INFO("dot file[%s] is generated", dot_name); +} + +static bool __meet_gst_state(mediaeditor_state_e state, GstState gst_state) +{ + if ((state == MEDIAEDITOR_STATE_IDLE && gst_state == GST_STATE_READY) || + ((state == MEDIAEDITOR_STATE_PREVIEW || state == MEDIAEDITOR_STATE_RENDERING) && gst_state == GST_STATE_PAUSED)) + return true; + + return false; +} + +static void __invoke_state_changed_cb(mediaeditor_s *editor, mediaeditor_state_e old, mediaeditor_state_e new) +{ + RET_IF(editor == NULL, "editor is NULL"); + + LOG_INFO("state is changed [%s] -> [%s]", __state_str[old], __state_str[new]); + + if (editor->state_changed_cb.callback) { + LOG_DEBUG(">>> callback[%p], user_data[%p]", editor->state_changed_cb.callback, editor->state_changed_cb.user_data); + ((mediaeditor_state_changed_cb)(editor->state_changed_cb.callback))(old, new, editor->state_changed_cb.user_data); + LOG_DEBUG("<<< end of the callback"); + } + + GENERATE_DOT(editor, "STATE_%s", __state_str[editor->state]); +} + +void _invoke_error_cb(mediaeditor_s *editor, mediaeditor_error_e error) +{ + RET_IF(editor == NULL, "editor is NULL"); + + LOG_ERROR("error[0x%x, %s]", error, __get_error_string(error)); + + if (editor->error_cb.callback) { + LOG_DEBUG(">>> callback[%p], user_data[%p]", editor->error_cb.callback, editor->error_cb.user_data); + ((mediaeditor_error_cb)(editor->error_cb.callback))(error, editor->state, editor->error_cb.user_data); + LOG_DEBUG("<<< end of the callback"); + } +} + +static void __invoke_render_completed_cb(mediaeditor_s *editor) +{ + RET_IF(editor == NULL, "editor is NULL"); + + if (editor->render_completed_cb.callback) { + LOG_DEBUG(">>> callback[%p] user_data[%p]", editor->render_completed_cb.callback, editor->render_completed_cb.user_data); + ((mediaeditor_render_completed_cb)(editor->render_completed_cb.callback))(editor->render_completed_cb.user_data); + LOG_DEBUG("<<< end of the callback"); + + editor->render_completed_cb.callback = NULL; + editor->render_completed_cb.user_data = NULL; + } +} + +static gboolean __bus_cb(GstBus *bus, GstMessage *message, gpointer *data) +{ + mediaeditor_s *editor = (mediaeditor_s *)data; + GError *err = NULL; + mediaeditor_error_e error = MEDIAEDITOR_ERROR_NONE; + GstState gst_state_old = GST_STATE_VOID_PENDING; + GstState gst_state_new = GST_STATE_VOID_PENDING; + GstState gst_state_pending = GST_STATE_VOID_PENDING; + gchar *state_transition_name = NULL; + + RET_VAL_IF(editor == NULL, FALSE, "editor is NULL"); + + switch (GST_MESSAGE_TYPE(message)) { + case GST_MESSAGE_ERROR: + gst_message_parse_error(message, &err, NULL); + + LOG_ERROR("Error[from %s]: message[%s], code[%d]", + GST_OBJECT_NAME(GST_OBJECT_CAST(GST_ELEMENT(GST_MESSAGE_SRC(message)))), err->message, err->code); + + if (err->domain == GST_RESOURCE_ERROR) + error = MEDIAEDITOR_ERROR_RESOURCE_FAILED; + else + error = MEDIAEDITOR_ERROR_INVALID_OPERATION; + + _invoke_error_cb(editor, error); + + g_error_free(err); + + break; + case GST_MESSAGE_STATE_CHANGED: + if (GST_MESSAGE_SRC(message) != GST_OBJECT(editor->gst.pipeline)) + return TRUE; + + gst_message_parse_state_changed(message, &gst_state_old, &gst_state_new, &gst_state_pending); + state_transition_name = g_strdup_printf("Old[GST_STATE_%s] New[GST_STATE_%s] Pending[GST_STATE_%s]", + gst_element_state_get_name(gst_state_old), gst_element_state_get_name(gst_state_new), + gst_element_state_get_name(gst_state_pending)); + + LOG_INFO("GST_MESSAGE_STATE_CHANGED: %s", state_transition_name); + g_free(state_transition_name); + + if (editor->pend_state == editor->state) { + LOG_DEBUG("pend_state[%s] is same with current state", __state_str[editor->pend_state]); + break; + } + + LOG_INFO("editor state[%d], pend_state[%d]", editor->state, editor->pend_state); + if (__meet_gst_state(editor->pend_state, gst_state_new)) { + mediaeditor_state_e old_state = editor->state; + editor->state = editor->pend_state; + __invoke_state_changed_cb(editor, old_state, editor->state); + break; + } + + break; + case GST_MESSAGE_EOS: + LOG_INFO("EOS reached"); + int ret = _gst_pipeline_set_state(editor, GST_STATE_NULL); + RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, FALSE, "failed to set GST_STATE_NULL"); + + __invoke_render_completed_cb(editor); + +#ifndef TIZEN_TV + ret = _release_all_resources(editor); + RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to release all resources"); +#endif + + editor->state = MEDIAEDITOR_STATE_IDLE; + + return TRUE; + default: + break; + } + + return TRUE; +} + +static gboolean __idle_cb(gpointer user_data) +{ + idle_userdata_s *data = (idle_userdata_s*)user_data; + mediaeditor_s *editor = NULL; + + RET_VAL_IF(data == NULL, G_SOURCE_REMOVE, "userdata is NULL"); + if (data->editor == NULL) { + LOG_ERROR("editor is NULL"); + return G_SOURCE_REMOVE; + } + + editor = data->editor; + + switch (data->type) { + case IDLE_CB_TYPE_STATE: { + mediaeditor_state_e old_state; + + g_mutex_lock(&editor->mutex); + g_mutex_lock(&editor->event_src_mutex); + editor->idle_cb_event_source_ids[data->type] = 0; + g_mutex_unlock(&editor->event_src_mutex); + old_state = editor->state; + editor->state = data->new.state; + g_mutex_unlock(&editor->mutex); + + __invoke_state_changed_cb(editor, old_state, editor->state); + break; + } + case IDLE_CB_TYPE_ERROR: + g_mutex_lock(&editor->event_src_mutex); + editor->idle_cb_event_source_ids[data->type] = 0; + g_mutex_unlock(&editor->event_src_mutex); + + _invoke_error_cb(editor, data->new.error); + break; + default: + LOG_ERROR_IF_REACHED("type(%d)", data->type); + } + + return G_SOURCE_REMOVE; +} + +static void __post_state_cb_in_idle(mediaeditor_s *editor, mediaeditor_state_e new_state) +{ + idle_userdata_s *data = NULL; + + RET_IF(editor == NULL, "editor is NULL"); + + if (editor->state == new_state) + return; + + data = g_new0(idle_userdata_s, 1); + data->editor = editor; + data->type = IDLE_CB_TYPE_STATE; + data->new.state = new_state; + + editor->pend_state = new_state; + + g_mutex_lock(&editor->event_src_mutex); + editor->idle_cb_event_source_ids[data->type] = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, __idle_cb, data, g_free); + g_mutex_unlock(&editor->event_src_mutex); + + LOG_DEBUG("state will be changed [%s] -> [%s]", __state_str[editor->state], __state_str[new_state]); +} + +void _post_error_cb_in_idle(mediaeditor_s *editor, mediaeditor_error_e error) +{ + idle_userdata_s *data = NULL; + + RET_IF(editor == NULL, "editor is NULL"); + + data = g_new0(idle_userdata_s, 1); + data->editor = editor; + data->type = IDLE_CB_TYPE_ERROR; + data->new.error = error; + + g_mutex_lock(&editor->event_src_mutex); + editor->idle_cb_event_source_ids[data->type] = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, __idle_cb, data, g_free); + g_mutex_unlock(&editor->event_src_mutex); + + LOG_DEBUG("error will occur [0x%x]", error); +} + +void _remove_remained_event_sources(mediaeditor_s *editor) +{ + RET_IF(editor == NULL, "editor is NULL"); + + for (int i = IDLE_CB_TYPE_STATE; i < IDLE_CB_TYPE_NUM; i++) { + if (editor->idle_cb_event_source_ids[i] == 0) + continue; + + g_source_remove(editor->idle_cb_event_source_ids[i]); + LOG_DEBUG("idle_cb_event_source_ids[%d] source id[%u]", i, editor->idle_cb_event_source_ids[i]); + editor->idle_cb_event_source_ids[i] = 0; + } +} + +int _gst_init(mediaeditor_s *editor) +{ + static gboolean initialized = FALSE; + gboolean ret = FALSE; + char **argv = NULL; + gint argc = 1; + gchar **gst_args; + GError *err = NULL; + + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + if (initialized) { + LOG_ERROR("gstreamer is already initialized."); + return TRUE; + } + + gst_args = editor->ini.general.gst_args; + + if (gst_args) + argc += g_strv_length(gst_args); + + argv = (char **)calloc(argc, sizeof(char*)); + RET_VAL_IF(argv == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to calloc()"); + + argv[0] = g_strdup("capi-media-editor"); + + for (int i = 0 ; gst_args && gst_args[i]; ++i) { + if (argc <= i + 1) { + LOG_ERROR("need to check, prevent overrun"); + break; + } + argv[i + 1] = gst_args[i]; + LOG_DEBUG("[%s] is added", argv[i + 1]); + } + + ret = gst_init_check(&argc, &argv, &err); + + /* we need to free argv even if gst_init_check() is failed */ + for (int i = 1 ; i < argc ; i++) + argv[i] = NULL; + + SAFE_FREE(argv[0]); + SAFE_FREE(argv); + + if (!ret) { + LOG_ERROR("Failed to initialize gstreamer; %s", err->message); + g_clear_error(&err); + return MEDIAEDITOR_ERROR_INVALID_OPERATION; + } + + /* Initialize the GStreamer Editing Services */ + ges_init(); + + return MEDIAEDITOR_ERROR_NONE; +} + +int _gst_pipeline_set_state(mediaeditor_s *editor, GstState state) +{ + GstStateChangeReturn ret = GST_STATE_CHANGE_FAILURE; + + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + RET_VAL_IF(editor->gst.pipeline == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "pipeline is NULL"); + + ret = gst_element_set_state(GST_ELEMENT(editor->gst.pipeline), state); + if (ret == GST_STATE_CHANGE_FAILURE) { + LOG_ERROR("failed to gst_element_set_state(), state[%s]", gst_element_state_get_name(state)); + return MEDIAEDITOR_ERROR_INVALID_OPERATION; + } + + return MEDIAEDITOR_ERROR_NONE; +} + +void _gst_pipeline_destroy(mediaeditor_s *editor) +{ + LOG_DEBUG("Enter"); + + RET_IF(editor == NULL, "editor is NULL"); + + if (editor->gst.bus) { + gst_object_unref(editor->gst.bus); + editor->gst.bus = NULL; + } + if (editor->gst.pipeline) { + gst_object_unref(G_OBJECT(editor->gst.pipeline)); + editor->gst.pipeline = NULL; + } +} + +static int __mediaeditor_set_bus_cb(mediaeditor_s *editor) +{ + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + editor->gst.bus = gst_pipeline_get_bus(GST_PIPELINE(editor->gst.pipeline)); + RET_VAL_IF(editor->gst.bus == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to get bus from pipeline"); + + gst_bus_add_watch_full(editor->gst.bus, G_PRIORITY_DEFAULT, (GstBusFunc) __bus_cb, editor, NULL); + + return MEDIAEDITOR_ERROR_NONE; +} + +static bool __check_clip_added(mediaeditor_s *editor) +{ + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + return editor->clips != NULL; +} + +int _mediaeditor_stop(mediaeditor_s *editor) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + g_autoptr(GMutexLocker) locker = NULL; + + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + locker = g_mutex_locker_new(&editor->mutex); + + RET_VAL_IF(editor->state == MEDIAEDITOR_STATE_IDLE, MEDIAEDITOR_ERROR_INVALID_STATE, + "the state should not be IDLE"); + + ret = _gst_pipeline_set_state(editor, GST_STATE_NULL); + RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to set GST_STATE_NULL"); + +#ifndef TIZEN_TV + ret = _release_all_resources(editor); + RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to release all resources"); +#endif + + __post_state_cb_in_idle(editor, MEDIAEDITOR_STATE_IDLE); + + LOG_INFO("media editor[%p] is destroyed", editor); + + return MEDIAEDITOR_ERROR_NONE; +} + +int _mediaeditor_create_pipeline(mediaeditor_s *editor) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + editor->gst.pipeline = ges_pipeline_new(); + RET_VAL_IF(editor->gst.pipeline == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to create ges pipeline"); + + if (editor->gst.timeline == NULL) { +#ifdef TIZEN_MEDIAEDITOR_EXPERIMENTAL_AUDIO_TRACK_ONLY + LOG_DEBUG("Create timeline with audio track only"); + GESTrack *tracka; + GESTimeline *timeline; + + /* This is our main GESTimeline */ + timeline = ges_timeline_new(); + + tracka = GES_TRACK(ges_audio_track_new()); + + if (!ges_timeline_add_track(timeline, tracka)) { + gst_object_unref(timeline); + timeline = NULL; + } + editor->gst.timeline = timeline; +#else + LOG_DEBUG("Create timeline with audio, video track"); + editor->gst.timeline = ges_timeline_new_audio_video(); +#endif + if (editor->gst.timeline == NULL) { + LOG_ERROR("failed to create ges timeline"); + gst_object_unref(editor->gst.pipeline); + return MEDIAEDITOR_ERROR_INVALID_OPERATION; + } + } + + if (!ges_pipeline_set_timeline(editor->gst.pipeline, editor->gst.timeline)) { + LOG_ERROR("failed to set ges timeline to ges pipeline"); + gst_object_unref(editor->gst.pipeline); + gst_object_unref(editor->gst.timeline); + return MEDIAEDITOR_ERROR_INVALID_OPERATION; + } + + ret = __mediaeditor_set_bus_cb(editor); + RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to set bus callback"); + + return MEDIAEDITOR_ERROR_NONE; +} + +int _mediaeditor_start_render(mediaeditor_s *editor, const char* path) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + RET_VAL_IF(path == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "path is NULL"); + RET_VAL_IF(!__check_clip_added(editor), MEDIAEDITOR_ERROR_INVALID_OPERATION, "There's no clip to render"); + +#ifndef TIZEN_TV + if (editor->ini.resource_acquisition.video_decoder) { + ret = _acquire_resource_for_type(editor, MM_RESOURCE_MANAGER_RES_TYPE_VIDEO_DECODER); + RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to acquire video decoder resource"); + } + + if (editor->ini.resource_acquisition.video_encoder) { + ret = _acquire_resource_for_type(editor, MM_RESOURCE_MANAGER_RES_TYPE_VIDEO_ENCODER); + RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to acquire video encoder resource"); + } +#endif + + if(!ges_timeline_commit_sync(editor->gst.timeline)) { + LOG_ERROR("There's nothing to render"); + return MEDIAEDITOR_ERROR_INVALID_OPERATION; + } + + ret = __commit_audio_fade_effect(editor); + RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to commit audio fade effect"); + + ret = __create_encoding_profile(editor, path); + RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to create encoding profile"); + + if (!ges_pipeline_set_mode(editor->gst.pipeline, GES_PIPELINE_MODE_SMART_RENDER)) { + LOG_ERROR("failed to set smart render pipeline mode"); + return MEDIAEDITOR_ERROR_INVALID_OPERATION; + } + + ret = _gst_pipeline_set_state(editor, GST_STATE_PLAYING); + RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to set GST_STATE_PLAYING"); + + editor->pend_state = MEDIAEDITOR_STATE_RENDERING; + + GENERATE_DOT(editor, "%s", GST_ELEMENT_NAME(editor->gst.pipeline)); + + return MEDIAEDITOR_ERROR_NONE; +} + +int _mediaeditor_cancel_render(mediaeditor_s *editor) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + ret = _gst_pipeline_set_state(editor, GST_STATE_READY); + RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to set GST_STATE_READY"); + + __post_state_cb_in_idle(editor, MEDIAEDITOR_STATE_IDLE); + + return MEDIAEDITOR_ERROR_NONE; +} + +int _mediaeditor_start_preview(mediaeditor_s *editor) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + RET_VAL_IF(!__check_clip_added(editor), MEDIAEDITOR_ERROR_INVALID_OPERATION, "There's no clip to render"); + + if(!ges_timeline_commit_sync(editor->gst.timeline)) { + LOG_ERROR("There's nothing to render"); + return MEDIAEDITOR_ERROR_INVALID_OPERATION; + } + + if (!ges_pipeline_set_mode(editor->gst.pipeline, GES_PIPELINE_MODE_PREVIEW)) { + LOG_ERROR("failed to set preview pipeline mode"); + return MEDIAEDITOR_ERROR_INVALID_OPERATION; + } + + ret = _gst_pipeline_set_state(editor, GST_STATE_PLAYING); + RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to set GST_STATE_PLAYING"); + + editor->pend_state = MEDIAEDITOR_STATE_PREVIEW; + + GENERATE_DOT(editor, "%s", GST_ELEMENT_NAME(editor->gst.pipeline)); + + LOG_ERROR("State[%d],[%d]", editor->state, editor->pend_state); + + return MEDIAEDITOR_ERROR_NONE; +} + +int _mediaeditor_stop_preview(mediaeditor_s *editor) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + ret = _gst_pipeline_set_state(editor, GST_STATE_READY); + RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to set GST_STATE_READY"); + + __post_state_cb_in_idle(editor, MEDIAEDITOR_STATE_IDLE); + + return MEDIAEDITOR_ERROR_NONE; +} + +int _check_privilege(const char *path, bool file_exist, int mode) +{ + int ret = 0; + + RET_VAL_IF(!path, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "path is null"); + + if (file_exist) { + ret = access(path, mode); + } else { + g_autofree gchar *dirname = g_path_get_dirname(path); + RET_VAL_IF(!dirname, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "invalid path"); + + ret = access(dirname, mode); + } + + if (ret != 0) { + if (errno == EACCES || errno == EPERM) { + LOG_ERROR("fail to access path[%s]: permission denied", path); + return MEDIAEDITOR_ERROR_PERMISSION_DENIED; + } else { + LOG_ERROR("fail to access path[%s]: invalid path", path); + return MEDIAEDITOR_ERROR_INVALID_PARAMETER; + } + } + + return MEDIAEDITOR_ERROR_NONE; + } + +int _check_feature(const char *feature) +{ + bool supported = false; + + RET_VAL_IF(feature == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "feature is NULL"); + + if (system_info_get_platform_bool(feature, &supported) != SYSTEM_INFO_ERROR_NONE) { + LOG_ERROR("failed to system_info_get_platform_bool(), feature[%s]", feature); + return MEDIAEDITOR_ERROR_INVALID_OPERATION; + } + + if (!supported) { + LOG_ERROR("feature[%s] is not supported", feature); + return MEDIAEDITOR_ERROR_NOT_SUPPORTED; + } + + LOG_INFO("feature[%s] is supported", feature); + + return MEDIAEDITOR_ERROR_NONE; +} diff --git a/src/media_editor_project.c b/src/media_editor_project.c new file mode 100644 index 0000000..af4ba70 --- /dev/null +++ b/src/media_editor_project.c @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +static void __project_loaded_cb(GESProject *project, GESTimeline *timeline, mediaeditor_s *editor) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + + LOG_DEBUG("project[%p]:timeline[%p] is loaded", project, timeline); + + g_signal_handlers_disconnect_by_func(project, (GCallback)__project_loaded_cb, editor); + + ret = _mediaeditor_create_pipeline(editor); + if (ret != MEDIAEDITOR_ERROR_NONE) { + _invoke_error_cb(editor, ret); + return; + } + + if (editor->project_loaded_cb.callback) { + LOG_DEBUG(">>> callback[%p], user_data[%p]", editor->project_loaded_cb.callback, editor->project_loaded_cb.user_data); + ((mediaeditor_project_loaded_cb)(editor->project_loaded_cb.callback))(editor->project_loaded_cb.user_data); + LOG_DEBUG("<<< end of the callback"); + } +} + +static int __connect_project_loaded_signal(mediaeditor_s *editor) +{ + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + g_signal_connect(editor->gst.project, "loaded", (GCallback)__project_loaded_cb, editor); + + return MEDIAEDITOR_ERROR_NONE; +} + +static int __extract_timeline(mediaeditor_s *editor) +{ + GESTimeline *timeline = NULL; + + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + timeline = GES_TIMELINE(ges_asset_extract(GES_ASSET(editor->gst.project), NULL)); + RET_VAL_IF(timeline == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to extract timeline from project"); + + editor->gst.timeline = timeline; + + return MEDIAEDITOR_ERROR_NONE; +} + +static int __mediaeditor_create_ges_project(mediaeditor_s *editor, gchar *uri) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + GESProject *project = NULL; + + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + project = ges_project_new(uri); + RET_VAL_IF(project == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to create project"); + + editor->gst.project = project; + + ret = __extract_timeline(editor); + RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to load timeline"); + + return MEDIAEDITOR_ERROR_NONE; +} + +int _mediaeditor_create_project(mediaeditor_s *editor, const char *path) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + g_autofree gchar *uri = NULL; + + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + RET_VAL_IF(path == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "path is NULL"); + + uri = gst_filename_to_uri(path, NULL); + RET_VAL_IF(uri == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "failed to get uri"); + LOGI("uri : [%s]", uri); + + if (editor->gst.pipeline) + _gst_pipeline_destroy(editor); + + ret = __mediaeditor_create_ges_project(editor, NULL); + RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to create ges project"); + + ret = _mediaeditor_create_pipeline(editor); + RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to create pipeline"); + + ret = _mediaeditor_save_project(editor, uri); + RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to save project"); + + return MEDIAEDITOR_ERROR_NONE; +} + +int _mediaeditor_load_project(mediaeditor_s *editor, const char *path) +{ + int ret = MEDIAEDITOR_ERROR_NONE; + g_autofree gchar *uri = NULL; + + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + RET_VAL_IF(path == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "path is NULL"); + + uri = gst_filename_to_uri(path, NULL); + RET_VAL_IF(uri == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "failed to get uri"); + LOGI("uri : [%s]", uri); + + if (editor->gst.pipeline) + _gst_pipeline_destroy(editor); + + if (editor->gst.project) + gst_object_unref(editor->gst.project); + + ret = __mediaeditor_create_ges_project(editor, uri); + RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to create ges project"); + + ret = __connect_project_loaded_signal(editor); + RET_VAL_IF(ret != MEDIAEDITOR_ERROR_NONE, ret, "failed to connect signal"); + + return MEDIAEDITOR_ERROR_NONE; +} + +int _mediaeditor_save_project(mediaeditor_s *editor, const gchar *uri) +{ + GESAsset *formatter_asset = NULL; + + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + RET_VAL_IF(uri == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "uri is NULL"); + + formatter_asset = ges_asset_request(GES_TYPE_FORMATTER, "ges", NULL); + RET_VAL_IF(formatter_asset == NULL, MEDIAEDITOR_ERROR_INVALID_OPERATION, "failed to request formatter"); + + if (!ges_project_save(editor->gst.project, editor->gst.timeline, uri, formatter_asset, TRUE, NULL)) { + LOGE("failed to save project"); + return MEDIAEDITOR_ERROR_INVALID_OPERATION; + } + + return MEDIAEDITOR_ERROR_NONE; +} diff --git a/src/media_editor_resource.c b/src/media_editor_resource.c new file mode 100644 index 0000000..220f14d --- /dev/null +++ b/src/media_editor_resource.c @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "media_editor.h" +#include "media_editor_private.h" + +#ifndef TIZEN_TV +static int __resource_release_cb(mm_resource_manager_h mgr, + mm_resource_manager_res_h res, void *user_data) +{ + int ret = true; + mediaeditor_s *editor = (mediaeditor_s *)user_data; + + RET_VAL_IF(editor == NULL, false, "editor is NULL"); + + editor->resource.release_cb_is_calling = true; + + for (int i = 0; i < RESOURCE_TYPE_MAX; i++) { + if (editor->resource.res[i] == res) { + LOG_INFO("type[%d] resource was released by resource manager", i); + editor->resource.res[i] = NULL; + } + } + + if (_mediaeditor_stop(editor) != MEDIAEDITOR_ERROR_NONE) + ret = false; + + _post_error_cb_in_idle(editor, MEDIAEDITOR_ERROR_RESOURCE_CONFLICT); + + editor->resource.release_cb_is_calling = false; + + return ret; +} + +static bool __is_valid_resource_type(mm_resource_manager_res_type_e type) +{ + if (type < MM_RESOURCE_MANAGER_RES_TYPE_VIDEO_DECODER || type >= RESOURCE_TYPE_MAX) { + LOG_ERROR("Type[%d] is a invalid resource type", type); + return false; + } + return true; +} + +int _create_resource_manager(mediaeditor_s *editor) +{ + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + if (mm_resource_manager_create(MM_RESOURCE_MANAGER_APP_CLASS_MEDIA, + __resource_release_cb, editor, + &editor->resource.mgr) != MM_RESOURCE_MANAGER_ERROR_NONE) { + LOG_ERROR("Failed to init resource manager for media"); + return MEDIAEDITOR_ERROR_RESOURCE_FAILED; + } + + return MEDIAEDITOR_ERROR_NONE; +} + +int _acquire_resource_for_type(mediaeditor_s *editor, mm_resource_manager_res_type_e type) +{ + int ret = MM_RESOURCE_MANAGER_ERROR_NONE; + + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + if (!editor->resource.mgr) { + LOG_DEBUG("There's no acquired hw encoder, decoder resources"); + return MEDIAEDITOR_ERROR_NONE; + } + + RET_VAL_IF(!__is_valid_resource_type(type), MEDIAEDITOR_ERROR_INVALID_PARAMETER, + "type is wrong"); + + if (editor->resource.res[type] != NULL) { + LOG_ERROR("type[%d] resource was already acquired", type); + return MEDIAEDITOR_ERROR_RESOURCE_FAILED; + } + + LOG_DEBUG("mark for acquire type[%d] resource", type); + ret = mm_resource_manager_mark_for_acquire(editor->resource.mgr, type, + MM_RESOURCE_MANAGER_RES_VOLUME_FULL, &editor->resource.res[type]); + if (ret != MM_RESOURCE_MANAGER_ERROR_NONE) { + LOG_ERROR("failed to mark resource for acquire, ret[0x%x]", ret); + return MEDIAEDITOR_ERROR_RESOURCE_FAILED; + } + + LOG_DEBUG("commit type[%d] resource", type); + ret = mm_resource_manager_commit(editor->resource.mgr); + if (ret != MM_RESOURCE_MANAGER_ERROR_NONE) { + LOG_ERROR("failed to commit of resource, ret([0x%x]", ret); + return MEDIAEDITOR_ERROR_RESOURCE_FAILED; + } + + return MEDIAEDITOR_ERROR_NONE; +} + +int _release_all_resources(mediaeditor_s *editor) +{ + int ret = MM_RESOURCE_MANAGER_ERROR_NONE; + + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + if (!editor->resource.mgr) { + LOG_DEBUG("There's no acquired hw encoder, decoder resources"); + return MEDIAEDITOR_ERROR_NONE; + } + + if(editor->resource.release_cb_is_calling) { + LOG_INFO("__resource_release_cb is calling, so skip"); + return MEDIAEDITOR_ERROR_NONE; + } + + ret = mm_resource_manager_mark_all_for_release(editor->resource.mgr); + if (ret != MM_RESOURCE_MANAGER_ERROR_NONE) { + LOG_ERROR("failed to mark all for release, ret[0x%x]", ret); + return MEDIAEDITOR_ERROR_RESOURCE_FAILED; + } + ret = mm_resource_manager_commit(editor->resource.mgr); + if (ret != MM_RESOURCE_MANAGER_ERROR_NONE) { + LOG_ERROR("failed to commit resource, ret[0x%x]", ret); + return MEDIAEDITOR_ERROR_RESOURCE_FAILED; + } + LOG_DEBUG("all resources were released by resource manager"); + + for (int i = 0; i < RESOURCE_TYPE_MAX; i++) { + editor->resource.need_to_acquire[i] = false; + editor->resource.res[i] = NULL; + } + + return MEDIAEDITOR_ERROR_NONE; +} + +int _destroy_resource_manager(mediaeditor_s *editor) +{ + int ret = MM_RESOURCE_MANAGER_ERROR_NONE; + + RET_VAL_IF(editor == NULL, MEDIAEDITOR_ERROR_INVALID_PARAMETER, "editor is NULL"); + + if (!editor->resource.mgr) { + LOG_DEBUG("There's no acquired hw encoder, decoder resources"); + return MEDIAEDITOR_ERROR_NONE; + } + + ret = mm_resource_manager_destroy(editor->resource.mgr); + if (ret != MM_RESOURCE_MANAGER_ERROR_NONE) { + LOG_ERROR("failed to destroy resource manager, ret[0x%x]", ret); + return MEDIAEDITOR_ERROR_RESOURCE_FAILED; + } + + editor->resource.mgr = NULL; + LOG_DEBUG("destroyed resource manager"); + + return MEDIAEDITOR_ERROR_NONE; +} +#endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..86d77fa --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,22 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 2.8.12) +SET(fw_test "${fw_name}-test") + +INCLUDE(FindPkgConfig) +pkg_check_modules(${fw_test} REQUIRED elementary evas ecore appcore-efl) +FOREACH(flag ${${fw_test}_CFLAGS}) + SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} ${flag}") + MESSAGE(${flag}) +ENDFOREACH() + +SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${EXTRA_CFLAGS} -fPIC -pie -Wall -Werror") + +aux_source_directory(. sources) + +FOREACH(src ${sources}) + GET_FILENAME_COMPONENT(src_name ${src} NAME_WE) + MESSAGE("${src_name}") + ADD_EXECUTABLE(${src_name} ${src}) + TARGET_LINK_LIBRARIES(${src_name} ${fw_name} ${${fw_test}_LDFLAGS}) + INSTALL(TARGETS ${src_name} DESTINATION bin) + +ENDFOREACH() diff --git a/test/media_editor_test.c b/test/media_editor_test.c new file mode 100644 index 0000000..826652d --- /dev/null +++ b/test/media_editor_test.c @@ -0,0 +1,417 @@ +/* + * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define EXPORT_API __attribute__((__visibility__("default"))) + +#ifdef PACKAGE +#undef PACKAGE +#endif +#define PACKAGE "media_editor_test" + +#define GET_DISPLAY(x) (void*)(x) +#define DEFAULT_FILE_PATH "/tmp" +#define MAX_FILE_NAME_LENGTH 256 +#define MAX_FILE_PATH_LENGTH (MAX_FILE_NAME_LENGTH - 20) + +#define CHECK_MM_ERROR(expr) \ +do { \ + int ret = 0; \ + ret = expr; \ + if (ret != 0) { \ + g_print("[%s:%d] error code : %x \n", __func__, __LINE__, ret); \ + return; \ + } \ +} while (0) + +#ifndef SAFE_FREE +#define SAFE_FREE(x) \ +if (x) { \ + g_free(x); \ + x = NULL; \ +} +#endif + +enum { + MENU_STATE_INIT, + MENU_STATE_MAIN, + MENU_STATE_NUM, +}; + +static void print_menu(); +static gboolean cmd_input(GIOChannel *channel, GIOCondition condition, gpointer data); +static gboolean mode_change(gchar buf); +static int app_create(void *data); +static int app_terminate(void *data); + +typedef struct _appdata { + Evas_Object *win; + Evas_Object *eo; + Evas_Object *bg; + Evas_Object *rect; +} appdata; + +struct appcore_ops ops = { + .create = app_create, + .terminate = app_terminate, +}; + +typedef struct _mediaeditor_handle { + mediaeditor_h mediaeditor; + int type; + int menu_state; +} mediaeditor_handle_t; + +static struct timeval previous_time; +static struct timeval current_time; +static struct timeval result_time; + +appdata ad; +GIOChannel *stdin_channel = NULL; +static GTimer *timer = NULL; +mediaeditor_h heditor = NULL; +static mediaeditor_handle_t *hmediaeditor; + +static inline void flush_stdin() +{ + int ch; + while ((ch = getchar()) != EOF && ch != '\n'); +} + +static void render_completed_cb(void *user_data) +{ + g_print("Enter\n"); +} + +static void print_menu() +{ + switch (hmediaeditor->menu_state) { + case MENU_STATE_INIT: + g_print("\n\t=======================================\n"); + g_print("\t MEDIAEDITOR_TESTSUITE\n"); + g_print("\t=======================================\n"); + g_print("\t '1' Start preview\n"); + g_print("\t '2' Stop preview\n"); + g_print("\t '3' Start render\n"); + g_print("\t 'q' Exit\n"); + g_print("\t=======================================\n"); + break; + default: + g_print("\n\tunknow menu state !!\n"); + return; + } + + g_print("\tCommand >> "); + + return; +} + +static gboolean cmd_input(GIOChannel *channel, GIOCondition condition, gpointer data) +{ + gchar *buf = NULL; + gsize read_size; + GError *g_error = NULL; + + g_print("\n\tENTER\n"); + + g_io_channel_read_line(channel, &buf, &read_size, NULL, &g_error); + if (g_error) { + g_print("\n\tg_io_channel_read_chars error\n"); + g_error_free(g_error); + g_error = NULL; + } + + if (buf) { + g_strstrip(buf); + + g_print("\n\tMenu Status : %d\n", hmediaeditor->menu_state); + switch (hmediaeditor->menu_state) { + case MENU_STATE_INIT: + mode_change(buf[0]); + break; + default: + break; + } + + g_free(buf); + buf = NULL; + + print_menu(); + } else { + g_print("\n\tNo read input\n"); + } + + return TRUE; +} + +static gboolean init_handle() +{ + hmediaeditor->menu_state = MENU_STATE_INIT; + + return TRUE; +} + + +static void start_preview() +{ + int ret = MEDIAEDITOR_ERROR_NONE; + unsigned int layer_id = 0; + unsigned int layer_priority = 0; + unsigned int clip_id = 0; + + ret = mediaeditor_add_layer(heditor, &layer_id, &layer_priority); + if (ret != MEDIAEDITOR_ERROR_NONE) { + g_print("failed to add layer : error[0x%x]\n", ret); + return; + } + + ret = mediaeditor_add_clip(heditor, "/tmp/test1.mp4", layer_priority, 0, G_TIME_SPAN_MILLISECOND * 10, 0, &clip_id); + if (ret != MEDIAEDITOR_ERROR_NONE) { + g_print("failed to add clip : error[0x%x]\n", ret); + return; + } + + ret = mediaeditor_set_display(heditor, MEDIAEDITOR_DISPLAY_TYPE_EVAS, GET_DISPLAY(ad.eo)); + if (ret != MEDIAEDITOR_ERROR_NONE) { + g_print("failed to set display : error[0x%x]\n", ret); + return; + } + + ret = mediaeditor_start_preview(heditor); + if (ret != MEDIAEDITOR_ERROR_NONE) { + g_print("failed to start preview : error[0x%x]\n", ret); + return; + } +} + +static void stop_preview() +{ + int ret = 0; + + ret = mediaeditor_stop_preview(heditor); + if (ret != MEDIAEDITOR_ERROR_NONE) { + g_print("failed to stop preview : error[0x%x]\n", ret); + return; + } +} + +static void start_render() +{ + int ret = MEDIAEDITOR_ERROR_NONE; + unsigned int layer_id = 0; + unsigned int layer_priority = 0; + unsigned int clip_id = 0; + + ret = mediaeditor_add_layer(heditor, &layer_id, &layer_priority); + if (ret != MEDIAEDITOR_ERROR_NONE) { + g_print("failed to add layer : error[0x%x]\n", ret); + return; + } + + ret = mediaeditor_add_clip(heditor, "/tmp/test1.mp4", layer_priority, 0, 100, 0, &clip_id); + if (ret != MEDIAEDITOR_ERROR_NONE) { + g_print("failed to add clip : error[0x%x]\n", ret); + return; + } + + g_print("Call render async\n"); + ret = mediaeditor_start_render(heditor, "/tmp/out_render.mp4", render_completed_cb, heditor); + if (ret != MEDIAEDITOR_ERROR_NONE) { + g_print("failed to start render : error[0x%x]\n", ret); + return; + } +} + +static gboolean mode_change(gchar buf) +{ + switch (buf) { + case '1': + g_print("Start preview\n"); + start_preview(); + break; + case '2': + g_print("Stop preview\n"); + stop_preview(); + break; + case '3': + g_print("Start render\n"); + start_render(); + break; + case 'q': + g_print("\t Quit MediaEditor Testsuite!!\n"); + elm_exit(); + return FALSE; + default: + return FALSE; + } + + gettimeofday(&previous_time, NULL); + + g_timer_reset(timer); + + gettimeofday(¤t_time, NULL); + timersub(¤t_time, &previous_time, &result_time); + + g_print("\n\tElapsed time : %ld.%lds\n", result_time.tv_sec, result_time.tv_usec); + + return TRUE; +} + +static int app_create(void *data) +{ + appdata *app_data = data; + int w = 0; + int h = 0; + Evas_Object *win = NULL; + Evas_Object *eo = NULL; + Evas_Object *bg = NULL; + Evas_Object *rect = NULL; + int ret = MEDIAEDITOR_ERROR_NONE; + + if (app_data == NULL) { + g_print("\t\nappdata is NULL\n"); + return 0; + } + + /* use gl backend */ + elm_config_accel_preference_set("opengl"); + + win = elm_win_add(NULL, "media_editor_test", ELM_WIN_BASIC); + if (win) { + elm_win_title_set(win, "media_editor_test"); + elm_win_borderless_set(win, EINA_TRUE); + elm_win_screen_size_get(win, NULL, NULL, &w, &h); + g_print("\n\tscreen size %dx%d\n\n", w, h); + evas_object_resize(win, w, h); + elm_win_autodel_set(win, EINA_TRUE); + elm_win_alpha_set(win, EINA_TRUE); + } else { + g_print("\n\tfailed to get window\n\n"); + return 1; + } + + bg = elm_bg_add(win); + if (bg) { + elm_win_resize_object_add(win, bg); + evas_object_size_hint_weight_set(bg, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_show(bg); + } else { + g_print("\n\tfailed to get elm bg\n\n"); + return 1; + } + + rect = evas_object_rectangle_add(evas_object_evas_get(win)); + if (rect) { + evas_object_color_set(rect, 0, 0, 0, 0); + evas_object_render_op_set(rect, EVAS_RENDER_COPY); + } else { + g_print("\n\tfailed to get rectangle\n\n"); + return 1; + } + + elm_win_resize_object_add(win, rect); + evas_object_size_hint_weight_set(rect, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_show(rect); + + /* Create evas image object for EVAS surface */ + eo = evas_object_image_add(evas_object_evas_get(win)); + evas_object_image_size_set(eo, w, h); + evas_object_image_fill_set(eo, 0, 0, w, h); + evas_object_resize(eo, w, h); + evas_object_show(eo); + + elm_win_activate(win); + evas_object_show(win); + + app_data->win = win; + app_data->eo = eo; + + timer = g_timer_new(); + g_timer_reset(timer); + + init_handle(); + + g_print("create"); + ret = mediaeditor_create(&heditor); + if (ret != MEDIAEDITOR_ERROR_NONE) { + g_print("failed to create mediaeditor : error[0x%x]", ret); + return 1; + } + + print_menu(); + + return 0; +} + +static int app_terminate(void *data) +{ + appdata *app_data = data; + int ret = MEDIAEDITOR_ERROR_NONE; + + if (app_data == NULL) { + g_print("\n\tappdata is NULL\n"); + return 0; + } + + if (timer) { + g_timer_stop(timer); + g_timer_destroy(timer); + timer = NULL; + } + + ret = mediaeditor_destroy(heditor); + if (ret != MEDIAEDITOR_ERROR_NONE) { + g_print("failed to destroy : error[0x%x]", ret); + return 1; + } + + return 0; +} + +/** + * This function is the example main function for media editor API. + * @return This function returns 0. + */ +int main(int argc, char **argv) +{ + int bret = 0; + + hmediaeditor = (mediaeditor_handle_t *) g_malloc0(sizeof(mediaeditor_handle_t)); + + stdin_channel = g_io_channel_unix_new(fileno(stdin));/* read from stdin */ + g_io_add_watch(stdin_channel, G_IO_IN, (GIOFunc)cmd_input, NULL); + + memset(&ad, 0x0, sizeof(appdata)); + ops.data = &ad; + + bret = appcore_efl_main(PACKAGE, &argc, &argv, &ops); + + g_print("\n\treturn appcore_efl : %d\n\n", bret); + + g_free(hmediaeditor); + g_io_channel_unref(stdin_channel); + + return bret; +}