diff --git a/code/DocsSample/Media/AVImageGenerator/AVImageGeneratorArkTS/Ability.test.ets b/code/DocsSample/Media/AVImageGenerator/AVImageGeneratorArkTS/Ability.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..10b545bd8b7f361b491ee1fe8d9bf56c33188f80 --- /dev/null +++ b/code/DocsSample/Media/AVImageGenerator/AVImageGeneratorArkTS/Ability.test.ets @@ -0,0 +1,52 @@ +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; +import { Driver, ON } from '@ohos.UiTest'; +import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; + +export default function abilityTest() { + const TAG = '[Sample_AVImageGenerator]'; + const driver = Driver.create(); + const abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator(); + const bundleName = AbilityDelegatorRegistry.getArguments().bundleName; + describe('ActsAbilityTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }) + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }) + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }) + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }) + it('FetchFrameButton_001', 0, async (done: Function) => { + console.info(TAG, 'FetchFrameButton_001 begin'); + try { + await abilityDelegator.startAbility({ + bundleName: bundleName, + abilityName: 'EntryAbility' + }); + } catch (exception) { + expect().assertFail(); + } + try { + const button = await driver.findComponent(ON.id('FetchFrameButton')); + await button.click(); + await driver.delayMs(3000); + done(); + } catch (exception) { + expect().assertFail(); + } + console.info(TAG, 'FetchFrameButton_001 end'); + }) + }) +} \ No newline at end of file diff --git a/code/DocsSample/Media/AVImageGenerator/AVImageGeneratorArkTS/Index.ets b/code/DocsSample/Media/AVImageGenerator/AVImageGeneratorArkTS/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..bc83312884b4b4e75a62d40fbfa3a8c451031270 --- /dev/null +++ b/code/DocsSample/Media/AVImageGenerator/AVImageGeneratorArkTS/Index.ets @@ -0,0 +1,68 @@ +import { media } from '@kit.MediaKit'; +import { image } from '@kit.ImageKit'; +import { common } from '@kit.AbilityKit'; + +const TAG = 'MetadataDemo'; +@Entry +@Component +struct Index { + @State message: string = 'Hello World'; + + // pixelMap对象声明,用于图片显示。 + @State pixelMap: image.PixelMap | undefined = undefined; + + build() { + Row() { + Column() { + Button() { + Text($r('app.string.FetchFrame')) + .fontSize(30) + .fontWeight(FontWeight.Bold) + } + .type(ButtonType.Capsule) + .margin({ + top: 20 + }) + .backgroundColor('#0D9FFB') + .width('60%') + .height('5%') + .onClick(async () => { + // 设置fdSrc, 获取视频的缩略图。 + await this.testFetchFrameByTime(); + }) + .id("FetchFrameButton") + Image(this.pixelMap).width(300).height(300) + .margin({ + top: 20 + }) + } + .width('100%') + } + .height('100%') + } + + // 在以下demo中,使用资源管理接口获取打包在HAP内的视频文件,通过设置fdSrc属性。 + // 获取视频指定时间的缩略图,并通过Image控件显示在屏幕上。 + async testFetchFrameByTime() { + // 创建AVImageGenerator对象。 + let avImageGenerator: media.AVImageGenerator = await media.createAVImageGenerator(); + // 设置fdSrc。 + let context = this.getUIContext().getHostContext() as common.UIAbilityContext; + avImageGenerator.fdSrc = await context.resourceManager.getRawFd('test1.mp4'); + + // 初始化入参。 + let timeUs = 0; + let queryOption = media.AVImageQueryOptions.AV_IMAGE_QUERY_NEXT_SYNC; + let param: media.PixelMapParams = { + width : 300, + height : 300 + }; + + // 获取缩略图(promise模式)。 + this.pixelMap = await avImageGenerator.fetchFrameByTime(timeUs, queryOption, param); + + // 释放资源(promise模式)。 + avImageGenerator.release(); + console.info(TAG, `release success.`); + } +} \ No newline at end of file diff --git a/code/DocsSample/Media/AVImageGenerator/AVImageGeneratorArkTS/string.json b/code/DocsSample/Media/AVImageGenerator/AVImageGeneratorArkTS/string.json new file mode 100644 index 0000000000000000000000000000000000000000..f7e641f33851f3251c515888ed8477a55afaea41 --- /dev/null +++ b/code/DocsSample/Media/AVImageGenerator/AVImageGeneratorArkTS/string.json @@ -0,0 +1,20 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "label" + }, + { + "name": "FetchFrame", + "value": "获取缩略图" + } + ] +} \ No newline at end of file diff --git a/code/DocsSample/Media/AVImageGenerator/AVImageGeneratorNDK/Ability.test.ets b/code/DocsSample/Media/AVImageGenerator/AVImageGeneratorNDK/Ability.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..4355522ac92ff382b60e90fe715d38ecfab2ab04 --- /dev/null +++ b/code/DocsSample/Media/AVImageGenerator/AVImageGeneratorNDK/Ability.test.ets @@ -0,0 +1,56 @@ +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; +import { Driver, ON } from '@ohos.UiTest'; +import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; + +export default function abilityTest() { + const TAG = '[Sample_AVImageGeneratorNDK]'; + const driver = Driver.create(); + const abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator(); + const bundleName = AbilityDelegatorRegistry.getArguments().bundleName; + + describe('ActsAbilityTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }) + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }) + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }) + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }) + it('AVImageGeneratorNDK_001', 0, async (done: Function) => { + console.info(TAG, 'AVImageGenerator_001 begin'); + try { + await abilityDelegator.startAbility({ + bundleName: bundleName, + abilityName: 'EntryAbility' + }); + } catch (exception) { + expect().assertFail(); + } + try { + const button = await driver.findComponent(ON.id('FetchFrameButton')); + await button.click(); + await driver.delayMs(2000); + const button2 = await driver.findComponent(ON.id('ClearPicturesButton')); + await button2.click(); + await driver.delayMs(2000); + done(); + } catch (exception) { + expect().assertFail(); + } + console.info(TAG, 'AVImageGeneratorNDK_001 end'); + }) + }) +} \ No newline at end of file diff --git a/code/DocsSample/Media/AVImageGenerator/AVImageGeneratorNDK/Index.ets b/code/DocsSample/Media/AVImageGenerator/AVImageGeneratorNDK/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..b4399e2aeab29d2b97148c09a8f6a4e3411bf6f2 --- /dev/null +++ b/code/DocsSample/Media/AVImageGenerator/AVImageGeneratorNDK/Index.ets @@ -0,0 +1,117 @@ +import { image } from '@kit.ImageKit'; +import avimagegenerator from 'libentry.so'; +import {promptAction} from '@kit.ArkUI'; +import { common } from '@kit.AbilityKit'; + +@Entry +@Component +struct Index { + @State message: string = 'Hello World'; + @State totalDuration: number = 0; + @State queryOption: string = "next sync" + @State arrowPosition: ArrowPosition = ArrowPosition.END + @State space: number = 8 + // pixelMap对象声明,用于图片显示。 + @State pixelMap: image.PixelMap | undefined = undefined; + @State queryOptionIndex: number = 0 + private videoName: string = "H264_AAC.mp4"; + + build() { + Column() { + + Text(this.videoName).fontSize(20).fontWeight(FontWeight.Bold) + + Select([{ value: $r('app.string.nextSync') }, + { value: $r('app.string.previousSync') }, + { value: $r('app.string.closestSync') }, + { value: $r('app.string.closest') }]) + .selected(this.queryOptionIndex) + .value(this.queryOption) + .font({ size: 16, weight: 500 }) + .fontColor('#182431') + .selectedOptionFont({ size: 16, weight: 400 }) + .optionFont({ size: 16, weight: 400 }) + .space(this.space) + .arrowPosition(this.arrowPosition) + .menuAlign(MenuAlignType.START, {dx:0, dy:0}) + .margin({ + top: 20 + }) + .width('60%') + .onSelect((index:number, text?: string | undefined)=>{ + console.info('Select:' + index) + this.queryOptionIndex = index; + if(text){ + this.queryOption = text; + } + }) + + Button() { + Text($r('app.string.FetchFrame')) + .fontSize(30) + .fontWeight(FontWeight.Bold); + } + .type(ButtonType.Capsule) + .margin({ + top: 20 + }) + .backgroundColor('#0D9FFB') + .width('60%') + .height('5%') + .onClick(() => { + this.testFetchFrameByTime(); + }) + .id("FetchFrameButton"); + + Row () { + if (this.pixelMap) { + Image(this.pixelMap) + .width('16.67%') + .margin({ + top: 20 + }) + .borderWidth(2) + .borderColor('#0D9FFB').objectFit(ImageFit.Fill) + } else { + Image(null) + .width('16.67%') + .margin({ + top: 20 + }) + .borderWidth(2) + .borderColor('#0D9FFB') + } + } + + Button($r('app.string.ClearPictures'), { type: ButtonType.Normal }) + .onClick(() => { + this.pixelMap = undefined; + promptAction.showToast({ + message:$r('app.string.ClearPicturesFinish'), + duration:2000, + showMode: promptAction.ToastShowMode.DEFAULT, + bottom:80 + }) + }) + .id("ClearPicturesButton"); + }.width('100%').height('100%') + } + + async testFetchFrameByTime() { + let context = this.getUIContext().getHostContext() as common.UIAbilityContext; + let AVFileDescriptor = await context.resourceManager.getRawFd(this.videoName); + let fdsrc : number = AVFileDescriptor.fd; + let size : number = AVFileDescriptor.length; + let offset : number = AVFileDescriptor.offset; + let timeMs : number = 0; + try { + timeMs = avimagegenerator.OhAVMetadataExtractorGetDuration(fdsrc, size, offset); + } catch (err) { + console.log(`OhAVMetadataExtractorGetDuration err ${err}`); + } + + this.totalDuration = timeMs / 1_000; + this.pixelMap = avimagegenerator.OhAvImageGeneratorFetchFrameByTime(fdsrc, size, timeMs * 1000 / 5, this.queryOptionIndex, offset) + } + +} \ No newline at end of file diff --git a/code/DocsSample/Media/AVImageGenerator/AVImageGeneratorNDK/build-profile.json5 b/code/DocsSample/Media/AVImageGenerator/AVImageGeneratorNDK/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..14bbd03b5cd8ccf4c6bec74c0b9550228d792cb5 --- /dev/null +++ b/code/DocsSample/Media/AVImageGenerator/AVImageGeneratorNDK/build-profile.json5 @@ -0,0 +1,39 @@ +{ + "apiType": "stageMode", + "buildOption": { + "externalNativeOptions": { + "path": "./src/main/cpp/CMakeLists.txt", + "arguments": "", + "cppFlags": "", + } + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": false, + "files": [ + "./obfuscation-rules.txt" + ] + } + } + }, + "nativeLib": { + "debugSymbol": { + "strip": true, + "exclude": [] + } + } + }, + ], + "targets": [ + { + "name": "default" + }, + { + "name": "ohosTest", + } + ] +} \ No newline at end of file diff --git a/code/DocsSample/Media/AVImageGenerator/AVImageGeneratorNDK/cpp/CMakeLists.txt b/code/DocsSample/Media/AVImageGenerator/AVImageGeneratorNDK/cpp/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..bcb95b3f6b35be2eedf9f62a4ef6a060bbf9ce3b --- /dev/null +++ b/code/DocsSample/Media/AVImageGenerator/AVImageGeneratorNDK/cpp/CMakeLists.txt @@ -0,0 +1,16 @@ +# the minimum version of CMake. +cmake_minimum_required(VERSION 3.5.0) +project(AVMetadataExtractorNDK) + +set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR}) + +if(DEFINED PACKAGE_FIND_FILE) + include(${PACKAGE_FIND_FILE}) +endif() + +include_directories(${NATIVERENDER_ROOT_PATH} + ${NATIVERENDER_ROOT_PATH}/include) + +add_library(entry SHARED napi_init.cpp) +target_link_libraries(entry PUBLIC libace_napi.z.so libavimage_generator.so libhilog_ndk.z.so libpixelmap.so libavmetadata_extractor.so + libpixelmap_ndk.z.so libnative_media_core.so) \ No newline at end of file diff --git a/code/DocsSample/Media/AVImageGenerator/AVImageGeneratorNDK/cpp/napi_init.cpp b/code/DocsSample/Media/AVImageGenerator/AVImageGeneratorNDK/cpp/napi_init.cpp new file mode 100644 index 0000000000000000000000000000000000000000..400af8bf113afdb393568156a86fd24f4e620169 --- /dev/null +++ b/code/DocsSample/Media/AVImageGenerator/AVImageGeneratorNDK/cpp/napi_init.cpp @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * 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 "napi/native_api.h" + +#include +#include +#include +#include +#include "multimedia/player_framework/avmetadata_extractor.h" +#include "multimedia/player_framework/avmetadata_extractor_base.h" + +#include + +#define LOG_PRINT_DOMAIN 0xFF00 +#define APP_LOG_DOMAIN 0x0001 +constexpr const char *APP_LOG_TAG = "AVImageGenerator"; +#define H_LOGI(...) ((void)OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, APP_LOG_TAG, __VA_ARGS__)) + +// 辅助函数:检查参数数量和类型。 +bool CheckArgs(napi_env env, napi_callback_info info, size_t expectedArgc) { + size_t argc; + napi_value thisArg; + void* data; + napi_get_cb_info(env, info, &argc, nullptr, &thisArg, &data); + if (argc < expectedArgc) { + napi_throw_error(env, "EINVAL", "Insufficient arguments"); + return false; + } + napi_value argv[expectedArgc]; + napi_get_cb_info(env, info, &argc, argv, &thisArg, &data); + for (size_t i = 0; i < expectedArgc; ++i) { + napi_valuetype type; + napi_typeof(env, argv[i], &type); + if (type != napi_number) { + napi_throw_type_error(env, "EINVAL", "All arguments must be numbers"); + return false; + } + } + return true; +} + +// 辅助函数:获取 int32 类型值并进行错误处理。 +bool GetInt32Value(napi_env env, napi_value value, int32_t* result) { + napi_status status = napi_get_value_int32(env, value, result); + if (status != napi_ok) { + napi_throw_error(env, "EINVAL", "Failed to get int32 value"); + return false; + } + return true; +} + +// 辅助函数:获取 int64 类型值并进行错误处理。 +bool GetInt64Value(napi_env env, napi_value value, int64_t* result) { + napi_status status = napi_get_value_int64(env, value, result); + if (status != napi_ok) { + napi_throw_error(env, "EINVAL", "Failed to get int64 value"); + return false; + } + return true; +} + +// 需要在index.d.ts文件内描述映射的OhAvImageGeneratorFetchFrameByTime方法。 +// export const OhAvImageGeneratorFetchFrameByTime: (fdsrc : number, size : number, timeus : number, +// options : number, offset : number) => image.PixelMap; +// 需要传入媒体文件描述符fdsrc、媒体文件大小size、指定的时间timeus(单位us)、 +// 指定时间点与视频帧的对应关系options、媒体源在文件描述符中的偏移量offset。 +// 返回PixelMap对象。 +static napi_value OhAvImageGeneratorFetchFrameByTime(napi_env env, napi_callback_info info) +{ + if (!CheckArgs(env, info, 5)) { + return nullptr; + } + size_t argc = 5; + napi_value argv[5]; + napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr); + int64_t timeUs = 0; + int64_t offset = 0; + int32_t fileDescribe = -1; + int32_t fileSize = 0; + int32_t options = OH_AVIMAGE_GENERATOR_QUERY_CLOSEST; + if (!GetInt32Value(env, argv[0], &fileDescribe)) return nullptr; + if (!GetInt32Value(env, argv[1], &fileSize)) return nullptr; + if (!GetInt64Value(env, argv[2], &timeUs)) return nullptr; + if (!GetInt32Value(env, argv[3], &options)) return nullptr; + if (!GetInt64Value(env, argv[4], &offset)) return nullptr; + // 参数校验。 + if (options < OH_AVIMAGE_GENERATOR_QUERY_NEXT_SYNC || options > OH_AVIMAGE_GENERATOR_QUERY_CLOSEST) { + napi_throw_range_error(env, "EINVAL", "Invalid options value"); + return nullptr; + } + // 创建OH_AVImageGenerator实例。 + OH_AVImageGenerator* generator = OH_AVImageGenerator_Create(); + // 异常处理。 + if (!generator) { + napi_throw_error(env, "EFAILED", "Create generator failed"); + return nullptr; + } + // 设置视频资源的文件描述符。 + OH_AVErrCode avErrCode = OH_AVImageGenerator_SetFDSource(generator, fileDescribe, offset, fileSize); + // 异常处理。 + if (avErrCode != AV_ERR_OK) { + OH_AVImageGenerator_Release(generator); + napi_throw_error(env, "EFAILED", "SetFDSource failed"); + return nullptr; + } + // 取指定时间的视频帧。 + OH_PixelmapNative* pixelMap = nullptr; + avErrCode = OH_AVImageGenerator_FetchFrameByTime(generator, timeUs, + (OH_AVImageGenerator_QueryOptions)options, &pixelMap); + // 异常处理。 + if (avErrCode != AV_ERR_OK || !pixelMap) { + OH_AVImageGenerator_Release(generator); + napi_throw_error(env, "EFAILED", "FetchFrameByTime failed"); + return nullptr; + } + // 将nativePixelMap对象转换为PixelMapnapi对象。 + napi_value pixelmapNapi = nullptr; + Image_ErrorCode errCode = OH_PixelmapNative_ConvertPixelmapNativeToNapi(env, pixelMap, &pixelmapNapi); + // 释放OH_PixelmapNative资源。 + OH_PixelmapNative_Release(pixelMap); + // 释放OH_AVImageGenerator资源。 + OH_AVImageGenerator_Release(generator); + // 异常处理。 + if (errCode != IMAGE_SUCCESS) { + napi_throw_error(env, "EFAILED", "Convert PixelMap failed"); + return nullptr; + } + return pixelmapNapi; +} + +static napi_value OhAVMetadataExtractorGetDuration(napi_env env, napi_callback_info info) { + if (!CheckArgs(env, info, 3)) { + return nullptr; + } + size_t argc = 3; + napi_value argv[3]; + napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr); + int64_t offset = 0; + int32_t fileDescribe = -1; + int64_t fileSize = 0; + if (!GetInt32Value(env, argv[0], &fileDescribe)) return nullptr; + if (!GetInt64Value(env, argv[1], &fileSize)) return nullptr; + if (!GetInt64Value(env, argv[2], &offset)) return nullptr; + OH_AVMetadataExtractor* mainExtractor = OH_AVMetadataExtractor_Create(); + if (!mainExtractor) { + napi_throw_error(env, "EFAILED", "Create metadata extractor failed"); + return nullptr; + } + OH_AVErrCode avErrCode = OH_AVMetadataExtractor_SetFDSource(mainExtractor, fileDescribe, offset, fileSize); + if (avErrCode != AV_ERR_OK) { + OH_AVMetadataExtractor_Release(mainExtractor); + napi_throw_error(env, "EFAILED", "SetFDSource for metadata extractor failed"); + return nullptr; + } + OH_AVFormat* avMetadata = OH_AVFormat_Create(); + avErrCode = OH_AVMetadataExtractor_FetchMetadata(mainExtractor, avMetadata); + if (avErrCode != AV_ERR_OK) { + OH_AVFormat_Destroy(avMetadata); + OH_AVMetadataExtractor_Release(mainExtractor); + napi_throw_error(env, "EFAILED", "Fetch metadata failed"); + return nullptr; + } + int64_t out; + if (!OH_AVFormat_GetLongValue(avMetadata, OH_AVMETADATA_EXTRACTOR_DURATION, &out)) { + OH_AVFormat_Destroy(avMetadata); + OH_AVMetadataExtractor_Release(mainExtractor); + napi_throw_error(env, "EFAILED", "Get duration failed"); + return nullptr; + } + napi_value duration; + napi_create_int64(env, out, &duration); + OH_AVFormat_Destroy(avMetadata); + OH_AVMetadataExtractor_Release(mainExtractor); + return duration; +} + +EXTERN_C_START +static napi_value Init(napi_env env, napi_value exports) +{ + napi_property_descriptor desc[] = { + {"OhAvImageGeneratorFetchFrameByTime", nullptr, OhAvImageGeneratorFetchFrameByTime, nullptr, nullptr, nullptr, napi_default, nullptr}, + {"OhAVMetadataExtractorGetDuration", nullptr, OhAVMetadataExtractorGetDuration, nullptr, nullptr, nullptr, napi_default, nullptr}, + }; + napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc); + return exports; +} +EXTERN_C_END + +static napi_module demoModule = { + .nm_version = 1, + .nm_flags = 0, + .nm_filename = nullptr, + .nm_register_func = Init, + .nm_modname = "entry", + .nm_priv = ((void *)0), + .reserved = {0}, +}; + +extern "C" __attribute__((constructor)) void RegisterEntryModule(void) +{ + napi_module_register(&demoModule); +} \ No newline at end of file diff --git a/code/DocsSample/Media/AVImageGenerator/AVImageGeneratorNDK/cpp/types/libentry/Index.d.ts b/code/DocsSample/Media/AVImageGenerator/AVImageGeneratorNDK/cpp/types/libentry/Index.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..a8903d1edc3a24d7bc44825eb47bec0b332195a6 --- /dev/null +++ b/code/DocsSample/Media/AVImageGenerator/AVImageGeneratorNDK/cpp/types/libentry/Index.d.ts @@ -0,0 +1,4 @@ +import { image } from "@kit.ImageKit"; + +export const OhAvImageGeneratorFetchFrameByTime: (fdsrc : number, size : number, timeus : number, options : number, offset : number) => image.PixelMap; +export const OhAVMetadataExtractorGetDuration: (fdsrc : number, fileSize : number, offset : number) => number; \ No newline at end of file diff --git a/code/DocsSample/Media/AVImageGenerator/AVImageGeneratorNDK/cpp/types/libentry/oh-package.json5 b/code/DocsSample/Media/AVImageGenerator/AVImageGeneratorNDK/cpp/types/libentry/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..c69ca2198f11d3288b2ac61227a583604b647c78 --- /dev/null +++ b/code/DocsSample/Media/AVImageGenerator/AVImageGeneratorNDK/cpp/types/libentry/oh-package.json5 @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * 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. + */ +{ + "name": "libentry.so", + "types": "./Index.d.ts", + "version": "1.0.0", + "description": "Please describe the basic information." +} \ No newline at end of file diff --git a/code/DocsSample/Media/AVImageGenerator/AVImageGeneratorNDK/oh-package.json5 b/code/DocsSample/Media/AVImageGenerator/AVImageGeneratorNDK/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..a52cd1c8fa3f1c13ea8feda736db7b2757b764f7 --- /dev/null +++ b/code/DocsSample/Media/AVImageGenerator/AVImageGeneratorNDK/oh-package.json5 @@ -0,0 +1,12 @@ +{ + "name": "entry", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "", + "author": "", + "license": "", + "dependencies": { + "libentry.so": "file:./src/main/cpp/types/libentry" + } +} + diff --git a/code/DocsSample/Media/AVImageGenerator/AVImageGeneratorNDK/string.json b/code/DocsSample/Media/AVImageGenerator/AVImageGeneratorNDK/string.json new file mode 100644 index 0000000000000000000000000000000000000000..4526272f44e46ea8ed9c52bb1400b6bfececb05c --- /dev/null +++ b/code/DocsSample/Media/AVImageGenerator/AVImageGeneratorNDK/string.json @@ -0,0 +1,44 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "label" + }, + { + "name": "nextSync", + "value": "next sync" + }, + { + "name": "previousSync", + "value": "previous sync" + }, + { + "name": "closestSync", + "value": "closest sync" + }, + { + "name": "closest", + "value": "closest" + }, + { + "name": "FetchFrame", + "value": "开始抽帧" + }, + { + "name": "ClearPictures", + "value": "清除图片" + }, + { + "name": "ClearPicturesFinish", + "value": "已清除图片" + } + ] +} \ No newline at end of file diff --git a/code/DocsSample/Media/AVMetadataExtractor/AVMetadataExtractorArkTS/Ability.test.ets b/code/DocsSample/Media/AVMetadataExtractor/AVMetadataExtractorArkTS/Ability.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..05c2b417e620bc51be209a4fac359cbcb6f74103 --- /dev/null +++ b/code/DocsSample/Media/AVMetadataExtractor/AVMetadataExtractorArkTS/Ability.test.ets @@ -0,0 +1,53 @@ +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; +import { Driver, ON } from '@ohos.UiTest'; +import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; + +export default function abilityTest() { + const TAG = '[Sample_AVMetadataExtractor]'; + const driver = Driver.create(); + const abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator(); + const bundleName = AbilityDelegatorRegistry.getArguments().bundleName; + + describe('ActsAbilityTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }) + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }) + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }) + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }) + it('FetchMetadata_001', 0, async (done: Function) => { + console.info(TAG, 'FetchFrameButton_001 begin'); + try { + await abilityDelegator.startAbility({ + bundleName: bundleName, + abilityName: 'EntryAbility' + }); + } catch (exception) { + expect().assertFail(); + } + try { + const button = await driver.findComponent(ON.id('FetchMetadataButton')); + await button.click(); + await driver.delayMs(5000); + done(); + } catch (exception) { + expect().assertFail(); + } + console.info(TAG, 'FetchMetadata_001 end'); + }) + }) +} \ No newline at end of file diff --git a/code/DocsSample/Media/AVMetadataExtractor/AVMetadataExtractorArkTS/Index.ets b/code/DocsSample/Media/AVMetadataExtractor/AVMetadataExtractorArkTS/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..19841148e086edbb71d66f0d2007574f7c64b3b6 --- /dev/null +++ b/code/DocsSample/Media/AVMetadataExtractor/AVMetadataExtractorArkTS/Index.ets @@ -0,0 +1,185 @@ +import { media } from '@kit.MediaKit'; +import { image } from '@kit.ImageKit'; +import { common } from '@kit.AbilityKit'; +import { fileIo as fs, ReadOptions } from '@kit.CoreFileKit'; + +const TAG = 'MetadataDemo'; + +@Entry +@Component +struct Index { + // pixelMap对象声明,用于图片显示。 + @State pixelMap: image.PixelMap | undefined = undefined; + context: Context = this.getUIContext().getHostContext() as common.UIAbilityContext; + rootPath: string = this.context.filesDir; + testFilename: string = '/MP3_SURFACE.mp3'; + + build() { + Row() { + Column() { + Button() { + Text($r('app.string.FetchMetadata')) + .fontSize(30) + .fontWeight(FontWeight.Bold) + } + .type(ButtonType.Capsule) + .margin({ + top: 20 + }) + .backgroundColor('#0D9FFB') + .width('60%') + .height('5%') + .onClick(async () => { + // 设置fdSrc, 获取音频元数据和专辑封面(异步接口以Callback形式调用)。 + await this.testFetchMetadataFromFdSrcByCallback(); + // 设置fdSrc, 获取音频元数据和专辑封面(异步接口以Promise形式调用)。 + await this.testFetchMetadataFromFdSrcByPromise(); + // 通过fdSrc获取沙箱路径下音频元数据和专辑封面(文件必须在沙箱路径里存在)。 + await this.testFetchMetadataFromFdSrc(); + // 设置dataSrc, 获取沙箱路径下音频元数据和专辑封面(文件必须在沙箱路径里存在)。 + await this.testFetchMetadataFromDataSrc(); + }) + .id("FetchMetadataButton") + + Image(this.pixelMap).width(300).height(300) + .margin({ + top: 20 + }) + } + .width('100%') + } + .height('100%') + } + + // 在以下demo中,使用资源管理接口获取打包在HAP内的媒体资源文件,通过设置fdSrc属性,获取音频元数据并打印。 + // 获取音频专辑封面并通过Image控件显示在屏幕上。该demo以Callback形式进行异步接口调用。 + async testFetchMetadataFromFdSrcByCallback() { + if (canIUse("SystemCapability.Multimedia.Media.AVMetadataExtractor")) { + // 创建AVMetadataExtractor对象。 + let avMetadataExtractor: media.AVMetadataExtractor = await media.createAVMetadataExtractor(); + + // 设置fdSrc。 + let context = this.getUIContext().getHostContext() as common.UIAbilityContext; + avMetadataExtractor.fdSrc = await context.resourceManager.getRawFd('MP3_SURFACE.mp3'); + + // 获取元数据(callback模式)。 + avMetadataExtractor.fetchMetadata((error, metadata) => { + if (error) { + console.error(TAG, `fetchMetadata callback failed, err = ${JSON.stringify(error)}`); + return; + } + console.info(TAG, `fetchMetadata callback success, genre: ${metadata.genre}`); + }) + + //获取专辑封面(callback模式)。 + avMetadataExtractor.fetchAlbumCover((err, pixelMap) => { + if (err) { + console.error(TAG, `fetchAlbumCover callback failed, err = ${JSON.stringify(err)}`); + return; + } + this.pixelMap = pixelMap; + + // 释放资源(callback模式)。 + avMetadataExtractor.release((error) => { + if (error) { + console.error(TAG, `release failed, err = ${JSON.stringify(error)}`); + return; + } + console.info(TAG, `release success.`); + }) + }) + } + } + + // 在以下demo中,使用资源管理接口获取打包在HAP内的媒体资源文件,通过设置fdSrc属性,获取音频元数据并打印。 + // 获取音频专辑封面并通过Image控件显示在屏幕上。该demo以Promise形式进行异步接口调用。 + async testFetchMetadataFromFdSrcByPromise() { + if (canIUse("SystemCapability.Multimedia.Media.AVMetadataExtractor")) { + // 创建AVMetadataExtractor对象。 + let avMetadataExtractor: media.AVMetadataExtractor = await media.createAVMetadataExtractor(); + // 设置fdSrc。 + let context = this.getUIContext().getHostContext() as common.UIAbilityContext; + avMetadataExtractor.fdSrc = await context.resourceManager.getRawFd('MP3_SURFACE.mp3'); + + // 获取元数据(promise模式)。 + let metadata = await avMetadataExtractor.fetchMetadata(); + console.info(TAG, `get meta data, hasAudio: ${metadata.hasAudio}`); + + // 获取专辑封面(promise模式)。 + this.pixelMap = await avMetadataExtractor.fetchAlbumCover(); + + // 释放资源(promise模式)。 + avMetadataExtractor.release(); + console.info(TAG, `release success.`); + } + } + + // 在以下demo中,使用fs文件系统打开沙箱地址获取媒体文件地址,设置fdSrc属性,获取音频元数据并打印。 + // 获取音频专辑封面并通过Image控件显示在屏幕上。 + async testFetchMetadataFromFdSrc() { + if (canIUse("SystemCapability.Multimedia.Media.AVMetadataExtractor")) { + // 创建AVMetadataExtractor对象。 + let avMetadataExtractor = await media.createAVMetadataExtractor(); + + // 设置fdSrc。 + console.info(TAG, `path: ${this.rootPath + this.testFilename}`); + avMetadataExtractor.fdSrc = fs.openSync(this.rootPath + this.testFilename); + + // 获取元数据(promise模式)。 + let metadata = await avMetadataExtractor.fetchMetadata(); + console.info(TAG, `get meta data, mimeType: ${metadata.mimeType}`); + + // 获取专辑封面(promise模式)。 + this.pixelMap = await avMetadataExtractor.fetchAlbumCover(); + + // 释放资源(promise模式)。 + avMetadataExtractor.release(); + console.info(TAG, `release data source success.`); + } + } + + // 在以下demo中,使用fs文件系统打开沙箱地址获取媒体文件地址,设置dataSrc属性,获取音频元数据并打印。 + // 获取音频专辑封面并通过Image控件显示在屏幕上。 + async testFetchMetadataFromDataSrc() { + // 通过UIAbilityContext获取沙箱地址filesDir(以Stage模型为例)。 + let fd: number = fs.openSync(this.rootPath + this.testFilename).fd; + let fileSize: number = fs.statSync(this.rootPath + this.testFilename).size; + // 设置dataSrc描述符,通过callback从文件中获取资源,写入buffer中。 + let dataSrc: media.AVDataSrcDescriptor = { + fileSize: fileSize, + callback: (buffer, len, pos) => { + if (buffer == undefined || len == undefined || pos == undefined) { + console.error(TAG, `dataSrc callback param invalid`); + return -1; + } + let options: ReadOptions = { + offset: pos, + length: len + }; + let num = fs.readSync(fd, buffer, options); + console.info(TAG, 'readAt end, num: ' + num); + if (num > 0 && fileSize >= pos) { + return num; + } + return -1; + } + }; + if (canIUse("SystemCapability.Multimedia.Media.AVMetadataExtractor")) { + // 创建AVMetadataExtractor对象。 + let avMetadataExtractor = await media.createAVMetadataExtractor(); + // 设置dataSrc。 + avMetadataExtractor.dataSrc = dataSrc; + + // 获取元数据(promise模式)。 + let metadata = await avMetadataExtractor.fetchMetadata(); + console.info(TAG, `get meta data, mimeType: ${metadata.mimeType}`); + + // 获取专辑封面(promise模式)。 + this.pixelMap = await avMetadataExtractor.fetchAlbumCover(); + + // 释放资源(promise模式)。 + avMetadataExtractor.release(); + console.info(TAG, `release data source success.`); + } + } +} \ No newline at end of file diff --git a/code/DocsSample/Media/AVMetadataExtractor/AVMetadataExtractorArkTS/string.json b/code/DocsSample/Media/AVMetadataExtractor/AVMetadataExtractorArkTS/string.json new file mode 100644 index 0000000000000000000000000000000000000000..9d969822037eeb95d5324d6a1ce872374c99c104 --- /dev/null +++ b/code/DocsSample/Media/AVMetadataExtractor/AVMetadataExtractorArkTS/string.json @@ -0,0 +1,20 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "label" + }, + { + "name": "FetchMetadata", + "value": "获取元数据信息" + } + ] +} \ No newline at end of file diff --git a/code/DocsSample/Media/AVMetadataExtractor/AVMetadataExtractorNDK2/Ability.test.ets b/code/DocsSample/Media/AVMetadataExtractor/AVMetadataExtractorNDK2/Ability.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..37c3a8196473730236f13b5c99d297c9120db13d --- /dev/null +++ b/code/DocsSample/Media/AVMetadataExtractor/AVMetadataExtractorNDK2/Ability.test.ets @@ -0,0 +1,56 @@ +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; +import { Driver, ON } from '@ohos.UiTest'; +import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; + +export default function abilityTest() { + const TAG = '[Sample_AVMetadataExtractorNDK]'; + const driver = Driver.create(); + const abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator(); + const bundleName = AbilityDelegatorRegistry.getArguments().bundleName; + + describe('ActsAbilityTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }) + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }) + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }) + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }) + it('AVMetadataExtractorNDK_001', 0, async (done: Function) => { + console.info(TAG, 'AVMetadataExtractorNDK_001 begin'); + try { + await abilityDelegator.startAbility({ + bundleName: bundleName, + abilityName: 'EntryAbility' + }); + } catch (exception) { + expect().assertFail(); + } + try { + const button = await driver.findComponent(ON.id('FetchCover')); + await button.click(); + await driver.delayMs(2000); + const button2 = await driver.findComponent(ON.id('FetchMetaData')); + await button2.click(); + await driver.delayMs(2000); + done(); + } catch (exception) { + expect().assertFail(); + } + console.info(TAG, 'AVMetadataExtractorNDK_001 end'); + }) + }) +} \ No newline at end of file diff --git a/code/DocsSample/Media/AVMetadataExtractor/AVMetadataExtractorNDK2/Index.ets b/code/DocsSample/Media/AVMetadataExtractor/AVMetadataExtractorNDK2/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..186636f017ff971bb28edcb5752f66e72e9fb5ad --- /dev/null +++ b/code/DocsSample/Media/AVMetadataExtractor/AVMetadataExtractorNDK2/Index.ets @@ -0,0 +1,213 @@ +import { image } from '@kit.ImageKit'; +import { media } from '@kit.MediaKit'; +import avmetadataextractor from 'libentry.so'; +import { promptAction } from '@kit.ArkUI'; +import { common } from '@kit.AbilityKit'; + +const TAG: string = 'AvImageGenerator Demo:' + +@Entry +@Component +struct Index { + private videoName: string = "MP3_SURFACE.mp3"; + @State coverPixelMap: image.PixelMap | undefined = undefined; + @State metadata: media.AVMetadata | undefined = undefined; + @State show: boolean = true; + + build() { + Column() { + Text(this.videoName).fontSize(20).fontWeight(FontWeight.Bold) + Row () { + Column() { + Button($r('app.string.FetchCover'), { type: ButtonType.Capsule }) + .margin({ top: 30 }) + .onClick(async() => { + await this.getSelectedVideoCover(); + this.show = true; + promptAction.showToast({ + message:$r('app.string.FetchCoverStart'), + duration:2000, + showMode: promptAction.ToastShowMode.DEFAULT, + bottom:80 + }) + }) + .id("FetchCover") + } + Column() { + Button($r('app.string.FetchMetaData'), { type: ButtonType.Capsule }) + .margin({ top: 30 }) + .onClick(async() => { + this.coverPixelMap = undefined; + await this.getMetadata(); + this.show = false; + promptAction.showToast({ + message:$r('app.string.FetchMetaDataStart'), + duration:2000, + showMode: promptAction.ToastShowMode.DEFAULT, + bottom:80 + }) + }) + .id("FetchMetaData") + } + } + + Row () { + if (this.show) { + Image(this.coverPixelMap) + .width('70%') + .height(300) + .margin({ + top: 50 + }) + .borderWidth(2) + .borderColor('#0D9FFB') + } else { + Column() { + Text($r('app.string.album')).fontSize(15).fontWeight(FontWeight.Bold) + Text($r('app.string.albumArtist')).fontSize(15).fontWeight(FontWeight.Bold) + Text($r('app.string.artist')).fontSize(15).fontWeight(FontWeight.Bold) + Text($r('app.string.author')).fontSize(15).fontWeight(FontWeight.Bold) + Text($r('app.string.dateTime')).fontSize(15).fontWeight(FontWeight.Bold) + Text($r('app.string.dateTimeFormat')).fontSize(15).fontWeight(FontWeight.Bold) + Text($r('app.string.composer')).fontSize(15).fontWeight(FontWeight.Bold) + Text($r('app.string.duration')).fontSize(15).fontWeight(FontWeight.Bold) + Text($r('app.string.genre')).fontSize(15).fontWeight(FontWeight.Bold) + Text($r('app.string.hasAudio')).fontSize(15).fontWeight(FontWeight.Bold) + Text($r('app.string.hasVideo')).fontSize(15).fontWeight(FontWeight.Bold) + Text($r('app.string.mimeType')).fontSize(15).fontWeight(FontWeight.Bold) + Text($r('app.string.trackCount')).fontSize(15).fontWeight(FontWeight.Bold) + Text($r('app.string.sampleRate')).fontSize(15).fontWeight(FontWeight.Bold) + Text($r('app.string.title')).fontSize(15).fontWeight(FontWeight.Bold) + Text($r('app.string.videoHeight')).fontSize(15).fontWeight(FontWeight.Bold) + Text($r('app.string.videoWidth')).fontSize(15).fontWeight(FontWeight.Bold) + Text($r('app.string.videoOrientation')).fontSize(15).fontWeight(FontWeight.Bold) + Text($r('app.string.hdrType')).fontSize(15).fontWeight(FontWeight.Bold) + Text($r('app.string.location')).fontSize(15).fontWeight(FontWeight.Bold) + } + .width('50%') + .margin({ + top: 50 + }) + + Column() { + Text(this.metadata ? this.metadata.album + " " : $r('app.string.noValue')) + .fontSize(15) + .fontWeight(FontWeight.Bold) + + Text(this.metadata ? this.metadata.albumArtist + " " : $r('app.string.noValue')) + .fontSize(15) + .fontWeight(FontWeight.Bold) + + Text(this.metadata ? this.metadata.artist + " " : $r('app.string.noValue')) + .fontSize(15) + .fontWeight(FontWeight.Bold) + + Text(this.metadata ? this.metadata.author + " " : $r('app.string.noValue')) + .fontSize(15) + .fontWeight(FontWeight.Bold) + + Text(this.metadata ? this.metadata.dateTime + " " : $r('app.string.noValue')) + .fontSize(15) + .fontWeight(FontWeight.Bold) + + Text(this.metadata ? this.metadata.dateTimeFormat + " " : $r('app.string.noValue')) + .fontSize(15) + .fontWeight(FontWeight.Bold) + + Text(this.metadata ? this.metadata.composer + " " : $r('app.string.noValue')) + .fontSize(15) + .fontWeight(FontWeight.Bold) + + Text(this.metadata ? this.metadata.duration + " " : $r('app.string.noValue')) + .fontSize(15) + .fontWeight(FontWeight.Bold) + + Text(this.metadata ? this.metadata.genre + " " : $r('app.string.noValue')) + .fontSize(15) + .fontWeight(FontWeight.Bold) + + Text(this.metadata ? this.metadata.hasAudio + " " : $r('app.string.noValue')) + .fontSize(15) + .fontWeight(FontWeight.Bold) + + Text(this.metadata ? this.metadata.hasVideo + " " : $r('app.string.noValue')) + .fontSize(15) + .fontWeight(FontWeight.Bold) + + Text(this.metadata ? this.metadata.mimeType + " " : $r('app.string.noValue')) + .fontSize(15) + .fontWeight(FontWeight.Bold) + + Text(this.metadata ? this.metadata.trackCount + " " : $r('app.string.noValue')) + .fontSize(15) + .fontWeight(FontWeight.Bold) + + Text(this.metadata ? this.metadata.sampleRate + " " : $r('app.string.noValue')) + .fontSize(15) + .fontWeight(FontWeight.Bold) + + Text(this.metadata ? this.metadata.title + " " : $r('app.string.noValue')) + .fontSize(15) + .fontWeight(FontWeight.Bold) + + Text(this.metadata ? this.metadata.videoHeight + " " : $r('app.string.noValue')) + .fontSize(15) + .fontWeight(FontWeight.Bold) + + Text(this.metadata ? this.metadata.videoWidth + " " : $r('app.string.noValue')) + .fontSize(15) + .fontWeight(FontWeight.Bold) + + Text(this.metadata ? this.metadata.videoOrientation + " " : $r('app.string.noValue')) + .fontSize(15) + .fontWeight(FontWeight.Bold) + + Text(this.metadata ? this.metadata.hdrType + " " : $r('app.string.noValue')) + .fontSize(15) + .fontWeight(FontWeight.Bold) + + Text(this.metadata ? `${JSON.stringify(this.metadata.location)} ` : $r('app.string.noValue')) + .fontSize(15) + .fontWeight(FontWeight.Bold) + } + .width('50%') + .margin({ + top: 50 + }) + } + + } + }.width('100%').height('100%') + } + + async getSelectedVideoCover() { + this.coverPixelMap = undefined; + console.info(TAG + 'getSelectedVideoCover: start') + let context = this.getUIContext().getHostContext() as common.UIAbilityContext; + let AVFileDescriptor = await context.resourceManager.getRawFd(this.videoName); + let fdsrc : number = AVFileDescriptor.fd; + let size : number = AVFileDescriptor.length; + let offset : number = AVFileDescriptor.offset; + try { + this.coverPixelMap = avmetadataextractor.OhAVMetadataExtractorFetchAlbumCover(fdsrc, size, offset); + } catch (e) { + console.error(TAG + `getSelectedVideoCover, code is ${e.code}, message is ${e.message}`); + } + console.info(TAG + 'getSelectedVideoCover: start') + } + + async getMetadata() { + console.debug(TAG + 'getMeter START') + try { + let context = this.getUIContext().getHostContext() as common.UIAbilityContext; + let AVFileDescriptor = await context.resourceManager.getRawFd(this.videoName); + let fdsrc : number = AVFileDescriptor.fd; + let size : number = AVFileDescriptor.length; + let offset : number = AVFileDescriptor.offset; + this.metadata = avmetadataextractor.OhAVMetadataExtractorFetchMetadata(fdsrc, size, offset) + } catch (e) { + console.error(TAG + 'getMetadata' + JSON.stringify(e)); + } + console.debug(TAG + 'getMeter END') + } +} diff --git a/code/DocsSample/Media/AVMetadataExtractor/AVMetadataExtractorNDK2/build-profile.json5 b/code/DocsSample/Media/AVMetadataExtractor/AVMetadataExtractorNDK2/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..14bbd03b5cd8ccf4c6bec74c0b9550228d792cb5 --- /dev/null +++ b/code/DocsSample/Media/AVMetadataExtractor/AVMetadataExtractorNDK2/build-profile.json5 @@ -0,0 +1,39 @@ +{ + "apiType": "stageMode", + "buildOption": { + "externalNativeOptions": { + "path": "./src/main/cpp/CMakeLists.txt", + "arguments": "", + "cppFlags": "", + } + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": false, + "files": [ + "./obfuscation-rules.txt" + ] + } + } + }, + "nativeLib": { + "debugSymbol": { + "strip": true, + "exclude": [] + } + } + }, + ], + "targets": [ + { + "name": "default" + }, + { + "name": "ohosTest", + } + ] +} \ No newline at end of file diff --git a/code/DocsSample/Media/AVMetadataExtractor/AVMetadataExtractorNDK2/cpp/CMakeLists.txt b/code/DocsSample/Media/AVMetadataExtractor/AVMetadataExtractorNDK2/cpp/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..775761578b12e3675e0778856e8eb272bf7a0b49 --- /dev/null +++ b/code/DocsSample/Media/AVMetadataExtractor/AVMetadataExtractorNDK2/cpp/CMakeLists.txt @@ -0,0 +1,16 @@ +# the minimum version of CMake. +cmake_minimum_required(VERSION 3.5.0) +project(AVImageGeneratorNDK) + +set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR}) + +if(DEFINED PACKAGE_FIND_FILE) + include(${PACKAGE_FIND_FILE}) +endif() + +include_directories(${NATIVERENDER_ROOT_PATH} + ${NATIVERENDER_ROOT_PATH}/include) + +add_library(entry SHARED napi_init.cpp) +target_link_libraries(entry PUBLIC libace_napi.z.so libhilog_ndk.z.so libpixelmap.so libavmetadata_extractor.so + libpixelmap_ndk.z.so libnative_media_core.so) \ No newline at end of file diff --git a/code/DocsSample/Media/AVMetadataExtractor/AVMetadataExtractorNDK2/cpp/napi_init.cpp b/code/DocsSample/Media/AVMetadataExtractor/AVMetadataExtractorNDK2/cpp/napi_init.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1be48f2801f959bb67ae3722f6c1607f8501d275 --- /dev/null +++ b/code/DocsSample/Media/AVMetadataExtractor/AVMetadataExtractorNDK2/cpp/napi_init.cpp @@ -0,0 +1,443 @@ +#include "napi/native_api.h" + +#include +#include +#include +#include +#include +#include + +#include + +#define LOG_PRINT_DOMAIN 0xFF00 +#define APP_LOG_DOMAIN 0x0001 +constexpr const char *APP_LOG_TAG = "AVMetadataExtractor"; +#define H_LOGI(...) ((void)OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, APP_LOG_TAG, __VA_ARGS__)) + +// 辅助函数:检查参数数量和类型。 +bool CheckArgs(napi_env env, napi_callback_info info, size_t expectedArgc) { + size_t argc; + napi_value thisArg; + void* data; + napi_get_cb_info(env, info, &argc, nullptr, &thisArg, &data); + if (argc < expectedArgc) { + napi_throw_error(env, "EINVAL", "Insufficient arguments"); + return false; + } + napi_value argv[expectedArgc]; + napi_get_cb_info(env, info, &argc, argv, &thisArg, &data); + for (size_t i = 0; i < expectedArgc; ++i) { + napi_valuetype type; + napi_typeof(env, argv[i], &type); + if (type != napi_number) { + napi_throw_type_error(env, "EINVAL", "All arguments must be numbers"); + return false; + } + } + return true; +} + +// 辅助函数:获取 int32 类型值并进行错误处理。 +bool GetInt32Value(napi_env env, napi_value value, int32_t* result) { + napi_status status = napi_get_value_int32(env, value, result); + if (status != napi_ok) { + napi_throw_error(env, "EINVAL", "Failed to get int32 value"); + return false; + } + return true; +} + +// 辅助函数:获取 int64 类型值并进行错误处理。 +bool GetInt64Value(napi_env env, napi_value value, int64_t* result) { + napi_status status = napi_get_value_int64(env, value, result); + if (status != napi_ok) { + napi_throw_error(env, "EINVAL", "Failed to get int64 value"); + return false; + } + return true; +} + +// 辅助函数:将 string 类型值转为napi对象并进行错误处理。 +bool SetPropertyString(napi_env env, napi_value &obj, const std::string &key, const std::string &value) { + napi_value keyNapi = nullptr; + napi_status status = napi_create_string_utf8(env, key.c_str(), NAPI_AUTO_LENGTH, &keyNapi); + if (status != napi_ok) { + // napi_throw_error(env, "EFAILED", "Failed to create string key"); + return false; + } + napi_value valueNapi = nullptr; + status = napi_create_string_utf8(env, value.c_str(), NAPI_AUTO_LENGTH, &valueNapi); + if (status != napi_ok) { + // napi_throw_error(env, "EFAILED", "Failed to create string value"); + return false; + } + status = napi_set_property(env, obj, keyNapi, valueNapi); + if (status != napi_ok) { + // napi_throw_error(env, "EFAILED", "Failed to set property"); + return false; + } + return true; +} + +// 辅助函数:将 double 类型值转为napi对象并进行错误处理。 +bool SetPropertyDouble(napi_env env, napi_value &obj, const std::string &key, double value) { + napi_value keyNapi = nullptr; + napi_status status = napi_create_string_utf8(env, key.c_str(), NAPI_AUTO_LENGTH, &keyNapi); + if (status != napi_ok) { + // napi_throw_error(env, "EFAILED", "Failed to create string key"); + return false; + } + napi_value valueNapi = nullptr; + status = napi_create_double(env, value, &valueNapi); + if (status != napi_ok) { + // napi_throw_error(env, "EFAILED", "Failed to create double value"); + return false; + } + status = napi_set_property(env, obj, keyNapi, valueNapi); + if (status != napi_ok) { + // napi_throw_error(env, "EFAILED", "Failed to set property"); + return false; + } + return true; +} + +// 辅助函数:将 int 类型值转为napi对象并进行错误处理。 +bool SetPropertyInt(napi_env env, napi_value &obj, const std::string &key, int value) { + napi_value keyNapi = nullptr; + napi_status status = napi_create_string_utf8(env, key.c_str(), NAPI_AUTO_LENGTH, &keyNapi); + if (status != napi_ok) { + // napi_throw_error(env, "EFAILED", "Failed to create string key"); + return false; + } + napi_value valueNapi = nullptr; + status = napi_create_int32(env, value, &valueNapi); + if (status != napi_ok) { + // napi_throw_error(env, "EFAILED", "Failed to create int value"); + return false; + } + status = napi_set_property(env, obj, keyNapi, valueNapi); + if (status != napi_ok) { + // napi_throw_error(env, "EFAILED", "Failed to set property"); + return false; + } + return true; +} + +// 获取专辑封面。 +// 需要在index.d.ts文件内描述映射的OhAVMetadataExtractorFetchAlbumCover方法。 +// export const OhAVMetadataExtractorFetchAlbumCover: (fdsrc : number, size : number, offset : number) => image.PixelMap; +// 需要传入媒体文件描述符fdsrc、媒体文件大小size、媒体源在文件描述符中的偏移量offset。 +// 返回PixelMap对象。 +static napi_value OhAVMetadataExtractorFetchAlbumCover(napi_env env, napi_callback_info info) { + if (!CheckArgs(env, info, 3)) { + return nullptr; + } + size_t argc = 3; + napi_value argv[3]; + napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr); + int64_t offset = 0; + int32_t fileDescribe = -1; + int64_t fileSize = 0; + if (!GetInt32Value(env, argv[0], &fileDescribe)) return nullptr; + if (!GetInt64Value(env, argv[1], &fileSize)) return nullptr; + if (!GetInt64Value(env, argv[2], &offset)) return nullptr; + // 创建OH_AVMetadataExtractor实例。 + OH_AVMetadataExtractor* mainExtractor = OH_AVMetadataExtractor_Create(); + // 处理异常。 + if (!mainExtractor) { + napi_throw_error(env, "EFAILED", "Create metadata extractor failed"); + return nullptr; + } + // 设置视频资源的文件描述符。 + OH_AVErrCode avErrCode = OH_AVMetadataExtractor_SetFDSource(mainExtractor, fileDescribe, offset, fileSize); + // 处理异常。 + if (avErrCode != AV_ERR_OK) { + OH_AVMetadataExtractor_Release(mainExtractor); + napi_throw_error(env, "EFAILED", "SetFDSource for metadata extractor failed"); + return nullptr; + } + // 获取专辑封面。 + OH_PixelmapNative* pixelMap = nullptr; + avErrCode = OH_AVMetadataExtractor_FetchAlbumCover(mainExtractor, &pixelMap); + // 处理异常。 + if (avErrCode != AV_ERR_OK || !pixelMap) { + OH_AVMetadataExtractor_Release(mainExtractor); + napi_throw_error(env, "EFAILED", "Fetch album cover failed"); + return nullptr; + } + // 将nativePixelMap对象转换为PixelMapnapi对象。 + napi_value pixelmapNapi = nullptr; + Image_ErrorCode errCode = OH_PixelmapNative_ConvertPixelmapNativeToNapi(env, pixelMap, &pixelmapNapi); + // 释放OH_PixelmapNative资源。 + OH_PixelmapNative_Release(pixelMap); + // 释放OH_AVMetadataExtractor资源。 + OH_AVMetadataExtractor_Release(mainExtractor); + // 处理异常。 + if (errCode != IMAGE_SUCCESS) { + napi_throw_error(env, "EFAILED", "Convert PixelMap failed"); + return nullptr; + } + return pixelmapNapi; +} + +// 获取元数据。 +// 需要在index.d.ts文件内描述映射的OhAVMetadataExtractorFetchMetadata方法。 +// export const OhAVMetadataExtractorFetchMetadata: (fdsrc : number, size : number, offset : number) => media.AVMetadata; +// 需要传入媒体文件描述符fdsrc、媒体文件大小size、媒体源在文件描述符中的偏移量offset。 +// 返回AVMetadata对象。 +static napi_value OhAVMetadataExtractorFetchMetadata(napi_env env, napi_callback_info info) { + if (!CheckArgs(env, info, 3)) { + return nullptr; + } + size_t argc = 3; + napi_value argv[3]; + napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr); + int64_t offset = 0; + int32_t fileDescribe = -1; + int64_t fileSize = 0; + if (!GetInt32Value(env, argv[0], &fileDescribe)) return nullptr; + if (!GetInt64Value(env, argv[1], &fileSize)) return nullptr; + if (!GetInt64Value(env, argv[2], &offset)) return nullptr; + // 创建OH_AVMetadataExtractor实例。 + OH_AVMetadataExtractor* mainExtractor = OH_AVMetadataExtractor_Create(); + // 异常处理。 + if (!mainExtractor) { + napi_throw_error(env, "EFAILED", "Create metadata extractor failed"); + return nullptr; + } + // 设置视频资源的文件描述符。 + OH_AVErrCode avErrCode = OH_AVMetadataExtractor_SetFDSource(mainExtractor, fileDescribe, offset, fileSize); + // 异常处理。 + if (avErrCode != AV_ERR_OK) { + OH_AVMetadataExtractor_Release(mainExtractor); + napi_throw_error(env, "EFAILED", "SetFDSource for metadata extractor failed"); + return nullptr; + } + // 创建OH_AVFormat对象。 + OH_AVFormat* avMetadata = OH_AVFormat_Create(); + // 异常处理。 + if (!avMetadata) { + OH_AVMetadataExtractor_Release(mainExtractor); + napi_throw_error(env, "EFAILED", "Create AVFormat failed"); + return nullptr; + } + // 获取元数据。 + avErrCode = OH_AVMetadataExtractor_FetchMetadata(mainExtractor, avMetadata); + // 异常处理。 + if (avErrCode != AV_ERR_OK) { + OH_AVFormat_Destroy(avMetadata); + OH_AVMetadataExtractor_Release(mainExtractor); + napi_throw_error(env, "EFAILED", "Fetch metadata failed"); + return nullptr; + } + napi_value JsMetadata = nullptr; + napi_status status = napi_create_object(env, &JsMetadata); + // 异常处理。 + if (status != napi_ok) { + OH_AVFormat_Destroy(avMetadata); + OH_AVMetadataExtractor_Release(mainExtractor); + napi_throw_error(env, "EFAILED", "Create JavaScript object failed"); + return nullptr; + } + + const char* out = nullptr; + bool ret = false; + // 从OH_AVFormat对象中解析出各个元数据值,再保存到JS所需的AVMetadata中。 + ret = OH_AVFormat_GetStringValue(avMetadata, OH_AVMETADATA_EXTRACTOR_ALBUM, &out); + H_LOGI("OH_AVMETADATA_EXTRACTOR_ALBUM : %{public}s",out); + if (ret && out) { + SetPropertyString(env, JsMetadata, OH_AVMETADATA_EXTRACTOR_ALBUM,out); + } + + out = nullptr; + ret = OH_AVFormat_GetStringValue(avMetadata, OH_AVMETADATA_EXTRACTOR_ALBUM_ARTIST, &out); + H_LOGI("OH_AVMETADATA_EXTRACTOR_ALBUM_ARTIST : %{public}s",out); + if (ret && out) { + SetPropertyString(env, JsMetadata, OH_AVMETADATA_EXTRACTOR_ALBUM_ARTIST,out); + } + + out = nullptr; + ret = OH_AVFormat_GetStringValue(avMetadata, OH_AVMETADATA_EXTRACTOR_ARTIST, &out); + H_LOGI("OH_AVMETADATA_EXTRACTOR_ARTIST : %{public}s",out); + if (ret && out) { + SetPropertyString(env, JsMetadata, OH_AVMETADATA_EXTRACTOR_ARTIST,out); + } + + out = nullptr; + ret = OH_AVFormat_GetStringValue(avMetadata, OH_AVMETADATA_EXTRACTOR_AUTHOR, &out); + H_LOGI("OH_AVMETADATA_EXTRACTOR_AUTHOR : %{public}s",out); + if (ret && out) { + SetPropertyString(env, JsMetadata, OH_AVMETADATA_EXTRACTOR_AUTHOR,out); + } + + out = nullptr; + ret = OH_AVFormat_GetStringValue(avMetadata, OH_AVMETADATA_EXTRACTOR_DATE_TIME, &out); + H_LOGI("OH_AVMETADATA_EXTRACTOR_DATE_TIME : %{public}s",out); + if (ret && out) { + SetPropertyString(env, JsMetadata, OH_AVMETADATA_EXTRACTOR_DATE_TIME,out); + } + + out = nullptr; + ret = OH_AVFormat_GetStringValue(avMetadata, OH_AVMETADATA_EXTRACTOR_DATE_TIME_FORMAT, &out); + H_LOGI("OH_AVMETADATA_EXTRACTOR_DATE_TIME_FORMAT : %{public}s",out); + if (ret && out) { + SetPropertyString(env, JsMetadata, OH_AVMETADATA_EXTRACTOR_DATE_TIME_FORMAT,out); + } + + out = nullptr; + ret = OH_AVFormat_GetStringValue(avMetadata, OH_AVMETADATA_EXTRACTOR_COMPOSER, &out); + H_LOGI("OH_AVMETADATA_EXTRACTOR_COMPOSER : %{public}s",out); + if (ret && out) { + SetPropertyString(env, JsMetadata, OH_AVMETADATA_EXTRACTOR_COMPOSER,out); + } + + int64_t duration = 0; + ret = OH_AVFormat_GetLongValue(avMetadata, OH_AVMETADATA_EXTRACTOR_DURATION, &duration); + H_LOGI("OH_AVMETADATA_EXTRACTOR_DURATION : %{public}lld",duration); + if (ret) { + out = std::to_string(duration).c_str(); + SetPropertyString(env, JsMetadata, OH_AVMETADATA_EXTRACTOR_DURATION,out); + } + + out = nullptr; + ret = OH_AVFormat_GetStringValue(avMetadata, OH_AVMETADATA_EXTRACTOR_GENRE, &out); + H_LOGI("OH_AVMETADATA_EXTRACTOR_GENRE : %{public}s",out); + if (ret && out) { + SetPropertyString(env, JsMetadata, OH_AVMETADATA_EXTRACTOR_GENRE,out); + } + + int32_t hasAudio; + ret = OH_AVFormat_GetIntValue(avMetadata, OH_AVMETADATA_EXTRACTOR_HAS_AUDIO, &hasAudio); + H_LOGI("OH_AVMETADATA_EXTRACTOR_HAS_AUDIO : %{public}d",hasAudio); + if (ret) { + out = std::to_string(hasAudio).c_str(); + SetPropertyString(env, JsMetadata, OH_AVMETADATA_EXTRACTOR_HAS_AUDIO,out); + } + + int32_t hasVideo; + ret = OH_AVFormat_GetIntValue(avMetadata, OH_AVMETADATA_EXTRACTOR_HAS_VIDEO, &hasVideo); + H_LOGI("OH_AVMETADATA_EXTRACTOR_HAS_VIDEO : %{public}d",hasVideo); + if (ret) { + out = std::to_string(hasVideo).c_str(); + SetPropertyString(env, JsMetadata, OH_AVMETADATA_EXTRACTOR_HAS_VIDEO,out); + } + + out = nullptr; + ret = OH_AVFormat_GetStringValue(avMetadata, OH_AVMETADATA_EXTRACTOR_MIME_TYPE, &out); + H_LOGI("OH_AVMETADATA_EXTRACTOR_MIME_TYPE : %{public}s",out); + if (ret && out) { + SetPropertyString(env, JsMetadata, OH_AVMETADATA_EXTRACTOR_MIME_TYPE,out); + } + + int32_t trackCount; + ret = OH_AVFormat_GetIntValue(avMetadata, OH_AVMETADATA_EXTRACTOR_TRACK_COUNT, &trackCount); + H_LOGI("OH_AVMETADATA_EXTRACTOR_NUM_TRACKS : %{public}d",trackCount); + if (ret) { + out = std::to_string(trackCount).c_str(); + SetPropertyString(env, JsMetadata, OH_AVMETADATA_EXTRACTOR_TRACK_COUNT,out); + } + + int32_t sampleRate; + ret = OH_AVFormat_GetIntValue(avMetadata, OH_AVMETADATA_EXTRACTOR_SAMPLE_RATE, &sampleRate); + H_LOGI("OH_AVMETADATA_EXTRACTOR_SAMPLE_RATE : %{public}d",sampleRate); + if (ret) { + out = std::to_string(sampleRate).c_str(); + SetPropertyString(env, JsMetadata, OH_AVMETADATA_EXTRACTOR_SAMPLE_RATE,out); + } + + out = nullptr; + ret = OH_AVFormat_GetStringValue(avMetadata, OH_AVMETADATA_EXTRACTOR_TITLE, &out); + H_LOGI("OH_AVMETADATA_EXTRACTOR_TITLE : %{public}s",out); + if (ret && out) { + SetPropertyString(env, JsMetadata, OH_AVMETADATA_EXTRACTOR_TITLE,out); + } + + int32_t videoHeight; + ret = OH_AVFormat_GetIntValue(avMetadata, OH_AVMETADATA_EXTRACTOR_VIDEO_HEIGHT, &videoHeight); + H_LOGI("OH_AVMETADATA_EXTRACTOR_VIDEO_HEIGHT : %{public}d",videoHeight); + if (ret) { + out = std::to_string(videoHeight).c_str(); + SetPropertyString(env, JsMetadata, OH_AVMETADATA_EXTRACTOR_VIDEO_HEIGHT,out); + } + + int32_t videoWidth; + ret = OH_AVFormat_GetIntValue(avMetadata, OH_AVMETADATA_EXTRACTOR_VIDEO_WIDTH, &videoWidth); + H_LOGI("OH_AVMETADATA_EXTRACTOR_VIDEO_WIDTH : %{public}d",videoWidth); + if (ret) { + out = std::to_string(videoWidth).c_str(); + SetPropertyString(env, JsMetadata, OH_AVMETADATA_EXTRACTOR_VIDEO_WIDTH,out); + } + + int32_t videoOrientation; + ret = OH_AVFormat_GetIntValue(avMetadata, OH_AVMETADATA_EXTRACTOR_VIDEO_ORIENTATION, &videoOrientation); + H_LOGI("OH_AVMETADATA_EXTRACTOR_VIDEO_ORIENTATION : %{public}d",videoOrientation); + if (ret) { + out = std::to_string(videoOrientation).c_str(); + SetPropertyString(env, JsMetadata, OH_AVMETADATA_EXTRACTOR_VIDEO_ORIENTATION,out); + } + + int32_t hdrType; + ret = OH_AVFormat_GetIntValue(avMetadata, OH_AVMETADATA_EXTRACTOR_VIDEO_IS_HDR_VIVID, &hdrType); + H_LOGI("OH_AVMETADATA_EXTRACTOR_VIDEO_IS_HDR_VIVID : %{public}d ret %{public}d",hdrType, ret); + if (ret) { + bool hh = SetPropertyInt(env, JsMetadata, OH_AVMETADATA_EXTRACTOR_VIDEO_IS_HDR_VIVID,hdrType); + H_LOGI("OH_AVMETADATA_EXTRACTOR_VIDEO_IS_HDR_VIVID : %{public}d hh %{public}d",hdrType, hh); + } + + napi_value location = nullptr; + napi_create_object(env, &location); + float latitude; + bool retLatitude = OH_AVFormat_GetFloatValue(avMetadata, OH_AVMETADATA_EXTRACTOR_LOCATION_LATITUDE, &latitude); + H_LOGI("OH_AVMETADATA_EXTRACTOR_LOCATION_LATITUDE : %{public}f",latitude); + if (retLatitude) { + SetPropertyDouble(env, location, OH_AVMETADATA_EXTRACTOR_LOCATION_LATITUDE,latitude); + } + + float longitude; + bool retLongitude = OH_AVFormat_GetFloatValue(avMetadata, OH_AVMETADATA_EXTRACTOR_LOCATION_LONGITUDE, &longitude); + H_LOGI("OH_AVMETADATA_EXTRACTOR_LOCATION_LONGITUDE : %{public}f",longitude); + if (retLongitude) { + SetPropertyDouble(env, location, OH_AVMETADATA_EXTRACTOR_LOCATION_LONGITUDE,longitude); + } + + if (retLatitude || retLongitude) { + napi_value keyNapi = nullptr; + std::string key = "location"; + napi_create_string_utf8(env, key.c_str(), NAPI_AUTO_LENGTH, &keyNapi); + napi_set_property(env, JsMetadata, keyNapi, location); + } + // 释放OH_AVFormat资源。 + OH_AVFormat_Destroy(avMetadata); + // 释放OH_AVMetadataExtractor资源。 + OH_AVMetadataExtractor_Release(mainExtractor); + return JsMetadata; +} + +EXTERN_C_START +static napi_value Init(napi_env env, napi_value exports) +{ + napi_property_descriptor desc[] = { + {"OhAVMetadataExtractorFetchAlbumCover", nullptr, OhAVMetadataExtractorFetchAlbumCover, nullptr, nullptr, + nullptr, napi_default, nullptr}, + {"OhAVMetadataExtractorFetchMetadata", nullptr, OhAVMetadataExtractorFetchMetadata, nullptr, nullptr, + nullptr, napi_default, nullptr}, + }; + napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc); + return exports; +} +EXTERN_C_END + +static napi_module demoModule = { + .nm_version = 1, + .nm_flags = 0, + .nm_filename = nullptr, + .nm_register_func = Init, + .nm_modname = "entry", + .nm_priv = ((void *)0), + .reserved = {0}, +}; + +extern "C" __attribute__((constructor)) void RegisterEntryModule(void) +{ + napi_module_register(&demoModule); +} \ No newline at end of file diff --git a/code/DocsSample/Media/AVMetadataExtractor/AVMetadataExtractorNDK2/cpp/types/libentry/Index.d.ts b/code/DocsSample/Media/AVMetadataExtractor/AVMetadataExtractorNDK2/cpp/types/libentry/Index.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..672a130b946f20379ec6da89f2e6f834823d96c9 --- /dev/null +++ b/code/DocsSample/Media/AVMetadataExtractor/AVMetadataExtractorNDK2/cpp/types/libentry/Index.d.ts @@ -0,0 +1,5 @@ +import { image } from "@kit.ImageKit"; +import { media } from '@kit.MediaKit'; + +export const OhAVMetadataExtractorFetchAlbumCover: (a:number,b:number,c:number) => image.PixelMap; +export const OhAVMetadataExtractorFetchMetadata: (a:number,b:number,c:number) => media.AVMetadata; diff --git a/code/DocsSample/Media/AVMetadataExtractor/AVMetadataExtractorNDK2/cpp/types/libentry/oh-package.json5 b/code/DocsSample/Media/AVMetadataExtractor/AVMetadataExtractorNDK2/cpp/types/libentry/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..56322781032ce5be8c48efc7fe8e4bf5b46baaa9 --- /dev/null +++ b/code/DocsSample/Media/AVMetadataExtractor/AVMetadataExtractorNDK2/cpp/types/libentry/oh-package.json5 @@ -0,0 +1,6 @@ +{ + "name": "libentry.so", + "types": "./Index.d.ts", + "version": "1.0.0", + "description": "Please describe the basic information." +} \ No newline at end of file diff --git a/code/DocsSample/Media/AVMetadataExtractor/AVMetadataExtractorNDK2/oh-package.json5 b/code/DocsSample/Media/AVMetadataExtractor/AVMetadataExtractorNDK2/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..21c03437d918edb098d7704a86a1cd6983d3bb36 --- /dev/null +++ b/code/DocsSample/Media/AVMetadataExtractor/AVMetadataExtractorNDK2/oh-package.json5 @@ -0,0 +1,11 @@ +{ + "name": "entry", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "", + "author": "", + "license": "", + "dependencies": { + "libentry.so": "file:./src/main/cpp/types/libentry" + } +} diff --git a/code/DocsSample/Media/AVMetadataExtractor/AVMetadataExtractorNDK2/string.json b/code/DocsSample/Media/AVMetadataExtractor/AVMetadataExtractorNDK2/string.json new file mode 100644 index 0000000000000000000000000000000000000000..b086aa7a00cebfa1880d4d31a8399aeed72feade --- /dev/null +++ b/code/DocsSample/Media/AVMetadataExtractor/AVMetadataExtractorNDK2/string.json @@ -0,0 +1,116 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "label" + }, + { + "name": "FetchCover", + "value": "获取视频的封面" + }, + { + "name": "FetchCoverStart", + "value": "开始获取视频的封面" + }, + { + "name": "FetchMetaData", + "value": "获取视频的元数据" + }, + { + "name": "FetchMetaDataStart", + "value": "开始获取视频的元数据" + }, + { + "name": "album", + "value": "album" + }, + { + "name": "albumArtist", + "value": "albumArtist" + }, + { + "name": "artist", + "value": "artist" + }, + { + "name": "author", + "value": "author" + }, + { + "name": "dateTime", + "value": "dateTime" + }, + { + "name": "dateTimeFormat", + "value": "dateTimeFormat" + }, + { + "name": "composer", + "value": "composer" + }, + { + "name": "duration", + "value": "duration" + }, + { + "name": "genre", + "value": "genre" + }, + { + "name": "hasAudio", + "value": "hasAudio" + }, + { + "name": "hasVideo", + "value": "hasVideo" + }, + { + "name": "mimeType", + "value": "mimeType" + }, + { + "name": "trackCount", + "value": "trackCount" + }, + { + "name": "sampleRate", + "value": "sampleRate" + }, + { + "name": "title", + "value": "title" + }, + { + "name": "videoHeight", + "value": "videoHeight" + }, + { + "name": "videoWidth", + "value": "videoWidth" + }, + { + "name": "videoOrientation", + "value": "videoOrientation" + }, + { + "name": "hdrType", + "value": "hdrType" + }, + { + "name": "location", + "value": "location" + }, + { + "name": "noValue", + "value": "无" + } + ] +} \ No newline at end of file diff --git a/code/DocsSample/Media/AVTranscoder/AVTranscoderArkTS/AVTranscoderManager.ets b/code/DocsSample/Media/AVTranscoder/AVTranscoderArkTS/AVTranscoderManager.ets new file mode 100644 index 0000000000000000000000000000000000000000..1c06a3e8858588c1faa3da7856e11f310fa6f0d1 --- /dev/null +++ b/code/DocsSample/Media/AVTranscoder/AVTranscoderArkTS/AVTranscoderManager.ets @@ -0,0 +1,106 @@ +import { media } from '@kit.MediaKit'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { common } from '@kit.AbilityKit'; +import fs from '@ohos.file.fs'; + +export class AVTranscoderDemo { + private avTranscoder: media.AVTranscoder | undefined = undefined; + private context: Context | undefined; + constructor(context: Context | undefined) { + if (context != undefined) { + this.context = context; + } + } + private avConfig: media.AVTranscoderConfig = { + audioBitrate: 100000, // 音频比特率。 + audioCodec: media.CodecMimeType.AUDIO_AAC, // 音频编码格式。 + fileFormat: media.ContainerFormatType.CFT_MPEG_4, // 封装格式。 + videoBitrate: 200000, // 视频比特率。 + videoCodec: media.CodecMimeType.VIDEO_AVC, // 视频编码格式。 + }; + + // 注册avTranscoder回调函数。 + setAVTranscoderCallback() { + if (canIUse("SystemCapability.Multimedia.Media.AVTranscoder")) { + if (this.avTranscoder != undefined) { + // 转码完成回调函数。 + this.avTranscoder.on('complete', async () => { + console.log(`AVTranscoder is completed`); + await this.releaseTranscoderingProcess(); + }); + // 错误上报回调函数。 + this.avTranscoder.on('error', (err: BusinessError) => { + console.error(`AVTranscoder failed, code is ${err.code}, message is ${err.message}`); + }); + } + } + } + + // 开始转码对应的流程。 + async startTranscoderingProcess() { + if (canIUse("SystemCapability.Multimedia.Media.AVTranscoder")) { + if (this.avTranscoder != undefined) { + await this.avTranscoder.release(); + this.avTranscoder = undefined; + } + // 1.创建转码实例。 + this.avTranscoder = await media.createAVTranscoder(); + this.setAVTranscoderCallback(); + // 2.获取转码源文件fd和目标文件fd赋予avTranscoder;参考FilePicker文档。 + if (this.context != undefined) { + try { + // 获取输入文件fd,H264_AAC.mp4为rawfile目录下的预置资源,需要开发者根据实际情况进行替换。 + let fileDescriptor = await this.context.resourceManager.getRawFd('H264_AAC.mp4'); + this.avTranscoder.fdSrc = fileDescriptor; + } catch (error) { + console.error('Failed to get the file descriptor, please check the resource and path.'); + } + let outputFilePath = this.context.filesDir + "/output.mp4"; + let file = fs.openSync(outputFilePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE); + this.avTranscoder.fdDst = file.fd; + } + // 3.配置转码参数完成准备工作。 + await this.avTranscoder.prepare(this.avConfig); + // 4.开始转码。 + await this.avTranscoder.start(); + } + } + + // 暂停转码对应的流程。 + async pauseTranscoderingProcess() { + if (canIUse("SystemCapability.Multimedia.Media.AVTranscoder")) { + if (this.avTranscoder != undefined) { // 仅在调用start返回后调用pause为合理调用。 + await this.avTranscoder.pause(); + } + } + } + + // 恢复对应的转码流程。 + async resumeTranscoderingProcess() { + if (canIUse("SystemCapability.Multimedia.Media.AVTranscoder")) { + if (this.avTranscoder != undefined) { // 仅在调用pause返回后调用resume为合理调用。 + await this.avTranscoder.resume(); + } + } + } + + // 释放转码流程。 + async releaseTranscoderingProcess() { + if (canIUse("SystemCapability.Multimedia.Media.AVTranscoder")) { + if (this.avTranscoder != undefined) { + // 1.释放转码实例。 + await this.avTranscoder.release(); + this.avTranscoder = undefined; + // 2.关闭转码目标文件fd。 + fs.closeSync(this.avTranscoder!.fdDst); + } + } + } + + // 一个完整的【开始转码-暂停转码-恢复转码-转码完成】示例。 + async avTranscoderDemo() { + await this.startTranscoderingProcess(); // 开始转码。 + await this.pauseTranscoderingProcess(); //暂停转码。 + await this.resumeTranscoderingProcess(); // 恢复转码。 + } +} \ No newline at end of file diff --git a/code/DocsSample/Media/AVTranscoder/AVTranscoderArkTS/Ability.test.ets b/code/DocsSample/Media/AVTranscoder/AVTranscoderArkTS/Ability.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..7afd5b6bf3f1e256e97e4edab6d06ceae0f9f05a --- /dev/null +++ b/code/DocsSample/Media/AVTranscoder/AVTranscoderArkTS/Ability.test.ets @@ -0,0 +1,52 @@ +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; +import { Driver, ON } from '@ohos.UiTest'; +import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; + +export default function abilityTest() { + const TAG = '[Sample_AVTranscoder]'; + const driver = Driver.create(); + const abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator(); + const bundleName = AbilityDelegatorRegistry.getArguments().bundleName; + describe('ActsAbilityTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }) + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }) + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }) + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }) + it('AVTranscoderTest_001', 0, async (done: Function) => { + console.info(TAG, 'AVTranscoderTest_001 begin'); + try { + await abilityDelegator.startAbility({ + bundleName: bundleName, + abilityName: 'EntryAbility' + }); + } catch (exception) { + expect().assertFail(); + } + try { + const button = await driver.findComponent(ON.id('AVTranscoderButton')); + await button.click(); + await driver.delayMs(9000); + done(); + } catch (exception) { + expect().assertFail(); + } + console.info(TAG, 'StartAbility_001 end'); + }) + }) +} \ No newline at end of file diff --git a/code/DocsSample/Media/AVTranscoder/AVTranscoderArkTS/Index.ets b/code/DocsSample/Media/AVTranscoder/AVTranscoderArkTS/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..35a61568c40e18e60ebab2f06fba893cd479ce22 --- /dev/null +++ b/code/DocsSample/Media/AVTranscoder/AVTranscoderArkTS/Index.ets @@ -0,0 +1,27 @@ +import {AVTranscoderDemo} from '../transcoder/AVTranscoderManager' + +@Entry +@Component +struct Index { + private context:Context | undefined = this.getUIContext().getHostContext(); + private avTranscoder: AVTranscoderDemo = new AVTranscoderDemo(this.context); + + build() { + RelativeContainer() { + Button($r('app.string.StartTranscoder')) + .onClick(async () => { + console.info(`Button put`); + await this.avTranscoder.avTranscoderDemo(); + }) + .id('AVTranscoderButton') + .alignRules({ + center: { anchor: '__container__', align: VerticalAlign.Center }, + middle: { anchor: '__container__', align: HorizontalAlign.Center } + }) + } + .height('100%') + .width('100%') + } + + +} \ No newline at end of file diff --git a/code/DocsSample/Media/AVTranscoder/AVTranscoderArkTS/string.json b/code/DocsSample/Media/AVTranscoder/AVTranscoderArkTS/string.json new file mode 100644 index 0000000000000000000000000000000000000000..03a70850102d6ef94b1a5e748997d9a14ae3e925 --- /dev/null +++ b/code/DocsSample/Media/AVTranscoder/AVTranscoderArkTS/string.json @@ -0,0 +1,20 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "label" + }, + { + "name": "StartTranscoder", + "value": "启动转码" + } + ] +} \ No newline at end of file diff --git a/code/DocsSample/Media/AVTranscoder/AsyncTranscoder/Ability.test.ets b/code/DocsSample/Media/AVTranscoder/AsyncTranscoder/Ability.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..e46932a7dc0e444afb46a892a7056dd1a0d88380 --- /dev/null +++ b/code/DocsSample/Media/AVTranscoder/AsyncTranscoder/Ability.test.ets @@ -0,0 +1,52 @@ +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; +import { Driver, ON } from '@ohos.UiTest'; +import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; + +export default function abilityTest() { + const TAG = '[Sample_AsyncAVTranscoder]'; + const driver = Driver.create(); + const abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator(); + const bundleName = AbilityDelegatorRegistry.getArguments().bundleName; + describe('ActsAbilityTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }) + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }) + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }) + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }) + it('AsyncAVTranscoderTest_001', 0, async (done: Function) => { + console.info(TAG, 'AsyncAVTranscoderTest_001 begin'); + try { + await abilityDelegator.startAbility({ + bundleName: bundleName, + abilityName: 'EntryAbility' + }); + } catch (exception) { + expect().assertFail(); + } + try { + const button = await driver.findComponent(ON.id('AsyncAVTranscoderButton')); + await button.click(); + await driver.delayMs(9000); + done(); + } catch (exception) { + expect().assertFail(); + } + console.info(TAG, 'AsyncAVTranscoderTest_001 end'); + }) + }) +} \ No newline at end of file diff --git a/code/DocsSample/Media/AVTranscoder/AsyncTranscoder/Index.ets b/code/DocsSample/Media/AVTranscoder/AsyncTranscoder/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..fdb6d474940a22abf8e4c2620e8f4ae9a0a76b5a --- /dev/null +++ b/code/DocsSample/Media/AVTranscoder/AsyncTranscoder/Index.ets @@ -0,0 +1,76 @@ +import { ErrorEvent, MessageEvents, worker } from '@kit.ArkTS' +import { SendableObject } from '../util/SendableObject'; +import { common, sendableContextManager } from '@kit.AbilityKit'; + + +@Entry +@Component +struct Index { + private workerInstance?: worker.ThreadWorker; + private context: Context | undefined; + + + build() { + RelativeContainer() { + Button($r('app.string.StartTranscoder')) + .onClick(async () => { + console.info(`Button put`); + await this.startWorker(); + }) + .id('AsyncAVTranscoderButton') + .alignRules({ + center: { anchor: '__container__', align: VerticalAlign.Center }, + middle: { anchor: '__container__', align: HorizontalAlign.Center } + }) + } + .height('100%') + .width('100%') + } + + + async startWorker() { + // 创建Worker对象 + this.workerInstance = new worker.ThreadWorker('entry/ets/workers/task.ets'); + + + // 注册onmessage回调,当宿主线程接收到来自其创建的Worker通过workerPort.postMessage接口发送的消息时被调用, + // 在宿主线程执行 + this.workerInstance.onmessage = (e: MessageEvents) => { + let data: string = e.data; + console.info("workerInstance onmessage is: ", data); + if (data == 'complete') { + console.info("complete: ", data); + this.workerInstance?.terminate(); + } + } + + + // 注册onErrors回调,可以捕获Worker线程的onmessage回调、timer回调以及文件执行等流程产生的全局异常, + // 在宿主线程执行 + this.workerInstance.onerror = (err: ErrorEvent) => { + console.info("workerInstance onerror message is: " + err.message); + } + + + // 注册onmessageerror回调,当Worker对象接收到一条无法被序列化的消息时被调用,在宿主线程执行 + this.workerInstance.onmessageerror = () => { + console.info('workerInstance onmessageerror'); + } + + + // 注册onexit回调,当Worker销毁时被调用,在宿主线程执行 + this.workerInstance.onexit = (e: number) => { + // 当Worker正常退出时code为0,异常退出时code为1 + console.info("workerInstance onexit code is: ", e); + } + + + // 向Worker线程发送消息 + this.context = this.getUIContext().getHostContext(); + if (this.context != undefined) { + const sendableContext: sendableContextManager.SendableContext = sendableContextManager.convertFromContext(this.context); + const sendableObject: SendableObject = new SendableObject(sendableContext, "some information"); + this.workerInstance.postMessageWithSharedSendable(sendableObject); + } + } +} \ No newline at end of file diff --git a/code/DocsSample/Media/AVTranscoder/AsyncTranscoder/SendableObject.ets b/code/DocsSample/Media/AVTranscoder/AsyncTranscoder/SendableObject.ets new file mode 100644 index 0000000000000000000000000000000000000000..38955c88d8e024fbc134ee2e7e1422d9b98663d0 --- /dev/null +++ b/code/DocsSample/Media/AVTranscoder/AsyncTranscoder/SendableObject.ets @@ -0,0 +1,25 @@ +import { sendableContextManager } from '@kit.AbilityKit'; + + +//发送的参数必须加上@Sendable标注 +@Sendable +export class SendableObject { + constructor(sendableContext: sendableContextManager.SendableContext, data: string = '') { + this.sendableContext = sendableContext; + this.data = data; + } + + + private sendableContext: sendableContextManager.SendableContext; + private data: string; + + + public getSendableContext() { + return this.sendableContext; + } + + + public getData() { + return this.data; + } +} \ No newline at end of file diff --git a/code/DocsSample/Media/AVTranscoder/AsyncTranscoder/build-profile.json5 b/code/DocsSample/Media/AVTranscoder/AsyncTranscoder/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..941f8a0b63ca8f1d81e1a23797718c92b7f198ba --- /dev/null +++ b/code/DocsSample/Media/AVTranscoder/AsyncTranscoder/build-profile.json5 @@ -0,0 +1,33 @@ +{ + "apiType": "stageMode", + "buildOption": { + "sourceOption": { + "workers": [ + "./src/main/ets/workers/task.ets", + ] + } + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": false, + "files": [ + "./obfuscation-rules.txt" + ] + } + } + } + }, + ], + "targets": [ + { + "name": "default" + }, + { + "name": "ohosTest", + } + ] +} \ No newline at end of file diff --git a/code/DocsSample/Media/AVTranscoder/AsyncTranscoder/task.ets b/code/DocsSample/Media/AVTranscoder/AsyncTranscoder/task.ets new file mode 100644 index 0000000000000000000000000000000000000000..caca8f9554f2f0e9998c0eb38b5492946f4293d8 --- /dev/null +++ b/code/DocsSample/Media/AVTranscoder/AsyncTranscoder/task.ets @@ -0,0 +1,74 @@ +import { ErrorEvent, MessageEvents, ThreadWorkerGlobalScope, worker } from '@kit.ArkTS'; +import { media } from '@kit.MediaKit'; +import { BusinessError } from '@kit.BasicServicesKit'; +import fs from '@ohos.file.fs'; +import { SendableObject } from '../util/SendableObject'; +import { common, sendableContextManager } from '@kit.AbilityKit'; + +const workerPort: ThreadWorkerGlobalScope = worker.workerPort; + +workerPort.onmessage = async (event: MessageEvents) => { + console.info(`onmessage in`); + //worker线程接收参数 + const sendableObject: SendableObject = event.data; + const sendableContext: sendableContextManager.SendableContext = + sendableObject.getSendableContext() as sendableContextManager.SendableContext; + const context: common.Context = + sendableContextManager.convertToContext(sendableContext) as common.Context; + //执行转码逻辑 + await doSome(context); + // 向主线程发送消息 + workerPort.postMessage('start end'); +}; + +workerPort.onmessageerror = (event: MessageEvents) => { + console.info('workerPort onmessageerror'); +}; + +workerPort.onerror = (event: ErrorEvent) => { + console.info('workerPort onerror err is: ', event.message); +}; + +async function doSome(context: common.Context) { + console.info(`doSome in`); + try { + let transcoder = await media.createAVTranscoder(); + // 转码完成回调函数 + transcoder.on('complete', async () => { + console.info(`transcode complete`); + await transcoder?.release() + //向主线程发送转码结束的消息 + workerPort.postMessage('complete'); + }) + // 转码错误回调函数 + transcoder.on('error', async (err: BusinessError) => { + await transcoder?.release(); + }) + // 转码进度更新 + transcoder.on('progressUpdate', (progress: number) => { + }) + try { + // 获取输入文件fd,3.mkv为rawfile目录下的预置资源,需要开发者根据实际情况进行替换。 + let fileDescriptor = await context.resourceManager.getRawFd('H264_AAC.mp4'); + transcoder.fdSrc = fileDescriptor; + } catch (error) { + console.error('Failed to get the file descriptor, please check the resource and path.'); + } + let fdPath = context.filesDir + "/" + "VID_" + Date.parse(new Date().toString()) + ".mp4"; + let file = fs.openSync(fdPath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE); + let fd = file.fd; + console.info(`file fd ${fd}`); + transcoder.fdDst = file.fd; + + let config: media.AVTranscoderConfig = { + fileFormat: media.ContainerFormatType.CFT_MPEG_4, + audioCodec: media.CodecMimeType.AUDIO_AAC, + videoCodec: media.CodecMimeType.VIDEO_AVC, + videoBitrate: 200000, + } + await transcoder?.prepare(config); + await transcoder?.start(); + } catch (e) { + console.error(`transcode error: code = ` + e.code.toString() + `, message = ${JSON.stringify(e.message)}`); + } +} \ No newline at end of file diff --git a/code/DocsSample/Media/SoundPool/SoundPoolArkTS/Ability.test.ets b/code/DocsSample/Media/SoundPool/SoundPoolArkTS/Ability.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..6ddae64cd8df61b2c55be62b34b736f33bdb8f3b --- /dev/null +++ b/code/DocsSample/Media/SoundPool/SoundPoolArkTS/Ability.test.ets @@ -0,0 +1,68 @@ +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; +import { Driver, ON } from '@ohos.UiTest'; +import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; + +export default function abilityTest() { + const TAG = '[Sample_SoundPool]'; + const driver = Driver.create(); + const abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator(); + const bundleName = AbilityDelegatorRegistry.getArguments().bundleName; + + describe('ActsAbilityTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }) + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }) + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }) + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }) + it('assertContain', 0, () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + hilog.info(0x0000, 'testTag', '%{public}s', 'it begin'); + let a = 'abc'; + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b); + expect(a).assertEqual(a); + }) + + /** + * 打开应用 + */ + it('StartAbility_001', 0, async (done: Function) => { + console.info(TAG, 'StartAbility_001 begin'); + + try { + await abilityDelegator.startAbility({ + bundleName: bundleName, + abilityName: 'EntryAbility' + }); + } catch (exception) { + expect().assertFail(); + } + await driver.delayMs(1000); + // 通过 id 'ButtonId2' 找到对应的button + const button = await driver.findComponent(ON.id('playSoundPoolButton')); + await button.click(); + await driver.delayMs(1000); + await button.click(); + await driver.delayMs(3000); + done(); + console.info(TAG, 'StartAbility_001 end'); + }) + + }) +} \ No newline at end of file diff --git a/code/DocsSample/Media/SoundPool/SoundPoolArkTS/Index.ets b/code/DocsSample/Media/SoundPool/SoundPoolArkTS/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..a93bf498762a65b049eda3557bf06dfcde908999 --- /dev/null +++ b/code/DocsSample/Media/SoundPool/SoundPoolArkTS/Index.ets @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2023-2025 Huawei Device Co., Ltd. + * 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. + */ + +import { media } from '@kit.MediaKit'; +import { audio } from '@kit.AudioKit'; +import { systemDateTime } from '@kit.BasicServicesKit'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { fileIo as fs } from '@kit.CoreFileKit'; +import { common } from '@kit.AbilityKit'; +import resourceManager from '@ohos.resourceManager' + +@Entry +@Component +struct Index { + @State message: string = 'Hello World'; + private soundPool: media.SoundPool | undefined = undefined; + private soundId: number = 0; + private streamId: number = 0; + private uri: string = ""; + + async aboutToAppear(): Promise { + this.create(); + } + + async aboutToDisappear() { + this.release(); + } + + build() { + RelativeContainer() { + Row() { + Button($r('app.string.playSoundPool')) + .width(100) + .height(100) + .onClick(() => { + this.PlaySoundPool() + }) + .id('playSoundPoolButton') + }.alignRules({ + center: { anchor: '__container__', align: VerticalAlign.Center }, + middle: { anchor: '__container__', align: HorizontalAlign.Center } + }) + } + .height('100%') + .width('100%') + } + + async create() { + try { + // audioRenderInfo中的参数usage取值为STREAM_USAGE_UNKNOWN,STREAM_USAGE_MUSIC,STREAM_USAGE_MOVIE。 + // STREAM_USAGE_AUDIOBOOK时,SoundPool播放短音时为混音模式,不会打断其他音频播放。 + let audioRendererInfo: audio.AudioRendererInfo = { + usage: audio.StreamUsage.STREAM_USAGE_MUSIC, // 音频流使用类型:音乐。根据业务场景配置,参考StreamUsage。 + rendererFlags: 1 // 音频渲染器标志。 + } + //创建soundPool实例。 + this.soundPool = await media.createSoundPool(14,audioRendererInfo); + //注册监听。 + this.loadCallback(); + this.finishPlayCallback(); + this.setErrorCallback(); + + // 加载音频资源。 + let context = this.getUIContext().getHostContext();; + let fileDescriptor = await context!.resourceManager.getRawFd('rubberduck.ogg'); + this.soundId = await this.soundPool!.load(fileDescriptor.fd, fileDescriptor.offset, fileDescriptor.length); + console.info(`load soundPool soundId: ${this.soundId}`) + } catch (e) { + console.error('createSoundPool error: ' + e); + } + } + + async loadCallback() { + // 加载完成回调。 + this.soundPool!.on('loadComplete', (soundId_: number) => { + this.soundId = soundId_; + console.info('loadComplete soundId: ' + soundId_); + }) + } + + //设置播放完成监听。 + async finishPlayCallback() { + this.soundPool!.on('playFinished', () => { + console.info("receive play finished message"); + // 可进行下次播放。 + }) + } + //设置错误类型监听。 + async setErrorCallback() { + this.soundPool!.on('error', (error: BusinessError) => { + console.error('error happened,message is :' + error.code); + console.error('error happened,message is :' + error.message); + }) + } + + async PlaySoundPool() { + let playParameters: media.PlayParameters = { + loop: 0, // 循环0次。 + rate: 2, // 2倍速。 + leftVolume: 0.5, // range = 0.0-1.0 + rightVolume: 0.5, // range = 0.0-1.0 + priority: 0, // 最低优先级。 + }; + // 开始播放,这边play也可带播放播放的参数PlayParameters,请在音频资源加载完毕,即收到loadComplete回调之后再执行play操作。 + this.soundPool!.play(this.soundId, playParameters, (error, streamID: number) => { + if (error) { + console.error(`play sound Error: errCode is ${error.code}, errMessage is ${error.message}`) + } else { + this.streamId = streamID; + console.info('play success soundid:' + this.streamId); + } + }); + // 设置循环播放次数。 + await this.soundPool!.setLoop(this.streamId, 2); // 播放3次。 + // 设置对应流的优先级。 + await this.soundPool!.setPriority(this.streamId, 1); + // 设置音量。 + await this.soundPool!.setVolume(this.streamId, 0.5, 0.5); + } + + async release() { + // 终止指定流的播放。 + await this.soundPool!.stop(this.streamId); + // 卸载音频资源。 + await this.soundPool!.unload(this.soundId); + //关闭监听。 + this.setOffCallback(); + // 释放SoundPool。 + await this.soundPool!.release(); + } + + + + async setOffCallback() { + this.soundPool!.off('loadComplete'); + this.soundPool!.off('playFinished'); + this.soundPool!.off('error'); + } +} \ No newline at end of file diff --git a/code/DocsSample/Media/SoundPool/SoundPoolArkTS/string.json b/code/DocsSample/Media/SoundPool/SoundPoolArkTS/string.json new file mode 100644 index 0000000000000000000000000000000000000000..97b33141edb6a0a7b18422d69348aec065121c44 --- /dev/null +++ b/code/DocsSample/Media/SoundPool/SoundPoolArkTS/string.json @@ -0,0 +1,20 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "label" + }, + { + "name": "playSoundPool", + "value": "播放RawFile" + } + ] +} \ No newline at end of file