提交 e5828f15 编写于 作者: Z zengyawen

update docs

Signed-off-by: Nzengyawen <zengyawen1@huawei.com>
上级 d57dbc67
# 文件管理
- 媒体库管理
- [媒体库开发概述](medialibrary-overview.md)
- [媒体资源使用指导](medialibrary-resource-guidelines.md)
- [文件路径使用指导](medialibrary-filepath-guidelines.md)
- [相册资源使用指导](medialibrary-album-guidelines.md)
# 文件
- 文件访问框架
- [用户公共文件访问框架概述](file-access-framework-overview.md)
- [文件选择器使用指导](filepicker-guidelines.md)
\ No newline at end of file
- [文件管理概述](file-management-overview.md)
- 应用文件
- [应用文件概述](app-file-overview.md)
- [应用沙箱目录](app-sandbox-directory.md)
- 应用文件访问与管理
- [应用文件访问](app-file-access.md)
- [应用文件上传下载](app-file-upload-download.md)
- [应用及文件系统空间统计](app-fs-space-statistics.md)
- [向应用沙箱推送文件](send-file-to-app-sandbox.md)
- [应用文件分享](share-app-file.md)
- 用户文件
- [用户文件概述](user-file-overview.md)
- 选择与保存用户文件(FilePicker)
- [选择用户文件](select-user-file.md)
- [保存用户文件](save-user-file.md)
- [开发用户文件管理器(仅对系统应用开放)](dev-user-file-manager.md)
- [管理外置存储设备(仅对系统应用开放)](manage-external-storage.md)
- 分布式文件系统
- [分布式文件系统概述](distributed-fs-overview.md)
- [设置分布式文件数据等级](set-security-label.md)
- [跨设备文件访问](file-access-across-devices.md)
# 应用文件访问
应用需要对应用文件目录下的应用文件进行查看、创建、读写、删除、移动、复制、获取属性等访问操作,下文介绍具体方法。
## 接口说明
开发者通过基础文件操作接口([ohos.file.fs](../reference/apis/js-apis-file-fs.md))实现应用文件访问能力,主要功能如下表所示。
**表1** 基础文件操作接口功能
| 接口名 | 功能 | 接口类型 | 支持同步 | 支持异步 |
| -------- | -------- | -------- | -------- | -------- |
| access | 检查文件是否存在 | 方法 | √ | √ |
| close | 关闭文件 | 方法 | √ | √ |
| copyFile | 复制文件 | 方法 | √ | √ |
| createStream | 基于文件路径打开文件流 | 方法 | √ | √ |
| listFile | 列出文件夹下所有文件名 | 方法 | √ | √ |
| mkdir | 创建目录 | 方法 | √ | √ |
| moveFile | 移动文件 | 方法 | √ | √ |
| open | 打开文件 | 方法 | √ | √ |
| read | 从文件读取数据 | 方法 | √ | √ |
| rename | 重命名文件或文件夹 | 方法 | √ | √ |
| rmdir | 删除整个目录 | 方法 | √ | √ |
| stat | 获取文件详细属性信息 | 方法 | √ | √ |
| unlink | 删除单个文件 | 方法 | √ | √ |
| write | 将数据写入文件 | 方法 | √ | √ |
| Stream.close | 关闭文件流 | 方法 | √ | √ |
| Stream.flush | 刷新文件流 | 方法 | √ | √ |
| Stream.write | 将数据写入流文件 | 方法 | √ | √ |
| Stream.read | 从流文件读取数据 | 方法 | √ | √ |
| File.fd | 获取文件描述符 | 属性 | √ | × |
| OpenMode | 设置文件打开标签 | 属性 | √ | × |
| Filter | 设置文件过滤配置项 | 类型 | × | × |
## 开发示例
在对应用文件开始访问前,开发者需要[获取应用文件路径](../application-models/application-context-stage.md#获取应用开发路径)。以从UIAbilityContext获取HAP级别的文件路径为例进行说明,UIAbilityContext的获取方式请参见[获取UIAbility的上下文信息](../application-models/uiability-usage.md#获取uiability的上下文信息)
下面介绍几种常用操作示例。
### 新建并读写一个文件
以下示例代码演示了如何新建一个文件并对其读写。
```ts
// pages/xxx.ets
import fs from '@ohos.file.fs';
import common from '@ohos.app.ability.common';
function createFile() {
// 获取应用文件路径
let context = getContext(this) as common.UIAbilityContext;
let filesDir = context.filesDir;
// 新建并打开文件
let file = fs.openSync(filesDir + '/test.txt', fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
// 写入一段内容至文件
let writeLen = fs.writeSync(file.fd, "Try to write str.");
console.info("The length of str is: " + writeLen);
// 从文件读取一段内容
let buf = new ArrayBuffer(1024);
let readLen = fs.readSync(file.fd, buf, { offset: 0 });
console.info("the content of file: " + String.fromCharCode.apply(null, new Uint8Array(buf.slice(0, readLen))));
// 关闭文件
fs.closeSync(file);
}
```
### 读取文件内容并写入到另一个文件
以下示例代码演示了如何从一个文件读写内容到另一个文件。
```ts
// pages/xxx.ets
import fs from '@ohos.file.fs';
import common from '@ohos.app.ability.common';
function readWriteFile() {
// 获取应用文件路径
let context = getContext(this) as common.UIAbilityContext;
let filesDir = context.filesDir;
// 打开文件
let srcFile = fs.openSync(filesDir + '/test.txt', fs.OpenMode.READ_WRITE);
let destFile = fs.openSync(filesDir + '/destFile.txt', fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
// 读取源文件内容并写入至目的文件
let bufSize = 4096;
let readSize = 0;
let buf = new ArrayBuffer(bufSize);
let readLen = fs.readSync(srcFile.fd, buf, { offset: readSize });
while (readLen > 0) {
readSize += readLen;
fs.writeSync(destFile.fd, buf);
readLen = fs.readSync(srcFile.fd, buf, { offset: readSize });
}
// 关闭文件
fs.closeSync(srcFile);
fs.closeSync(destFile);
}
```
> **说明:**
>
> 使用读写接口时,需注意可选项参数offset的设置。对于已存在且读写过的文件,文件偏移指针默认在上次读写操作的终止位置。
### 以流的形式读写文件
以下示例代码演示了如何使用流接口进行文件读写:
```ts
// pages/xxx.ets
import fs from '@ohos.file.fs';
import common from '@ohos.app.ability.common';
async function readWriteFileWithStream() {
// 获取应用文件路径
let context = getContext(this) as common.UIAbilityContext;
let filesDir = context.filesDir;
// 打开文件流
let inputStream = fs.createStreamSync(filesDir + '/test.txt', 'r+');
let outputStream = fs.createStreamSync(filesDir + '/destFile.txt', "w+");
// 以流的形式读取源文件内容并写入目的文件
let bufSize = 4096;
let readSize = 0;
let buf = new ArrayBuffer(bufSize);
let readLen = await inputStream.read(buf, { offset: readSize });
readSize += readLen;
while (readLen > 0) {
await outputStream.write(buf);
readLen = await inputStream.read(buf, { offset: readSize });
readSize += readLen;
}
// 关闭文件流
inputStream.closeSync();
outputStream.closeSync();
}
```
> **说明:**
> 使用流接口时,需注意流的及时关闭。同时流的异步接口应严格遵循异步接口使用规范,避免同步、异步接口混用。流接口不支持并发读写。
### 查看文件列表
以下示例代码演示了如何查看文件列表:
```ts
// 查看文件列表
import fs from '@ohos.file.fs';
import common from '@ohos.app.ability.common';
// 获取应用文件路径
let context = getContext(this) as common.UIAbilityContext;
let filesDir = context.filesDir;
// 查看文件列表
let options = {
recursion: false,
listNum: 0,
filter: {
suffix: ['.png', '.jpg', '.txt'], // 匹配文件后缀名为'.png','.jpg','.txt'
displayName: ['test%'], // 匹配文件全名以'test'开头
fileSizeOver: 0, // 匹配文件大小大于等于0
lastModifiedAfter: new Date(0).getTime(), // 匹配文件最近修改时间在1970年1月1日之后
},
}
let files = fs.listFileSync(filesDir, options);
for (let i = 0; i < files.length; i++) {
console.info(`The name of file: ${files[i]}`);
}
```
# 应用文件概述
应用文件:文件所有者为应用,包括应用安装文件、应用资源文件、应用缓存文件等。
- 设备上应用所使用及存储的数据,以文件、键值对、数据库等形式保存在一个应用专属的目录内。该专属目录我们称为“应用文件目录”,该目录下所有数据以不同的文件格式存放,这些文件即应用文件。
- “应用文件目录”与一部分系统文件(应用运行必须使用的系统文件)所在的目录组成了一个集合,该集合称为“[应用沙箱目录](app-sandbox-directory.md)”,代表应用可见的所有目录范围。因此“应用文件目录”是在“应用沙箱目录”内的。
- 系统文件及其目录对于应用是只读的;应用仅能保存文件到“[应用文件目录](app-sandbox-directory.md#应用文件目录与应用文件路径)”下,根据目录的使用规范和注意事项来选择将数据保存到不同的子目录中。
下文将详细介绍应用沙箱、应用文件目录、应用文件访问与管理、应用文件分享等相关内容。
# 应用文件上传下载
应用可以将应用文件上传到网络服务器,也可以从网络服务器下载网络资源文件到本地应用文件目录。
## 上传应用文件
开发者可以使用上传下载模块([ohos.request](../reference/apis/js-apis-request.md))的上传接口将本地文件上传。文件上传过程使用系统服务代理完成。
> **说明:**
> 当前上传应用文件功能,仅支持上传应用缓存文件路径(cacheDir)下的文件。
>
> 使用上传下载模块,需[申请相关权限](../security/accesstoken-guidelines.md):ohos.permission.INTERNET。
以下示例代码演示了如何将应用缓存文件路径下的文件上传至网络服务器。
```ts
// pages/xxx.ets
import common from '@ohos.app.ability.common';
import fs from '@ohos.file.fs';
import request from '@ohos.request';
// 获取应用文件路径
let context = getContext(this) as common.UIAbilityContext;
let cacheDir = context.cacheDir;
// 新建一个本地应用文件
let file = fs.openSync(cacheDir + '/test.txt', fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
fs.writeSync(file.fd, 'upload file test');
fs.closeSync(file);
// 上传任务配置项
let uploadConfig = {
url: 'https://xxx',
header: { key1: 'value1', key2: 'value2' },
method: 'POST',
files: [
{ filename: 'test.txt', name: 'test', uri: 'internal://cache/test.txt', type: 'txt' }
],
data: [
{ name: 'name', value: 'value' }
]
}
// 将本地应用文件上传至网络服务器
try {
request.uploadFile(context, uploadConfig)
.then((uploadTask) => {
uploadTask.on('complete', (taskStates) => {
for (let i = 0; i < taskStates.length; i++) {
console.info(`upload complete taskState: ${JSON.stringify(taskStates[i])}');
}
});
})
.catch((err) => {
console.error(`Invoke uploadFile failed, code is ${err.code}, message is ${err.message}`);
})
} catch (err) {
console.error(`Invoke uploadFile failed, code is ${err.code}, message is ${err.message}`);
}
```
## 下载网络资源文件至应用文件目录
开发者可以使用上传下载模块([ohos.request](../reference/apis/js-apis-request.md))的下载接口将网络资源文件下载到应用文件目录。对已下载的网络资源文件,开发者可以使用基础文件IO接口([ohos.file.fs](../reference/apis/js-apis-file-fs.md))对其进行访问,使用方式与[应用文件访问](app-file-access.md)一致。文件下载过程使用系统服务代理完成。
> **说明:**
> 当前网络资源文件仅支持下载至应用文件目录。
>
> 使用上传下载模块,需[申请相关权限](../security/accesstoken-guidelines.md):ohos.permission.INTERNET。
以下示例代码演示了如何将网络资源文件下载到应用文件目录:
```
// pages/xxx.ets
// 将网络资源文件下载到应用文件目录并读取一段内容
import common from '@ohos.app.ability.common';
import fs from '@ohos.file.fs';
import request from '@ohos.request';
// 获取应用文件路径
let context = getContext(this) as common.UIAbilityContext;
let filesDir = context.filesDir;
try {
request.downloadFile(context, {
url: 'https://xxxx/xxxx.txt',
filePath: filesDir + '/xxxx.txt'
}).then((downloadTask) => {
downloadTask.on('complete', () => {
console.info('download complete');
let file = fs.openSync(filesDir + '/xxxx.txt', fs.OpenMode.READ_WRITE);
let buf = new ArrayBuffer(1024);
let readLen = fs.readSync(file.fd, buf);
console.info(`The content of file: ${String.fromCharCode.apply(null, new Uint8Array(buf.slice(0, readLen)))}`);
fs.closeSync(file);
})
}).catch((err) => {
console.error(`Invoke downloadTask failed, code is ${err.code}, message is ${err.message}`);
});
} catch (err) {
console.error(`Invoke downloadFile failed, code is ${err.code}, message is ${err.message}`);
}
```
# 应用及文件系统空间统计
在系统中,可能出现系统空间不够或者cacheDir等目录受系统配额限制等情况,需要应用开发者关注系统剩余空间,同时控制应用自身占用的空间大小。
## 接口说明
API的详细介绍请参见[ohos.file.statvfs](../reference/apis/js-apis-file-statvfs.md)[ohos.file.storageStatistics](../reference/apis/js-apis-storage-statistics.md)
**表1** 文件系统空间和应用空间统计
| 模块 | 接口名 | 功能 |
| -------- | -------- | -------- |
| \@ohos.file.storageStatistic | getCurrentBundleStats | 获取当前应用的存储空间大小(单位为Byte)。 |
| \@ohos.file.statvfs | getFreeSize | 获取指定文件系统的剩余空间大小(单位为Byte)。 |
| \@ohos.file.statvfs | getTotalSize | 获取指定文件系统的总空间大小(单位为Byte)。 |
**表2** 应用空间统计
| BundleStats属性 | 含义 | 统计路径 |
| -------- | -------- | -------- |
| appSize | 应用安装文件大小(单位为Byte) | 应用安装文件保存在以下目录:<br/>/data/storage/el1/bundle |
| cacheSize | 应用缓存文件大小(单位为Byte) | 应用的缓存文件保存在以下目录:<br/>/data/storage/el1/base/cache<br/>/data/storage/el1/base/haps/entry/cache<br/>/data/storage/el2/base/cache<br/>/data/storage/el2/base/haps/entry/cache |
| dataSize | 应用文件存储大小(除应用安装文件和缓存文件)(单位为Byte) | 应用文件由本地文件、分布式文件以及数据库文件组成。<br/>本地文件保存在以下目录(注意缓存文件目录为以下目录的子目录):<br/>/data/storage/el1/base<br/>/data/storage/el2/base<br/>分布式文件保存在以下目录:<br/>/data/storage/el2/distributedfiles<br/>数据库文件保存在以下目录:<br/>/data/storage/el1/database<br/>/data/storage/el2/database |
## 开发示例
- 获取文件系统数据分区剩余空间大小。
```ts
import statvfs from '@ohos.file.statvfs';
let path = "/data";
statvfs.getFreeSize(path, (err, number) => {
if (err) {
console.error(`Invoke getFreeSize failed, code is ${err.code}, message is ${err.message}`);
} else {
console.info(`Invoke getFreeSize succeeded, size is ${number}`);
}
});
```
- 获取当前应用的存储空间大小。
```ts
import storageStatistics from "@ohos.file.storageStatistics";
storageStatistics.getCurrentBundleStats((err, bundleStats) => {
if (err) {
console.error(`Invoke getCurrentBundleStats failed, code is ${err.code}, message is ${err.message}`);
} else {
console.info(`Invoke getCurrentBundleStats succeeded, appsize is ${bundleStats.appSize}`);
}
});
```
# 应用沙箱目录
应用沙箱是一种以安全防护为目的的隔离机制,避免数据受到恶意路径穿越访问。在这种沙箱的保护机制下,应用可见的目录范围即为“应用沙箱目录”。
- 对于每个应用,系统会在内部存储空间映射出一个专属的“应用沙箱目录”,它是“[应用文件目录](app-file-overview.md)”与一部分系统文件(应用运行必需的少量系统文件)所在的目录组成的集合。
- 应用沙箱限制了应用可见的数据的最小范围。在“应用沙箱目录”中,应用仅能看到自己的应用文件以及少量的系统文件(应用运行必需的少量系统文件)。因此,本应用的文件也不为其他应用可见,从而保护了应用文件的安全。
- 应用可以在“[应用文件目录](app-file-overview.md)”下保存和处理自己的应用文件;系统文件及其目录对于应用是只读的;而应用若需访问[用户文件](user-file-overview.md),则需要通过特定API同时经过用户的相应授权才能进行。
下图展示了应用沙箱下,应用可访问的文件范围和方式。
**图1** 应用沙箱文件访问关系图  
![Application sandbox file access relationship](figures/application-sandbox-file-access-relationship.png)
## 应用沙箱目录与应用沙箱路径
在应用沙箱保护机制下,应用无法获知除自身应用文件目录之外的其他应用或用户的数据目录位置及存在。同时,所有应用的目录可见范围均经过权限隔离与文件路径挂载隔离,形成了独立的路径视图,屏蔽了实际物理路径:
- 如下图所示,在普通应用(也称三方应用)视角下,不仅可见的目录与文件数量限制到了最小范围,并且可见的目录与文件路径也与系统进程等其他进程看到的不同。我们将普通应用视角下看到的“应用沙箱目录”下某个文件或某个具体目录的路径,称为“应用沙箱路径”。
- 一般情况下,开发者的hdc shell环境等效于系统进程视角,因此“应用沙箱路径”与开发者使用hdc工具调试时看到的真实物理路径不同,其对应关系详见[应用沙箱路径和调试进程视角下的真实物理路径](send-file-to-app-sandbox.md#应用沙箱路径和调试进程视角下的真实物理路径)
- 从实际物理路径推导物理路径与沙箱路径并不是1:1的映射关系,沙箱路径总是少于系统进程视角可见的物理路径。有些调试进程视角下的物理路径在对应的应用沙箱目录是无法找到的,而沙箱路径总是能够找到其对应的物理路径。
**图2** 应用沙箱路径(不同权限与角色的进程下可见的文件路径不同)  
![Application sandbox path](figures/application-sandbox-path.png)
## 应用文件目录与应用文件路径
如前文所述,“应用沙箱目录”内分为两类:应用文件目录和系统文件目录。
系统文件目录对应用的可见范围由OpenHarmony系统预置,开发者无需关注。
在此主要介绍应用文件目录,如下图所示。应用文件目录下某个文件或某个具体目录的路径称为应用文件路径。应用文件目录下的各个文件路径,具备不同的属性和特征。
**图3** 应用文件目录结构图  
![Application file directory structure](figures/application-file-directory-structure.png)
1. 一级目录data/:代表应用文件目录。
2. 二级目录storage/:代表本应用持久化文件目录。
3. 三级目录el1/、el2/:代表不同文件加密类型。
- el1,设备级加密区:设备开机后即可访问的数据区。
- el2,用户级加密区:设备开机后,需要至少一次解锁对应用户的锁屏界面(密码、指纹、人脸等方式或无密码状态)后,才能够访问的加密数据区。<br>
应用如无特殊需要,应将数据存放在el2加密目录下,以尽可能保证数据安全。但是对于某些场景,一些应用文件需要在用户解锁前就可被访问,例如时钟、闹铃、壁纸等,此时应用需要将这些文件存放到设备级加密区(el1)。切换应用文件加密类型目录的方法请参见[获取和修改加密分区](../application-models/application-context-stage.md#获取和修改加密分区)
4. 四级、五级目录:
通过ApplicationContext可以获取base下的files、cache、preferences、temp、distributedfiles等目录的应用文件路径,应用全局信息可以存放在这些目录下。
通过UIAbilityContext、AbilityStageContext、ExtensionContext可以获取hap级别应用文件路径。HAP信息可以存放在这些目录下,存放在此目录的文件会跟随HAP的卸载而删除,不会影响app级别目录下的文件。在开发态,一个应用包含一个或者多个HAP,详见[Stage模型应用程序包结构](../quick-start/application-package-structure-stage.md)
Context上下文获取及上述应用文件路径的获取,详见[应用上下文Context](../application-models/application-context-stage.md)
> **说明:**
> - 禁止直接使用上图中四级目录之前的目录名组成的路径字符串,否则可能导致后续应用版本因应用文件路径变化导致不兼容问题。
>
> - 应通过Context属性获取应用文件路径,包括但不限于上图中绿色背景的路径。
应用文件路径具体说明及生命周期如下表所示。
**表1** 应用文件路径详细说明
| 目录名 | Context属性名称 | 类型 | 说明 |
| -------- | -------- | -------- | -------- |
| bundle | bundleCodeDir | 安装文件路径 | 应用安装后的app的hap资源包所在目录;随应用卸载而清理。 |
| base | NA | 本设备文件路径 | 应用在本设备上存放持久化数据的目录,子目录包含files/、cache/、temp/和haps/;随应用卸载而清理。 |
| database | databaseDir | 数据库路径 | 应用在el1加密条件下存放通过分布式数据库服务操作的文件目录;随应用卸载而清理。 |
| distributedfiles | distributedFilesDir | 分布式文件路径 | 应用在el2加密条件下存放分布式文件的目录,应用将文件放入该目录可分布式跨设备直接访问;随应用卸载而清理。 |
| files | filesDir | 应用通用文件路径 | 应用在本设备内部存储上通用的存放默认长期保存的文件路径;随应用卸载而清理。 |
| cache | cacheDir | 应用缓存文件路径 | 应用在本设备内部存储上用于缓存下载的文件或可重新生成的缓存文件的路径,应用cache目录大小超过配额或者系统空间达到一定条件,自动触发清理该目录下文件;用户通过系统空间管理类应用也可能触发清理该目录。应用需判断文件是否仍存在,决策是否需重新缓存该文件。 |
| preferences | preferencesDir | 应用首选项文件路径 | 应用在本设备内部存储上通过数据库API存储配置类或首选项的目录;随应用卸载而清理。详见[通过用户首选项实现数据持久化](../database/data-persistence-by-preferences.md)。 |
| temp | tempDir | 应用临时文件路径 | 应用在本设备内部存储上仅在应用运行期间产生和需要的文件,应用退出后即清理。 |
对于上述各类应用文件路径,常见使用场景如下:
- 安装文件路径
可以用于存储应用的代码资源数据,主要包括应用安装的HAP资源包、可重复使用的库文件以及插件资源等。此路径下存储的代码资源数据可以被用于动态加载。
- 数据库路径
仅用于保存应用的私有数据库数据,主要包括数据库文件等。此路径下仅适用于存储分布式数据库相关文件数据。
- 分布式文件路径
可以用于保存应用分布式场景下的数据,主要包括应用多设备共享文件、应用多设备备份文件、应用多设备群组协助文件。此路径下存储这些数据,使得应用更加适合多设备使用场景。
- 应用通用文件路径
可以用于保存应用的任何私有数据,主要包括用户持久性文件、图片、媒体文件以及日志文件等。此路径下存储这些数据,使得数据保持私有、安全且持久有效。
- 应用缓存文件路径
可以用于保存应用的缓存数据,主要包括离线数据、图片缓存、数据库备份以及临时文件等。此路径下存储的数据可能会被系统自动清理,因此不要存储重要数据。
- 应用首选项文件路径
可以用于保存应用的首选项数据,主要包括应用首选项文件以及配置文件等。此路径下仅适用于存储小量数据。
- 应用临时文件路径
可以用于保存应用的临时生成的数据,主要包括数据库缓存、图片缓存、临时日志文件、以及下载的应用安装包文件等。此路径下存储使用后即可删除的数据。
# 开发用户文件管理器(仅对系统应用开放)
OpenHarmony预置了FileManager文件管理器。系统应用开发者也可以根据需要,按以下指导自行开发文件管理器。
## 接口说明
开发用户文件管理器的相关API详细介绍请参见[API参考](../reference/apis/js-apis-fileAccess.md)
## 开发步骤
1. 权限配置和导入模块。
申请ohos.permission.FILE_ACCESS_MANAGER和ohos.permission.GET_BUNDLE_INFO_PRIVILEGED权限,配置方式请参见[访问控制授权申请](../security/accesstoken-guidelines.md)
> **说明:**
>
> ohos.permission.FILE_ACCESS_MANAGER是使用文件访问框架接口的基础权限。
>
> ohos.permission.GET_BUNDLE_INFO_PRIVILEGED权限可以用于查询系统内当前支持的文件管理服务端应用信息。
2. 导入依赖模块。
```ts
import fileAccess from '@ohos.file.fileAccess';
import fileExtensionInfo from '@ohos.file.fileExtensionInfo';
```
其中fileAccess提供了文件基础操作的API,fileExtensionInfo提供了应用开发的关键结构体。
3. 查询设备列表。
开发者可以获取当前系统所有文件管理服务端管理的设备属性,也可以获取某个文件管理服务端管理的设备属性。应用开发者可以按需过滤设备。
在文件访问框架中,使用RootInfo用于表示设备的属性信息。以下示例可以获取所有设备的RootInfo。
```ts
// 创建连接系统内所有文件管理服务端的helper对象
let fileAccessHelperAllServer = null;
createFileAccessHelper() {
try { // this.context是EntryAbility传过来的Context
fileAccessHelperAllServer = fileAccess.createFileAccessHelper(this.context);
if (!fileAccessHelperAllServer) {
console.error("createFileAccessHelper interface returns an undefined object");
}
} catch (error) {
console.error("createFileAccessHelper failed, errCode:" + error.code + ", errMessage:" + error.message);
}
}
async getRoots() {
let rootIterator = null;
let rootInfos = [];
let isDone = false;
try {
rootIterator = await fileAccessHelperAllServer.getRoots();
if (!rootIterator) {
console.error("getRoots interface returns an undefined object");
return;
}
while (!isDone) {
let result = rootIterator.next();
console.info("next result = " + JSON.stringify(result));
isDone = result.done;
if (!isDone)
rootinfos.push(result.value);
}
} catch (error) {
console.error("getRoots failed, errCode:" + error.code + ", errMessage:" + error.message);
}
}
```
4. 浏览目录。
在文件访问框架中,使用FileInfo表示一个文件(目录)的基础信息。开发者可以使用listfile接口遍历下一级所有文件(目录)的迭代器对象;也可以通过scanfile过滤指定目录,获取满足条件的迭代器对象。
listfile和scanfile接口当前支持RootInfo对象调用,可用于支撑遍历下一级文件或过滤整个目录树。同时,接口也支持FileInfo对象调用,用于支撑遍历下一级文件或过滤指定目录。
```ts
// 从根目录开始
let rootInfo = rootinfos[0];
let fileInfos = [];
let isDone = false;
let filter = {suffix : [".txt", ".jpg", ".xlsx"]}; // 设定过滤条件
try {
let fileIterator = rootInfo.listFile(); // 遍历设备rootinfos[0]的根目录,返回迭代器对象
// let fileIterator = rootInfo.scanFile(filter); // 过滤设备rootinfos[0]满足指定条件的文件信息,返回迭代对象
if (!fileIterator) {
console.error("listFile interface returns an undefined object");
return;
}
while (!isDone) {
let result = fileIterator.next();
console.info("next result = " + JSON.stringify(result));
isDone = result.done;
if (!isDone)
fileInfos.push(result.value);
}
} catch (error) {
console.error("listFile failed, errCode:" + error.code + ", errMessage:" + error.message);
}
// 从指定的目录开始
let fileInfoDir = fileInfos[0]; // fileInfoDir 表示某个目录信息
let subFileInfos = [];
let isDone = false;
let filter = {suffix : [".txt", ".jpg", ".xlsx"]}; // 设定过滤条件
try {
let fileIterator = fileInfoDir.listFile(); // 遍历特定的目录fileinfo,返回迭代器对象
// let fileIterator = rootInfo.scanFile(filter); // 过滤特定的目录fileinfo,返回迭代器对象
if (!fileIterator) {
console.error("listFile interface returns an undefined object");
return;
}
while (!isDone) {
let result = fileIterator.next();
console.info("next result = " + JSON.stringify(result));
isDone = result.done;
if (!isDone)
subfileInfos.push(result.value);
}
} catch (error) {
console.error("listFile failed, errCode:" + error.code + ", errMessage:" + error.message);
}
```
5. 操作文件或目录。
开发者可以集成文件访问框架的接口,完成一些用户行为,比如删除文件(目录)、重命名文件(目录)、新建文件(目录)、移动文件(目录)等。以下示例展示了如何创建一个文件,其他接口请参见[API参考](../reference/apis/js-apis-fileAccess.md)
```ts
// 以本地设备为例
// 创建文件
// 示例代码sourceUri是Download目录的fileinfo中的URI
// 开发者应根据自己实际获取fileinfo的URI进行开发
let sourceUri = "datashare:///media/file/6";
let displayName = "file1";
let fileUri = null;
try {
// fileAccessHelper 参考 fileAccess.createFileAccessHelper 示例代码获取
fileUri = await fileAccessHelper.createFile(sourceUri, displayName);
if (!fileUri) {
console.error("createFile return undefined object");
return;
}
console.info("createFile sucess, fileUri: " + JSON.stringify(fileUri));
} catch (error) {
console.error("createFile failed, errCode:" + error.code + ", errMessage:" + error.message);
};
```
# 分布式文件系统概述
分布式文件系统(hmdfs,Harmony Distributed File System)提供跨设备的文件访问能力,适用于如下场景:
- 两台设备组网,用户可以利用一台设备上的编辑软件编辑另外一台设备上的文档。
- 平板保存的音乐,车载系统直接可见并可播放。
- 户外拍摄的照片,回家打开平板直接访问原设备拍摄的照片。
hmdfs在分布式软总线动态组网的基础上,为网络上各个设备结点提供一个全局一致的访问视图,支持开发者通过基础文件系统接口进行读写访问,具有高性能、低延时等优点。
## 分布式文件系统架构
![Distributed File System Architecture](figures/distributed-file-system-architecture.png)
- distributedfile_daemon:主要负责设备上线监听、通过软总线建立链路,并根据分布式的设备安全等级执行不同的数据流转策略。
- hmdfs:实现在内核的网络文件系统,包括缓存管理、文件访问、元数据管理和冲突管理等。
- 缓存管理
- 设备分布式组网后,hmdfs提供文件的互访能力,但不会主动进行文件数据传输和拷贝。如果应用需要将数据保存到本地,需主动拷贝。
- hmdfs保证Close-to-Open的一致性,即一端写关闭后,另外一端可以读取到最新数据,不保证文件内容的实时一致性。
- 数据在远端写入,但是由于网络原因未及时回刷,文件系统会在下次网络接入时回刷本地,但是如果远端已修改则无法回刷。
- 文件访问
- 文件访问接口与本地一致([ohos.file.fs](../reference/apis/js-apis-file-fs.md))。
- 如果文件在本地,则堆叠访问本地文件系统。
- 如果文件在其他设备,则同步网络访问远端设备文件。
> **说明:**
>
> symlink:不支持。
- 元数据管理
- 分布式组网下,文件一端创建、删除、修改,另一端可以“立即”查看到最新文件,看到速度取决于网络情况。
- 远端设备离线后,该设备数据将不再在本端设备呈现。但由于设备离线的感知具有延迟,可能会造成部分消息4s超时,因此开发者需要考虑接口的网络超时或一些文件虽然可以看到,但实际设备可能已离线的场景。
- 冲突处理
- 本地与远端冲突 ,远端文件被重命名,看到的同名文件是本地同名文件,远端文件被重命名。
- 远端多个设备冲突,以接入本设备ID为顺序,显示设备ID小的同名文件,其他文件被依次重命名。
- 如果组网场景,目录树下已经有远端文件,创建同名文件,提示文件已存在。
- 冲突文件显示_conflict_dev后依次加id,id从1自动递增。
- 同名目录之间仅融合不存在冲突,文件和远端目录同名冲突,远端目录后缀加_remote_directory。
# 跨设备文件访问
分布式文件系统为应用提供了跨设备文件访问的能力,开发者在多个设备安装同一应用时,通过[基础文件接口](app-file-access-mgmt.md),可跨设备读写其他设备该应用分布式文件路径(/data/storage/el2/distributedfiles/)下的文件。例如:多设备数据流转的场景,设备组网互联之后,设备A上的应用可访问设备B同应用分布式路径下的文件,当期望应用文件被其他设备访问时,只需将文件移动到分布式文件路径即可。
## 开发步骤
1. 完成分布式组网。
首先将需要进行跨设备访问的设备连接到同一局域网中,同帐号认证完成组网。
2. 访问跨设备文件。
同一应用不同设备之间实现跨设备文件访问,只需要将对应的文件放在应用沙箱的分布式文件路径即可。
设备A上在分布式路径下创建测试文件,并写入内容。示例中的context的获取方式请参见[获取UIAbility的上下文信息](../application-models/uiability-usage.md#获取uiability的上下文信息)
```ts
import fs from '@ohos.file.fs';
let context = ...; // 获取设备A的UIAbilityContext信息
let pathDir = context.distributedFilesDir;
// 获取分布式目录的文件路径
let filePath = pathDir + '/test.txt';
try {
// 在分布式目录下创建文件
let file = fs.openSync(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
console.info('Succeeded in createing.');
// 向文件中写入内容
fs.writeSync(file.fd, 'content');
// 关闭文件
fs.closeSync(file.fd);
} catch (err) {
console.error(`Failed to openSync / writeSync / closeSync. Code: ${err.code}, message: ${err.message}`);
}
```
设备B上在分布式路径下读取测试文件。
```ts
import fs from '@ohos.file.fs';
let context = ...; // 获取设备B的UIAbilityContext信息
let pathDir = context.distributedFilesDir;
// 获取分布式目录的文件路径
let filePath = pathDir + '/test.txt';
try {
// 打开分布式目录下的文件
let file = fs.openSync(filePath, fs.OpenMode.READ_WRITE);
// 定义接收读取数据的缓存
let buffer = new ArrayBuffer(4096);
// 读取文件的内容,返回值是读取到的字节个数
let num = fs.readSync(file.fd, buffer, {
offset: 0
});
// 打印读取到的文件数据
console.info('read result: ' + String.fromCharCode.apply(null, new Uint8Array(buffer.slice(0, num))));
} catch (err) {
console.error(`Failed to openSync / readSync. Code: ${err.code}, message: ${err.message}`);
}
```
# 用户公共文件访问框架概述
在搭载OpenHarmony 3.2 (API 9)及更高版本的设备上,应用可以基于FileAccessFramework(简称为FAF)对本地公共文件、分布式设备文件、外部存储设备文件、多用户共享文件进行访问。
出于对用户数据的隐私安全考虑,目前此框架仅支持用户通过**文件管理器****文件选择器**对文件访问服务端进行操作,包括创建、打开、删除、重命名、移动等。
应用卸载,不会影响到用户数据,实际用户数据仍保留在对应设备中。
其它系统应用如需要访问本地公共文件,请参考使用[媒体库接口](medialibrary-filepath-guidelines.md)
> **注意:**
> 1:如果应用是系统非管理类应用,比如:系统图库,请优先考虑直接使用媒体库接口,媒体库接口提供了一系列直接操作文件的接口。
> 2:FAF 接口与媒体库接口原则上不能混用。
## FileAccessFramework机制介绍
FAF依托于OpenHarmony上[ExtensionAbility机制](../application-models/extensionability-overview.md),实现了一套对外提供能力的统一接口。应用可以通过这套接口预览和操作公共文件,实现自己的逻辑。
有兴趣的开发者,可以预览我们的[源码仓](https://gitee.com/openharmony/filemanagement_user_file_service),提出您宝贵的意见。
基于FAF进行文件操作的全流程,包含以下几个元素,如图所示:
**图1 公共文件操作层次图**
![](figures/公共文件操作层次图.png)
- **文件访问客户端应用** - 需要访问或操作公共文件的应用。通过拉起文件选择器,用户可以在可视化界面上进行文件操作。
- **文件选择器应用** - 可以让用户访问所有共享数据集的系统应用。通过使用 FAF 的对上接口,完成各种文件操作。
- **文件访问服务端应用** - 系统内支持将数据集进行共享的服务。目前有[UserFileManager](https://gitee.com/openharmony/multimedia_medialibrary_standard)、ExternalFileManager等。前者管理了本地磁盘、分布式设备的数据集,后者管理了SD卡、U盘等多种外置存储设备的数据集。开发者也可以基于 FAF 的服务端配置,共享自己的数据集。
FAF提供的主要功能:
- 可以让用户浏览系统内所有文件服务端应用提供的数据集,而不仅仅是单一应用的数据集。
- 客户端应用不需要获取FAF的使用权限,直接通过选择器应用操作文件。
- 支持访问多个临时挂载的设备,比如外置存储卡、分布式设备等。
## 数据模型
FAF 中数据模型主要通过URI、FileInfo、RootInfo 进行传递。详情参考[fileExtension](../reference/apis/js-apis-fileExtensionInfo.md)。文件访问服务端应用可以通过 FileAccessExtensionAbility API,将自身的数据安全的共享出去。
**图2 公共文件访问框架数据流**
![](figures/公共文件访问框架数据流.png)
注意事项:
- 在FAF中,文件访问客户端和文件访问服务端并不直接交互。只需要具备拉起文件选择器应用的权限即可。
- 文件选择器应用会为用户提供标准的文档访问界面,即使底层的文件访问服务端相互之间差异很大,一致性也不受影响。
\ No newline at end of file
# 文件管理概述
在操作系统中,存在各种各样的数据,按数据结构可分为:
- 结构化数据:能够用统一的数据模型加以描述的数据。常见的是各类数据库数据。在应用开发中,对结构化数据的开发活动隶属于[数据管理模块](database/data-mgmt-overview.md)
- 非结构化数据:指数据结构不规则或不完整,没有预定义的数据结构/模型,不方便用数据库二维逻辑表来表现的数据。常见的是各类文件,如文档、图片、音频、视频等。在应用开发中,对非结构化数据的开发活动隶属于文件管理模块,将在下文展开介绍。
在文件管理模块中,按文件所有者的不同,有如下文件分类模型,其示意图如下面文件分类模型示意图:
- [应用文件](app-file-overview.md):文件所有者为应用,包括应用安装文件、应用资源文件、应用缓存文件等。
- [用户文件](user-file-overview.md):文件所有者为登录到该终端设备的用户,包括用户私有的图片、视频、音频、文档等。
- 系统文件:与应用和用户无关的其他文件,包括公共库、设备文件、系统资源文件等。这类文件不需要开发者进行文件管理,本文不展开介绍。
按文件系统管理的文件存储位置(数据源位置)的不同,有如下文件系统分类模型:
- 本地文件系统:提供本地设备或外置存储设备(如U盘、移动硬盘)的文件访问能力。本地文件系统是最基本的文件系统,本文不展开介绍。
- [分布式文件系统](distributed-fs-overview.md):提供跨设备的文件访问能力。所谓跨设备,指文件不一定存储在本地设备或外置存储设备,而是通过计算机网络与其他分布式设备相连。
**图1** 文件分类模型示意图
![File classification model](figures/file-classification-model.png)
# 文件选择器使用指导
文件选择器(FilePicker)是OpenHarmony中预置的系统应用,为用户提供文件选择及保存功能。具体实现可以参考[FilePicker代码仓库](https://gitee.com/openharmony/applications_filepicker)
应用可以通过FilePicker的两种模式实现文件选择和文件保存的功能。
- choose模式(文件选择):当应用需要选择并上传、发送设备中的文件(包括图片、音视频等媒体资源)时,可以选择该模式。拉起FilePicker的choose模式窗口,系统将弹出弹框供用户选择具体文件。用户通过界面选择目标文件并点击“上传”按钮,应用将接收到FilePicker传回的目标文件uri。
- save模式(文件保存):当应用需要下载保存文件(包括图片、音视频等媒体资源)时,可以选择该模式。拉起FilePicker的save模式窗口,系统将弹出弹框供用户选择保存文件的目标路径。用户通过界面选择目标路径并点击“保存”按钮,应用将接收到FilePicker传回的已保存文件uri。
## 开发指导
> **说明:**
> FilePicker仅支持基于Stage模型开发的应用拉起。<br/>
> Stage模型介绍请参考[应用模型解读](../application-models/application-model-description.md)。
使用不同的参数调用[AbilityContext.startAbilityForResult(want, options)](../reference/apis/js-apis-inner-application-uiAbilityContext.md#uiabilitycontextstartabilityforresult-1)可以拉起FilePicker不同模式的窗口。
开发者需要通过[Want](../reference/apis/js-apis-application-want.md)指定"bundleName"和"abilityName"来拉起FilePicker,具体写法可参见下方的示例代码。
同时,开发者还需要设置属性Want.parameters,来指定FilePicker拉起的模式以及文件保存的名称:
- 文件选择:仅需要设置FilePicker拉起的窗口模式为`'startMode': 'choose'`
- 文件保存:除了设置FilePicker拉起的窗口模式为`'startMode': 'save'`,还需要设置文件保存名称`'saveFile'`
可以通过设置类型为[StartOptions](../reference/apis/js-apis-app-ability-startOptions.md)的入参options来指定弹出窗口样式,推荐设置为`windowMode: 102`,即自由悬浮形式窗口。
> **注意:**
> 1. save模式下,用户保存路径根据保存文件名执行强校验,用户保存路径参考[公共目录路径支持的文件格式](medialibrary-filepath-guidelines.md)。
> 2. 用户选择目标文件保存路径后,如存在同名文件,FilePicker将弹窗提醒用户是否覆盖原文件。
ArkTS语言示例代码如下:
```ts
// 拉起FilePicker选择文件
globalThis.context.startAbilityForResult(
{
action: "ohos.want.action.OPEN_FILE",
parameters: {
'startMode': 'choose', //choose or save
}
},
{ windowMode: 102 }
)
// 拉起FilePicker保存文件
globalThis.context.startAbilityForResult(
{
action: "ohos.want.action.CREATE_FILE",
parameters: {
'startMode': 'save', //choose or save
'saveFile': 'test.jpg',
}
},
{ windowMode: 102 }
)
// FilePicker返回给startAbilityForResult的数据
let abilityResult = {
resultCode: resultCode,
want: {
parameters: {
'startMode': startMode,
'result': result
}
}
}
globalThis.context.terminateSelfWithResult(abilityResult)
```
# 管理外置存储设备(仅对系统应用开放)
外置存储设备具备可插拔属性,因此系统提供了设备插拔事件的监听及挂载功能,用于管理外置存储设备。
外置存储设备的管理由StorageManager和StorageDaemon两个服务完成。StorageDaemon实现底层的的监听挂载等功能;StorageManager则对系统应用提供状态变更通知、查询和管理能力。
**图1** 外置存储设备管理示意图  
![External storage device management](figures/external-storage-device-management.png)
- 插入外卡时,StorageDaemon进程通过netlink监听获取到外卡插入事件,创建对应的磁盘设备以及卷设备,此时,已创建的卷设备状态为卸载状态(UNMOUNTED)。
- StorageDaemon进程在创建完卷设备后,会对卷设备进行检查,此时卷状态为检查状态(CHECKING)。
- 检查成功后,会对卷设备进行挂载,挂载成功后,卷状态更改为挂载状态(MOUNTED),并通知StorageManager发送COMMON_EVENT_VOLUME_MOUNTED广播。
- 检查失败,则返回卸载状态(UNMOUNTED)。
- 当卷设备处于挂载状态时:
- 拔出卷设备,会直接删除相关卷设备信息,并发送COMMON_EVENT_VOLUME_BAD_REMOVAL广播。
- 当用户选择弹出时,卷状态设备更改为正在弹出状态(EJECTING),并发送COMMON_EVENT_VOLUME_EJECT广播。StorageDaemon进程将卷设备卸载成功后,卷状态更改为卸载状态(UNMOUNTED),并发送COMMON_EVENT_VOLUME_UNMOUNTED广播。
- 当卷设备处于卸载状态时,拔出卷设备会删除相关卷设备信息,并发送COMMON_EVENT_VOLUME_REMOVED广播。
## 接口说明
外置存储设备管理相关API的详细介绍请参见[API参考](../reference/apis/js-apis-file-volumemanager.md)
各类广播传递的相关参数,请见下表。
**表1** 广播传递的参数
| 广播类型 | 参数 |
| -------- | -------- |
| usual.event.data.VOLUME_REMOVED | id:卷设备ID<br/>diskId:卷设备所属磁盘设备ID |
| usual.event.data.VOLUME_UNMOUNTED | id:卷设备ID<br/>diskId:卷设备所属磁盘设备ID<br/>volumeState:卷设备状态 |
| usual.event.data.VOLUME_MOUNTED | id:卷设备ID<br/>diskId:卷设备所属磁盘设备ID<br/>volumeState:卷设备状态<br/>fsUuid:卷设备uuid<br/>path:卷设备挂载路径 |
| usual.event.data.VOLUME_BAD_REMOVAL | id:卷设备ID<br/>diskId:卷设备所属磁盘设备ID |
| usual.event.data.VOLUME_EJECT | id:卷设备ID<br/>diskId:卷设备所属磁盘设备ID<br/>volumeState:卷设备状态 |
## 开发步骤
开发者通过订阅卷设备相关的广播事件来感知外置存储的插入,通过广播传递的信息获取卷设备信息后可以对卷设备进行查询以及管理操作。
1. 获取权限。
订阅卷设备广播事件需要申请ohos.permission.STORAGE_MANAGER权限,配置方式请参见[配置文件权限声明](../security/accesstoken-guidelines.md#配置文件权限声明)
2. 订阅广播事件。
需订阅的事件如下:
- 卷设备移除:"usual.event.data.VOLUME_REMOVED"
- 卷设备卸载:"usual.event.data.VOLUME_UNMOUNTED"
- 卷设备挂载:"usual.event.data.VOLUME_MOUNTED"
- 卷设备异常移除:"usual.event.data.VOLUME_BAD_REMOVAL"
- 卷设备正在弹出:"usual.event.data.VOLUME_EJECT"
```ts
import CommonEvent from '@ohos.commonEventManager';
import volumeManager from '@ohos.file.volumeManager';
const subscribeInfo = {
events: [
"usual.event.data.VOLUME_REMOVED",
"usual.event.data.VOLUME_UNMOUNTED",
"usual.event.data.VOLUME_MOUNTED",
"usual.event.data.VOLUME_BAD_REMOVAL",
"usual.event.data.VOLUME_EJECT"
]
};
let subscriber = await CommonEvent.createSubscriber(subscribeInfo);
```
3. 收到广播通知后获取卷设备信息。
```ts
CommonEvent.subscribe(subscriber, function (err, data) {
if (data.event === 'usual.event.data.VOLUME_MOUNTED') {
// 开发者可以通过广播传递的卷设备信息来管理卷设备
let volId = data.parameters.id;
volumeManager.getVolumeById(volId, function(error, vol) {
if (error) {
console.error('volumeManager getVolumeById failed');
} else {
console.info('volumeManager getVolumeById successfully, the volume state is ' + vol.state);
}
})
}
})
```
# 相册资源使用指导
mediaLibrary提供相册相关的接口,供开发者创建、删除相册,获取相册中的图片资源等。
> **说明:**
>
> 在进行功能开发前,请开发者查阅[媒体库开发概述](medialibrary-overview.md),了解如何获取媒体库实例和如何申请媒体库功能开发相关权限。
为了保证应用的运行效率,大部分MediaLibrary调用都是异步的,对于异步调用的API均提供了callback和Promise两种方式,以下示例均采用Promise函数,更多方式可以查阅[API参考](../reference/apis/js-apis-medialibrary.md)
## 获取相册中的图片/视频
获取相册中的图片、视频有两种方式:
一是通过[MediaLibrary.getFileAssets](../reference/apis/js-apis-medialibrary.md#getfileassets7-1)指定相册以获取媒体资源,参考[获取指定相册的媒体资源](medialibrary-resource-guidelines.md#指定相册)
二是通过[Album.getFileAssets](../reference/apis/js-apis-medialibrary.md#getfileassets7-3)使用相册Album实例获取媒体资源,参考[获取相册中的图片或视频](medialibrary-resource-guidelines.md#获取相册中的图片或视频)
## 创建相册
通过[MediaLibrary.createAsset](../reference/apis/js-apis-medialibrary.md#createasset8-1)可以创建媒体资源,可以通过创建图片或视频文件时设置的相对路径,创建出相册。相对路径的命名即为相册名称。
**前提条件**
- 获取媒体库mediaLibrary实例。
- 申请媒体库读写权限“ohos.permission.WRITE_MEDIA”。
下面以创建相册myAlbum为例。
**开发步骤**
1. 调用getPublicDirectory获取文件公共路径。
获取文件公共路径的更多指导可参考[获取文件保存的公共目录](medialibrary-filepath-guidelines.md#获取文件保存的公共目录)
2. 调用createAsset新建图片,并设置相对路径为path+'myAlbum/'。
即在创建相册的同时,往里面放了一张图片。
```ts
async function example() {
let mediaType = mediaLibrary.MediaType.IMAGE;
let DIR_IMAGE = mediaLibrary.DirectoryType.DIR_IMAGE;
const context = getContext(this);
let media = mediaLibrary.getMediaLibrary(context);
const path = await media.getPublicDirectory(DIR_IMAGE);
//myAlbum为新建文件保存路径,也是新建相册的名称
media.createAsset(mediaType, 'test.jpg', path + 'myAlbum/', (err, fileAsset) => {
if (fileAsset === undefined) {
console.error('createAlbum failed, message = ' + err);
} else {
console.info('createAlbum successfully, message = ' + JSON.stringify(fileAsset));
}
});
}
```
## 重命名相册
重命名修改的是相册的FileAsset.albumName属性,即相册名称。修改后再通过[Album.commitModify](../reference/apis/js-apis-medialibrary.md#commitmodify8-3)更新到数据库中。
**前提条件**
- 获取媒体库mediaLibrary实例。
- 申请媒体库读写权限“ohos.permission.WRITE_MEDIA”。
下面以重命名相册为“newAlbum“为例。
**开发步骤**
1. 建立检索条件,用于获取目标相册。
2. 调用getAlbums获取相册列表。
3. 将相册重命名为“newAlbum“。
4. 调用Album.commitModify将更新的相册属性修改到数据库中。
```ts
async function example() {
let AlbumNoArgsfetchOp = {
selections: '',
selectionArgs: [],
};
const context = getContext(this);
let media = mediaLibrary.getMediaLibrary(context);
let albumList = await media.getAlbums(AlbumNoArgsfetchOp);
let album = albumList[0];
album.albumName = 'newAlbum';
//回调返回空
album.commitModify().then(() => {
console.info("albumRename successfully");
}).catch((err) => {
console.error("albumRename failed with error: " + err);
});
}
```
# 文件路径使用指导
Openharmony上用户数据统一由媒体库进行管理,用户数据用户数据可以通过mediaLibrary提供的接口能力进行访问和操作。
> **说明:**
>
> 在进行功能开发前,请开发者查阅[媒体库开发概述](medialibrary-overview.md),了解如何获取媒体库实例和如何申请媒体库功能开发相关权限。
为了保证应用的运行效率,大部分MediaLibrary调用都是异步的,对于异步调用的API均提供了callback和Promise两种方式,以下示例均采用Promise函数,更多方式可以查阅[API参考](../reference/apis/js-apis-medialibrary.md)
## 公共目录路径支持的文件格式
在使用文件路径进行开发之前,需要了解各公共目录路径支持的文件格式说明。
> **注意:** <br/>
> 下表仅表示系统能识别的文件类型,在具体的开发中,开发者需要关注对应接口支持的文件格式。<br/> 如image编码功能只支持.jpeg和.webp,解码功能只支持.jpg .png .gif .bmp .webp RAW。
| 目录路径 | 目录类型 | 媒体类型 | 说明 | 支持的文件格式 |
| ---------- | ------------- | ------------- | -------------- | ------------------------------------------------------------ |
| Camera/ | DIR_CAMERA | VIDEO amd IMAGE | 相机拍摄图片与录像的存放路径,目录与子目录下可以存放视频,图片类型文件。 | .bmp / .bm / .gif / .jpg /. jpeg / .jpe / .png / .webp / .raw / .svg / .heif / .mp4 / .3gp / .mpg / .mov / .webm / .mkv |
| Videos/ | DIR_VIDEO | VIDEO | 视频专有目录,目录与子目录下只可以存放视频类型文件。| .mp4 / .3gp / .mpg / .mov / .webm / .mkv |
| Pictures/ | DIR_IMAGE | IMAGE | 图片专有目录,目录与子目录下只可以存放图片类型文件。 | .bmp / .bm / .gif / .jpg /. jpeg / .jpe / .png / .webp / .raw / .svg / .heif |
| Audios/ | DIR_AUDIO | AUDIO |音频专有目录,目录与子目录下只可以存放音频类型文件。| .aac/.mp3/.flac/.wav/.ogg |
| Documents/ | DIR_DOCUMENTS | FILE |文档类型目录,目录与子目录下只可以存放音频,图片,视频以外类型文件。| - |
| Download/ | DIR_DOWNLOAD | ALLTYPE |下载文件存放目录,目录与子目录下文件类型不受限制。| - |
## 获取文件保存的公共目录
不同类型的文件会保存到不同的公共目录下,可以通过接口[getPublicDirectory](../reference/apis/js-apis-medialibrary.md#getpublicdirectory8-1)来获取公共目录路径。
**前提条件**
- 获取媒体库mediaLibrary实例。
- 申请媒体库读权限"ohos.permission.READ_MEDIA"。
下面以获取Camera文件保存的公共目录为例。
```ts
async function example(){
const context = getContext(this);
let media = mediaLibrary.getMediaLibrary(context);
let DIR_CAMERA = mediaLibrary.DirectoryType.DIR_CAMERA;
const dicResult = await media.getPublicDirectory(DIR_CAMERA);
if (dicResult == 'Camera/') {
console.info('mediaLibraryTest : getPublicDirectory passed');
} else {
console.error('mediaLibraryTest : getPublicDirectory failed');
}
}
```
## 沙箱与公共路径间文件的复制
OpenHarmony提供应用沙箱机制,增加目录可见性数据访问防线,减少了应用数据和用户隐私信息泄露,建立了更加严格安全的应用沙盒隔离能力。
放在公共路径下的文件,用户可以通过系统应用“文件管理”、“图库”访问,但应用沙箱内的文件,只有应用本身可以访问。
### 复制文件
通过接口[mediaLibrary.FileAsset.open](../reference/apis/js-apis-medialibrary.md#open8-1)可以打开公共路径文件。
通过接口[fs.open](../reference/apis/js-apis-file-fs.md#fsopen)可以打开沙箱路径文件,沙箱路径必须通过应用上下文context进行访问。
**前提条件**
- 获取媒体库mediaLibrary实例。
- 申请媒体库读写权限"ohos.permission.READ_MEDIA, ohos.permission.WRITE_MEDIA"。
- 除了@ohos.multimedia.mediaLibrary外,还需要导入模块[@ohos.file.fs](../reference/apis/js-apis-file-fs.md)
- 测试文件 "testFile.txt" 已创建且有文件内容。
**开发步骤**
1. 调用[context.filesDir](../reference/apis/js-apis-file-fs.md)获取应用沙箱路径。
2. 调用MediaLibrary.getFileAssets和FetchFileResult.getFirstObject获取公共目录中的FileAsset实例。
3. 调用fs.open打开沙箱路径文件。
4. 调用fileAsset.open打开公共路径文件。
5. 调用[fs.copyfile](../reference/apis/js-apis-file-fs.md#fscopyfile)复制文件。
6. 调用fileAsset.close和[fs.close](../reference/apis/js-apis-file-fs.md#fsclose)关闭文件。
**示例1 将公共路径文件复制到沙箱路径下**
```ts
async function copyPublic2Sandbox() {
try {
const context = getContext(this);
let media = mediaLibrary.getMediaLibrary(context);
let sandboxDirPath = context.filesDir;
let fileKeyObj = mediaLibrary.FileKey;
let fileAssetFetchOp = {
selections: fileKeyObj.DISPLAY_NAME + '= ?',
selectionArgs: ['testFile.txt'],
};
let fetchResult = await media.getFileAssets(fileAssetFetchOp);
let fileAsset = await fetchResult.getFirstObject();
let fdPub = await fileAsset.open('rw');
let fdSand = await fs.open(sandboxDirPath + '/testFile.txt', fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
await fs.copyFile(fdPub, fdSand.fd);
await fileAsset.close(fdPub);
await fs.close(fdSand.fd);
let content_sand = await fs.readText(sandboxDirPath + '/testFile.txt');
console.info('content read from sandbox file: ', content_sand)
} catch (err) {
console.info('[demo] copyPublic2Sandbox fail, err: ', err);
}
}
```
**示例2 将应用沙箱路径文件复制到公共路径**
```ts
async function copySandbox2Public() {
const context = getContext(this);
let media = mediaLibrary.getMediaLibrary(context);
let sandboxDirPath = context.filesDir;
let DIR_DOCUMENTS = mediaLibrary.DirectoryType.DIR_DOCUMENTS;
const publicDirPath = await media.getPublicDirectory(DIR_DOCUMENTS);
try {
let fileAsset = await media.createAsset(mediaLibrary.MediaType.FILE, 'testFile02.txt', publicDirPath);
console.info('createFile successfully, message = ' + fileAsset);
} catch (err) {
console.error('createFile failed, message = ' + err);
}
try {
let fileKeyObj = mediaLibrary.FileKey;
let fileAssetFetchOp = {
selections: fileKeyObj.DISPLAY_NAME + '= ?',
selectionArgs: ['testFile02.txt'],
};
let fetchResult = await media.getFileAssets(fileAssetFetchOp);
var fileAsset = await fetchResult.getFirstObject();
} catch (err) {
console.error('file asset get failed, message = ' + err);
}
let fdPub = await fileAsset.open('rw');
let fdSand = await fs.open(sandboxDirPath + 'testFile.txt', fs.OpenMode.READ_WRITE);
await fs.copyFile(fdSand.fd, fdPub);
await fileAsset.close(fdPub);
await fs.close(fdSand.fd);
let fdPubRead = await fileAsset.open('rw');
try {
let arrayBuffer = new ArrayBuffer(4096);
await fs.read(fdPubRead, arrayBuffer);
var content_pub = String.fromCharCode(...new Uint8Array(arrayBuffer));
fileAsset.close(fdPubRead);
} catch (err) {
console.error('read text failed, message = ', err);
}
console.info('content read from public file: ', content_pub);
}
```
### 读写文件内容
通过[mediaLibrary](../reference/apis/js-apis-medialibrary.md)的接口FileAsset.open和FileAsset.close可以打开和关闭文件。通过[file.fs](../reference/apis/js-apis-file-fs.md)中的接口fs.read和fs.write可以读写文件。
**前提条件**
- 获取媒体库mediaLibrary实例。
- 申请媒体库读写权限"ohos.permission.READ_MEDIA, ohos.permission.WRITE_MEDIA"。
- 除了@ohos.multimedia.mediaLibrary外,还需要导入模块[@ohos.file.fs](../reference/apis/js-apis-file-fs.md)
**开发步骤**
1. 创建用于读写示例的文件。
```ts
async function example() {
let mediaType = mediaLibrary.MediaType.FILE;
let DIR_DOCUMENTS = mediaLibrary.DirectoryType.DIR_DOCUMENTS;
const context = getContext(this);
let media = mediaLibrary.getMediaLibrary(context);
const path = await media.getPublicDirectory(DIR_DOCUMENTS);
media.createAsset(mediaType, "testFile.txt", path).then((asset) => {
console.info("createAsset successfully:" + JSON.stringify(asset));
}).catch((err) => {
console.error("createAsset failed with error: " + err);
});
}
```
2. 使用open打开文件。
3. 使用[fs.write](../reference/apis/js-apis-file-fs.md#fswrite)写入文件,以string形式传入写入数据。
4. 使用[fs.read](../reference/apis/js-apis-file-fs.md#fsread)读取文件,以 ArrayBuffer 形式保存读取结果。
5. 将ArrayBuffer转化为string,以string形式得到文件内容。
6. 使用close关闭文件。
**示例1 打开现有文件、向文件中写入**
```ts
async function writeOnlyPromise() {
const context = getContext(this);
let media = mediaLibrary.getMediaLibrary(context);
let fileKeyObj = mediaLibrary.FileKey;
let fileAssetFetchOp = {
selections: fileKeyObj.DISPLAY_NAME + '= ?',
selectionArgs: ['testFile.txt'],
};
let fetchResult = await media.getFileAssets(fileAssetFetchOp);
let fileAsset = await fetchResult.getFirstObject();
console.info('fileAssetName: ', fileAsset.displayName);
try {
let fd = await fileAsset.open('w');
console.info('file descriptor: ', fd);
await fs.write(fd, "Write file test content.");
await fileAsset.close(fd);
} catch (err) {
console.error('write file failed, message = ', err);
}
}
```
**示例2 打开现有文件,读取文件内容**
```ts
async function readOnlyPromise() {
const context = getContext(this);
let media = mediaLibrary.getMediaLibrary(context);
let fileKeyObj = mediaLibrary.FileKey;
let fileAssetFetchOp = {
selections: fileKeyObj.DISPLAY_NAME + '= ?' ,
selectionArgs: ['testFile.txt'],
};
let fetchResult = await media.getFileAssets(fileAssetFetchOp);
let fileAsset = await fetchResult.getFirstObject();
console.info('fileAssetName: ', fileAsset.displayName);
try {
let fd = await fileAsset.open('r');
let arrayBuffer = new ArrayBuffer(4096);
await fs.read(fd, arrayBuffer);
let fileContent = String.fromCharCode(...new Uint8Array(arrayBuffer));
globalThis.fileContent = fileContent;
globalThis.fileName = fileAsset.displayName;
console.info('file content: ', fileContent);
await fileAsset.close(fd);
} catch (err) {
console.error('read file failed, message = ', err);
}
}
```
# 媒体库开发概述
MediaLibrary提供媒体库相关能力,帮助开发者更方便地访问和修改媒体文件,具体分为:
- [媒体资源(音频、视频、图片文件等)相关](medialibrary-resource-guidelines.md),包括:
- 查询指定媒体资源
- 获取图片/视频
- 获取图片/视频缩略图
- 创建媒体资源
- 重命名媒体资源
- 将媒体资源放入回收站
- [文件路径相关](medialibrary-filepath-guidelines.md),包括:
- 获取文件保存的公共目录
- 沙箱与公共路径间文件的复制
- 读写文件内容
- [相册相关](medialibrary-album-guidelines.md),包括:
- 获取相册中的图片/视频
- 创建相册
- 重命名相册
> **说明:**<br/>
> 本开发指导基于API Version 9,仅适用于Stage模型。
应用需要先获取媒体库实例,才能访问和修改用户等个人媒体数据信息。媒体库涉及用户个人数据信息,所以应用需要向用户申请媒体库读写操作权限才能保证功能的正常运行。在使用媒体库相关接口时如无其他注明则默认在工程代码的pages/index.ets或者其他自创的ets文件中使用
开发者在使用MediaLibrary进行功能开发前,请先掌握以下内容:
- [获取媒体库实例](#获取媒体库实例)
- [申请媒体库功能相关权限](#申请媒体库功能相关权限)
## 获取媒体库实例
应用需要使用应用上下文Context通过接口[getMediaLibrary](../reference/apis/js-apis-medialibrary.md#medialibrarygetmedialibrary8),获取媒体库实例,用于访问和修改用户等个人媒体数据信息(如音频、视频、图片、文档等)。
**开发步骤**
1. 导入mediaLibrary模块以使用媒体库相关接口。
2. 通过getContext获取应用上下文。
3. 获取媒体库实例。
```ts
import mediaLibrary from '@ohos.multimedia.mediaLibrary';
const context = getContext(this);
let media = mediaLibrary.getMediaLibrary(context);
```
## 申请媒体库功能相关权限
媒体库的读写操作需要相应权限,在申请权限前,请保证符合[权限使用的基本原则](../security/accesstoken-overview.md#权限使用的基本原则)。涉及的权限如下表。
| 权限名 | 说明 | 授权方式 |
| ------------------------------ | ------------------------------------------ | ---------- |
| ohos.permission.READ_MEDIA | 允许应用读取用户外部存储中的媒体文件信息。 | user_grant |
| ohos.permission.WRITE_MEDIA | 允许应用读写用户外部存储中的媒体文件信息。 | user_grant |
| ohos.permission.MEDIA_LOCATION | 允许应用访问用户媒体文件中的地理位置信息。 | user_grant |
以上权限的授权方式均为user_grant(用户授权),即开发者在module.json5文件中配置对应的权限后,需要使用接口[abilityAccessCtrl.requestPermissionsFromUser](../reference/apis/js-apis-abilityAccessCtrl.md#requestpermissionsfromuser9)去校验当前用户是否已授权。如果是,应用可以直接访问/操作目标对象;否则需要弹框向用户申请授权。
> **说明:**<br/>即使用户曾经授予权限,应用在调用受此权限保护的接口前,也应该先检查是否有权限。不能把之前授予的状态持久化,因为用户在动态授予后还可以通过“设置”取消应用的权限。
**开发步骤**
1. 在配置文件module.json5中声明权限。在配置文件的“module”标签内,增加“requestPermissions”标签,标签内容请根据实际情况填写。标签说明可参考[访问控制(权限)开发指导](../security/accesstoken-guidelines.md)
```json
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.MEDIA_LOCATION",
"reason": "$string:reason",
"usedScene": {
"abilities": [
"EntryAbility"
],
"when": "always"
}
},
{
"name": "ohos.permission.READ_MEDIA",
"reason": "$string:reason",
"usedScene": {
"abilities": [
"EntryAbility"
],
"when": "always"
}
},
{
"name": "ohos.permission.WRITE_MEDIA",
"reason": "$string:reason",
"usedScene": {
"abilities": [
"EntryAbility"
],
"when": "always"
}
}
]
}
}
```
2. 在Ability.ts中onWindowStageCreate里调用requestPermissionsFromUser进行权限校验,可以选择需要动态申请获取的权限自行添加相应代码
```ts
import UIAbility from '@ohos.app.ability.UIAbility';
import abilityAccessCtrl, {Permissions} from '@ohos.abilityAccessCtrl';
export default class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage) {
let list : Array<Permissions> = ['ohos.permission.READ_MEDIA', 'ohos.permission.WRITE_MEDIA'];
let permissionRequestResult;
let atManager = abilityAccessCtrl.createAtManager();
atManager.requestPermissionsFromUser(this.context, list, (err, result) => {
if (err) {
console.error('requestPermissionsFromUserError: ' + JSON.stringify(err));
} else {
permissionRequestResult = result;
console.info('permissionRequestResult: ' + JSON.stringify(permissionRequestResult));
}
});
}
}
```
# 媒体资源使用指导
应用可以通过mediaLibrary的接口,进行媒体资源(音频、视频、图片文件等)相关操作。
> **说明:**
>
> 在进行功能开发前,请开发者查阅[媒体库开发概述](medialibrary-overview.md),了解如何获取媒体库实例和如何申请媒体库功能开发相关权限。
为了保证应用的运行效率,大部分MediaLibrary调用都是异步的,对于异步调用的API均提供了callback和Promise两种方式,以下示例均采用Promise函数,更多方式可以查阅[API参考](../reference/apis/js-apis-medialibrary.md)
## 获取媒体资源
开发者可以根据特定的条件查询媒体资源,如指定类型、指定日期、指定相册等。
应用通过调用[MediaLibrary.getFileAssets](../reference/apis/js-apis-medialibrary.md#getfileassets7-1)获取媒体资源,并传入MediaFetchOptions对象指定检索条件。MediaFetchOptions.selections为检索条件,使用FileKey中的枚举值作为检索条件的列名;MediaFetchOptions.selectionArgs对应selections中检索条件列的值;除此之外,可以使用order(结果排序方式)、uri(文件URI)、networkId(注册设备网络ID)作为检索条件。
如果只想获取某个位置的对象(如第一个、最后一个、指定索引等),可以通过[FetchFileResult](../reference/apis/js-apis-medialibrary.md#fetchfileresult7)中的接口获取对应位置的媒体资源对象。本小节均采用getNextObject循环获取检索结果中的所有媒体资源。
**前提条件**
- 获取媒体库mediaLibrary实例。
- 申请媒体库读权限“ohos.permission.READ_MEDIA”。
### 指定媒体类型
下面以查询图片类型的媒体资源为例。
**开发步骤**
selections: FileKey.MEDIA_TYPE,根据媒体类型检索。
selectionArgs: MediaType.IMAGE,媒体类型为图片。
```ts
async function example() {
let fileKeyObj = mediaLibrary.FileKey;
let fileType = mediaLibrary.MediaType.IMAGE;
let option = {
selections: fileKeyObj.MEDIA_TYPE + '= ?',
selectionArgs: [fileType.toString()],
};
const context = getContext(this);
let media = mediaLibrary.getMediaLibrary(context);
const fetchFileResult = await media.getFileAssets(option);
fetchFileResult.getFirstObject().then(async (fileAsset) => {
console.log('getFirstObject.displayName : ' + fileAsset.displayName);
for (let i = 1; i < fetchFileResult.getCount(); i++) {
let fileAsset = await fetchFileResult.getNextObject();
console.info('fileAsset.displayName ' + i + ': ' + fileAsset.displayName);
}
}).catch((err) => {
console.error('Failed to get first object: ' + err);
});
}
```
### 指定日期
下面以查询指定添加日期至今的所有媒体资源为例。实际开发中可以设置添加日期、修改日期、拍摄日期。
selections: FileKey.DATE_ADDED,根据文件添加日期检索。
selectionArgs:2022-8-5,具体添加时间的字符串。
```ts
async function example() {
let fileKeyObj = mediaLibrary.FileKey;
let option = {
selections: fileKeyObj.DATE_ADDED + '> ?',
selectionArgs: ['2022-8-5'],
};
const context = getContext(this);
let media = mediaLibrary.getMediaLibrary(context);
const fetchFileResult = await media.getFileAssets(option);
fetchFileResult.getFirstObject().then(async (fileAsset) => {
console.info('getFirstObject.displayName : ' + fileAsset.displayName);
for (let i = 1; i < fetchFileResult.getCount(); i++) {
let fileAsset = await fetchFileResult.getNextObject();
console.info('fileAsset.displayName ' + i + ': ' + fileAsset.displayName);
}
}).catch((err) => {
console.error('Failed to get first object: ' + err);
});
}
```
### 按指定顺序排列
下面以查询图片并按文件添加日期降序排列为例。实际开发中可以设置升序(ASC)和降序(DESC)。
order: FileKey.DATE_ADDED,根据文件添加日期排序;并设置排列顺序为DESC降序。
```ts
async function example() {
let fileKeyObj = mediaLibrary.FileKey;
let fileType = mediaLibrary.MediaType.IMAGE;
let option = {
selections: fileKeyObj.MEDIA_TYPE + '= ?',
selectionArgs: [fileType.toString()],
order: fileKeyObj.DATE_ADDED + " DESC",
};
const context = getContext(this);
let media = mediaLibrary.getMediaLibrary(context);
const fetchFileResult = await media.getFileAssets(option);
fetchFileResult.getFirstObject().then(async (fileAsset) => {
console.info('getFirstObject.displayName : ' + fileAsset.displayName);
for (let i = 1; i < fetchFileResult.getCount(); i++) {
let fileAsset = await fetchFileResult.getNextObject();
console.info('fileAsset.displayName ' + i + ': ' + fileAsset.displayName);
}
}).catch((err) => {
console.error('Failed to get first object: ' + err);
});
}
```
### 指定相册
下面以指定相册myAlbum为例。
selections: FileKey.ALBUM_NAME,根据相册名称检索。
selectionArgs:'myAlbum',具体相册名称。
```ts
async function example() {
let fileKeyObj = mediaLibrary.FileKey;
let option = {
selections: fileKeyObj.ALBUM_NAME + '= ?',
selectionArgs: ['myAlbum'],
};
const context = getContext(this);
let media = mediaLibrary.getMediaLibrary(context);
const fetchFileResult = await media.getFileAssets(option);
if (albumList.length > 0) {
fetchFileResult.getFirstObject().then((album) => {
console.info('getFirstObject.displayName : ' + album.albumName);
}).catch((err) => {
console.error('Failed to get first object: ' + err);
});
} else {
console.info('getAlbum list is: 0');
}
}
```
## 获取相册中的图片或视频
获取相册的媒体资源有两种方式,一是通过[MediaLibrary.getFileAssets](../reference/apis/js-apis-medialibrary.md#getfileassets7-1)指定相册以获取媒体资源,参考[获取指定相册的媒体资源](#指定相册);二是通过[Album.getFileAssets](../reference/apis/js-apis-medialibrary.md#getfileassets7-3)使用相册Album实例获取媒体资源。
**前提条件**
- 获取媒体库mediaLibrary实例。
- 申请媒体库读权限“ohos.permission.READ_MEDIA”。
**开发步骤**
下面以获取相册名称为“新建相册1”的视频为例。
1. 建立检索条件,用于获取目的相册实例。
```ts
let fileKeyObj = mediaLibrary.FileKey;
let AlbumNoArgsFetchOp = {
selections: fileKeyObj.ALBUM_NAME + '= ?',
selectionArgs: ['新建相册1']
}
```
2. 建立检索条件,用于获取目的相册下的视频资源。
```ts
let fileKeyObj = mediaLibrary.FileKey;
let videoType = mediaLibrary.MediaType.VIDEO;
let videoFetchOp = {
selections: fileKeyObj.MEDIA_TYPE + '= ?',
selectionArgs: [videoType.toString()],
}
```
3. 通过Album.getFileAssets获取对应的资源。
以下为**完整示例**
```ts
async function getCameraImagePromise() {
const context = getContext(this);
let media = mediaLibrary.getMediaLibrary(context);
let fileKeyObj = mediaLibrary.FileKey;
let videoType = mediaLibrary.MediaType.VIDEO;
let videoFetchOp = {
selections: fileKeyObj.MEDIA_TYPE + '= ?',
selectionArgs: [videoType.toString()],
}
let AlbumNoArgsFetchOp = {
selections: fileKeyObj.ALBUM_NAME + '= ?',
selectionArgs: ['新建相册1']
}
let albumList = await media.getAlbums(AlbumNoArgsFetchOp);
if (albumList.length > 0) {
const album = albumList[0];
let fetchFileResult = await album.getFileAssets(videoFetchOp);
let count = fetchFileResult.getCount();
console.info("get mediaLibrary VIDEO number", count);
} else {
console.info('getAlbum list is: 0');
}
}
```
## 获取图片/视频缩略图
通过接口[FileAsset.getThumbnail](../reference/apis/js-apis-medialibrary.md#getthumbnail8-2),传入缩略图尺寸,可以获取图片/视频缩略图。缩略图常用于UI界面展示。
**前提条件**
- 获取媒体库mediaLibrary实例。
- 申请媒体库读权限“ohos.permission.READ_MEDIA”。
### 获取某张图片的缩略图
当需要在相册展示图片/视频、编辑预览,应用需要获取某张图片的缩略图。
下面以获取相册第一张图片的缩略图为例,缩略图尺寸为720*720。
**开发步骤**
1. 建立检索条件,用于获取目的相册下的图片资源。
2. 调用getFileAssets获取目标图片资源。
3. 调用getFirstObject获取第一张图片。
4. 调用getThumbnail获取相册中图片的缩略图。
```ts
async function getFirstThumbnailPromise() {
const context = getContext(this);
let media = mediaLibrary.getMediaLibrary(context);
let fileKeyObj = mediaLibrary.FileKey;
let imageType = mediaLibrary.MediaType.IMAGE;
let imagesFetchOp = {
selections: fileKeyObj.MEDIA_TYPE + '= ?',
selectionArgs: [imageType.toString()],
}
let size = { width: 720, height: 720 };
const fetchFileResult = await media.getFileAssets(imagesFetchOp);
if (fetchFileResult === undefined) {
console.error("get image failed with error");
return;
} else {
const asset = await fetchFileResult.getFirstObject();
asset.getThumbnail(size).then((pixelMap) => {
pixelMap.getImageInfo().then((info) => {
console.info('get Thumbnail info: ' + "width: " + info.size.width + " height: " + info.size.height);
}).catch((err) => {
console.error("getImageInfo failed with error: " + err);
});
}).catch((err) => {
console.error("getImageInfo failed with error: " + err);
});
}
}
```
## 创建媒体资源
通过接口[MediaLibrary.createAsset](../reference/apis/js-apis-medialibrary.md#createasset8-1)可以创建媒体资源。
**前提条件**
- 获取媒体库mediaLibrary实例。
- 申请媒体库读写权限“ohos.permission.WRITE_MEDIA”。
- [获取公共目录路径](medialibrary-filepath-guidelines.md)
下面以创建文件类型(MediaType.FILE)的文件为例。
```ts
async function example() {
let mediaType = mediaLibrary.MediaType.FILE;
let DIR_DOCUMENTS = mediaLibrary.DirectoryType.DIR_DOCUMENTS;
const context = getContext(this);
let media = mediaLibrary.getMediaLibrary(context);
const path = await media.getPublicDirectory(DIR_DOCUMENTS);
media.createAsset(mediaType, "testFile.text", path).then((asset) => {
console.info("createAsset successfully:"+ JSON.stringify(asset));
}).catch((err) => {
console.error("createAsset failed with error: " + err);
});
}
```
## 将文件放入回收站
通过[FileAsset.trash](../reference/apis/js-apis-medialibrary.md#trash8)可以将文件放入回收站。
放入回收站的文件将会保存30天,在此期间,开发者可以将trash的入参isTrash设置为false将其恢复为正常文件;应用用户也可以通过系统应用“文件管理”或“图库”恢复文件。
**前提条件**
- 获取媒体库mediaLibrary实例。
- 申请媒体库读写权限“ohos.permission.WRITE_MEDIA”。
下面以将文件检索结果中第一个文件放入回收站为例。
**开发步骤**
1. 建立检索条件,用于获取目的相册下的图片资源。
2. 调用getFileAssets获取目标图片资源。
3. 调用getFirstObject获取第一张图片,即要放入回收站的图片对象。
4. 调用trash将文件放入回收站。
```ts
async function example() {
let fileKeyObj = mediaLibrary.FileKey;
let fileType = mediaLibrary.MediaType.FILE;
let option = {
selections: fileKeyObj.MEDIA_TYPE + '= ?',
selectionArgs: [fileType.toString()],
};
const context = getContext(this);
let media = mediaLibrary.getMediaLibrary(context);
const fetchFileResult = await media.getFileAssets(option);
let asset = await fetchFileResult.getFirstObject();
if (asset === undefined) {
console.error('asset not exist');
return;
}
//回调为空
asset.trash(true).then(() => {
console.info("trash successfully");
}).catch((err) => {
console.error("trash failed with error: " + err);
});
}
```
## 重命名媒体资源
重命名修改的是文件的FileAsset.displayName属性,即文件的显示文件名,包含文件后缀。
修改后再通过[FileAsset.commitModify](../reference/apis/js-apis-medialibrary.md#commitmodify8-1)更新到数据库中。
在重命名文件之前,需要先获取文件对象,可以通过[FetchFileResult](../reference/apis/js-apis-medialibrary.md#fetchfileresult7)中的接口获取对应位置的文件。
**前提条件**
- 获取媒体库mediaLibrary实例。
- 申请媒体库读写权限“ohos.permission.WRITE_MEDIA”。
下面以将文件检索结果中第一个文件重命名为“newImage.jpg”为例。
**开发步骤**
1. 建立检索条件,用于获取目的相册下的图片资源。
2. 调用getFileAssets获取目标图片资源。
3. 调用getFirstObject获取第一张图片,即要重命名的图片对象。
4. 将图片重命名为“newImage.jpg“。
5. 调用FileAsset.commitModify将更新的图片属性修改到数据库中。
```ts
async function example() {
let fileKeyObj = mediaLibrary.FileKey;
let fileType = mediaLibrary.MediaType.IMAGE;
let option = {
selections: fileKeyObj.MEDIA_TYPE + '= ?',
selectionArgs: [fileType.toString()],
};
const context = getContext(this);
let media = mediaLibrary.getMediaLibrary(context);
const fetchFileResult = await media.getFileAssets(option);
let asset = await fetchFileResult.getFirstObject();
if (asset === undefined) {
console.error('asset not exist');
return;
}
asset.displayName = 'newImage.jpg';
//回调为空
asset.commitModify((err) => {
if (err) {
console.error('fileRename Failed ');
return;
}
console.info('fileRename successful.');
});
}
```
# 保存用户文件
在从网络下载文件到本地、或将已有用户文件另存为新的文件路径等场景下,需要使用FilePicker提供的保存用户文件的能力。
对音频、图片、视频、文档类文件的保存操作类似,均通过调用对应picker的save()接口并传入对应的saveOptions来实现。
## 保存图片或视频类文件
1. 导入选择器模块。
```ts
import picker from '@ohos.file.picker';
```
2. 创建图库保存选项实例。
```ts
const photoSaveOptions = new picker.PhotoSaveOptions(); // 创建文件管理器保存选项实例
photoSaveOptions.newFileNames = ["PhotoViewPicker01.jpg"]; // 保存文件名(可选)
```
3. 创建图库选择器实例,调用[save()](../reference/apis/js-apis-file-picker.md#save)接口拉起FilePicker界面进行文件保存。
用户选择目标文件夹,用户选择与文件类型相对应的文件夹,即可完成文件保存操作。保存成功后,返回保存文档的URI。
```ts
const photoViewPicker = new picker.PhotoViewPicker();
photoViewPicker.save(photoSaveOptions)
.then(async (photoSaveResult) => {
let uri = photoSaveResult[0];
// 获取到到图片或者视频文件的URI后进行文件读取等操作
})
.catch((err) => {
console.error(`Invoke documentPicker.select failed, code is ${err.code}, message is ${err.message}`);
})
```
## 保存文档类文件
1. 导入选择器模块。
```ts
import picker from '@ohos.file.picker';
```
2. 创建文档保存选项实例。
```ts
const documentSaveOptions = new picker.DocumentSaveOptions(); // 创建文件管理器选项实例
documentSaveOptions.newFileNames = ["DocumentViewPicker01.txt"]; // 保存文件名(可选)
```
3. 创建文档选择器实例。调用[save()](../reference/apis/js-apis-file-picker.md#save-3)接口拉起FilePicker界面进行文件保存。
用户选择目标文件夹,用户选择与文件类型相对应的文件夹,即可完成文件保存操作。保存成功后,返回保存文档的URI。
> **说明:**
>
> 目前DocumentSelectOptions不支持参数配置,默认可以选择所有类型的用户文件。
```ts
const documentViewPicker = new picker.DocumentViewPicker(); // 创建文件选择器实例
documentViewPicker.save(documentSaveOptions)
.then(async (documentSaveResult) => {
let uri = documentSaveResult[0];
// 例如,可以根据获取的URI进行文件写入等操作
})
.catch((err) => {
console.error(`Invoke documentPicker.save failed, code is ${err.code}, message is ${err.message}`);
})
```
## 保存音频类文件
1. 导入选择器模块。
```ts
import picker from '@ohos.file.picker';
```
2. 创建音频保存选项实例。
```ts
const audioSaveOptions = new picker.AudioSaveOptions(); // 创建文件管理器选项实例
audioSaveOptions.newFileNames = ['AudioViewPicker01.mp3']; // 保存文件名(可选)
```
3. 创建音频选择器实例。调用[save()](../reference/apis/js-apis-file-picker.md#save-6)接口拉起FilePicker界面进行文件保存。
用户选择目标文件夹,用户选择与文件类型相对应的文件夹,即可完成文件保存操作。保存成功后,返回保存文档的URI。
> **说明:**
>
> 目前AudioSelectOptions不支持参数配置,默认可以选择所有类型的用户文件。
```ts
const audioViewPicker = new picker.AudioViewPicker();
audioViewPicker.save(audioSaveOptions)
.then((audioSelectResult) => {
let uri = audioSelectResult[0];
// 获取到到音频文件的URI后进行文件读取等操作
})
.catch((err) => {
console.error(`Invoke audioPicker.select failed, code is ${err.code}, message is ${err.message}`);
})
```
# 选择用户文件
终端用户有时需要分享、保存一些图片、视频等用户文件,开发者需要在应用中支持此类使用场景。此时,开发者可以使用OpenHarmony系统预置的[文件选择器(FilePicker)](../reference/apis/js-apis-file-picker.md),实现用户文件选择及保存能力。
根据用户文件的常见类型,文件选择器(FilePicker)分别提供以下接口:
- [PhotoViewPicker](../reference/apis/js-apis-file-picker.md#photoviewpicker):适用于图片或视频类文件的选择与保存。
- [DocumentViewPicker](../reference/apis/js-apis-file-picker.md#documentviewpicker):适用于文档类文件的选择与保存。
- [AudioViewPicker](../reference/apis/js-apis-file-picker.md#audioviewpicker):适用于音频类文件的选择与保存。
## 选择图片或视频类文件
1. 导入选择器模块。
```ts
import picker from '@ohos.file.picker';
```
2. 创建图库选择选项实例。
```ts
const photoSelectOptions = new picker.PhotoSelectOptions();
```
3. 选择媒体文件类型和选择媒体文件的最大数目。
以下示例以图片选择为例,媒体文件类型请参见[PhotoViewMIMETypes](../reference/apis/js-apis-file-picker.md#photoviewmimetypes)
```ts
photoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE; // 过滤选择媒体文件类型为IMAGE
photoSelectOptions.maxSelectNumber = 5; // 选择媒体文件的最大数目
```
4. 创建图库选择器实例,调用[select()](../reference/apis/js-apis-file-picker.md#select)接口拉起FilePicker界面进行文件选择。
文件选择成功后,返回[PhotoSelectResult](../reference/apis/js-apis-file-picker.md#photoselectresult)结果集,可以根据结果集中URI进行文件读取等操作。
```ts
const photoPicker = new picker.PhotoViewPicker();
photoPicker.select(photoSelectOptions)
.then(async (photoSelectResult) => {
let uri = photoSelectResult.photoUris[0];
// 获取到到图片或者视频文件的URI后进行文件读取等操作
})
.catch((err) => {
console.error(`Invoke documentPicker.select failed, code is ${err.code}, message is ${err.message}`);
})
```
## 选择文档类文件
1. 导入选择器模块。
```ts
import picker from '@ohos.file.picker';
```
2. 创建文档选择选项实例。
```ts
const documentSelectOptions = new picker.DocumentSelectOptions();
```
3. 创建文档选择器实例。调用[select()](../reference/apis/js-apis-file-picker.md#select-3)接口拉起FilePicker界面进行文件选择。
文件选择成功后,返回被选中文档的URI结果集。开发者可以根据结果集中URI做进一步的处理。
> **说明:**
>
> 目前DocumentSelectOptions不支持参数配置,默认可以选择所有类型的用户文件。
```ts
const documentViewPicker = new picker.DocumentViewPicker(); // 创建文件选择器实例
documentViewPicker.select(documentSelectOptions)
.then((documentSelectResult) => {
let uri = documentSelectResult[0];
// 获取到到文档文件的URI后进行文件读取等操作
})
.catch((err) => {
console.error(`Invoke documentPicker.select failed, code is ${err.code}, message is ${err.message}`);
})
```
## 选择音频类文件
1. 导入选择器模块。
```ts
import picker from '@ohos.file.picker';
```
2. 创建音频选择选项实例。
```ts
const audioSelectOptions = new picker.AudioSelectOptions();
```
3. 创建音频选择器实例。调用[select()](../reference/apis/js-apis-file-picker.md#select-6)接口拉起FilePicker界面进行文件选择。
文件选择成功后,返回被选中音频的URI结果集。开发者可以根据结果集中URI做进一步的处理。
例如通过[文件管理接口](../reference/apis/js-apis-file-fs.md#ohosfilefs-%E6%96%87%E4%BB%B6%E7%AE%A1%E7%90%86)根据URI拿到音频资源的文件句柄(FD),再配合媒体服务实现音频播放的开发,具体请参考[音频播放开发指导](../media/audio-playback-overview.md)。
> **说明:**
>
> 目前AudioSelectOptions不支持参数配置,默认可以选择所有类型的用户文件。
```ts
const audioViewPicker = new picker.AudioViewPicker();
audioViewPicker.select(audioSelectOptions)
.then(audioSelectResult => {
let uri = audioSelectOptions[0];
// 获取到到音频文件的URI后进行文件读取等操作
})
.catch((err) => {
console.error(`Invoke audioPicker.select failed, code is ${err.code}, message is ${err.message}`);
})
```
# 向应用沙箱推送文件
开发者在应用开发调试时,可能需要向应用沙箱下推送一些文件以期望在应用内访问或测试,此时有两种方式:
1. 可以通过DevEco Studio向应用安装路径中放入目标文件,详见[应用安装资源访问](../quick-start/resource-categories-and-access.md#资源访问)
2. 在具备设备环境时,可以使用另一种更为灵活的方式,通过hdc工具来向设备中应用沙箱路径推送文件。即本文介绍的内容。
但是hdc shell看到的调试进程下的文件路径与应用视角的应用沙箱路径不同,开发者需要先了解如下路径映射关系。
## 应用沙箱路径和调试进程视角下的真实物理路径
在应用沙箱路径下读写文件,经过映射转换,实际读写的是在hdc进程视角下看到真实物理路径中的应用文件,其对应关系如下表所示。
**表1** 应用沙箱路径与真实物理路径对应关系
| 应用沙箱路径 | 调试进程(hdc)视角下的实际路径 | 说明 |
| -------- | -------- | -------- |
| /data/storage/el1/bundle | /data/app/el1/bundle/public/&lt;PACKAGENAME&gt; | 应用安装包目录 |
| /data/storage/el1/base | /data/app/el1/&lt;USERID&gt;/base/&lt;PACKAGENAME&gt; | 应用el1级别加密数据目录 |
| /data/storage/el2/base | /data/app/el2/&lt;USERID&gt;/base/&lt;PACKAGENAME&gt; | 应用el2级别加密数据目录 |
| /data/storage/el1/database | /data/app/el1/&lt;USERID&gt;/database/&lt;PACKAGENAME&gt; | 应用el1级别加密数据库目录 |
| /data/storage/el2/database | /data/app/el2/&lt;USERID&gt;/database/&lt;PACKAGENAME&gt; | 应用el2级别加密数据库目录 |
| /data/storage/el2/distributedfiles | /mnt/hmdfs/&lt;USERID&gt;/account/merge_view/data/&lt;PACKAGENAME&gt; | 应用el2加密级别有帐号分布式数据融合目录 |
## 开发示例
以应用包com.ohos.example为例,如果是在example的应用沙箱路径“/data/storage/el1/bundle”下读写文件,从上表可知,对应的真实物理路径为“/data/app/el1/bundle/public/&lt;PACKAGENAME&gt;”,即“/data/app/el1/bundle/public/com.ohos.example”。
推送命令示例如下:
```
hdc file send ${待推送文件的本地路径} /data/app/el1/bundle/public/com.ohos.example/
```
## 切换应用沙箱视角
在调试过程中,如果权限不对或文件不存在,开发者需要从调试进程视角切换为应用视角,以便直观分析权限及文件目录问题。视角切换命令如下:
```
hdc shell // 进入shell
ps -ef|grep [hapName] // 通过ps命令找到对应应用的pid
nsenter -t [hapPid] -m /bin/sh // 通过上一步找到的应用pid进入对应应用的沙箱环境中
```
执行完成后,即切换到了应用视角,该视角下的目录路径为应用沙箱路径,可以去排查沙箱路径相关问题。
# 设置分布式文件数据等级
不同设备本身的安全能力差异较大,一些小的嵌入式设备安全能力远弱于平板等设备类型。用户或者应用不同的文件数据有不同安全诉求,例如个人的健康信息和银行卡信息等不期望被弱设备读取。因此,OpenHarmony提供一套完整的数据分级、设备分级标准,并针对不同设备制定不同的数据流转策略,具体规则请参见[数据、设备安全分级](../database/access-control-by-device-and-data-level.md)
## 接口说明
API详细介绍请参见[ohos.file.securityLabel](../reference/apis/js-apis-file-securityLabel.md)
**表1** 设置文件数据等级
| 接口名 | 功能 | 接口类型 | 支持同步 | 支持异步 |
| -------- | -------- | -------- | -------- | -------- |
| setSecurityLabel | 设置文件安全标签 | 方法 | √ | √ |
| getSecurityLabel | 获取文件安全标签 | 方法 | √ | √ |
> **须知:**
> 1. 对于不满足安全等级的文件,跨设备仍然可以看到该文件,但是无权限打开访问该文件。
>
> 2. 分布式文件系统的数据等级默认为S3,应用可以主动设置文件的安全等级。
## 开发示例
获取通用文件沙箱路径,并设置数据等级标签。示例中的context的获取方式请参见[获取UIAbility的上下文信息](../application-models/uiability-usage.md#获取uiability的上下文信息)
```ts
import securityLabel from '@ohos.file.securityLabel';
// 获取需要设备数据等级的文件沙箱路径
let context = ...; // 获取UIAbilityContext信息
let pathDir = context.filesDir;
let filePath = pathDir + '/test.txt';
// 设置文件的数据等级为s0
securityLabel.setSecurityLabel(filePath, 's0').then(() => {
console.info('Succeeded in setSecurityLabeling.');
}).catch((err) => {
console.error(`Failed to setSecurityLabel. Code: ${err.code}, message: ${err.message}`);
});
```
# 应用文件分享
应用文件分享是应用之间通过分享URI(Uniform Resource Identifier)或文件描述符FD(File Descriptor)的方式,进行文件共享的过程。由于FD分享的文件关闭FD后,无法再打开分享文件,因此不推荐使用,本文重点介绍URI分享方式。
- 基于URI分享方式,应用可分享单个文件,通过[ohos.app.ability.wantConstant](../reference/apis/js-apis-app-ability-wantConstant.md#wantconstantflags)的wantConstant.Flags接口以只读或读写权限授权给其他应用。应用可通过[ohos.file.fs](../reference/apis/js-apis-file-fs.md#fsopen)的open()接口打开URI,并进行读写操作。当前OpenHarmony API 9仅支持临时授权,分享给其他应用的文件在被分享应用退出时权限被收回。
- 基于FD分享方式,应用可分享单个文件,通过ohos.file.fs的open接口以指定权限授权给其他应用。应用从Want中解析拿到FD后可通过ohos.file.fs的读写接口对文件进行读写。
开发者可以使用相关接口,[分享文件给其他应用](#分享文件给其他应用)[使用其他应用分享的文件](#使用其他应用分享的文件)
## 文件URI规范
文件URI的格式为:
格式为file://&lt;bundleName&gt;/&lt;path&gt;/\#networkid=&lt;networkid&gt;
- file:文件URI的标志。
- bundleName:该文件资源的属主。
- path:文件资源在应用沙箱中的路径。
- networkid:为可选项,用于分布式文件系统标志该文件资源所归属的设备;当不需要区分文件位置时,该选项可不填写。
## 分享文件给其他应用
在分享文件给其他应用前,开发者需要先[获取应用文件路径](../application-models/application-context-stage.md#获取应用开发路径)
1. 获取文件在应用沙箱中的路径,并转换为文件URI。
```ts
import UIAbility from '@ohos.app.ability.UIAbility';
import fileuri from '@ohos.file.fileuri';
import window from '@ohos.window';
export default class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage: window.WindowStage) {
// 获取文件的沙箱路径
let pathInSandbox = this.context.filesDir + "/test.txt";
// 将沙箱路径转换为uri
let uri = fileuri.getUriFromPath(pathInSandbox);
// 获取的uri为"file://com.example.demo/data/storage/el2/base/files/test.txt"
}
}
```
2. 设置获取文件的权限以及选择要分享的应用。
分享文件给其他应用需要使用[startAbility()](../reference/apis/js-apis-inner-application-uiAbilityContext.md#uiabilitycontextstartability)接口,将获取到的URI填充在want的参数uri中,标注URI的文件类型,type字段可参考[Want属性](../reference/apis/js-apis-app-ability-want.md#属性),并通过设置want的flag来设置对应的读写权限,action字段配置为wantConstant.Action.ACTION_SEND_DATA表示进行应用文件分享,开发示例如下。
> **说明:**
>
> 写权限分享时,同时授予读权限。
```ts
import fileuri from '@ohos.file.fileuri';
import window from '@ohos.window';
import wantConstant from '@ohos.app.ability.wantConstant';
import UIAbility from '@ohos.app.ability.UIAbility';
export default class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage: window.WindowStage) {
// 获取文件沙箱路径
let filePath = this.context.filesDir + '/test.txt';
// 将沙箱路径转换为uri
let uri = fileuri.getUriFromPath(filePath);
let want = {
// 配置被分享文件的读写权限,例如对被分享应用进行读写授权
flags: wantConstant.Flags.FLAG_AUTH_WRITE_URI_PERMISSION | wantConstant.Flags.FLAG_AUTH_READ_URI_PERMISSION,
// 配置分享应用的隐式拉起规则
action: 'ohos.want.action.sendData',
uri: uri,
type: 'text/plain'
}
this.context.startAbility(want)
.then(() => {
console.info('Invoke getCurrentBundleStats succeeded.');
})
.catch((err) => {
console.error(`Invoke startAbility failed, code is ${err.code}, message is ${err.message}`);
});
}
...
}
```
## 使用其他应用分享的文件
被分享应用需要在[module.json5配置文件](../quick-start/module-configuration-file.md)的actions标签的值配置为"ohos.want.action.sendData",表示接收应用分享文件,配置uris字段,表示接收URI的类型,即只接收其他应用分享该类型的URI,如下表示本应用只接收scheme为file,类型为txt的文件,示例如下。
```json
{
"module": {
...
"abilities": [
{
...
"skills": [
{
...
"actions": [
"ohos.want.action.sendData"
],
"uris": [
{
"scheme": "file",
"type": "text/plain"
}
]
}
]
}
]
}
}
```
被分享方的UIAbility被启动后,可以在其[onCreate()](../reference/apis/js-apis-app-ability-uiAbility.md#uiabilityoncreate)或者[onNewWant()](../reference/apis/js-apis-app-ability-uiAbility.md#uiabilityonnewwant)回调中获取传入的Want参数信息。
通过接口want的参数获取分享文件的URI,获取文件URI后通过fs.open()接口打开文件,获取对应的file对象后,可对文件进行读写操作。
```ts
// xxx.ets
import fs from '@ohos.file.fs';
function getShareFile() {
try {
let want = ...; // 获取分享方传递过来的want信息
// 从want信息中获取uri字段
let uri = want.uri;
if (uri == null || uri == undefined) {
console.info('uri is invalid');
return;
}
try {
// 根据需要对被分享文件的URI进行相应操作。例如读写的方式打开URI获取file对象
let file = fs.openSync(uri, fs.OpenMode.READ_WRITE);
console.info('open file successfully!');
} catch (error) {
console.error(`Invoke openSync failed, code is ${error.code}, message is ${error.message}`);
}
} catch (error) {
console.error(`Invoke openSync failed, code is ${error.code}, message is ${error.message}`);
}
}
```
# 用户文件概述
用户文件:文件所有者为登录到该终端设备的用户,包括用户私有的图片、视频、音频、文档等。
1. 用户文件存放在用户目录下,归属于该设备上登录的用户。
2. 用户文件存储位置主要分为[内置存储](#内置存储)[外置存储](#外置存储)
3. 应用对用户文件的创建、访问、删除等行为,需要提前获取用户授权,或由用户操作完成。
OpenHarmony提供[用户文件访问框架](#用户文件访问框架),用于开发者访问和管理用户文件,将在下文详细介绍。
## 用户文件存储位置
### 内置存储
内置存储,是指用户文件存储在终端设备的内部存储设备(空间)上。内置存储设备无法被移除。内置存储的用户文件主要有:
- 用户特有的文件:这部分文件归属于登录该设备的用户,不同用户登录后,仅可看到该用户自己的文件。
按照这些文件的特征/属性,以及用户/应用的使用习惯,可分为:
- 图片/视频类媒体文件
所具有的特征包括拍摄时间、地点、旋转角度、文件宽高等信息,以媒体文件的形式存储在系统中,通常是以所有文件、相册的形式对外呈现,不会展示其在系统中存储的具体位置。
- 音频类媒体文件
所具有的特征包括所属专辑、音频创作者、持续时间等信息,以媒体文件的形式存储在系统中,通常会以所有文件、专辑、作家等形式对外部呈现,不会展示其在系统中存储的具体位置。
- 其他文件(统称为文档类文件)
以普通文件的形式存储在系统中,该类文件既包括普通的文本文件、压缩文件等,又包括以普通文件形式存储的图片/视频、音频文件,该类文件通常是以目录树的形式对外展示。
- 多用户共享的文件:用户可以通过将文件放在共享文件区,实现多个用户之间文件的共享访问。
共享文件区的文件,也是以普通文件的形式存储在系统中,以目录树的形式对外展示。
### 外置存储
外置存储,是指用户文件存储在外置可插拔设备上(如SD卡、U盘等)。外置存储设备上的文件,和内置存储设备共享区文件一样,可以被所有登录到系统中的用户看到。
外置存储设备具备可插拔属性,因此系统提供了设备插拔事件的监听及挂载功能,用于管理外置存储设备,具体可参考[管理外置存储设备(仅对系统应用开放)](manage-external-storage.md)
外置存储设备上的文件,全部以普通文件的形式呈现,和内置存储设备上的文档类文件一样,采用目录树的形式对外展示。
## 用户文件访问框架
用户文件访问框架(File Access Framework)是一套提供给开发者访问和管理用户文件的基础框架。该框架依托于OpenHarmony的ExtensionAbility组件机制,提供了一套统一访问用户文件的方法和接口。
**图1** 用户文件访问框架示意图  
![User file access framework](figures/user-file-access-framework.png)
- 各类系统应用或三方应用(即图中的文件访问客户端)若需访问用户文件,如选择一张照片或保存多个文档等,可以通过拉起“文件选择器应用”来实现。
- OpenHarmony系统预置了文件选择器应用FilePicker和文件管理器应用FileManager。
- FilePicker:系统预置应用,提供文件访问客户端选择和保存文件的能力,且不需要配置任何权限。FilePicker的使用指导请参见[选择用户文件](select-user-file.md)
- FileManager:系统预置应用,终端用户可通过系统文件管理器实现查看文件、修改文件、删除文件(目录)、重命名文件(目录)、移动文件(目录)、创建文件(目录)等操作。
对于系统应用开发者,还可以按需开发自己的文件选择器或文件管理器应用。其中,选择器功能是管理器的子集,本文目前提供了管理器的开发指导,请参见[开发用户文件管理器(仅对系统应用开放)](dev-user-file-manager.md)
- File Access Framework(用户文件访问框架)的主要功能模块如下:
- File Access Helper:提供给文件管理器和文件选择器访问用户文件的API接口。
- File Access ExtensionAbility:提供文件访问框架能力,由内卡文件管理服务UserFileManager和外卡文件管理服务ExternalFileManager组成,实现对应的文件访问功能。
- UserFileManager:内卡文件管理服务,基于File Access ExtensionAbility框架实现,用于管理内置存储设备上的文件。
- ExternalFileManager:外卡文件管理服务,基于File Access ExtensionAbility框架实现,用于管理外置存储设备上的文件。
# 媒体
- [媒体应用开发概述](media-application-overview.md)
- 音视频
- [音频开发概述](audio-overview.md)
- [音频渲染开发指导](audio-renderer.md)
- [音频流管理开发指导](audio-stream-manager.md)
- [音频采集开发指导](audio-capturer.md)
- [OpenSL ES播放开发指导](opensles-playback.md)
- [OpenSL ES录音开发指导](opensles-capture.md)
- [音频焦点模式开发指导](audio-interruptmode.md)
- [音量管理开发指导](audio-volume-manager.md)
- [路由、设备管理开发指导](audio-routing-manager.md)
- [音视频播放器开发指导(推荐使用)](avplayer-playback.md)
- [音视频录制开发指导(推荐使用)](avrecorder.md)
- [音频播放开发指导(待停用)](audio-playback.md)
- [音频录制开发指导(待停用)](audio-recorder.md)
- [视频播放开发指导(待停用)](video-playback.md)
- [视频录制开发指导(待停用)](video-recorder.md)
- 媒体会话
- [AVSession开发概述](avsession-overview.md)
- [AVSession开发指导](avsession-guidelines.md)
- [音视频概述](av-overview.md)
- [AVPlayer和AVRecorder](avplayer-avrecorder-overview.md)
- 音频播放
- [音频播放开发概述](audio-playback-overview.md)
- [使用AVPlayer开发音频播放功能](using-avplayer-for-playback.md)
- [使用AudioRenderer开发音频播放功能](using-audiorenderer-for-playback.md)
- [使用OpenSL ES开发音频播放功能](using-opensl-es-for-playback.md)
- [使用TonePlayer开发音频播放功能(仅对系统应用开放)](using-toneplayer-for-playback.md)
- [多音频播放的并发策略](audio-playback-concurrency.md)
- [播放音量管理](volume-management.md)
- [音频播放流管理](audio-playback-stream-management.md)
- [音频输出设备管理](audio-output-device-management.md)
- [分布式音频播放(仅对系统应用开放)](distributed-audio-playback.md)
- 音频录制
- [音频录制开发概述](audio-recording-overview.md)
- [使用AVRecorder开发音频录制功能](using-avrecorder-for-recording.md)
- [使用AudioCapturer开发音频录制功能](using-audiocapturer-for-recording.md)
- [使用OpenSLES开发音频录制功能](using-opensl-es-for-recording.md)
- [管理麦克风](mic-management.md)
- [音频录制流管理](audio-recording-stream-management.md)
- [音频输入设备管理](audio-input-device-management.md)
- 音频通话
- [音频通话开发概述](audio-call-overview.md)
- [开发音频通话功能](audio-call-development.md)
- [视频播放](video-playback.md)
- [视频录制](video-recording.md)
- 媒体会话(仅对系统应用开放)
- [媒体会话概述](avsession-overview.md)
- 本地媒体会话
- [本地媒体会话概述](local-avsession-overview.md)
- [媒体会话提供方](using-avsession-developer.md)
- [媒体会话控制方](using-avsession-controller.md)
- 分布式媒体会话
- [分布式媒体会话概述](distributed-avsession-overview.md)
- [使用分布式媒体会话](using-distributed-avsession.md)
- 相机(仅对系统应用开放)
- [相机开发概述](camera-overview.md)
- 相机开发指导
- [开发准备](camera-preparation.md)
- [设备输入](camera-device-input.md)
- [会话管理](camera-session-management.md)
- [预览](camera-preview.md)
- [拍照](camera-shooting.md)
- [录像](camera-recording.md)
- [元数据](camera-metadata.md)
- 相机最佳实践
- [拍照实现方案](camera-shooting-case.md)
- [录像实现方案](camera-recording-case.md)
- 图片
- [图片开发指导](image.md)
- 相机
- [相机开发指导](camera.md)
- [分布式相机开发指导](remote-camera.md)
\ No newline at end of file
- [图片开发概述](image-overview.md)
- [图片解码](image-decoding.md)
- 图片处理
- [图像变换](image-transformation.md)
- [位图操作](image-pixelmap-operation.md)
- [图片编码](image-encoding.md)
- [图片工具](image-tool.md)
# 开发音频通话功能
在音频通话场景下,音频输出(播放对端声音)和音频输入(录制本端声音)会同时进行,应用可以通过使用AudioRenderer来实现音频输出,通过使用AudioCapturer来实现音频输入,同时使用AudioRenderer和AudioCapturer即可实现音频通话功能。
在音频通话开始和结束时,应用可以自行检查当前的[音频场景模式](audio-call-overview.md#音频场景模式)[铃声模式](audio-call-overview.md#铃声模式),以便采取合适的音频管理及提示策略。
以下代码示范了同时使用AudioRenderer和AudioCapturer实现音频通话功能的基本过程,其中未包含音频通话数据的传输过程,实际开发中,需要将网络传输来的对端通话数据解码播放,此处仅以读取音频文件的数据代替;同时需要将本端录制的通话数据编码打包,通过网络发送给对端,此处仅以将数据写入音频文件代替。
## 使用AudioRenderer播放对端的通话声音
该过程与[使用AudioRenderer开发音频播放功能](using-audiorenderer-for-playback.md)过程相似,关键区别在于audioRenderInfo参数和音频数据来源。audioRenderInfo参数中,音频内容类型需设置为语音,CONTENT_TYPE_SPEECH,音频流使用类型需设置为语音通信,STREAM_USAGE_VOICE_COMMUNICATION。
```ts
import audio from '@ohos.multimedia.audio';
import fs from '@ohos.file.fs';
const TAG = 'VoiceCallDemoForAudioRenderer';
// 与使用AudioRenderer开发音频播放功能过程相似,关键区别在于audioRenderInfo参数和音频数据来源
export default class VoiceCallDemoForAudioRenderer {
private renderModel = undefined;
private audioStreamInfo = {
samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_48000, // 采样率
channels: audio.AudioChannel.CHANNEL_2, // 通道数
sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, // 采样格式
encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW // 编码格式
}
private audioRendererInfo = {
// 需使用通话场景相应的参数
content: audio.ContentType.CONTENT_TYPE_SPEECH, // 音频内容类型:语音
usage: audio.StreamUsage.STREAM_USAGE_VOICE_COMMUNICATION, // 音频流使用类型:语音通信
rendererFlags: 0 // 音频渲染器标志:默认为0即可
}
private audioRendererOptions = {
streamInfo: this.audioStreamInfo,
rendererInfo: this.audioRendererInfo
}
// 初始化,创建实例,设置监听事件
init() {
audio.createAudioRenderer(this.audioRendererOptions, (err, renderer) => { // 创建AudioRenderer实例
if (!err) {
console.info(`${TAG}: creating AudioRenderer success`);
this.renderModel = renderer;
this.renderModel.on('stateChange', (state) => { // 设置监听事件,当转换到指定的状态时触发回调
if (state == 1) {
console.info('audio renderer state is: STATE_PREPARED');
}
if (state == 2) {
console.info('audio renderer state is: STATE_RUNNING');
}
});
this.renderModel.on('markReach', 1000, (position) => { // 订阅markReach事件,当渲染的帧数达到1000帧时触发回调
if (position == 1000) {
console.info('ON Triggered successfully');
}
});
} else {
console.info(`${TAG}: creating AudioRenderer failed, error: ${err.message}`);
}
});
}
// 开始一次音频渲染
async start() {
let stateGroup = [audio.AudioState.STATE_PREPARED, audio.AudioState.STATE_PAUSED, audio.AudioState.STATE_STOPPED];
if (stateGroup.indexOf(this.renderModel.state) === -1) { // 当且仅当状态为prepared、paused和stopped之一时才能启动渲染
console.error(TAG + 'start failed');
return;
}
await this.renderModel.start(); // 启动渲染
const bufferSize = await this.renderModel.getBufferSize();
// 此处仅以读取音频文件的数据举例,实际音频通话开发中,需要读取的是通话对端传输来的音频数据
let context = getContext(this);
let path = context.filesDir;
const filePath = path + '/voice_call_data.wav'; // 沙箱路径,实际路径为/data/storage/el2/base/haps/entry/files/voice_call_data.wav
let file = fs.openSync(filePath, fs.OpenMode.READ_ONLY);
let stat = await fs.stat(filePath);
let buf = new ArrayBuffer(bufferSize);
let len = stat.size % bufferSize === 0 ? Math.floor(stat.size / bufferSize) : Math.floor(stat.size / bufferSize + 1);
for (let i = 0; i < len; i++) {
let options = {
offset: i * bufferSize,
length: bufferSize
};
let readsize = await fs.read(file.fd, buf, options);
// buf是要写入缓冲区的音频数据,在调用AudioRenderer.write()方法前可以进行音频数据的预处理,实现个性化的音频播放功能,AudioRenderer会读出写入缓冲区的音频数据进行渲染
let writeSize = await new Promise((resolve, reject) => {
this.renderModel.write(buf, (err, writeSize) => {
if (err) {
reject(err);
} else {
resolve(writeSize);
}
});
});
if (this.renderModel.state === audio.AudioState.STATE_RELEASED) { // 如果渲染器状态为released,停止渲染
fs.close(file);
await this.renderModel.stop();
}
if (this.renderModel.state === audio.AudioState.STATE_RUNNING) {
if (i === len - 1) { // 如果音频文件已经被读取完,停止渲染
fs.close(file);
await this.renderModel.stop();
}
}
}
}
// 暂停渲染
async pause() {
// 只有渲染器状态为running的时候才能暂停
if (this.renderModel.state !== audio.AudioState.STATE_RUNNING) {
console.info('Renderer is not running');
return;
}
await this.renderModel.pause(); // 暂停渲染
if (this.renderModel.state === audio.AudioState.STATE_PAUSED) {
console.info('Renderer is paused.');
} else {
console.error('Pausing renderer failed.');
}
}
// 停止渲染
async stop() {
// 只有渲染器状态为running或paused的时候才可以停止
if (this.renderModel.state !== audio.AudioState.STATE_RUNNING && this.renderModel.state !== audio.AudioState.STATE_PAUSED) {
console.info('Renderer is not running or paused.');
return;
}
await this.renderModel.stop(); // 停止渲染
if (this.renderModel.state === audio.AudioState.STATE_STOPPED) {
console.info('Renderer stopped.');
} else {
console.error('Stopping renderer failed.');
}
}
// 销毁实例,释放资源
async release() {
// 渲染器状态不是released状态,才能release
if (this.renderModel.state === audio.AudioState.STATE_RELEASED) {
console.info('Renderer already released');
return;
}
await this.renderModel.release(); // 释放资源
if (this.renderModel.state === audio.AudioState.STATE_RELEASED) {
console.info('Renderer released');
} else {
console.error('Renderer release failed.');
}
}
}
```
## 使用AudioCapturer录制本端的通话声音
该过程与[使用AudioCapturer开发音频录制功能](using-audiocapturer-for-recording.md)过程相似,关键区别在于audioCapturerInfo参数和音频数据流向。audioCapturerInfo参数中音源类型需设置为语音通话,SOURCE_TYPE_VOICE_COMMUNICATION。
```ts
import audio from '@ohos.multimedia.audio';
import fs from '@ohos.file.fs';
const TAG = 'VoiceCallDemoForAudioCapturer';
// 与使用AudioCapturer开发音频录制功能过程相似,关键区别在于audioCapturerInfo参数和音频数据流向
export default class VoiceCallDemoForAudioCapturer {
private audioCapturer = undefined;
private audioStreamInfo = {
samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_44100, // 采样率
channels: audio.AudioChannel.CHANNEL_1, // 通道数
sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, // 采样格式
encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW // 编码格式
}
private audioCapturerInfo = {
// 需使用通话场景相应的参数
source: audio.SourceType.SOURCE_TYPE_VOICE_COMMUNICATION, // 音源类型:语音通话
capturerFlags: 0 // 音频采集器标志:默认为0即可
}
private audioCapturerOptions = {
streamInfo: this.audioStreamInfo,
capturerInfo: this.audioCapturerInfo
}
// 初始化,创建实例,设置监听事件
init() {
audio.createAudioCapturer(this.audioCapturerOptions, (err, capturer) => { // 创建AudioCapturer实例
if (err) {
console.error(`Invoke createAudioCapturer failed, code is ${err.code}, message is ${err.message}`);
return;
}
console.info(`${TAG}: create AudioCapturer success`);
this.audioCapturer = capturer;
this.audioCapturer.on('markReach', 1000, (position) => { // 订阅markReach事件,当采集的帧数达到1000时触发回调
if (position === 1000) {
console.info('ON Triggered successfully');
}
});
this.audioCapturer.on('periodReach', 2000, (position) => { // 订阅periodReach事件,当采集的帧数达到2000时触发回调
if (position === 2000) {
console.info('ON Triggered successfully');
}
});
});
}
// 开始一次音频采集
async start() {
let stateGroup = [audio.AudioState.STATE_PREPARED, audio.AudioState.STATE_PAUSED, audio.AudioState.STATE_STOPPED];
if (stateGroup.indexOf(this.audioCapturer.state) === -1) { // 当且仅当状态为prepared、paused和stopped之一时才能启动采集
console.error(`${TAG}: start failed`);
return;
}
await this.audioCapturer.start(); // 启动采集
// 此处仅以将音频数据写入文件举例,实际音频通话开发中,需要将本端采集的音频数据编码打包,通过网络发送给通话对端
let context = getContext(this);
const path = context.filesDir + '/voice_call_data.wav'; // 采集到的音频文件存储路径
let file = fs.openSync(path, 0o2 | 0o100); // 如果文件不存在则创建文件
let fd = file.fd;
let numBuffersToCapture = 150; // 循环写入150次
let count = 0;
while (numBuffersToCapture) {
let bufferSize = await this.audioCapturer.getBufferSize();
let buffer = await this.audioCapturer.read(bufferSize, true);
let options = {
offset: count * bufferSize,
length: bufferSize
};
if (buffer === undefined) {
console.error(`${TAG}: read buffer failed`);
} else {
let number = fs.writeSync(fd, buffer, options);
console.info(`${TAG}: write date: ${number}`);
}
numBuffersToCapture--;
count++;
}
}
// 停止采集
async stop() {
// 只有采集器状态为STATE_RUNNING或STATE_PAUSED的时候才可以停止
if (this.audioCapturer.state !== audio.AudioState.STATE_RUNNING && this.audioCapturer.state !== audio.AudioState.STATE_PAUSED) {
console.info('Capturer is not running or paused');
return;
}
await this.audioCapturer.stop(); // 停止采集
if (this.audioCapturer.state === audio.AudioState.STATE_STOPPED) {
console.info('Capturer stopped');
} else {
console.error('Capturer stop failed');
}
}
// 销毁实例,释放资源
async release() {
// 采集器状态不是STATE_RELEASED或STATE_NEW状态,才能release
if (this.audioCapturer.state === audio.AudioState.STATE_RELEASED || this.audioCapturer.state === audio.AudioState.STATE_NEW) {
console.info('Capturer already released');
return;
}
await this.audioCapturer.release(); // 释放资源
if (this.audioCapturer.state == audio.AudioState.STATE_RELEASED) {
console.info('Capturer released');
} else {
console.error('Capturer release failed');
}
}
}
```
# 音频通话开发概述
常用的音频通话模式包括VOIP通话和蜂窝通话。
- VOIP通话:
VOIP(Voice over Internet Protocol)通话是指基于互联网协议(IP)进行通讯的一种语音通话技术。VOIP通话会将通话信息打包成数据包,通过网络进行传输,因此VOIP通话对网络要求较高,通话质量与网络连接速度紧密相关。
- 蜂窝通话(仅对系统应用开放)
蜂窝通话是指传统的电话功能,由运营商提供服务,目前仅对系统应用开放,未向第三方应用提供开发接口。
在开发音频通话相关功能时,开发者可以根据实际情况,检查当前的[音频场景模式](#音频场景模式)[铃声模式](#铃声模式),以使用相应的音频处理策略。
## 音频场景模式
应用使用音频通话相关功能时,系统会切换至与通话相关的音频场景模式([AudioScene](../reference/apis/js-apis-audio.md#audioscene8)),当前预置了多种音频场景,包括响铃、通话、语音聊天等,在不同的场景下,系统会采用不同的策略来处理音频。
如在蜂窝通话场景中会更注重人声的清晰度。系统会使用3A算法对音频数据进行预处理,抑制通话回声,消除背景噪音,调整音量范围,从而达到清晰人声的效果。3A算法,指声学回声消除(Acoustic Echo Cancelling, AEC)、背景噪声抑制(Acitve Noise Control, ANC)、自动增益控制(Automatic Gain Control, AGC)三种音频处理算法。
当前预置的四种音频场景:
- AUDIO_SCENE_DEFAULT:默认音频场景,音频通话之外的场景均可使用。
- AUDIO_SCENE_RINGING:响铃音频场景,来电响铃时使用,仅对系统应用开放。
- AUDIO_SCENE_PHONE_CALL:蜂窝通话音频场景,蜂窝通话时使用,仅对系统应用开放。
- AUDIO_SCENE_VOICE_CHAT:语音聊天音频场景,VOIP通话时使用。
应用可通过[AudioManager](../reference/apis/js-apis-audio.md#audiomanager)的getAudioScene来获取当前的音频场景模式。当应用开始或结束使用音频通话相关功能时,可通过此方法检查系统是否已切换为合适的音频场景模式。
## 铃声模式
在用户进入到音频通话时,应用可以使用铃声或振动来提示用户。系统通过调整铃声模式([AudioRingMode](../reference/apis/js-apis-audio.md#audioringmode)),实现便捷地管理铃声音量,并调整设备的振动模式。
当前预置的三种铃声模式:
- RINGER_MODE_SILENT:静音模式,此模式下铃声音量为零(即静音)。
- RINGER_MODE_VIBRATE:振动模式,此模式下铃声音量为零,设备振动开启(即响铃时静音,触发振动)。
- RINGER_MODE_NORMAL:响铃模式,此模式下铃声音量正常。
应用可以调用[AudioVolumeGroupManager](../reference/apis/js-apis-audio.md#audiovolumegroupmanager9)中的getRingerMode获取当前的铃声模式,以便采取合适的提示策略。
如果应用希望及时获取铃声模式的变化情况,可以通过AudioVolumeGroupManager中的on('ringerModeChange')监听铃声模式变化事件,使应用在铃声模式发生变化时及时收到通知,方便应用做出相应的调整。
## 通话场景音频设备切换
在通话场景下,系统会根据默认优先级选择合适的音频设备。应用可以根据需要,自主切换音频设备。
通信设备类型([CommunicationDeviceType](../reference/apis/js-apis-audio.md#communicationdevicetype9))是系统预置的可用于通话场景的设备,应用可以使用[AudioRoutingManager](../reference/apis/js-apis-audio.md#audioroutingmanager9)的isCommunicationDeviceActive函数获取指定通信设备的激活状态,并且可以使用AudioRoutingManager的setCommunicationDevice设置通信设备的激活状态,通过激活设备来实现通话场景音频设备的切换。
# 音频采集开发指导
## 简介
AudioCapturer提供了用于获取原始音频文件的方法。开发者可以通过本指导了解应用如何通过AudioCapturer接口的调用实现音频数据的采集。
- **状态检查**:在进行应用开发的过程中,建议开发者通过on('stateChange')方法订阅AudioCapturer的状态变更。因为针对AudioCapturer的某些操作,仅在音频采集器在固定状态时才能执行。如果应用在音频采集器处于错误状态时执行操作,系统可能会抛出异常或生成其他未定义的行为。
## 运作机制
该模块提供了音频采集模块的状态变化示意图。
**图1** 音频采集状态变化示意图
![audio-capturer-state](figures/audio-capturer-state.png)
**PREPARED状态:** 通过调用create()方法进入到该状态。<br>
**RUNNING状态:** 正在进行音频数据播放,可以在prepared状态通过调用start()方法进入此状态,也可以在stopped状态通过调用start()方法进入此状态。<br>
**STOPPED状态:** 在running状态可以通过stop()方法停止音频数据的播放。<br>
**RELEASED状态:** 在prepared和stop状态,用户均可通过release()方法释放掉所有占用的硬件和软件资源,并且不会再进入到其他的任何一种状态了。<br>
## 约束与限制
开发者在进行音频数据采集功能开发前,需要先对所开发的应用配置麦克风权限(ohos.permission.MICROPHONE),配置方式请参见[访问控制授权申请](../security/accesstoken-guidelines.md#配置文件权限声明)
## 开发指导
详细API含义可参考:[音频管理API文档AudioCapturer](../reference/apis/js-apis-audio.md#audiocapturer8)
1. 使用createAudioCapturer()创建一个全局的AudioCapturer实例。
在audioCapturerOptions中设置音频采集器的相关参数。该实例可用于音频采集、控制和获取采集状态,以及注册通知回调。
```js
import audio from '@ohos.multimedia.audio';
import fs from '@ohos.file.fs'; //便于步骤3 read函数调用
//音频渲染相关接口自测试
@Entry
@Component
struct AudioRenderer {
@State message: string = 'Hello World'
private audioCapturer : audio.AudioCapturer; //供全局调用
async initAudioCapturer(){
let audioStreamInfo = {
samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_44100,
channels: audio.AudioChannel.CHANNEL_1,
sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE,
encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW
}
let audioCapturerInfo = {
source: audio.SourceType.SOURCE_TYPE_MIC,
capturerFlags: 0 // 0是音频采集器的扩展标志位,默认为0
}
let audioCapturerOptions = {
streamInfo: audioStreamInfo,
capturerInfo: audioCapturerInfo
}
this.audioCapturer = await audio.createAudioCapturer(audioCapturerOptions);
console.log('AudioRecLog: Create audio capturer success.');
}
```
2. 调用start()方法来启动/恢复采集任务。
启动完成后,采集器状态将变更为STATE_RUNNING,然后应用可以开始读取缓冲区。
```js
async startCapturer() {
let state = this.audioCapturer.state;
// Capturer start时的状态应该是STATE_PREPARED、STATE_PAUSED和STATE_STOPPED之一.
if (state == audio.AudioState.STATE_PREPARED || state == audio.AudioState.STATE_PAUSED ||
state == audio.AudioState.STATE_STOPPED) {
await this.audioCapturer.start();
state = this.audioCapturer.state;
if (state == audio.AudioState.STATE_RUNNING) {
console.info('AudioRecLog: Capturer started');
} else {
console.error('AudioRecLog: Capturer start failed');
}
}
}
```
3. 读取采集器的音频数据并将其转换为字节流。重复调用read()方法读取数据,直到应用准备停止采集。
参考以下示例,将采集到的数据写入文件。
```js
async readData(){
let state = this.audioCapturer.state;
// 只有状态为STATE_RUNNING的时候才可以read.
if (state != audio.AudioState.STATE_RUNNING) {
console.info('Capturer is not in a correct state to read');
return;
}
const path = '/data/data/.pulse_dir/capture_js.wav'; // 采集到的音频文件存储路径
let file = fs.openSync(path, 0o2);
let fd = file.fd;
if (file !== null) {
console.info('AudioRecLog: file created');
} else {
console.info('AudioRecLog: file create : FAILED');
return;
}
if (fd !== null) {
console.info('AudioRecLog: file fd opened in append mode');
}
let numBuffersToCapture = 150; // 循环写入150次
let count = 0;
while (numBuffersToCapture) {
this.bufferSize = await this.audioCapturer.getBufferSize();
let buffer = await this.audioCapturer.read(this.bufferSize, true);
let options = {
offset: count * this.bufferSize,
length: this.bufferSize
}
if (typeof(buffer) == undefined) {
console.info('AudioRecLog: read buffer failed');
} else {
let number = fs.writeSync(fd, buffer, options);
console.info(`AudioRecLog: data written: ${number}`);
}
numBuffersToCapture--;
count++;
}
}
```
4. 采集完成后,调用stop方法,停止录制。
```js
async StopCapturer() {
let state = this.audioCapturer.state;
// 只有采集器状态为STATE_RUNNING或STATE_PAUSED的时候才可以停止
if (state != audio.AudioState.STATE_RUNNING && state != audio.AudioState.STATE_PAUSED) {
console.info('AudioRecLog: Capturer is not running or paused');
return;
}
await this.audioCapturer.stop();
state = this.audioCapturer.state;
if (state == audio.AudioState.STATE_STOPPED) {
console.info('AudioRecLog: Capturer stopped');
} else {
console.error('AudioRecLog: Capturer stop failed');
}
}
```
5. 任务结束,调用release()方法释放相关资源。
```js
async releaseCapturer() {
let state = this.audioCapturer.state;
// 采集器状态不是STATE_RELEASED或STATE_NEW状态,才能release
if (state == audio.AudioState.STATE_RELEASED || state == audio.AudioState.STATE_NEW) {
console.info('AudioRecLog: Capturer already released');
return;
}
await this.audioCapturer.release();
state = this.audioCapturer.state;
if (state == audio.AudioState.STATE_RELEASED) {
console.info('AudioRecLog: Capturer released');
} else {
console.info('AudioRecLog: Capturer release failed');
}
}
```
6. (可选)获取采集器相关信息
通过以下代码,可以获取采集器的相关信息。
```js
async getAudioCapturerInfo(){
// 获取当前采集器状态
let state = this.audioCapturer.state;
// 获取采集器信息
let audioCapturerInfo : audio.AudioCapturerInfo = await this.audioCapturer.getCapturerInfo();
// 获取音频流信息
let audioStreamInfo : audio.AudioStreamInfo = await this.audioCapturer.getStreamInfo();
// 获取音频流ID
let audioStreamId : number = await this.audioCapturer.getAudioStreamId();
// 获取纳秒形式的Unix时间戳
let audioTime : number = await this.audioCapturer.getAudioTime();
// 获取合理的最小缓冲区大小
let bufferSize : number = await this.audioCapturer.getBufferSize();
}
```
7. (可选)使用on('markReach')方法订阅采集器标记到达事件,使用off('markReach')取消订阅事件。
注册markReach监听后,当采集器采集的帧数到达设定值时,会触发回调并返回设定的值。
```js
async markReach(){
this.audioCapturer.on('markReach', 10, (reachNumber) => {
console.info('Mark reach event Received');
console.info(`The Capturer reached frame: ${reachNumber}`);
});
this.audioCapturer.off('markReach'); // 取消markReach事件的订阅,后续将无法监听到“标记到达”事件
}
```
8. (可选)使用on('periodReach')方法订阅采集器区间标记到达事件,使用off('periodReach')取消订阅事件。
注册periodReach监听后,**每当**采集器采集的帧数到达设定值时,会触发回调并返回设定的值。
```js
async periodReach(){
this.audioCapturer.on('periodReach', 10, (reachNumber) => {
console.info('Period reach event Received');
console.info(`In this period, the Capturer reached frame: ${reachNumber}`);
});
this.audioCapturer.off('periodReach'); // 取消periodReach事件的订阅,后续将无法监听到“区间标记到达”事件
}
```
9. 如果应用需要在采集器状态更新时进行一些操作,可以订阅该事件,当采集器状态更新时,会受到一个包含有事件类型的回调。
```js
async stateChange(){
this.audioCapturer.on('stateChange', (state) => {
console.info(`AudioCapturerLog: Changed State to : ${state}`)
switch (state) {
case audio.AudioState.STATE_PREPARED:
console.info('--------CHANGE IN AUDIO STATE----------PREPARED--------------');
console.info('Audio State is : Prepared');
break;
case audio.AudioState.STATE_RUNNING:
console.info('--------CHANGE IN AUDIO STATE----------RUNNING--------------');
console.info('Audio State is : Running');
break;
case audio.AudioState.STATE_STOPPED:
console.info('--------CHANGE IN AUDIO STATE----------STOPPED--------------');
console.info('Audio State is : stopped');
break;
case audio.AudioState.STATE_RELEASED:
console.info('--------CHANGE IN AUDIO STATE----------RELEASED--------------');
console.info('Audio State is : released');
break;
default:
console.info('--------CHANGE IN AUDIO STATE----------INVALID--------------');
console.info('Audio State is : invalid');
break;
}
});
}
```
\ No newline at end of file
# 音频输入设备管理
有时设备同时连接多个音频输入设备,需要指定音频输入设备进行音频录制,此时需要使用AudioRoutingManager接口进行输入设备的管理,API说明可以参考[AudioRoutingManager API文档](../reference/apis/js-apis-audio.md#audioroutingmanager9)
## 创建AudioRoutingManager实例
在使用AudioRoutingManager管理音频设备前,需要先导入模块并创建实例。
```ts
import audio from '@ohos.multimedia.audio'; // 导入audio模块
let audioManager = audio.getAudioManager(); // 需要先创建AudioManager实例
let audioRoutingManager = audioManager.getRoutingManager(); // 再调用AudioManager的方法创建AudioRoutingManager实例
```
## 支持的音频输入设备类型
目前支持的音频输入设备见下表:
| 名称 | 值 | 说明 |
| -------- | -------- | -------- |
| WIRED_HEADSET | 3 | 有线耳机,带麦克风。 |
| BLUETOOTH_SCO | 7 | 蓝牙设备SCO(Synchronous Connection Oriented)连接。 |
| MIC | 15 | 麦克风。 |
| USB_HEADSET | 22 | USB耳机,带麦克风。 |
## 获取输入设备信息
使用getDevices()方法可以获取当前所有输入设备的信息。
```ts
audioRoutingManager.getDevices(audio.DeviceFlag.INPUT_DEVICES_FLAG).then((data) => {
console.info('Promise returned to indicate that the device list is obtained.');
});
```
## 监听设备连接状态变化
可以设置监听事件来监听设备连接状态的变化,当有设备连接或断开时触发回调:
```ts
// 监听音频设备状态变化
audioRoutingManager.on('deviceChange', audio.DeviceFlag.INPUT_DEVICES_FLAG, (deviceChanged) => {
console.info('device change type : ' + deviceChanged.type); // 设备连接状态变化,0为连接,1为断开连接
console.info('device descriptor size : ' + deviceChanged.deviceDescriptors.length);
console.info('device change descriptor : ' + deviceChanged.deviceDescriptors[0].deviceRole); // 设备角色
console.info('device change descriptor : ' + deviceChanged.deviceDescriptors[0].deviceType); // 设备类型
});
// 取消监听音频设备状态变化
audioRoutingManager.off('deviceChange', (deviceChanged) => {
console.info('Should be no callback.');
});
```
## 选择音频输入设备(仅对系统应用开放)
选择音频输入设备,当前只能选择一个输入设备,以设备id作为唯一标识。AudioDeviceDescriptors的具体信息可以参考[AudioDeviceDescriptors](../reference/apis/js-apis-audio.md#audiodevicedescriptors)
> **说明:**
>
> 用户可以选择连接一组音频设备(如一对蓝牙耳机),但系统侧只感知为一个设备,该组设备共用一个设备id。
```ts
let inputAudioDeviceDescriptor = [{
deviceRole : audio.DeviceRole.INPUT_DEVICE,
deviceType : audio.DeviceType.EARPIECE,
id : 1,
name : "",
address : "",
sampleRates : [44100],
channelCounts : [2],
channelMasks : [0],
networkId : audio.LOCAL_NETWORK_ID,
interruptGroupId : 1,
volumeGroupId : 1,
}];
async function getRoutingManager(){
audioRoutingManager.selectInputDevice(inputAudioDeviceDescriptor).then(() => {
console.info('Invoke selectInputDevice succeeded.');
}).catch((err) => {
console.error(`Invoke selectInputDevice failed, code is ${err.code}, message is ${err.message}`);
});
}
```
# 音频焦点模式开发指导
## 简介
音频焦点模式指的是应用内,允许对多个声音的播放进行控制。<br>
音频应用可以在AudioRenderer下设置独立焦点模式、共享焦点模式。<br>
当设置在共享的模式下,多个音频共用一个会话ID;独立焦点模式下,每一个音频拥有单独会话ID。
- **异步操作**:为保证UI线程不被阻塞,大部分AudioRenderer调用都是异步的。对于每个API均提供了callback函数和Promise函数,以下示例均采用Promise函数。
## 开发指导
详细API含义可参考:[音频管理API文档AudioRenderer](../reference/apis/js-apis-audio.md#audiorenderer8)
1. 使用createAudioRenderer()创建一个AudioRenderer实例。<br>
在audioRendererOptions中设置相关参数。<br>
该实例可用于音频渲染、控制和获取采集状态,以及注册通知回调。<br>
```js
import audio from '@ohos.multimedia.audio';
var audioStreamInfo = {
samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_44100,
channels: audio.AudioChannel.CHANNEL_1,
sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE,
encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW
}
var audioRendererInfo = {
content: audio.ContentType.CONTENT_TYPE_SPEECH,
usage: audio.StreamUsage.STREAM_USAGE_VOICE_COMMUNICATION,
rendererFlags: 1
}
var audioRendererOptions = {
streamInfo: audioStreamInfo,
rendererInfo: audioRendererInfo
}
let audioRenderer = await audio.createAudioRenderer(audioRendererOptions);
```
2. 设置焦点模式。
在AudioRenderer初始化完毕后,可以进行焦点模式的设置。<br>
```js
var mode_ = audio.InterruptMode.SHARE_MODE;
await this.audioRenderer.setInterruptMode(mode_).then(() => {
console.log('[JSAR] [SetInterruptMode] 设置: ' + (mode_ == 0 ? "共享模式":"独立焦点模式") + "成功" );
});
```
# 音频输出设备管理
有时设备同时连接多个音频输出设备,需要指定音频输出设备进行音频播放,此时需要使用AudioRoutingManager接口进行输出设备的管理,API说明可以参考[AudioRoutingManager API文档](../reference/apis/js-apis-audio.md#audiomanager)
## 创建AudioRoutingManager实例
在使用AudioRoutingManager管理音频设备前,需要先导入模块并创建实例。
```ts
import audio from '@ohos.multimedia.audio'; // 导入audio模块
let audioManager = audio.getAudioManager(); // 需要先创建AudioManager实例
let audioRoutingManager = audioManager.getRoutingManager(); // 再调用AudioManager的方法创建AudioRoutingManager实例
```
## 支持的音频输出设备类型
目前支持的音频输出设备见下表:
| 名称 | 值 | 说明 |
| -------- | -------- | -------- |
| EARPIECE | 1 | 听筒。 |
| SPEAKER | 2 | 扬声器。 |
| WIRED_HEADSET | 3 | 有线耳机,带麦克风。 |
| WIRED_HEADPHONES | 4 | 有线耳机,无麦克风。 |
| BLUETOOTH_SCO | 7 | 蓝牙设备SCO(Synchronous&nbsp;Connection&nbsp;Oriented)连接。 |
| BLUETOOTH_A2DP | 8 | 蓝牙设备A2DP(Advanced&nbsp;Audio&nbsp;Distribution&nbsp;Profile)连接。 |
| USB_HEADSET | 22 | USB耳机,带麦克风。 |
## 获取输出设备信息
使用getDevices()方法可以获取当前所有输出设备的信息。
```ts
audioRoutingManager.getDevices(audio.DeviceFlag.OUTPUT_DEVICES_FLAG).then((data) => {
console.info('Promise returned to indicate that the device list is obtained.');
});
```
## 监听设备连接状态变化
可以设置监听事件来监听设备连接状态的变化,当有设备连接或断开时触发回调:
```ts
// 监听音频设备状态变化
audioRoutingManager.on('deviceChange', audio.DeviceFlag.OUTPUT_DEVICES_FLAG, (deviceChanged) => {
console.info('device change type : ' + deviceChanged.type); // 设备连接状态变化,0为连接,1为断开连接
console.info('device descriptor size : ' + deviceChanged.deviceDescriptors.length);
console.info('device change descriptor : ' + deviceChanged.deviceDescriptors[0].deviceRole); // 设备角色
console.info('device change descriptor : ' + deviceChanged.deviceDescriptors[0].deviceType); // 设备类型
});
// 取消监听音频设备状态变化
audioRoutingManager.off('deviceChange', (deviceChanged) => {
console.info('Should be no callback.');
});
```
## 选择音频输出设备(仅对系统应用开放)
选择音频输出设备,当前只能选择一个输出设备,以设备ID作为唯一标识。AudioDeviceDescriptors的具体信息可以参考[AudioDeviceDescriptors](../reference/apis/js-apis-audio.md#audiodevicedescriptors)
> **说明:**
>
> 用户可以选择连接一组音频设备(如一对蓝牙耳机),但系统侧只感知为一个设备,该组设备共用一个设备ID。
```ts
let outputAudioDeviceDescriptor = [{
deviceRole : audio.DeviceRole.OUTPUT_DEVICE,
deviceType : audio.DeviceType.SPEAKER,
id : 1,
name : "",
address : "",
sampleRates : [44100],
channelCounts : [2],
channelMasks : [0],
networkId : audio.LOCAL_NETWORK_ID,
interruptGroupId : 1,
volumeGroupId : 1,
}];
async function selectOutputDevice(){
audioRoutingManager.selectOutputDevice(outputAudioDeviceDescriptor).then(() => {
console.info('Invoke selectOutputDevice succeeded.');
}).catch((err) => {
console.error(`Invoke selectOutputDevice failed, code is ${err.code}, message is ${err.message}`);
});
}
```
# 音频开发概述
音频模块支持音频业务的开发,提供音频相关的功能,主要包括音频播放、音量管理等。
## 基本概念
- **采样**<br/>
采样是指将连续时域上的模拟信号按照一定的时间间隔采样,获取到离散时域上离散信号的过程。
- **采样率**<br/>
采样率为每秒从连续信号中提取并组成离散信号的采样次数,单位用赫兹(Hz)来表示。通常人耳能听到频率范围大约在20Hz~20kHz之间的声音。常用的音频采样频率有:8kHz、11.025kHz、22.05kHz、16kHz、37.8kHz、44.1kHz、48kHz、96kHz、192kHz等。
- **声道**<br/>
声道是指声音在录制或播放时在不同空间位置采集或回放的相互独立的音频信号,所以声道数也就是声音录制时的音源数量或回放时相应的扬声器数量。
- **音频帧**<br/>
音频数据是流式的,本身没有明确的一帧帧的概念,在实际的应用中,为了音频算法处理/传输的方便,一般约定俗成取2.5ms~60ms为单位的数据量为一帧音频。这个时间被称之为“采样时间”,其长度没有特别的标准,它是根据编解码器和具体应用的需求来决定的。
- **PCM**<br/>
PCM(Pulse Code Modulation),即脉冲编码调制,是一种将模拟信号数字化的方法,是将时间连续、取值连续的模拟信号转换成时间离散、抽样值离散的数字信号的过程。
# 多音频播放的并发策略
## 音频打断策略
多音频并发,即多个音频流同时播放。此场景下,如果系统不加管控,会造成多个音频流混音播放,容易让用户感到嘈杂,造成不好的用户体验。为了解决这个问题,系统预设了音频打断策略,对多音频播放的并发进行管控,只有持有音频焦点的音频流才可以正常播放,避免多个音频流无序并发播放的现象出现。
当应用开始播放音频时,系统首先为相应的音频流申请音频焦点,获得焦点的音频流可以播放;若焦点申请被拒绝,则不能播放。在音频流播放的过程中,若被其他音频流打断,则会失去音频焦点。当音频流失去音频焦点时,只能暂停播放。在应用播放音频的过程中,这些动作均由系统自行完成,无需应用主动触发。但为了维持应用和系统的状态一致性,保证良好的用户体验,推荐应用[监听音频打断事件](#监听音频打断事件),并在收到音频打断事件([InterruptEvent](../reference/apis/js-apis-audio.md#interruptevent9))时做出相应处理。
为满足应用对多音频并发策略的不同需求,音频打断策略预设了两种焦点模式,针对同一应用创建的多个音频流,应用可通过设置[焦点模式](#焦点模式),选择由应用自主管控或由系统统一管控。
音频打断策略决定了应该对音频流采取何种操作,如暂停播放、继续播放、降低音量播放、恢复音量播放等,这些操作可能由系统或应用来执行。音频打断策略预置了两种[打断类型](#打断类型),用于区分音频打断事件(InterruptEvent)的执行者。
### 焦点模式
音频打断策略预设了两种焦点模式([InterruptMode](../reference/apis/js-apis-audio.md#interruptmode9)):
- 共享焦点模式(SHARED_MODE):由同一应用创建的多个音频流,共享一个音频焦点。这些音频流之间的并发规则由应用自主决定,音频打断策略不会介入。当其他应用创建的音频流与该应用的音频流并发播放时,才会触发音频打断策略的管控。
- 独立焦点模式(INDEPENDENT_MODE):应用创建的每一个音频流均会独立拥有一个音频焦点,当多个音频流并发播放时,会触发音频打断策略的管控。
应用可以按需选择合适的焦点模式,在创建音频流时,系统默认采用共享焦点模式,应用可主动设置所需的模式。
设置焦点模式的方法:
-[使用AVPlayer开发音频播放功能](using-avplayer-for-playback.md),则可以通过修改AVPlayer的[audioInterruptMode](../reference/apis/js-apis-media.md#avplayer9)属性进行设置。
-[使用AudioRenderer开发音频播放功能](using-audiorenderer-for-playback.md),则可以调用AudioRenderer的[setInterruptMode](../reference/apis/js-apis-audio.md#setinterruptmode9)函数进行设置。
### 打断类型
音频打断策略(包括两种焦点模式)决定了应该对各个音频流采取何种操作,如暂停播放、继续播放、降低音量播放、恢复音量播放等。而针对这些操作的执行过程,根据执行者的不同,可以分为两种打断类型([InterruptForceType](../reference/apis/js-apis-audio.md#interruptforcetype9)):
- 强制打断类型(INTERRUPT_FORCE):由系统进行操作,强制打断音频播放。
- 共享打断类型(INTERRUPT_SHARE):由应用进行操作,可以选择打断或忽略。
对于音频打断策略的执行,系统默认采用强制打断类型(INTERRUPT_FORCE),应用无法更改。但对于一些策略(如继续播放等),系统无法强制执行,所以这两种打断类型均可能出现。应用可根据音频打断事件(InterruptEvent)的成员变量forceType的值,获取该事件采用的打断类型。
在应用播放音频的过程中,系统自动为音频流执行申请焦点、持有焦点、释放焦点等动作,当发生音频打断事件时,系统强制对音频流执行暂停、停止、降低音量、恢复音量等操作,并向应用发送音频打断事件(InterruptEvent)回调。由于系统会强制改变音频流状态,为了维持应用和系统的状态一致性,保证良好的用户体验,推荐应用[监听音频打断事件](#监听音频打断事件),并在收到音频打断事件(InterruptEvent)时做出相应处理。
对于一些系统无法强制执行的操作(例如音频流继续播放的场景),会向应用发送包含了共享打断类型的音频打断事件,由应用自行执行相应操作,此时应用可以选择执行或忽略,系统不会干涉。
## 监听音频打断事件
在应用播放音频时,推荐应用监听音频打断事件,当音频打断事件发生时,系统会根据预设策略,对音频流做出相应的操作,并针对状态发生改变的音频流,向所属的应用发送音频打断事件。
应用收到音频打断事件后,需根据其内容提示,做出相应的处理,避免出现应用状态与预期效果不一致的问题。
监听音频打断事件的方法:
-[使用AVPlayer开发音频播放功能](using-avplayer-for-playback.md),则可以调用AVPlayer的[on('audioInterrupt')](../reference/apis/js-apis-media.md#onaudiointerrupt9)函数进行监听,当收到音频打断事件(InterruptEvent)时,应用需根据其内容,做出相应的调整。
-[使用AudioRenderer开发音频播放功能](using-audiorenderer-for-playback.md),则可以调用AudioRenderer的[on('audioInterrupt')](../reference/apis/js-apis-audio.md#onaudiointerrupt9)函数进行监听,当收到音频打断事件(InterruptEvent)时,应用需根据其内容,做出相应的调整。
为了带给用户更好的体验,针对不同的音频打断事件内容,应用需要做出相应的处理操作。此处以使用AudioRenderer开发音频播放功能为例,展示推荐应用采取的处理方法,提供伪代码供开发者参考(若使用AVPlayer开发音频播放功能,处理方法类似),具体的代码实现,开发者可结合实际情况编写,处理方法也可自行调整。
```ts
let isPlay; // 是否正在播放,实际开发中,对应与音频播放状态相关的模块
let isDucked; //是否降低音量,实际开发中,对应与音频音量相关的模块
let started; // 标识符,记录“开始播放(start)”操作是否成功
async function onAudioInterrupt(){
// 此处以使用AudioRenderer开发音频播放功能举例,变量audioRenderer即为播放时创建的AudioRenderer实例。
audioRenderer.on('audioInterrupt', async(interruptEvent) => {
// 在发生音频打断事件时,audioRenderer收到interruptEvent回调,此处根据其内容做相应处理
// 先读取interruptEvent.forceType的类型,判断系统是否已强制执行相应操作
// 再读取interruptEvent.hintType的类型,做出相应的处理
if (interruptEvent.forceType === audio.InterruptForceType.INTERRUPT_FORCE) {
// 强制打断类型(INTERRUPT_FORCE):音频相关处理已由系统执行,应用需更新自身状态,做相应调整
switch (interruptEvent.hintType) {
case audio.InterruptHint.INTERRUPT_HINT_PAUSE:
// 此分支表示系统已将音频流暂停(临时失去焦点),为保持状态一致,应用需切换至音频暂停状态
// 临时失去焦点:待其他音频流释放音频焦点后,本音频流会收到resume对应的音频打断事件,到时可自行继续播放
isPlay = false; // 此句为简化处理,代表应用切换至音频暂停状态的若干操作
break;
case audio.InterruptHint.INTERRUPT_HINT_STOP:
// 此分支表示系统已将音频流停止(永久失去焦点),为保持状态一致,应用需切换至音频暂停状态
// 永久失去焦点:后续不会再收到任何音频打断事件,若想恢复播放,需要用户主动触发。
isPlay = false; // 此句为简化处理,代表应用切换至音频暂停状态的若干操作
break;
case audio.InterruptHint.INTERRUPT_HINT_DUCK:
// 此分支表示系统已将音频音量降低(默认降到正常音量的20%),为保持状态一致,应用需切换至降低音量播放状态
// 若应用不接受降低音量播放,可在此处选择其他处理方式,如主动暂停等
isDucked = true; // 此句为简化处理,代表应用切换至降低音量播放状态的若干操作
break;
case audio.InterruptHint.INTERRUPT_HINT_UNDUCK:
// 此分支表示系统已将音频音量恢复正常,为保持状态一致,应用需切换至正常音量播放状态
isDucked = false; // 此句为简化处理,代表应用切换至正常音量播放状态的若干操作
break;
default:
break;
}
} else if (interruptEvent.forceType === audio.InterruptForceType.INTERRUPT_SHARE) {
// 共享打断类型(INTERRUPT_SHARE):应用可自主选择执行相关操作或忽略音频打断事件
switch (interruptEvent.hintType) {
case audio.InterruptHint.INTERRUPT_HINT_RESUME:
// 此分支表示临时失去焦点后被暂停的音频流此时可以继续播放,建议应用继续播放,切换至音频播放状态
// 若应用此时不想继续播放,可以忽略此音频打断事件,不进行处理即可
// 继续播放,此处主动执行start(),以标识符变量started记录start()的执行结果
await audioRenderer.start().then(async function () {
started = true; // start()执行成功
}).catch((err) => {
started = false; // start()执行失败
});
// 若start()执行成功,则切换至音频播放状态
if (started) {
isPlay = true; // 此句为简化处理,代表应用切换至音频播放状态的若干操作
} else {
// 音频继续播放执行失败
}
break;
default:
break;
}
}
});
}
```
# 音频播放开发概述
## 如何选择音频播放开发方式
在OpenHarmony系统中,多种API都提供了音频播放开发的支持,不同的API适用于不同音频数据格式、音频资源来源、音频使用场景,甚至是不同开发语言。因此,选择合适的音频播放API,有助于降低开发工作量,实现更佳的音频播放效果。
- [AVPlayer](using-avplayer-for-playback.md):功能较完善的音频、视频播放ArkTS/JS API,集成了流媒体和本地资源解析、媒体资源解封装、音频解码和音频输出功能。可以用于直接播放mp3、m4a等格式的音频文件,不支持直接播放PCM格式文件。
- [AudioRenderer](using-audiorenderer-for-playback.md):用于音频输出的的ArkTS/JS API,仅支持PCM格式,需要应用需要持续写入音频数据进行工作。应用可以在输入前添加数据预处理,如设定音频文件的采样率、位宽等,要求开发者具备音频处理的基础知识,适用于更专业、更多样化的媒体播放应用开发。
- [OpenSLES](using-opensl-es-for-playback.md):一套跨平台标准化的音频Native API,目前阶段唯一的音频类Native API,同样提供音频输出能力,仅支持PCM格式,适用于从其他嵌入式平台移植,或依赖在Native层实现音频输出功能的播放应用使用。
- [TonePlayer](using-toneplayer-for-playback.md):拨号和回铃音播放ArkTS/JS API,只能在固定的类型范围内选择播放内容,无需输入媒体资源或音频数据,适用于拨号盘按键和通话回铃音的特定场景。该功能当前仅对系统应用开放。
- 在音频播放中,应用时常需要用到一些急促简短的音效,如相机快门音效、按键音效、游戏射击音效等,当前只能使用AVPlayer播放音频文件替代实现,在OpenHarmony后续版本将会推出相关接口来支持该场景。
## 开发音频播放应用须知
应用如果要实现后台播放或熄屏播放,需要同时满足:
1. 使用媒体会话功能注册到系统内统一管理,否则在应用进入后台时,播放将被强制停止。具体参考[媒体会话开发指导](avsession-overview.md)
2. 申请长时任务避免进入挂起(Suspend)状态。具体参考[长时任务开发指导](../task-management/continuous-task-dev-guide.md)
当应用进入后台,播放被中断,如果被媒体会话管控,将打印日志“pause id”;如果没有该日志,则说明被长时任务管控。
# 音频播放流管理
对于播放音频类的应用,开发者需要关注该应用的音频流的状态以做出相应的操作,比如监听到状态为播放中/暂停时,及时改变播放按钮的UI显示。
## 读取或监听应用内音频流状态变化
参考[使用AudioRenderer开发音频播放功能](using-audiorenderer-for-playback.md)[audio.createAudioRenderer](../reference/apis/js-apis-audio.md#audiocreateaudiorenderer8),完成AudioRenderer的创建,然后可以通过以下两种方式查看音频流状态的变化:
- 方法1:直接查看AudioRenderer的[state](../reference/apis/js-apis-audio.md#属性)
```ts
let audioRendererState = audioRenderer.state;
console.info(`Current state is: ${audioRendererState }`)
```
- 方法2:注册stateChange监听AudioRenderer的状态变化:
```ts
audioRenderer.on('stateChange', (rendererState) => {
console.info(`State change to: ${rendererState}`)
});
```
获取state后可对照[AudioState](../reference/apis/js-apis-audio.md#audiostate8)来进行相应的操作,比如更改暂停播放按钮的显示等。
## 读取或监听所有音频流的变化
如果部分应用需要查询获取所有音频流的变化信息,可以通过AudioStreamManager读取或监听所有音频流的变化。
> **说明:**
>
> 对于标记为系统接口(system api)的音频流变化信息需要系统级别应用才可查看,若应用不是系统应用,将无法获取准确信息。
如下为音频流管理调用关系图:
![Audio stream management invoking relationship](figures/audio-stream-mgmt-invoking-relationship.png)
在进行应用开发的过程中,开发者需要使用getStreamManager()创建一个AudioStreamManager实例,进而通过该实例管理音频流。开发者可通过调用on('audioRendererChange')监听音频流的变化,在音频流状态变化、设备变化时获得通知。同时可通过off('audioRendererChange')取消相关事件的监听。另外,开发者可以主动调用getCurrentAudioRendererInfoArray()来查询播放流的唯一ID、播放流客户端的UID、音频流状态等信息。
详细API含义可参考[音频管理API文档AudioStreamManager](../reference/apis/js-apis-audio.md#audiostreammanager9)
## 开发步骤及注意事项
1. 创建AudioStreamManager实例。
在使用AudioStreamManager的API前,需要使用getStreamManager()创建一个AudioStreamManager实例。
```ts
import audio from '@ohos.multimedia.audio';
let audioManager = audio.getAudioManager();
let audioStreamManager = audioManager.getStreamManager();
```
2. 使用on('audioRendererChange')监听音频播放流的变化。 如果音频流监听应用需要在音频播放流状态变化、设备变化时获取通知,可以订阅该事件。
```ts
audioStreamManager.on('audioRendererChange', (AudioRendererChangeInfoArray) => {
for (let i = 0; i < AudioRendererChangeInfoArray.length; i++) {
let AudioRendererChangeInfo = AudioRendererChangeInfoArray[i];
console.info(`## RendererChange on is called for ${i} ##`);
console.info(`StreamId for ${i} is: ${AudioRendererChangeInfo.streamId}`);
console.info(`Content ${i} is: ${AudioRendererChangeInfo.rendererInfo.content}`);
console.info(`Stream ${i} is: ${AudioRendererChangeInfo.rendererInfo.usage}`);
console.info(`Flag ${i} is: ${AudioRendererChangeInfo.rendererInfo.rendererFlags}`);
for (let j = 0;j < AudioRendererChangeInfo.deviceDescriptors.length; j++) {
console.info(`Id: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].id}`);
console.info(`Type: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].deviceType}`);
console.info(`Role: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].deviceRole}`);
console.info(`Name: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].name}`);
console.info(`Address: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].address}`);
console.info(`SampleRates: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].sampleRates[0]}`);
console.info(`ChannelCount ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].channelCounts[0]}`);
console.info(`ChannelMask: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].channelMasks}`);
}
}
});
```
3. (可选)使用off('audioRendererChange')取消监听音频播放流变化。
```ts
audioStreamManager.off('audioRendererChange');
console.info('RendererChange Off is called ');
```
4. (可选)使用getCurrentAudioRendererInfoArray()获取所有音频播放流的信息。
该接口可获取音频播放流唯一ID,音频播放客户端的UID,音频状态以及音频播放器的其他信息。
> **说明:**
>
> 对所有音频流状态进行监听的应用需要[申请权限](../security/accesstoken-guidelines.md)ohos.permission.USE_BLUETOOTH,否则无法获得实际的设备名称和设备地址信息,查询到的设备名称和设备地址(蓝牙设备的相关属性)将为空字符串。
```ts
async function getCurrentAudioRendererInfoArray(){
await audioStreamManager.getCurrentAudioRendererInfoArray().then( function (AudioRendererChangeInfoArray) {
console.info(`getCurrentAudioRendererInfoArray Get Promise is called `);
if (AudioRendererChangeInfoArray != null) {
for (let i = 0; i < AudioRendererChangeInfoArray.length; i++) {
let AudioRendererChangeInfo = AudioRendererChangeInfoArray[i];
console.info(`StreamId for ${i} is: ${AudioRendererChangeInfo.streamId}`);
console.info(`Content ${i} is: ${AudioRendererChangeInfo.rendererInfo.content}`);
console.info(`Stream ${i} is: ${AudioRendererChangeInfo.rendererInfo.usage}`);
console.info(`Flag ${i} is: ${AudioRendererChangeInfo.rendererInfo.rendererFlags}`);
for (let j = 0;j < AudioRendererChangeInfo.deviceDescriptors.length; j++) {
console.info(`Id: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].id}`);
console.info(`Type: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].deviceType}`);
console.info(`Role: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].deviceRole}`);
console.info(`Name: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].name}`);
console.info(`Address: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].address}`);
console.info(`SampleRates: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].sampleRates[0]}`);
console.info(`ChannelCount ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].channelCounts[0]}`);
console.info(`ChannelMask: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].channelMasks}`);
}
}
}
}).catch((err) => {
console.error(`Invoke getCurrentAudioRendererInfoArray failed, code is ${err.code}, message is ${err.message}`);
});
}
```
# 音频播放开发指导
## 简介
音频播放的主要工作是将音频数据转码为可听见的音频模拟信号,并通过输出设备进行播放,同时对播放任务进行管理,包括开始播放、暂停播放、停止播放、释放资源、设置音量、跳转播放位置、获取轨道信息等功能控制。
## 运作机制
该模块提供了音频播放状态变化示意图和音频播放外部模块交互图。
**图1** 音频播放状态变化示意图
![zh-ch_image_audio_state_machine](figures/zh-ch_image_audio_state_machine.png)
**注意**:当前为Idle状态,设置src不会改变状态;且src设置成功后,不能再次设置其它src,需调用reset()接口后,才能重新设置src。
**图2** 音频播放外部模块交互图
![zh-ch_image_audio_player](figures/zh-ch_image_audio_player.png)
**说明**:三方应用通过调用JS接口层提供的js接口实现相应功能时,框架层会通过Native Framework的媒体服务,调用音频部件,将软件解码后的音频数据输出至硬件接口层的音频HDI,实现音频播放功能。
## 开发指导
详细API含义可参考:[媒体服务API文档AudioPlayer](../reference/apis/js-apis-media.md#audioplayer)
> **说明:**
>
> path路径在FA模型和Stage模型下的获取方式不同,示例代码中仅给出pathDir示例,具体的path路径请开发者根据实际情况获取。获取方式请参考[应用沙箱路径使用说明](../reference/apis/js-apis-fileio.md#使用说明)。
### 全流程场景
音频播放的全流程场景包含:创建实例,设置uri,播放音频,跳转播放位置,设置音量,暂停播放,获取轨道信息,停止播放,重置,释放资源等流程。
AudioPlayer支持的src媒体源输入类型可参考:[src属性说明](../reference/apis/js-apis-media.md#audioplayer_属性)
```js
import media from '@ohos.multimedia.media'
import fs from '@ohos.file.fs'
// 打印码流轨道信息
function printfDescription(obj) {
for (let item in obj) {
let property = obj[item];
console.info('audio key is ' + item);
console.info('audio value is ' + property);
}
}
// 设置播放器回调函数
function setCallBack(audioPlayer) {
audioPlayer.on('dataLoad', () => { // 设置'dataLoad'事件回调,src属性设置成功后,触发此回调
console.info('audio set source success');
audioPlayer.play(); // 需等待'dataLoad'事件回调完成后,才可调用play进行播放,触发'play'事件回调
});
audioPlayer.on('play', () => { // 设置'play'事件回调
console.info('audio play success');
audioPlayer.pause(); // 触发'pause'事件回调,暂停播放
});
audioPlayer.on('pause', () => { // 设置'pause'事件回调
console.info('audio pause success');
audioPlayer.seek(5000); // 触发'timeUpdate'事件回调,seek到5000ms处播放
});
audioPlayer.on('stop', () => { // 设置'stop'事件回调
console.info('audio stop success');
audioPlayer.reset(); // 触发'reset'事件回调后,重新设置src属性,可完成切歌
});
audioPlayer.on('reset', () => { // 设置'reset'事件回调
console.info('audio reset success');
audioPlayer.release(); // audioPlayer资源被销毁
audioPlayer = undefined;
});
audioPlayer.on('timeUpdate', (seekDoneTime) => { // 设置'timeUpdate'事件回调
if (typeof(seekDoneTime) == 'undefined') {
console.info('audio seek fail');
return;
}
console.info('audio seek success, and seek time is ' + seekDoneTime);
audioPlayer.setVolume(0.5); // 触发'volumeChange'事件回调
});
audioPlayer.on('volumeChange', () => { // 设置'volumeChange'事件回调
console.info('audio volumeChange success');
audioPlayer.getTrackDescription((error, arrlist) => { // 通过回调方式获取音频轨道信息
if (typeof (arrlist) != 'undefined') {
for (let i = 0; i < arrlist.length; i++) {
printfDescription(arrlist[i]);
}
} else {
console.log(`audio getTrackDescription fail, error:${error.message}`);
}
audioPlayer.stop(); // 触发'stop'事件回调,停止播放
});
});
audioPlayer.on('finish', () => { // 设置'finish'事件回调,播放完成触发
console.info('audio play finish');
});
audioPlayer.on('error', (error) => { // 设置'error'事件回调
console.info(`audio error called, errName is ${error.name}`);
console.info(`audio error called, errCode is ${error.code}`);
console.info(`audio error called, errMessage is ${error.message}`);
});
}
async function audioPlayerDemo() {
// 1. 创建实例
let audioPlayer = media.createAudioPlayer();
setCallBack(audioPlayer); // 设置事件回调
// 2. 用户选择音频,设置uri
let fdPath = 'fd://'
let pathDir = "/data/storage/el2/base/haps/entry/files" // pathDir在FA模型和Stage模型的获取方式不同,请参考开发步骤首行的说明,根据实际情况自行获取。
// path路径的码流可通过"hdc file send D:\xxx\01.mp3 /data/app/el2/100/base/ohos.acts.multimedia.audio.audioplayer/haps/entry/files" 命令,将其推送到设备上
let path = pathDir + '/01.mp3'
let file = await fs.open(path);
fdPath = fdPath + '' + file.fd;
audioPlayer.src = fdPath; // 设置src属性,并触发'dataLoad'事件回调
}
```
### 正常播放场景
```js
import media from '@ohos.multimedia.media'
import fs from '@ohos.file.fs'
export class AudioDemo {
// 设置播放器回调函数
setCallBack(audioPlayer) {
audioPlayer.on('dataLoad', () => { // 设置'dataLoad'事件回调,src属性设置成功后,触发此回调
console.info('audio set source success');
audioPlayer.play(); // 调用play方法开始播放,触发'play'事件回调
});
audioPlayer.on('play', () => { // 设置'play'事件回调
console.info('audio play success');
});
audioPlayer.on('finish', () => { // 设置'finish'事件回调,播放完成触发
console.info('audio play finish');
audioPlayer.release(); // audioPlayer资源被销毁
audioPlayer = undefined;
});
}
async audioPlayerDemo() {
let audioPlayer = media.createAudioPlayer(); // 创建一个音频播放实例
this.setCallBack(audioPlayer); // 设置事件回调
let fdPath = 'fd://'
let pathDir = "/data/storage/el2/base/haps/entry/files" // pathDir在FA模型和Stage模型的获取方式不同,请参考开发步骤首行的说明,根据实际情况自行获取。
// path路径的码流可通过"hdc file send D:\xxx\01.mp3 /data/app/el2/100/base/ohos.acts.multimedia.audio.audioplayer/haps/entry/files" 命令,将其推送到设备上
let path = pathDir + '/01.mp3'
let file = await fs.open(path);
fdPath = fdPath + '' + file.fd;
audioPlayer.src = fdPath; // 设置src属性,并触发'dataLoad'事件回调
}
}
```
### 切歌场景
```js
import media from '@ohos.multimedia.media'
import fs from '@ohos.file.fs'
export class AudioDemo {
// 设置播放器回调函数
private isNextMusic = false;
setCallBack(audioPlayer) {
audioPlayer.on('dataLoad', () => { // 设置'dataLoad'事件回调,src属性设置成功后,触发此回调
console.info('audio set source success');
audioPlayer.play(); // 调用play方法开始播放,触发'play'事件回调
});
audioPlayer.on('play', () => { // 设置'play'事件回调
console.info('audio play success');
audioPlayer.reset(); // 调用reset方法,触发'reset'事件回调
});
audioPlayer.on('reset', () => { // 设置'reset'事件回调
console.info('audio play success');
if (!this.isNextMusic) { // 当isNextMusic 为false时,实现切歌功能
this.nextMusic(audioPlayer); // 实现切歌功能
} else {
audioPlayer.release(); // audioPlayer资源被销毁
audioPlayer = undefined;
}
});
}
async nextMusic(audioPlayer) {
this.isNextMusic = true;
let nextFdPath = 'fd://'
let pathDir = "/data/storage/el2/base/haps/entry/files" // pathDir在FA模型和Stage模型的获取方式不同,请参考开发步骤首行的说明,根据实际情况自行获取。
// path路径的码流可通过"hdc file send D:\xxx\02.mp3 /data/app/el2/100/base/ohos.acts.multimedia.audio.audioplayer/haps/entry/files" 命令,将其推送到设备上
let nextpath = pathDir + '/02.mp3'
let nextFile = await fs.open(nextpath);
nextFdPath = nextFdPath + '' + nextFile.fd;
audioPlayer.src = nextFdPath; // 设置src属性,并重新触发触发'dataLoad'事件回调
}
async audioPlayerDemo() {
let audioPlayer = media.createAudioPlayer(); // 创建一个音频播放实例
this.setCallBack(audioPlayer); // 设置事件回调
let fdPath = 'fd://'
let pathDir = "/data/storage/el2/base/haps/entry/files" // pathDir在FA模型和Stage模型的获取方式不同,请参考开发步骤首行的说明,根据实际情况自行获取。
// path路径的码流可通过"hdc file send D:\xxx\01.mp3 /data/app/el2/100/base/ohos.acts.multimedia.audio.audioplayer/haps/entry/files" 命令,将其推送到设备上
let path = pathDir + '/01.mp3'
let file = await fs.open(path);
fdPath = fdPath + '' + file.fd;
audioPlayer.src = fdPath; // 设置src属性,并触发'dataLoad'事件回调
}
}
```
### 单曲循环场景
```js
import media from '@ohos.multimedia.media'
import fs from '@ohos.file.fs'
export class AudioDemo {
// 设置播放器回调函数
setCallBack(audioPlayer) {
audioPlayer.on('dataLoad', () => { // 设置'dataLoad'事件回调,src属性设置成功后,触发此回调
console.info('audio set source success');
audioPlayer.loop = true; // 设置循环播放属性
audioPlayer.play(); // 调用play方法开始播放,触发'play'事件回调
});
audioPlayer.on('play', () => { // 设置'play'事件回调,开始循环播放
console.info('audio play success');
});
}
async audioPlayerDemo() {
let audioPlayer = media.createAudioPlayer(); // 创建一个音频播放实例
this.setCallBack(audioPlayer); // 设置事件回调
let fdPath = 'fd://'
let pathDir = "/data/storage/el2/base/haps/entry/files" // pathDir在FA模型和Stage模型的获取方式不同,请参考开发步骤首行的说明,根据实际情况自行获取。
// path路径的码流可通过"hdc file send D:\xxx\01.mp3 /data/app/el2/100/base/ohos.acts.multimedia.audio.audioplayer/haps/entry/files" 命令,将其推送到设备上
let path = pathDir + '/01.mp3'
let file = await fs.open(path);
fdPath = fdPath + '' + file.fd;
audioPlayer.src = fdPath; // 设置src属性,并触发'dataLoad'事件回调
}
}
```
## 相关实例
针对音频播放开发,有以下相关实例可供参考:
- [`JsDistributedMusicPlayer:`分布式音乐播放(JS)(API10)(Full SDK)](https://gitee.com/openharmony/applications_app_samples/tree/master/code/SuperFeature/DistributedAppDev/JsDistributedMusicPlayer)
- [`MediaCollections`:媒体管理合集(ArkTS)(API9)](https://gitee.com/openharmony/applications_app_samples/tree/master/code/BasicFeature/FileManagement/MediaCollections)
- [`Recorder`: 录音机(ArkTS)(API9)(Full SDK)](https://gitee.com/openharmony/applications_app_samples/tree/master/code/BasicFeature/Media/Recorder)
- [音频播放器(ArkTS)(API9)](https://gitee.com/openharmony/codelabs/tree/master/Media/Audio_OH_ETS)
\ No newline at end of file
# 音频录制开发指导
## 简介
音频录制的主要工作是捕获音频信号,完成音频编码并保存到文件中,帮助开发者轻松实现音频录制功能。该模块允许调用者指定音频录制的采样率、声道数、编码格式、封装格式、输出文件的路径等参数。
## 运作机制
该模块提供了音频录制状态变化示意图和音频录制外部模块交互图。
**图1** 音频录制状态变化变化示意图
![zh-ch_image_audio_recorder_state_machine](figures/zh-ch_image_audio_recorder_state_machine.png)
**图2** 音频录制外部模块交互图
![zh-ch_image_audio_recorder_zero](figures/zh-ch_image_audio_recorder_zero.png)
**说明**:三方录音应用或录音机通过调用JS接口层提供的js接口实现相应功能时,框架层会通过Native Framework的媒体服务,调用音频部件获取通过音频HDI捕获的音频数据,再通过软件编码输出编码封装后的音频数据保存至文件中,实现音频录制功能。
## 约束与限制
开发者在进行录制功能开发前,需要先对所开发的应用配置麦克风权限(ohos.permission.MICROPHONE),权限配置相关内容可参考:[访问控制权限申请指导](../security/accesstoken-guidelines.md)
## 开发指导
详细API含义可参考:[媒体服务API文档AudioRecorder](../reference/apis/js-apis-media.md#audiorecorder)
### 全流程场景
音频录制的全流程场景包含:创建实例,设置录制参数,开始录制,暂停录制,恢复录制,停止录制,释放资源等流程。
```js
import media from '@ohos.multimedia.media'
import mediaLibrary from '@ohos.multimedia.mediaLibrary'
export class AudioRecorderDemo {
private testFdNumber; // 用于保存fd地址
// 设置音频录制相关回调函数
setCallBack(audioRecorder) {
audioRecorder.on('prepare', () => { // 设置'prepare'事件回调
console.log('prepare success');
audioRecorder.start(); // 调用start方法开始录制,并触发start回调
});
audioRecorder.on('start', () => { // 设置'start'事件回调
console.log('audio recorder start success');
audioRecorder.pause(); // 调用pause方法暂停录制,并触发pause回调
});
audioRecorder.on('pause', () => { // 设置'pause'事件回调
console.log('audio recorder pause success');
audioRecorder.resume(); // 调用resume方法恢复录制,并触发resume回调
});
audioRecorder.on('resume', () => { // 设置'resume'事件回调
console.log('audio recorder resume success');
audioRecorder.stop(); // 调用stop方法停止录制,并触发stop回调
});
audioRecorder.on('stop', () => { // 设置'stop'事件回调
console.log('audio recorder stop success');
audioRecorder.reset(); // 调用reset方法重置录制,并触发reset回调
});
audioRecorder.on('reset', () => { // 设置'reset'事件回调
console.log('audio recorder reset success');
audioRecorder.release(); // 调用release方法,释放资源,并触发release回调
});
audioRecorder.on('release', () => { // 设置'release'事件回调
console.log('audio recorder release success');
audioRecorder = undefined;
});
audioRecorder.on('error', (error) => { // 设置'error'事件回调
console.info(`audio error called, errName is ${error.name}`);
console.info(`audio error called, errCode is ${error.code}`);
console.info(`audio error called, errMessage is ${error.message}`);
});
}
// pathName是传入的录制文件名,例如:01.mp3,生成后的文件地址:/storage/media/100/local/files/Video/01.mp3
// 使用mediaLibrary需要添加以下权限, ohos.permission.MEDIA_LOCATION、ohos.permission.WRITE_MEDIA、ohos.permission.READ_MEDIA
async getFd(pathName) {
let displayName = pathName;
const mediaTest = mediaLibrary.getMediaLibrary();
let fileKeyObj = mediaLibrary.FileKey;
let mediaType = mediaLibrary.MediaType.VIDEO;
let publicPath = await mediaTest.getPublicDirectory(mediaLibrary.DirectoryType.DIR_VIDEO);
let dataUri = await mediaTest.createAsset(mediaType, displayName, publicPath);
if (dataUri != undefined) {
let args = dataUri.id.toString();
let fetchOp = {
selections : fileKeyObj.ID + "=?",
selectionArgs : [args],
}
let fetchFileResult = await mediaTest.getFileAssets(fetchOp);
let fileAsset = await fetchFileResult.getAllObject();
let fdNumber = await fileAsset[0].open('Rw');
this.testFdNumber = "fd://" + fdNumber.toString();
}
}
async audioRecorderDemo() {
// 1.创建实例
let audioRecorder = media.createAudioRecorder();
// 2.设置回调
this.setCallBack(audioRecorder);
await this.getFd('01.mp3'); // 调用getFd方法获取需要录制文件的fd地址
// 3.设置录制参数
let audioRecorderConfig = {
audioEncodeBitRate : 22050,
audioSampleRate : 22050,
numberOfChannels : 2,
uri : this.testFdNumber, // testFdNumber由getFd生成
location : { latitude : 30, longitude : 130},
audioEncoderMime : media.CodecMimeType.AUDIO_AAC,
fileFormat : media.ContainerFormatType.CFT_MPEG_4A,
}
audioRecorder.prepare(audioRecorderConfig); // 调用prepare方法,触发prepare回调函数
}
}
```
### 正常录制场景
与全流程场景不同,不包括暂停录制,恢复录制的过程。
```js
import media from '@ohos.multimedia.media'
import mediaLibrary from '@ohos.multimedia.mediaLibrary'
export class AudioRecorderDemo {
private testFdNumber; // 用于保存fd地址
// 设置音频录制相关回调函数
setCallBack(audioRecorder) {
audioRecorder.on('prepare', () => { // 设置'prepare'事件回调
console.log('prepare success');
audioRecorder.start(); // 调用start方法开始录制,并触发start回调
});
audioRecorder.on('start', () => { // 设置'start'事件回调
console.log('audio recorder start success');
audioRecorder.stop(); // 调用stop方法停止录制,并触发stop回调
});
audioRecorder.on('stop', () => { // 设置'stop'事件回调
console.log('audio recorder stop success');
audioRecorder.release(); // 调用release方法,释放资源,并触发release回调
});
audioRecorder.on('release', () => { // 设置'release'事件回调
console.log('audio recorder release success');
audioRecorder = undefined;
});
audioRecorder.on('error', (error) => { // 设置'error'事件回调
console.info(`audio error called, errName is ${error.name}`);
console.info(`audio error called, errCode is ${error.code}`);
console.info(`audio error called, errMessage is ${error.message}`);
});
}
// pathName是传入的录制文件名,例如:01.mp3,生成后的文件地址:/storage/media/100/local/files/Video/01.mp3
// 使用mediaLibrary需要添加以下权限, ohos.permission.MEDIA_LOCATION、ohos.permission.WRITE_MEDIA、ohos.permission.READ_MEDIA
async getFd(pathName) {
let displayName = pathName;
const mediaTest = mediaLibrary.getMediaLibrary();
let fileKeyObj = mediaLibrary.FileKey;
let mediaType = mediaLibrary.MediaType.VIDEO;
let publicPath = await mediaTest.getPublicDirectory(mediaLibrary.DirectoryType.DIR_VIDEO);
let dataUri = await mediaTest.createAsset(mediaType, displayName, publicPath);
if (dataUri != undefined) {
let args = dataUri.id.toString();
let fetchOp = {
selections : fileKeyObj.ID + "=?",
selectionArgs : [args],
}
let fetchFileResult = await mediaTest.getFileAssets(fetchOp);
let fileAsset = await fetchFileResult.getAllObject();
let fdNumber = await fileAsset[0].open('Rw');
this.testFdNumber = "fd://" + fdNumber.toString();
}
}
async audioRecorderDemo() {
// 1.创建实例
let audioRecorder = media.createAudioRecorder();
// 2.设置回调
this.setCallBack(audioRecorder);
await this.getFd('01.mp3'); // 调用getFd方法获取需要录制文件的fd地址
// 3.设置录制参数
let audioRecorderConfig = {
audioEncodeBitRate : 22050,
audioSampleRate : 22050,
numberOfChannels : 2,
uri : this.testFdNumber, // testFdNumber由getFd生成
location : { latitude : 30, longitude : 130},
audioEncoderMime : media.CodecMimeType.AUDIO_AAC,
fileFormat : media.ContainerFormatType.CFT_MPEG_4A,
}
audioRecorder.prepare(audioRecorderConfig); // 调用prepare方法,触发prepare回调函数
}
}
```
## 相关实例
针对音频录制开发,有以下相关实例可供参考:
- [`Recorder:`录音机(ArkTS)(API9)(Full SDK)](https://gitee.com/openharmony/applications_app_samples/tree/master/code/BasicFeature/Media/Recorder)
- [音频播放器(ArkTS)(API9)](https://gitee.com/openharmony/codelabs/tree/master/Media/Audio_OH_ETS)
# 音频录制开发概述
## 如何选择音频录制开发方式
在OpenHarmony系统中,多种API都提供了音频录制开发的支持,不同的API适用于不同录音输出格式、音频使用场景或不同开发语言。因此,选择合适的音频录制API,有助于降低开发工作量,实现更佳的音频录制效果。
- [AVRecorder](using-avrecorder-for-recording.md):功能较完善的音频、视频录制ArkTS/JS API,集成了音频输入录制、音频编码和媒体封装的功能。开发者可以直接调用设备硬件如麦克风录音,并生成m4a音频文件。
- [AudioCapturer](using-audiocapturer-for-recording.md):用于音频输入的的ArkTS/JS API,仅支持PCM格式,需要应用持续读取音频数据进行工作。应用可以在音频输出后添加数据处理,要求开发者具备音频处理的基础知识,适用于更专业、更多样化的媒体播放应用开发。
- [OpenSLES](using-opensl-es-for-recording.md):一套跨平台标准化的音频Native API,目前阶段唯一的音频类Native API,同样提供音频输入原子能力,仅支持PCM格式,适用于从其他嵌入式平台移植,或依赖在Native层实现音频输入功能的录音应用使用。
## 开发音频录制应用须知
应用可以调用麦克风录制音频,但该行为属于隐私敏感行为,在调用麦克风前,需要先向用户申请权限“ohos.permission.MICROPHONE”。
权限申请的流程请参考[访问控制授权申请指导](../security/accesstoken-guidelines.md)。如何使用和管理麦克风请参考[管理麦克风](mic-management.md)
# 音频录制流管理
对于播放音频类的应用,开发者需要关注该应用的音频流的状态以做出相应的操作,比如监听到状态为结束时,及时提示用户录制已结束。
## 读取或监听应用内音频流状态变化
参考[使用AudioCapturer开发音频录制功能](using-audiocapturer-for-recording.md)[audio.createAudioCapturer](../apis/js-apis-audio.md#audiocreateaudiocapturer8),完成AudioRenderer的创建,然后可以通过以下两种方式查看音频流状态的变化:
- 方法1:直接查看AudioCapturer的[state](../apis/js-apis-audio.md#属性)
```ts
let audioCapturerState = audioCapturer.state;
console.info(`Current state is: ${audioCapturerState }`)
```
- 方法2:注册stateChange监听AudioCapturer的状态变化:
```ts
audioCapturer.on('stateChange', (capturerState) => {
console.info(`State change to: ${capturerState}`)
});
```
获取state后可对照[AudioState](../apis/js-apis-audio.md#audiostate8)来进行相应的操作,比如显示录制结束的提示等。
## 读取或监听所有录制流的变化
如果部分应用需要查询获取所有音频流的变化信息,可以通过AudioStreamManager读取或监听所有音频流的变化。
> **说明:**
>
> 对于标记为系统接口(system api)的音频流变化信息需要系统级别应用才可查看,若应用不是系统应用,将无法获取准确信息。
如下为音频流管理调用关系图:
![Invoking relationship of recording stream management](figures/invoking-relationship-recording-stream-mgmt.png)
在进行应用开发的过程中,开发者需要使用getStreamManager()创建一个AudioStreamManager实例,进而通过该实例管理音频流。开发者可通过调用on('audioCapturerChange')监听音频流的变化,在音频流状态变化、设备变化时获得通知,同时可通过off('audioCapturerChange')取消相关事件的监听。另外,开发者可以通过主动调用getCurrentAudioCapturerInfoArray()查询录制流的唯一ID、录制流客户端的UID、以及流状态等信息。
详细API含义可参考[音频管理API文档AudioStreamManager](../apis/js-apis-audio.md#audiostreammanager9)
## 开发步骤及注意事项
1. 创建AudioStreamManager实例。
在使用AudioStreamManager的API前,需要使用getStreamManager()创建一个AudioStreamManager实例。
```ts
import audio from '@ohos.multimedia.audio';
let audioManager = audio.getAudioManager();
let audioStreamManager = audioManager.getStreamManager();
```
2. 使用on('audioCapturerChange')监听音频录制流更改事件。 如果音频流监听应用需要在音频录制流状态变化、设备变化时获取通知,可以订阅该事件。
```ts
audioStreamManager.on('audioCapturerChange', (AudioCapturerChangeInfoArray) => {
for (let i = 0; i < AudioCapturerChangeInfoArray.length; i++) {
console.info(`## CapChange on is called for element ${i} ##`);
console.info(`StreamId for ${i} is: ${AudioCapturerChangeInfoArray[i].streamId}`);
console.info(`Source for ${i} is: ${AudioCapturerChangeInfoArray[i].capturerInfo.source}`);
console.info(`Flag ${i} is: ${AudioCapturerChangeInfoArray[i].capturerInfo.capturerFlags}`);
let devDescriptor = AudioCapturerChangeInfoArray[i].deviceDescriptors;
for (let j = 0; j < AudioCapturerChangeInfoArray[i].deviceDescriptors.length; j++) {
console.info(`Id: ${i} : ${AudioCapturerChangeInfoArray[i].deviceDescriptors[j].id}`);
console.info(`Type: ${i} : ${AudioCapturerChangeInfoArray[i].deviceDescriptors[j].deviceType}`);
console.info(`Role: ${i} : ${AudioCapturerChangeInfoArray[i].deviceDescriptors[j].deviceRole}`);
console.info(`Name: ${i} : ${AudioCapturerChangeInfoArray[i].deviceDescriptors[j].name}`);
console.info(`Address: ${i} : ${AudioCapturerChangeInfoArray[i].deviceDescriptors[j].address}`);
console.info(`SampleRates: ${i} : ${AudioCapturerChangeInfoArray[i].deviceDescriptors[j].sampleRates[0]}`);
console.info(`ChannelCounts ${i} : ${AudioCapturerChangeInfoArray[i].deviceDescriptors[j].channelCounts[0]}`);
console.info(`ChannelMask: ${i} : ${AudioCapturerChangeInfoArray[i].deviceDescriptors[j].channelMasks}`);
}
}
});
```
3. (可选)使用off('audioCapturerChange')取消监听音频录制流变化。
```ts
audioStreamManager.off('audioCapturerChange');
console.info('CapturerChange Off is called');
```
4. (可选)使用getCurrentAudioCapturerInfoArray()获取当前音频录制流的信息。
该接口可获取音频录制流唯一ID,音频录制客户端的UID,音频状态以及音频捕获器的其他信息。
> **说明:**
> 对所有音频流状态进行监听的应用需要[申请权限](../security/accesstoken-guidelines.md)ohos.permission.USE_BLUETOOTH,否则无法获得实际的设备名称和设备地址信息,查询到的设备名称和设备地址(蓝牙设备的相关属性)将为空字符串。
```ts
async function getCurrentAudioCapturerInfoArray(){
await audioStreamManager.getCurrentAudioCapturerInfoArray().then( function (AudioCapturerChangeInfoArray) {
console.info('getCurrentAudioCapturerInfoArray Get Promise Called ');
if (AudioCapturerChangeInfoArray != null) {
for (let i = 0; i < AudioCapturerChangeInfoArray.length; i++) {
console.info(`StreamId for ${i} is: ${AudioCapturerChangeInfoArray[i].streamId}`);
console.info(`Source for ${i} is: ${AudioCapturerChangeInfoArray[i].capturerInfo.source}`);
console.info(`Flag ${i} is: ${AudioCapturerChangeInfoArray[i].capturerInfo.capturerFlags}`);
for (let j = 0; j < AudioCapturerChangeInfoArray[i].deviceDescriptors.length; j++) {
console.info(`Id: ${i} : ${AudioCapturerChangeInfoArray[i].deviceDescriptors[j].id}`);
console.info(`Type: ${i} : ${AudioCapturerChangeInfoArray[i].deviceDescriptors[j].deviceType}`);
console.info(`Role: ${i} : ${AudioCapturerChangeInfoArray[i].deviceDescriptors[j].deviceRole}`);
console.info(`Name: ${i} : ${AudioCapturerChangeInfoArray[i].deviceDescriptors[j].name}`);
console.info(`Address: ${i} : ${AudioCapturerChangeInfoArray[i].deviceDescriptors[j].address}`);
console.info(`SampleRates: ${i} : ${AudioCapturerChangeInfoArray[i].deviceDescriptors[j].sampleRates[0]}`);
console.info(`ChannelCounts ${i} : ${AudioCapturerChangeInfoArray[i].deviceDescriptors[j].channelCounts[0]}`);
console.info(`ChannelMask: ${i} : ${AudioCapturerChangeInfoArray[i].deviceDescriptors[j].channelMasks}`);
}
}
}
}).catch((err) => {
console.error(`Invoke getCurrentAudioCapturerInfoArray failed, code is ${err.code}, message is ${err.message}`);
});
}
```
此差异已折叠。
# 路由、设备管理开发指导
## 简介
AudioRoutingManager提供了音频路由、设备管理的方法。开发者可以通过本指导了解应用如何通过AudioRoutingManager获取当前工作的输入、输出音频设备,监听音频设备的连接状态变化,激活通信设备等。
## 运作机制
该模块提供了路由、设备管理模块常用接口
**图1** 路由、设备管理常用接口
![zh-ch_image_audio_routing_manager](figures/zh-ch_image_audio_routing_manager.png)
**说明:** AudioRoutingManager主要接口有:获取设备列表信息、监听与取消监听设备连接状态、激活通信设备、查询通信设备激活状态。更多介绍请参考[API参考文档](../reference/apis/js-apis-audio.md)
## 开发指导
详细API含义可参考:[音频路由、设备管理API文档AudioRoutingManager](../reference/apis/js-apis-audio.md#audioroutingmanager9)
1. 创建AudioRoutingManager实例。
在使用AudioRoutingManager的API前,需要使用getRoutingManager创建一个AudioRoutingManager实例。
```js
import audio from '@ohos.multimedia.audio';
async loadAudioRoutingManager() {
var audioRoutingManager = await audio.getAudioManager().getRoutingManager();
console.info('audioRoutingManager------create-------success.');
}
```
2. (可选)获取设备列表信息、监听设备链接状态变化。
如果开发者需要获取设备列表信息(输入、输出、分布式输入、分布式输出等),或者监听音频设备的链接状态变化时,可参考并调用以下接口。
```js
import audio from '@ohos.multimedia.audio';
//创建AudioRoutingManager实例
async loadAudioRoutingManager() {
var audioRoutingManager = await audio.getAudioManager().getRoutingManager();
console.info('audioRoutingManager------create-------success.');
}
//获取全部音频设备信息(开发者可以根据自身需要填入适当的DeviceFlag)
async getDevices() {
await loadAudioRoutingManager();
await audioRoutingManager.getDevices(audio.DeviceFlag.ALL_DEVICES_FLAG).then((data) => {
console.info(`getDevices success and data is: ${JSON.stringify(data)}.`);
});
}
//监听音频设备状态变化
async onDeviceChange() {
await loadAudioRoutingManager();
await audioRoutingManager.on('deviceChange', audio.DeviceFlag.ALL_DEVICES_FLAG, (deviceChanged) => {
console.info('on device change type : ' + deviceChanged.type);
console.info('on device descriptor size : ' + deviceChanged.deviceDescriptors.length);
console.info('on device change descriptor : ' + deviceChanged.deviceDescriptors[0].deviceRole);
console.info('on device change descriptor : ' + deviceChanged.deviceDescriptors[0].deviceType);
});
}
//取消监听音频设备状态变化
async offDeviceChange() {
await loadAudioRoutingManager();
await audioRoutingManager.off('deviceChange', (deviceChanged) => {
console.info('off device change type : ' + deviceChanged.type);
console.info('off device descriptor size : ' + deviceChanged.deviceDescriptors.length);
console.info('off device change descriptor : ' + deviceChanged.deviceDescriptors[0].deviceRole);
console.info('off device change descriptor : ' + deviceChanged.deviceDescriptors[0].deviceType);
});
}
//综合调用:先查询所有设备,设置监听,然后开发者手动变更设备连接(例如有线耳机),再次查询所有设备,最后取消设备状态变化的监听。
async test(){
await getDevices();
await onDeviceChange()();
//开发者手动断开/连接设备
await getDevices();
await offDeviceChange();
}
```
3. (可选)设置通信设备激活并查询激活状态。
```js
import audio from '@ohos.multimedia.audio';
//创建AudioRoutingManager实例
async loadAudioRoutingManager() {
var audioRoutingManager = await audio.getAudioManager().getRoutingManager();
console.info('audioRoutingManager------create-------success.');
}
//设置通信设备激活状态
async setCommunicationDevice() {
await loadAudioRoutingManager();
await audioRoutingManager.setCommunicationDevice(audio.CommunicationDeviceType.SPEAKER, true).then(() => {
console.info('setCommunicationDevice true is success.');
});
}
//查询通信设备激活状态
async isCommunicationDeviceActive() {
await loadAudioRoutingManager();
await audioRoutingManager.isCommunicationDeviceActive(audio.CommunicationDeviceType.SPEAKER).then((value) => {
console.info(`CommunicationDevice state is: ${value}.`);
});
}
//综合调用:先设置设备激活,然后查询设备状态。
async test(){
await setCommunicationDevice();
await isCommunicationDeviceActive();
}
```
# 音频流管理开发指导
## 简介
AudioStreamManager提供了音频流管理的方法。开发者可以通过本指导了解应用如何通过AudioStreamManager管理音频流。
## 运作机制
该模块提供了音频流管理调用关系图
**图1** 音频流管理调用关系图
![zh-ch_image_audio_stream_manager](figures/zh-ch_image_audio_stream_manager.png)
**说明**:在进行应用开发的过程中,开发者需要使用getStreamManager()创建一个AudioStreamManager实例,进而通过该实例管理音频流。开发者可通过调用on('audioRendererChange')、on('audioCapturerChange')监听音频播放应用和音频录制应用,在应用状态变化、设备变化、音频属性变化时获得通知。同时可通过off('audioRendererChange')、off('audioCapturerChange')取消相关事件的监听。与此同时,开发者可以通过调用(可选)使用getCurrentAudioRendererInfoArray()获取当前音频播放应用的音频流唯一ID、音频播放客户端的UID、音频状态等信息,同理可调用getCurrentAudioCapturerInfoArray()获取音频录制应用的信息。
## 开发指导
详细API含义可参考:[音频管理API文档AudioStreamManager](../reference/apis/js-apis-audio.md#audiostreammanager9)
1. 创建AudioStreamManager实例。
在使用AudioStreamManager的API前,需要使用getStreamManager()创建一个AudioStreamManager实例。
```js
var audioManager = audio.getAudioManager();
var audioStreamManager = audioManager.getStreamManager();
```
2. (可选)使用on('audioRendererChange')监听音频渲染器更改事件。
如果音频流监听应用需要在音频播放应用状态变化、设备变化、音频属性变化时获取通知,可以订阅该事件。更多事件请参考[API参考文档](../reference/apis/js-apis-audio.md)
```js
audioStreamManager.on('audioRendererChange', (AudioRendererChangeInfoArray) => {
for (let i = 0; i < AudioRendererChangeInfoArray.length; i++) {
AudioRendererChangeInfo = AudioRendererChangeInfoArray[i];
console.info('## RendererChange on is called for ' + i + ' ##');
console.info('StreamId for ' + i + ' is:' + AudioRendererChangeInfo.streamId);
console.info('ClientUid for ' + i + ' is:' + AudioRendererChangeInfo.clientUid);
console.info('Content for ' + i + ' is:' + AudioRendererChangeInfo.rendererInfo.content);
console.info('Stream for ' + i + ' is:' + AudioRendererChangeInfo.rendererInfo.usage);
console.info('Flag ' + i + ' is:' + AudioRendererChangeInfo.rendererInfo.rendererFlags);
console.info('State for ' + i + ' is:' + AudioRendererChangeInfo.rendererState);
var devDescriptor = AudioRendererChangeInfo.deviceDescriptors;
for (let j = 0; j < AudioRendererChangeInfo.deviceDescriptors.length; j++) {
console.info('Id:' + i + ':' + AudioRendererChangeInfo.deviceDescriptors[j].id);
console.info('Type:' + i + ':' + AudioRendererChangeInfo.deviceDescriptors[j].deviceType);
console.info('Role:' + i + ':' + AudioRendererChangeInfo.deviceDescriptors[j].deviceRole);
console.info('Name:' + i + ':' + AudioRendererChangeInfo.deviceDescriptors[j].name);
console.info('Address:' + i + ':' + AudioRendererChangeInfo.deviceDescriptors[j].address);
console.info('SampleRates:' + i + ':' + AudioRendererChangeInfo.deviceDescriptors[j].sampleRates[0]);
console.info('ChannelCounts' + i + ':' + AudioRendererChangeInfo.deviceDescriptors[j].channelCounts[0]);
console.info('ChannelMask:' + i + ':' + AudioRendererChangeInfo.deviceDescriptors[j].channelMasks);
}
}
});
```
3. (可选)使用off('audioRendererChange')取消监听音频渲染器更改事件。
```js
audioStreamManager.off('audioRendererChange');
console.info('######### RendererChange Off is called #########');
```
4. (可选)使用on('audioCapturerChange')监听音频捕获器更改事件。
如果音频流监听应用需要在音频录制应用状态变化、设备变化、音频属性变化时获取通知,可以订阅该事件。更多事件请参考[API参考文档](../reference/apis/js-apis-audio.md)
```js
audioStreamManager.on('audioCapturerChange', (AudioCapturerChangeInfoArray) => {
for (let i = 0; i < AudioCapturerChangeInfoArray.length; i++) {
console.info(' ## audioCapturerChange on is called for element ' + i + ' ##');
console.info('StreamId for ' + i + 'is:' + AudioCapturerChangeInfoArray[i].streamId);
console.info('ClientUid for ' + i + 'is:' + AudioCapturerChangeInfoArray[i].clientUid);
console.info('Source for ' + i + 'is:' + AudioCapturerChangeInfoArray[i].capturerInfo.source);
console.info('Flag ' + i + 'is:' + AudioCapturerChangeInfoArray[i].capturerInfo.capturerFlags);
console.info('State for ' + i + 'is:' + AudioCapturerChangeInfoArray[i].capturerState);
for (let j = 0; j < AudioCapturerChangeInfoArray[i].deviceDescriptors.length; j++) {
console.info('Id:' + i + ':' + AudioCapturerChangeInfoArray[i].deviceDescriptors[j].id);
console.info('Type:' + i + ':' + AudioCapturerChangeInfoArray[i].deviceDescriptors[j].deviceType);
console.info('Role:' + i + ':' + AudioCapturerChangeInfoArray[i].deviceDescriptors[j].deviceRole);
console.info('Name:' + i + ':' + AudioCapturerChangeInfoArray[i].deviceDescriptors[j].name);
console.info('Address:' + i + ':' + AudioCapturerChangeInfoArray[i].deviceDescriptors[j].address);
console.info('SampleRates:' + i + ':' + AudioCapturerChangeInfoArray[i].deviceDescriptors[j].sampleRates[0]);
console.info('ChannelCounts' + i + ':' + AudioCapturerChangeInfoArray[i].deviceDescriptors[j].channelCounts[0]);
console.info('ChannelMask:' + i + ':' + AudioCapturerChangeInfoArray[i].deviceDescriptors[j].channelMasks);
}
}
});
```
5. (可选)使用off('audioCapturerChange')取消监听音频捕获器更改事件。
```js
audioStreamManager.off('audioCapturerChange');
console.info('######### CapturerChange Off is called #########');
```
6. (可选)使用getCurrentAudioRendererInfoArray()获取当前音频渲染器的信息。
该接口可获取音频流唯一ID,音频播放客户端的UID,音频状态以及音频播放器的其他信息。需注意的是若对第三方音频流监听应用未配置ohos.permission.USE_BLUETOOTH权限,则查询到的设备名称和设备地址为空字符串,若正确配置权限,则显示的实际的设备名称和设备地址信息。
```js
await audioStreamManager.getCurrentAudioRendererInfoArray().then( function (AudioRendererChangeInfoArray) {
console.info('######### Get Promise is called ##########');
if (AudioRendererChangeInfoArray != null) {
for (let i = 0; i < AudioRendererChangeInfoArray.length; i++) {
AudioRendererChangeInfo = AudioRendererChangeInfoArray[i];
console.info('StreamId for ' + i +' is:' + AudioRendererChangeInfo.streamId);
console.info('ClientUid for ' + i + ' is:' + AudioRendererChangeInfo.clientUid);
console.info('Content ' + i + ' is:' + AudioRendererChangeInfo.rendererInfo.content);
console.info('Stream' + i +' is:' + AudioRendererChangeInfo.rendererInfo.usage);
console.info('Flag' + i + ' is:' + AudioRendererChangeInfo.rendererInfo.rendererFlags);
console.info('State for ' + i + ' is:' + AudioRendererChangeInfo.rendererState);
var devDescriptor = AudioRendererChangeInfo.deviceDescriptors;
for (let j = 0; j < AudioRendererChangeInfo.deviceDescriptors.length; j++) {
console.info('Id:' + i + ':' + AudioRendererChangeInfo.deviceDescriptors[j].id);
console.info('Type:' + i + ':' + AudioRendererChangeInfo.deviceDescriptors[j].deviceType);
console.info('Role:' + i + ':' + AudioRendererChangeInfo.deviceDescriptors[j].deviceRole);
console.info('Name:' + i + ':' + AudioRendererChangeInfo.deviceDescriptors[j].name);
console.info('Address:' + i + ':' + AudioRendererChangeInfo.deviceDescriptors[j].address);
console.info('SampleRates:' + i + ':' + AudioRendererChangeInfo.deviceDescriptors[j].sampleRates[0]);
console.info('ChannelCounts' + i + ':' + AudioRendererChangeInfo.deviceDescriptors[j].channelCounts[0]);
console.info('ChannelMask:' + i + ':' + AudioRendererChangeInfo.deviceDescriptors[j].channelMasks);
}
}
}
}).catch((err) => {
console.log('getCurrentAudioRendererInfoArray :ERROR: ' + err.message);
});
```
7. (可选)使用getCurrentAudioCapturerInfoArray()获取当前音频捕获器的信息。
该接口可获取音频流唯一ID,音频录制客户端的UID,音频状态以及音频捕获器的其他信息。需注意的是若对第三方音频流监听应用未配置ohos.permission.USE_BLUETOOTH权限,则查询到的设备名称和设备地址为空字符串,若正确配置权限,则显示的实际的设备名称和设备地址信息。
```js
await audioStreamManager.getCurrentAudioCapturerInfoArray().then( function (AudioCapturerChangeInfoArray) {
console.info('getCurrentAudioCapturerInfoArray: **** Get Promise Called ****');
if (AudioCapturerChangeInfoArray != null) {
for (let i = 0; i < AudioCapturerChangeInfoArray.length; i++) {
console.info('StreamId for ' + i + 'is:' + AudioCapturerChangeInfoArray[i].streamId);
console.info('ClientUid for ' + i + 'is:' + AudioCapturerChangeInfoArray[i].clientUid);
console.info('Source for ' + i + 'is:' + AudioCapturerChangeInfoArray[i].capturerInfo.source);
console.info('Flag ' + i + 'is:' + AudioCapturerChangeInfoArray[i].capturerInfo.capturerFlags);
console.info('State for ' + i + 'is:' + AudioCapturerChangeInfoArray[i].capturerState);
var devDescriptor = AudioCapturerChangeInfoArray[i].deviceDescriptors;
for (let j = 0; j < AudioCapturerChangeInfoArray[i].deviceDescriptors.length; j++) {
console.info('Id:' + i + ':' + AudioCapturerChangeInfoArray[i].deviceDescriptors[j].id);
console.info('Type:' + i + ':' + AudioCapturerChangeInfoArray[i].deviceDescriptors[j].deviceType);
console.info('Role:' + i + ':' + AudioCapturerChangeInfoArray[i].deviceDescriptors[j].deviceRole);
console.info('Name:' + i + ':' + AudioCapturerChangeInfoArray[i].deviceDescriptors[j].name)
console.info('Address:' + i + ':' + AudioCapturerChangeInfoArray[i].deviceDescriptors[j].address);
console.info('SampleRates:' + i + ':' + AudioCapturerChangeInfoArray[i].deviceDescriptors[j].sampleRates[0]);
console.info('ChannelCounts' + i + ':' + AudioCapturerChangeInfoArray[i].deviceDescriptors[j].channelCounts[0]);
console.info('ChannelMask:' + i + ':' + AudioCapturerChangeInfoArray[i].deviceDescriptors[j].channelMasks);
}
}
}
}).catch((err) => {
console.log('getCurrentAudioCapturerInfoArray :ERROR: ' + err.message);
});
```
\ No newline at end of file
# 音视频概述
在音视频开发指导中,将介绍各种涉及音频、视频播放或录制功能场景的开发方式,指导开发者如何使用系统提供的音视频API实现对应功能。比如使用TonePlayer实现简单的提示音,当设备接收到新消息时,会发出短促的“滴滴”声;使用AVPlayer实现音乐播放器,循环播放一首音乐。
在每个功能中,会介绍多种实现方式以应对不同的使用场景,以及该场景相关的子功能点。比如在音频播放功能内,会同时介绍音频的并发策略、音量管理和输出设备等在操作系统中的处理方式,帮助开发者能够开发出功能覆盖更全面的应用。
本开发指导仅针对音视频播放或录制本身,由[@ohos.multimedia.audio](../reference/apis/js-apis-audio.md)(下文简称audio模块)和[@ohos.multimedia.media](../reference/apis/js-apis-media.md)(下文简称media模块)提供相关能力,不涉及UI界面、图形处理、媒体存储或其他相关领域功能。
## 开发说明
在开发音频功能之前,尤其是要实现处理音频数据的功能前,建议开发者先了解声学相关的知识,帮助理解操作系统提供的API是如何控制音频系统,从而开发出更易用、体验更好的音视频类应用。建议了解的相关概念包括但不限于:
- 音频量化的过程:采样 &gt; 量化 &gt; 编码
- 音频量化过程的相关概念:模拟信号和数字信号、采样率、声道、采样格式、位宽、码率、常见编码格式(如AAC、MP3、PCM、WMA等)、常见封装格式(如WAV、MPA、FLAC、AAC、OGG等)
在开发音乐、视频播放功能之前,建议了解流媒体播放的相关概念包括但不限于:
- 播放过程:网络协议 &gt; 容器格式 &gt; 音视频编解码 &gt; 图形/音频渲染
- 网络协议:比如HLS、HTTP/HTTPS;容器格式:比如mp4,mkv,mpeg-ts,webm。
- 编码格式:比如h263/h264/h265,MPEG4/MPEG2。
## 音频流介绍
在开发音频应用之前,还需要了解什么是音频流,它是OpenHarmony音频系统中的关键概念,在之后的章节中会多次提及。
音频流,是音频系统中对一个具备音频格式和音频使用场景信息的独立音频数据处理单元的定义,可以表示播放,也可以表示录制,并且具备独立音量调节和音频设备路由切换能力。
音频流基础信息通过[AudioStreamInfo](../reference/apis/js-apis-audio.md#audiostreaminfo8)表示,包含采样、声道、位宽、编码信息,是创建音频播放或录制流的必要参数,描述了音频数据的基本属性。在配置时开发者需要保证基础信息与传输的音频数据是相匹配的,音频系统才能正确处理数据。
### 音频流使用场景信息
除了基本属性,音频流还需要具备使用场景信息。基础信息只能对音频数据进行描述,但在实际的使用过程中,不同的音频流,在音量大小,设备路由,并发策略上是有区别的。系统就是通过音频流所附带的使用场景信息,为不同的音频流制定合适的处理策略,以达到最佳的音频用户体验。
- 播放场景
音频播放场景的信息,通过[StreamUsage](../reference/apis/js-apis-audio.md#streamusage)[ContentType](../reference/apis/js-apis-audio.md#contenttype)进行描述。
StreamUsage指音频流本身的用途类型,包括媒体、语音通信、语音播报、通知、铃声等。
ContentType指音频流中数据的内容类型,包括语音、音乐、影视、通知、铃声等。
- 录制场景
音频流录制场景的信息,通过[SourceType](../reference/apis/js-apis-audio.md#sourcetype8)进行描述。
SourceType指音频流中录音源的类型,包括麦克风音频源、语音识别音频源、语音通话音频源等。
## 支持的音频格式
audio模块下的接口支持PCM编码,包括AudioRenderer、AudioCapturer、TonePlayer、OpenSL ES等。
音频格式说明:
- 支持的常用的音频采样率(Hz):8000、11025、12000、16000、22050、24000、32000、44100、48000、64000、96000,具体参考枚举[AudioSamplingRate](../reference/apis/js-apis-audio.md#audiosamplingrate8)
不同设备支持的采样率规格会存在差异。
- 支持单声道、双声道,具体参考[AudioChannel](../reference/apis/js-apis-audio.md#audiochannel8)
- 支持的采样格式:U8(无符号8位整数)、S16LE(带符号的16位整数,小尾数)、S24LE(带符号的24位整数,小尾数)、S32LE(带符号的32位整数,小尾数)、F32LE(带符号的32位浮点数,小尾数),具体参考[AudioSampleFormat](../reference/apis/js-apis-audio.md#audiosampleformat8)
由于系统限制,S24LE、S32LE、F32LE仅部分设备支持,请根据实际情况使用。
小尾数指的是小端模式,即数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中。这种存储模式将地址的高低和数据的位权有效结合起来,高地址部分权值高,低地址部分权值低。
media模块下的接口支持的音视频格式将在[AVPlayer和AVRecorder](avplayer-avrecorder-overview.md)的介绍中承载。
此差异已折叠。
此差异已折叠。
此差异已折叠。
# 相机开发概述
开发者通过调用OpenHarmony相机服务提供的接口可以开发相机应用,应用通过访问和操作相机硬件,实现基础操作,如预览、拍照和录像;还可以通过接口组合完成更多操作,如控制闪光灯和曝光时间、对焦或调焦等。
## 开发模型
相机调用摄像头采集、加工图像视频数据,精确控制对应的硬件,灵活输出图像、视频内容,满足多镜头硬件适配(如广角、长焦、TOF)、多业务场景适配(如不同分辨率、不同格式、不同效果)的要求。
相机的工作流程如图所示,可概括为相机输入设备管理、会话管理和相机输出管理三部分。
- 相机设备调用摄像头采集数据,作为相机输入流。
- 会话管理可配置输入流,即选择哪些镜头进行拍摄。另外还可以配置闪光灯、曝光时间、对焦和调焦等参数,实现不同效果的拍摄,从而适配不同的业务场景。应用可以通过切换会话满足不同场景的拍摄需求。
- 配置相机的输出流,即将内容以预览流、拍照流或视频流输出。
**图1** 相机工作流程  
![Camera Workflow](figures/camera-workflow.png)
了解相机工作流程后,建议开发者了解相机的开发模型,便于更好地开发相机应用。
**图2** 相机开发模型  
![Camera Development Model](figures/camera-development-model.png)
相机应用通过控制相机,实现图像显示(预览)、照片保存(拍照)、视频录制(录像)等基础操作。在实现基本操作过程中,相机服务会控制相机设备采集和输出数据,采集的图像数据在相机底层的设备硬件接口(HDI,Hardware Device Interfaces),直接通过BufferQueue传递到具体的功能模块进行处理。BufferQueue在应用开发中无需关注,用于将底层处理的数据及时送到上层进行图像显示。
以视频录制为例进行说明,相机应用在录制视频过程中,媒体录制服务先创建一个视频Surface用于传递数据,并提供给相机服务,相机服务可控制相机设备采集视频数据,生成视频流。采集的数据通过底层相机HDI处理后,通过Surface将视频流传递给媒体录制服务,媒体录制服务对视频数据进行处理后,保存为视频文件,完成视频录制。
# 开发准备
相机应用开发的主要流程包含开发准备、设备输入、会话管理、预览、拍照和录像等。
在开发相机应用时,需要先申请相机相关权限,确保应用拥有访问相机硬件及其他功能的权限,需要的权限如下表。在申请权限前,请保证符合[权限使用的基本原则](../security/accesstoken-overview.md#权限使用的基本原则)
| 权限名 | 说明 | 授权方式 |
| -------- | -------- | -------- |
| ohos.permission.CAMERA | 允许应用使用相机拍摄照片和录制视频。 | user_grant |
| ohos.permission.MICROPHONE | 允许应用使用麦克风(可选)。<br/>如需同时录制音频,需要申请该权限。 | user_grant |
| ohos.permission.WRITE_MEDIA | 允许应用读写用户外部存储中的媒体文件信息(可选)。 | user_grant |
| ohos.permission.READ_MEDIA | 允许应用读取用户外部存储中的媒体文件信息(可选)。 | user_grant |
| ohos.permission.MEDIA_LOCATION | 允许应用访问用户媒体文件中的地理位置信息(可选)。 | user_grant |
以上权限的授权方式均为user_grant(用户授权),即开发者在module.json5文件中配置对应的权限后,需要使用接口[abilityAccessCtrl.requestPermissionsFromUser](../reference/apis/js-apis-abilityAccessCtrl.md#requestpermissionsfromuser9)去校验当前用户是否已授权。如果是,应用可以直接访问/操作目标对象;否则需要弹框向用户申请授权。
具体申请方式及校验方式,请参考[访问控制授权申请指导](../security/accesstoken-guidelines.md/)
> **说明:**
> 即使用户曾被授予过权限,应用在调用此权限保护的接口前,也应该先检查是否有权限。不能把之前授予的状态持久化,因为用户在动态授予后可能通过“设置”取消应用权限。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册