提交 2aa9fd3e 编写于 作者: H huang-xl

avplayer+avrecorder Signed-off-by: huang-xl<huangxiaolong1@huawei.com>

Signed-off-by: Nhuang-xl <huangxiaolong1@huawei.com>
上级 a6b98e54
# AVPlayer播放器开发指导
## 简介
AVPlayer主要工作是将Audio/Video媒体资源转码为可供渲染的图像和可听见的音频模拟信号,并通过输出设备进行播放,同时对播放任务进行管理,包括开始播放、暂停播放、停止播放、释放资源、设置音量、跳转播放位置、获取轨道信息等功能控制。
## 运作机制
该模块提供了播放状态变化示意图[AVPlayerState](../reference/apis/js-apis-media.md#avplayerstate9)、音频播放外部模块交互图和视频播放外部模块交互图。
**图1** 播放状态变化示意图<a name = avplayer_state></a>
![zh-ch_image_avplayer_state_machine](figures/zh-ch_image_avplayer_state_machine.png)
**图2** 音频播放外部模块交互图
![zh-ch_image_avplayer_audio](figures/zh-ch_image_avplayer_audio.png)
**说明**:应用通过调用JS接口层提供的AVPlayer js接口实现相应功能时,框架层会通过Player Framework的播放服务解析成音频数据流,音频数据流经过软件解码后输出至Audio Framework的音频服务,由音频子系统输出至硬件接口层的音频HDI,实现音频播放功能。完整的音乐播放器工作需要:应用(应用适配)、Player Framework、Audio Framework、Audio HDI(驱动适配)共同实现。
*注意:音频播放需要音频子系统配合*
1. 应用把url传递给AVPlayer JS。
2. 播放服务把音频PCM数据流输出给音频服务,音频服务输出给Audio HDI。
**图3** 视频播放外部模块交互图
![zh-ch_image_avplayer_video](figures/zh-ch_image_avplayer_video.png)
**说明**:应用通过调用JS接口层提供的AVPlayer js接口实现相应功能时,框架层会通过Player Framework的播放服务解析成单独的音频数据流和视频数据流,音频数据流经过软件解码后输出至Audio Framework的音频服务,由音频子系统输出至硬件接口层的音频HDI,实现音频播放功能。视频数据流经过硬件(推荐)/软件解码后输出至Graphic Framework的渲染服务(Renderer Service),由RS子系统输出至硬件接口层的显示HDI。完整的视频播放器工作需要:应用(应用适配)、XCompomemt组件、Player Framework、Graphic Framework、Audio Framework、Display HDI(驱动适配)和Audio HDI(驱动适配)共同实现。
*注意:视频播放需要显示、音频、解码等多个子系统配合。*
1. 应用从Xcomponent组件获取surfaceID,[获取方式](../reference/arkui-ts/ts-basic-components-xcomponent.md)
2. 应用把url、surfaceID传递给AVPlayer JS。
3. 播放服务把视频ES数据流输出给Codec HDI,解码获得视频帧(NV12/NV21/RGBA)。
4. 播放服务把音频PCM数据流输出给音频服务,音频服务输出给Audio HDI。
5. 播放服务把视频帧(NV12/NV21/RGBA)输出给RS服务,RS服务输出给Display HDI。
## 兼容性说明
推荐使用主流的播放格式和主流分辨率,不建议开发者自制非常或者异常码流,以免产生无法播放、卡住、花屏等兼容性问题。若发生此类问题不会影响系统,退出码流播放即可。
主流的播放格式和主流分辨率如下:
| 视频容器规格 | 规格描述 | 分辨率 |
| :----------: | :-----------------------------------------------: | :--------------------------------: |
| mp4 | 视频格式:H264/MPEG2/MPEG4/H263 音频格式:AAC/MP3 | 主流分辨率,如1080P/720P/480P/270P |
| mkv | 视频格式:H264/MPEG2/MPEG4/H263 音频格式:AAC/MP3 | 主流分辨率,如1080P/720P/480P/270P |
| ts | 视频格式:H264/MPEG2/MPEG4 音频格式:AAC/MP3 | 主流分辨率,如1080P/720P/480P/270P |
| webm | 视频格式:VP8 音频格式:VORBIS | 主流分辨率,如1080P/720P/480P/270P |
| 音频容器规格 | 规格描述 |
| :----------: | :----------: |
| m4a | 音频格式:AAC |
| aac | 音频格式:AAC |
| mp3 | 音频格式:MP3 |
| ogg | 音频格式:VORBIS |
| wav | 音频格式:PCM |
## 开发指导
详细API含义可参考:[媒体服务API文档AVPlayer](../reference/apis/js-apis-media.md#avplayer9)
### 播放流程说明
播放的全流程场景包含:创建实例,设置资源,设置窗口(视频),准备播放(获取轨道信息/音量/倍速/焦点模式/缩放模式/设置bitrates),播控(播放/暂停/Seek/音量/停止),重置资源,销毁播放
1:创建实例[createAVPlayer()](../reference/apis/js-apis-media.md#mediacreateavplayer9),AVPlayer初始化[idle](#avplayer_state)状态
2:设置业务需要的监听事件,搭配全流程场景使用
3:设置资源 [url](../reference/apis/js-apis-media.md#avplayer_属性),AVPlayer进入[initialized](#avplayer_state)状态,此时可以设置视频窗口 [surfaceId](../reference/apis/js-apis-media.md#avplayer_属性),支持的规格可参考:[AVPlayer属性说明](../reference/apis/js-apis-media.md#avplayer_属性)
4:准备播放 [prepare()](../reference/apis/js-apis-media.md#avplayer_prepare),AVPlayer进入[prepared](#avplayer_state)状态
5:视频播控:播放 [play()](../reference/apis/js-apis-media.md#avplayer_play),暂停 [pause()](../reference/apis/js-apis-media.md#avplayer_pause),跳转 [seek()](../reference/apis/js-apis-media.md#avplayer_seek),停止 [stop()](../reference/apis/js-apis-media.md#avplayer_stop) 等操作
6:重置资源 [reset()](../reference/apis/js-apis-media.md#avplayer_reset),AVPlayer重新进入[idle](#avplayer_state)状态,允许更换资源 [url](../reference/apis/js-apis-media.md#avplayer_属性)
7:销毁播放 [release()](../reference/apis/js-apis-media.md#avplayer_release),AVPlayer进入[released](#avplayer_state)状态,退出播放
> **说明:**
>
> prepared/playing/paused/compeled 状态时,播放引擎处于工作状态,这需要占用系统较多的运行内存,当客户端暂时不使用播放器时,要求调用 reset() 或 release() 回收。
### 监听事件
| 事件类型 | 说明 |
| ------------------------------------------------- | ------------------------------------------------------------ |
| stateChange<a name = stateChange></a> | 必要事件,监听播放器的状态机 |
| error<a name = error></a> | 必要事件,监听播放器的错误信息 |
| durationUpdate<a name = durationUpdate></a> | 用于进度条,监听进度条长度,刷新资源时长 |
| timeUpdate<a name = timeUpdate></a> | 用于进度条,监听进度条当前位置,刷新当前时间 |
| seekDone<a name = seekDone></a> | 响应api调用,监听seek()请求完成情况 |
| speedDone<a name = speedDone></a> | 响应api调用,监听setSpeed()请求完成情况 |
| volumeChange<a name = volumeChange></a> | 响应api调用,监听setVolume()请求完成情况 |
| bitrateDone<a name = bitrateDone></a> | 响应api调用,用于HLS协议流,监听setBitrate()请求完成情况 |
| availableBitrates<a name = availableBitrates></a> | 用于HLS协议流,监听HLS资源的可选bitrates,用于setBitrate() |
| bufferingUpdate<a name = bufferingUpdate></a> | 用于网络播放,监听网络播放缓冲信息 |
| startRenderFrame<a name = startRenderFrame></a> | 用于视频播放,监听视频播放首帧渲染时间 |
| videoSizeChange<a name = videoSizeChange></a> | 用于视频播放,监听视频播放的宽高信息,可用于调整窗口大小、比例 |
| audioInterrupt<a name = audioInterrupt></a> | 用于视频播放,监听音频焦点切换信息,搭配属性audioInterruptMode使用 |
###
### 全量接口示例
```js
import media from '@ohos.multimedia.media'
import audio from '@ohos.multimedia.audio';
import fileIO from '@ohos.fileio'
const TAG = 'AVPlayerDemo:'
export class AVPlayerDemo {
private count:number = 0
private avPlayer
private surfaceID:string // surfaceID用于播放画面显示,具体的值需要通过Xcomponent接口获取,相关文档链接见上面Xcomponent创建方法
// 注册avplayer回调函数
setAVPlayerCallback() {
// 状态机变化回调函数
this.avPlayer.on('stateChange', async (state, reason) => {
switch (state) {
case 'idle': // 成功调用reset接口后触发该状态机上报
console.info(TAG + 'state idle called')
this.avPlayer.release() // 释放avplayer对象
break;
case 'initialized': // avplayer 设置播放源后触发该状态上报
console.info(TAG + 'state initialized called ')
this.avPlayer.surfaceId = this.surfaceID // 设置显示画面,当播放的资源为纯音频时无需设置
this.avPlayer.prepare().then(() => {
console.info(TAG+ 'prepare success');
}, (err) => {
console.error(TAG + 'prepare filed,error message is :' + err.message)
})
break;
case 'prepared': // prepare调用成功后上报该状态机
console.info(TAG + 'state prepared called')
this.avPlayer.play() // 调用播放接口开始播放
break;
case 'playing': // play成功调用后触发该状态机上报
console.info(TAG + 'state playing called')
if (this.count == 0) {
this.avPlayer.pause() // 调用暂停播放接口
} else {
this.avPlayer.seek(10000, media.SeekMode.SEEK_PREV_SYNC) // 前向seek置10秒处,触发seekDone回调函数
}
break;
case 'paused': // pause成功调用后触发该状态机上报
console.info(TAG + 'state paused called')
if (this.count == 0) {
this.count++
this.avPlayer.play() // 继续调用播放接口开始播放
}
break;
case 'completed': // 播放结束后触发该状态机上报
console.info(TAG + 'state completed called')
this.avPlayer.stop() //调用播放结束接口
break;
case 'stopped': // stop接口成功调用后触发该状态机上报
console.info(TAG + 'state stopped called')
this.avPlayer.reset() // 调用reset接口初始化avplayer状态
break;
case 'released':
console.info(TAG + 'state released called')
break;
case 'error':
console.info(TAG + 'state error called')
break;
default:
console.info(TAG + 'unkown state :' + state)
break;
}
})
// 时间上报监听函数
this.avPlayer.on('timeUpdate', (time:number) => {
console.info(TAG + 'timeUpdate success,and new time is :' + time)
})
// 音量变化回调函数
this.avPlayer.on('volumeChange', (vol:number) => {
console.info(TAG + 'volumeChange success,and new volume is :' + vol)
this.avPlayer.setSpeed(media.AVPlayerSpeed.SPEED_FORWARD_2_00_X) // 设置两倍速播放,并触发speedDone回调
})
// 视频播放结束触发回调
this.avPlayer.on('endOfStream', () => {
console.info(TAG + 'endOfStream success')
})
// seek操作回调函数
this.avPlayer.on('seekDone', (seekDoneTime:number) => {
console.info(TAG + 'seekDone success,and seek time is:' + seekDoneTime)
this.avPlayer.setVolume(0.5) // 设置音量为0.5,并触发volumeChange回调函数
})
// 设置倍速播放回调函数
this.avPlayer.on('speedDone', (speed:number) => {
console.info(TAG + 'speedDone success,and speed value is:' + speed)
})
// bitrate设置成功回调函数
this.avPlayer.on('bitrateDone', (bitrate:number) => {
console.info(TAG + 'bitrateDone success,and bitrate value is:' + bitrate)
})
// 缓冲上报回调函数
this.avPlayer.on('bufferingUpdate', (infoType: media.BufferingInfoType, value: number) => {
console.info(TAG + 'bufferingUpdate success,and infoType value is:' + infoType + ', value is :' + value)
})
// 首帧上报回调函数
this.avPlayer.on('startRenderFrame', () => {
console.info(TAG + 'startRenderFrame success')
})
// 视频宽高上报回调函数
this.avPlayer.on('videoSizeChange', (width: number, height: number) => {
console.info(TAG + 'videoSizeChange success,and width is:' + width + ', height is :' + height)
})
// 焦点上报回调函数
this.avPlayer.on('audioInterrupt', (info: audio.InterruptEvent) => {
console.info(TAG + 'audioInterrupt success,and InterruptEvent info is:' + info)
})
// HLS上报所有支持的比特率
this.avPlayer.on('availableBitrates', (bitrates: Array<number>) => {
console.info(TAG + 'availableBitrates success,and availableBitrates length is:' + bitrates.length)
})
}
async avPlayerDemo() {
// 创建avPlayer实例对象
this.avPlayer = await media.createAVPlayer()
let fdPath = 'fd://'
let pathDir = "/data/storage/el2/base/haps/entry/files" // pathDir在FA模型和Stage模型的获取方式不同,请参考开发步骤首行的说明,根据实际情况自行获取。
// path路径的码流可通过"hdc file send D:\xxx\H264_AAC.mp4 /data/app/el2/100/base/ohos.acts.multimedia.media.avplayer/haps/entry/files" 命令,将其推送到设备上
let path = pathDir + '/H264_AAC.mp4'
await fileIO.open(path).then((fdNumber) => {
fdPath = fdPath + '' + fdNumber
console.info('open fd success fd is' + fdPath)
}, (err) => {
console.info('open fd failed err is' + err)
}).catch((err) => {
console.info('open fd failed err is' + err)
});
this.avPlayer.url = fdPath
}
}
```
### 正常播放场景
```js
import media from '@ohos.multimedia.media'
import fileIO from '@ohos.fileio'
const TAG = 'AVPlayerDemo:'
export class AVPlayerDemo {
private avPlayer
private surfaceID:string // surfaceID用于播放画面显示,具体的值需要通过Xcomponent接口获取,相关文档链接见上面Xcomponent创建方法
// 注册avplayer回调函数
setAVPlayerCallback() {
// 状态机变化回调函数
this.avPlayer.on('stateChange', async (state, reason) => {
switch (state) {
case 'idle': // 成功调用reset接口后触发该状态机上报
console.info(TAG + 'state idle called')
break;
case 'initialized': // avplayer 设置播放源后触发该状态上报
console.info(TAG + 'state initialized called ')
this.avPlayer.surfaceId = this.surfaceID // 设置显示画面,当播放的资源为纯音频时无需设置
this.avPlayer.prepare().then(() => {
console.info(TAG+ 'prepare success');
}, (err) => {
console.error(TAG + 'prepare filed,error message is :' + err.message)
})
break;
case 'prepared': // prepare调用成功后上报该状态机
console.info(TAG + 'state prepared called')
this.avPlayer.play() // 调用播放接口开始播放
break;
case 'playing': // play成功调用后触发该状态机上报
console.info(TAG + 'state playing called')
break;
case 'paused': // pause成功调用后触发该状态机上报
console.info(TAG + 'state paused called')
break;
case 'completed': // 播放结束后触发该状态机上报
console.info(TAG + 'state completed called')
this.avPlayer.stop() //调用播放结束接口
break;
case 'stopped': // stop接口成功调用后触发该状态机上报
console.info(TAG + 'state stopped called')
this.avPlayer.release() // 调用reset接口初始化avplayer状态
break;
case 'released':
console.info(TAG + 'state released called')
break;
case 'error':
console.info(TAG + 'state error called')
break;
default:
console.info(TAG + 'unkown state :' + state)
break;
}
})
}
async avPlayerDemo() {
// 创建avPlayer实例对象
this.avPlayer = await media.createAVPlayer()
let fdPath = 'fd://'
let pathDir = "/data/storage/el2/base/haps/entry/files" // pathDir在FA模型和Stage模型的获取方式不同,请参考开发步骤首行的说明,根据实际情况自行获取。
// path路径的码流可通过"hdc file send D:\xxx\H264_AAC.mp4 /data/app/el2/100/base/ohos.acts.multimedia.media.avplayer/haps/entry/files" 命令,将其推送到设备上
let path = pathDir + '/H264_AAC.mp4'
await fileIO.open(path).then((fdNumber) => {
fdPath = fdPath + '' + fdNumber
console.info('open fd success fd is' + fdPath)
}, (err) => {
console.info('open fd failed err is' + err)
}).catch((err) => {
console.info('open fd failed err is' + err)
});
this.avPlayer.url = fdPath
}
}
```
### 视频切换场景
```js
import media from '@ohos.multimedia.media'
import fileIO from '@ohos.fileio'
const TAG = 'AVPlayerDemo:'
export class AVPlayerDemo {
private count:number = 0
private avPlayer
private surfaceID:string // surfaceID用于播放画面显示,具体的值需要通过Xcomponent接口获取,相关文档链接见上面Xcomponent创建方法
// 注册avplayer回调函数
setAVPlayerCallback() {
// 状态机变化回调函数
this.avPlayer.on('stateChange', async (state, reason) => {
switch (state) {
case 'idle': // 成功调用reset接口后触发该状态机上报
console.info(TAG + 'state idle called')
break;
case 'initialized': // avplayer 设置播放源后触发该状态上报
console.info(TAG + 'state initialized called ')
this.avPlayer.surfaceId = this.surfaceID // 设置显示画面,当播放的资源为纯音频时无需设置
this.avPlayer.prepare().then(() => {
console.info(TAG+ 'prepare success');
}, (err) => {
console.error(TAG + 'prepare filed,error message is :' + err.message)
})
break;
case 'prepared': // prepare调用成功后上报该状态机
console.info(TAG + 'state prepared called')
this.avPlayer.loop = true // 设置单曲循环播放,单曲循环播放至结尾后会触发endOfStream回调
this.avPlayer.play() // 调用播放接口开始播放
break;
case 'playing': // play成功调用后触发该状态机上报
console.info(TAG + 'state playing called')
break;
case 'paused': // pause成功调用后触发该状态机上报
console.info(TAG + 'state paused called')
break;
case 'completed': // 播放结束后触发该状态机上报
console.info(TAG + 'state completed called')
// 当第二次触发endOfStream回调后取消循环播放,再次播放到结尾后触发completed状态机上报
this.avPlayer.stop() //调用播放结束接口
break;
case 'stopped': // stop接口成功调用后触发该状态机上报
console.info(TAG + 'state stopped called')
this.avPlayer.release() // 调用reset接口初始化avplayer状态
break;
case 'released':
console.info(TAG + 'state released called')
break;
case 'error':
console.info(TAG + 'state error called')
break;
default:
console.info(TAG + 'unkown state :' + state)
break;
}
})
// 视频播放结束触发回调
this.avPlayer.on('endOfStream', () => {
console.info(TAG + 'endOfStream success')
if (this.count == 1) {
this.avPlayer.loop = false // 取消循环播放
} else {
this.count++
}
})
}
async avPlayerDemo() {
// 创建avPlayer实例对象
this.avPlayer = await media.createAVPlayer()
let fdPath = 'fd://'
let pathDir = "/data/storage/el2/base/haps/entry/files" // pathDir在FA模型和Stage模型的获取方式不同,请参考开发步骤首行的说明,根据实际情况自行获取。
// path路径的码流可通过"hdc file send D:\xxx\H264_AAC.mp4 /data/app/el2/100/base/ohos.acts.multimedia.media.avplayer/haps/entry/files" 命令,将其推送到设备上
let path = pathDir + '/H264_AAC.mp4'
await fileIO.open(path).then((fdNumber) => {
fdPath = fdPath + '' + fdNumber
console.info('open fd success fd is' + fdPath)
}, (err) => {
console.info('open fd failed err is' + err)
}).catch((err) => {
console.info('open fd failed err is' + err)
});
this.avPlayer.url = fdPath
}
}
```
### 单曲循环场景
```js
import media from '@ohos.multimedia.media'
import fileIO from '@ohos.fileio'
const TAG = 'AVPlayerDemo:'
export class AVPlayerDemo {
private count:number = 0
private avPlayer
private surfaceID:string // surfaceID用于播放画面显示,具体的值需要通过Xcomponent接口获取,相关文档链接见上面Xcomponent创建方法
async nextVideo() {
let fdPath = 'fd://'
let pathDir = "/data/storage/el2/base/haps/entry/files" // pathDir在FA模型和Stage模型的获取方式不同,请参考开发步骤首行的说明,根据实际情况自行获取。
// path路径的码流可通过"hdc file send D:\xxx\H264_MP3.mp4 /data/app/el2/100/base/ohos.acts.multimedia.media.avplayer/haps/entry/files" 命令,将其推送到设备上
let path = pathDir + '/H264_MP3.mp4'
await fileIO.open(path).then((fdNumber) => {
fdPath = fdPath + '' + fdNumber
console.info('open fd success fd is' + fdPath)
}, (err) => {
console.info('open fd failed err is' + err)
}).catch((err) => {
console.info('open fd failed err is' + err)
});
this.avPlayer.url = fdPath // 再次触发initialized状态机上报
}
// 注册avplayer回调函数
setAVPlayerCallback() {
// 状态机变化回调函数
this.avPlayer.on('stateChange', async (state, reason) => {
switch (state) {
case 'idle': // 成功调用reset接口后触发该状态机上报
console.info(TAG + 'state idle called')
await this.nextVideo() // 切换下一个视频播放
break;
case 'initialized': // avplayer 设置播放源后触发该状态上报
console.info(TAG + 'state initialized called ')
this.avPlayer.surfaceId = this.surfaceID // 设置显示画面,当播放的资源为纯音频时无需设置
this.avPlayer.prepare().then(() => {
console.info(TAG+ 'prepare success');
}, (err) => {
console.error(TAG + 'prepare filed,error message is :' + err.message)
})
break;
case 'prepared': // prepare调用成功后上报该状态机
console.info(TAG + 'state prepared called')
this.avPlayer.play() // 调用播放接口开始播放
break;
case 'playing': // play成功调用后触发该状态机上报
console.info(TAG + 'state playing called')
break;
case 'paused': // pause成功调用后触发该状态机上报
console.info(TAG + 'state paused called')
break;
case 'completed': // 播放结束后触发该状态机上报
console.info(TAG + 'state completed called')
if (this.count == 0) {
this.count++
this.avPlayer.reset() //调用重置接口准备切换下一个视频
} else {
this.avPlayer.release() //切换视频后播放至结尾释放avplayer对象
}
break;
case 'stopped': // stop接口成功调用后触发该状态机上报
console.info(TAG + 'state stopped called')
break;
case 'released':
console.info(TAG + 'state released called')
break;
case 'error':
console.info(TAG + 'state error called')
break;
default:
console.info(TAG + 'unkown state :' + state)
break;
}
})
}
async avPlayerDemo() {
// 创建avPlayer实例对象
this.avPlayer = await media.createAVPlayer()
let fdPath = 'fd://'
let pathDir = "/data/storage/el2/base/haps/entry/files" // pathDir在FA模型和Stage模型的获取方式不同,请参考开发步骤首行的说明,根据实际情况自行获取。
// path路径的码流可通过"hdc file send D:\xxx\H264_AAC.mp4 /data/app/el2/100/base/ohos.acts.multimedia.media.avplayer/haps/entry/files" 命令,将其推送到设备上
let path = pathDir + '/H264_AAC.mp4'
await fileIO.open(path).then((fdNumber) => {
fdPath = fdPath + '' + fdNumber
console.info('open fd success fd is' + fdPath)
}, (err) => {
console.info('open fd failed err is' + err)
}).catch((err) => {
console.info('open fd failed err is' + err)
});
this.avPlayer.url = fdPath
}
}
```
## 相关示例
针对AVPlayer播放器开发,有以下相关示例可供参考:
待补充
\ No newline at end of file
# 音视频录制开发指导
## 简介
音视频录制的主要工作是捕获音频信号,接收视频信号,完成音视频编码并保存到文件中,帮助开发者轻松实现音视频录制功能,包括开始录制、暂停录制、恢复录制、停止录制、释放资源等功能控制。它允许调用者指定录制的编码格式、封装格式、文件路径等参数。
## 运作机制
该模块提供了音视频录制状态变化示意图和音视频录制外部模块交互图。
**图1** 音视频录制状态变化示意图
![zh-ch_image_video_recorder_state_machine](figures/zh-ch_image_avrecorder_state_machine.png)
**图2** 视频录制外部模块交互图--待修改
![zh-ch_image_video_recorder_zero](figures/zh-ch_image_avrecorder_module_interaction.png)
**说明**:音频录制时,框架层会通过Native Framework的媒体服务,调用音频子系统通过音频HDI捕获音频数据,通过软件编码封装后保存至文件中,实现音频录制功能。视频录制时,由相机子系统通过视频HDI捕获图像数据,媒体服务将图像数据通过视频编码HDI编码,再将编码后的图像数据封装至文件中,实现视频录制功能。通过音视频录制组合,可分别实现纯音频录制、纯视频录制,音视频录制。
## 约束与限制
开发者在进行录制功能开发前,需要先对所开发的应用配置麦克风权限(ohos.permission.MICROPHONE)和相机权限(ohos.permission.CAMERA),权限配置相关内容可参考:[访问控制权限申请指导](../security/accesstoken-guidelines.md)
## 开发指导
详细API含义可参考:[媒体服务API文档AVRecorder](../reference/apis/js-apis-media.md#avrecorder9)
媒体库相关流程含义可参考:[媒体库管理](../reference/apis/js-apis-medialibrary.md)
相机相关流程含义可参考:[相机管理](../reference/apis/js-apis-camera.md)
### 音视频录制全流程场景
音视频录制全流程场景包含:创建实例、设置录制参数、获取输入surface、开始录制、暂停录制、恢复录制、停止录制、释放资源等流程。
音频录制相关配置参数范围,受到设备编解码性能,音频子系统性能等综合限制。
视频录制相关配置参数范围,受到设备编解码性能,相机子系统性能等综合限制。
```
import media from '@ohos.multimedia.media'
import camera from '@ohos.multimedia.camera'
import mediaLibrary from '@ohos.multimedia.mediaLibrary'
export class AVRecorderDemo {
private testFdNumber; // 用于保存fd地址
// 获取录制的音频文件fileName对应的fd,需借助媒体库能力。使用mediaLibrary需要添加以下权限, ohos.permission.MEDIA_LOCATION、ohos.permission.WRITE_MEDIA、ohos.permission.READ_MEDIA.
async getFd(fileName) {
// 实现方式参考媒体库资料文档。
this.testFdNumber = "fd://" + fdNumber.toString(); // e.g. fd://54
}
// 当promise接口发生错误上上报的错误回调接口
failureCallback(error) {
console.info('error happened, error message is ' + error.message);
}
// 当promise接口发生异常时,系统调用的错误回调接口
catchCallback(error) {
console.info('catch error happened, error message is ' + error.message);
}
async AVRecorderDemo() {
let AVRecorder; // AVRecorder空对象在createAVRecorder成功后赋值
let surfaceID; // 从getInputSurface获取surfaceID,传递给相机的videoOutput
await this.getFd('01.mp4');
// 音视频录制相关参数配置
let avProfile = {
audioBitrate : 48000,
audioChannels : 2,
audioCodec : media.CodecMimeType.AUDIO_AAC,
audioSampleRate : 48000,
fileFormat : media.ContainerFormatType.CFT_MPEG_4,
videoBitrate : 48000,
videoCodec : media.CodecMimeType.VIDEO_MPEG4,
videoFrameWidth : 640,
videoFrameHeight : 480,
videoFrameRate : 30
}
let avConfig = {
audioSourceType : media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC,
videoSourceType : media.VideoSourceType.VIDEO_SOURCE_TYPE_SURFACE_YUV,
profile : avProfile,
url : 'fd://',
rotation : 0,
location : { latitude : 30, longitude : 130 }
}
// 创建AVRecorder对象
await media.createAVRecorder().then((recorder) => {
console.info('case createAVRecorder called');
if (typeof (recorder) != 'undefined') {
AVRecorder = recorder;
console.info('createAVRecorder success');
} else {
console.info('createAVRecorder failed');
}
}, this.failureCallback).catch(this.catchCallback);
// 对象创建成功后创建on('stateChange')和on('error')监听回调用于监听状态机变化和错误上报
AVRecorder.on('stateChange', async (state, reason) => {
console.info('case state has changed, new state is :' + state);
switch (state) {
// 用户可以根据需求在不同状态设置自己想要进行的行为
case 'idle':
// 调用rest接口后触发idle状态;create后也在idle状态
break;
case 'prepared':
// 调用prepare接口后触发prepared状态;
break;
case 'started':
// 调用start接口后触发started状态;
break;
case 'paused':
// 调用pause接口后触发paused状态;
break;
case 'stopped':
// 调用stop接口后触发stopped状态;
break;
case 'released':
// 调用release接口后触发released状态;
break;
case 'error':
// error状态说明底层出错,用户需排查错误,重新创建avRecorder;
break;
default:
console.info('case state is unknown');
}
});
AVRecorder.on('error', (err) => {
// 监听非接口类错误上报
console.info('case avRecorder.on(error) called, errMessage is ' + err.message);
});
// 调用prepare完成音频录制前的准备工作;底层实际是根据prepare的入参来判断是音频录制、视频录制还是音视频录制
await AVRecorder.prepare(avConfig).then(() => {
console.info('prepare success');
}, this.failureCallback).catch(this.catchCallback);
// 包含视频的录制需要调用getInputSurface接口,并将返回值surfaceID传递给camera相关接口
await AVRecorder.getInputSurface().then((surface) => {
console.info('getInputSurface success');
surfaceID = surface; // surfaceID给camera的createVideoOutput()作为其中的一个入参
}, this.failureCallback).catch(this.catchCallback);
// 视频录制依赖相机相关接口,以下需要先调用相机起流接口后才能继续执行,具体的相机接口调用请参考sample用例
// 视频录制启动接口
await AVRecorder.start().then(() => {
console.info('start success');
}, this.failureCallback).catch(this.catchCallback);
// 调用pause接口时需要暂停camera出流
await AVRecorder.pause().then(() => {
console.info('pause success');
}, this.failureCallback).catch(this.catchCallback);
// 调用resume接口时需要恢复camera出流
await AVRecorder.resume().then(() => {
console.info('resume success');
}, this.failureCallback).catch(this.catchCallback);
// 停止camera出流后,停止视频录制
await AVRecorder.stop().then(() => {
console.info('stop success');
}, this.failureCallback).catch(this.catchCallback);
// 重置录制相关配置
await AVRecorder.reset().then(() => {
console.info('reset success');
}, this.failureCallback).catch(this.catchCallback);
// 关闭监听回调,如果用户不自行调用off接口,在调用release后,设置的回调接口也会无效
AVRecorder.off('stateChange');
AVRecorder.off('error');
// 释放视频录制相关资源并释放camera对象相关资源
await AVRecorder.release().then(() => {
console.info('release success');
}, this.failureCallback).catch(this.catchCallback);
// 相关对象置null
AVRecorder = undefined;
surfaceID = undefined;
}
}
```
### 纯音频录制全流程场景
纯音频录制全流程场景包含:创建实例、设置录制参数、开始录制、暂停录制、恢复录制、停止录制、释放资源等流程。
音频录制相关配置参数范围,受到设备编解码性能,音频子系统性能等综合限制。
```
import media from '@ohos.multimedia.media'
import mediaLibrary from '@ohos.multimedia.mediaLibrary'
export class AudioRecorderDemo {
private testFdNumber; // 用于保存fd地址
// 获取录制的音频文件fileName对应的fd,需借助媒体库能力。使用mediaLibrary需要添加以下权限, ohos.permission.MEDIA_LOCATION、ohos.permission.WRITE_MEDIA、ohos.permission.READ_MEDIA
async getFd(fileName) {
// 实现方式参考媒体库资料文档。
this.testFdNumber = "fd://" + fdNumber.toString(); // e.g. fd://54
}
// 当promise接口发生错误上报的错误回调接口
failureCallback(error) {
console.info('error happened, error message is ' + error.message);
}
// 当promise接口发生异常时,系统调用的错误回调接口
catchCallback(error) {
console.info('catch error happened, error message is ' + error.message);
}
async audioRecorderDemo() {
let audioRecorder; // audioRecorder空对象在createAVRecorder成功后赋值
await this.getFd('01.m4a');
// 音频录制相关参数配置
let audioProfile = {
audioBitrate : 48000,
audioChannels : 2,
audioCodec : media.CodecMimeType.AUDIO_AAC,
audioSampleRate : 48000,
fileFormat : media.ContainerFormatType.CFT_MPEG_4,
}
let audioConfig = {
audioSourceType : media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC,
profile : audioProfile,
url : this.testFdNumber,
rotation : 0,
location : { latitude : 30, longitude : 130 }
}
// 创建audioRecorder对象
await media.createAVRecorder().then((recorder) => {
console.info('case createAVRecorder called');
if (typeof (recorder) != 'undefined') {
audioRecorder = recorder;
console.info('createAudioRecorder success');
} else {
console.info('createAudioRecorder failed');
}
}, this.failureCallback).catch(this.catchCallback);
// 对象创建成功后创建on('stateChange')和on('error')监听回调用于监听状态机变化和错误上报
audioRecorder.on('stateChange', async (state, reason) => {
console.info('case state has changed, new state is :' + state);
switch (state) {
// 用户可以根据需求在不同状态设置自己想要进行的行为
case 'idle':
// 调用rest接口后触发idle状态;create后也在idle状态
break;
case 'prepared':
// 调用prepare接口后触发prepared状态;
break;
case 'started':
// 调用start接口后触发started状态;
break;
case 'paused':
// 调用pause接口后触发paused状态;
break;
case 'stopped':
// 调用stop接口后触发stopped状态;
break;
case 'released':
// 调用release接口后触发released状态;
break;
case 'error':
// error状态说明底层出错,用户需排查错误,重新创建avRecorder;
break;
default:
console.info('case state is unknown');
}
});
audioRecorder.on('error', (err) => {
// 监听非接口类错误上报
console.info('case avRecorder.on(error) called, errMessage is ' + err.message);
});
// 调用prepare完成音频录制前的准备工作;底层实际是根据prepare的入参来判断是音频录制、视频录制还是音视频录制
await audioRecorder.prepare(audioConfig).then(() => {
console.info('prepare success');
}, this.failureCallback).catch(this.catchCallback);
// 调用start接口启动音频录制
await audioRecorder.start().then(() => {
console.info('start success');
}, this.failureCallback).catch(this.catchCallback);
// 调用pause接口暂停音频录制
await audioRecorder.pause().then(() => {
console.info('pause success');
}, this.failureCallback).catch(this.catchCallback);
// 调用resume接口恢复音频录制
await audioRecorder.resume().then(() => {
console.info('resume success');
}, this.failureCallback).catch(this.catchCallback);
// 调用stop接口停止音频录制
await audioRecorder.stop().then(() => {
console.info('stop success');
}, this.failureCallback).catch(this.catchCallback);
// 调用reset接口重置录制相关配置
await audioRecorder.reset().then(() => {
console.info('reset success');
}, this.failureCallback).catch(this.catchCallback);
// 关闭监听回调,如果用户不自行调用off接口,在调用release后,设置的回调接口也会无效
avRecorder.off('stateChange');
avRecorder.off('error');
// 调用release接口释放音频录制相关资源
await audioRecorder.release().then(() => {
console.info('release success');
}, this.failureCallback).catch(this.catchCallback);
// 相关对象置null
audioRecorder = undefined;
}
}
```
### 纯视频录制全流程场景
纯视频录制全流程场景包含:创建实例、设置录制参数、获取输入surface、开始录制、暂停录制、恢复录制、停止录制、释放资源等流程。
视频录制相关配置参数范围,受到设备编解码性能,相机子系统性能等综合限制。
```
import media from '@ohos.multimedia.media'
import camera from '@ohos.multimedia.camera'
import mediaLibrary from '@ohos.multimedia.mediaLibrary'
export class VideoRecorderDemo {
private testFdNumber; // 用于保存fd地址
// 获取录制的音频文件fileName对应的fd,需借助媒体库能力。使用mediaLibrary需要添加以下权限, ohos.permission.MEDIA_LOCATION、ohos.permission.WRITE_MEDIA、ohos.permission.READ_MEDIA.
async getFd(fileName) {
// 实现方式参考媒体库资料文档。
this.testFdNumber = "fd://" + fdNumber.toString(); // e.g. fd://54
}
// 当promise接口发生错误上上报的错误回调接口
failureCallback(error) {
console.info('error happened, error message is ' + error.message);
}
// 当promise接口发生异常时,系统调用的错误回调接口
catchCallback(error) {
console.info('catch error happened, error message is ' + error.message);
}
async videoRecorderDemo() {
let videoRecorder; // videoRecorder空对象在createAVRecorder成功后赋值
let surfaceID; // 从getInputSurface获取surfaceID,传递给相机的videoOutput
await this.getFd('01.mp4');
// 纯视频录制相关参数配置
let videoProfile = {
fileFormat : media.ContainerFormatType.CFT_MPEG_4,
videoBitrate : 48000,
videoCodec : media.CodecMimeType.VIDEO_MPEG4,
videoFrameWidth : 640,
videoFrameHeight : 480,
videoFrameRate : 30
}
let videoConfig = {
videoSourceType : media.VideoSourceType.VIDEO_SOURCE_TYPE_SURFACE_YUV,
profile : videoProfile,
url : 'fd://',
rotation : 0,
location : { latitude : 30, longitude : 130 }
}
// 创建videoRecorder对象
await media.createAVRecorder().then((recorder) => {
console.info('case createVideoRecorder called');
if (typeof (recorder) != 'undefined') {
videoRecorder = recorder;
console.info('createVideoRecorder success');
} else {
console.info('createVideoRecorder failed');
}
}, this.failureCallback).catch(this.catchCallback);
// 对象创建成功后创建on('stateChange')和on('error')监听回调用于监听状态机变化和错误上报
videoRecorder.on('stateChange', async (state, reason) => {
console.info('case state has changed, new state is :' + state);
switch (state) {
// 用户可以根据需求在不同状态设置自己想要进行的行为
case 'idle':
// 调用rest接口后触发idle状态;create后也在idle状态
break;
case 'prepared':
// 调用prepare接口后触发prepared状态;
break;
case 'started':
// 调用start接口后触发started状态;
break;
case 'paused':
// 调用pause接口后触发paused状态;
break;
case 'stopped':
// 调用stop接口后触发stopped状态;
break;
case 'released':
// 调用release接口后触发released状态;
break;
case 'error':
// error状态说明底层出错,用户需排查错误,重新创建avRecorder;
break;
default:
console.info('case state is unknown');
}
});
videoRecorder.on('error', (err) => {
// 监听非接口类错误上报
console.info('case avRecorder.on(error) called, errMessage is ' + err.message);
});
// 调用prepare完成音频录制前的准备工作;底层实际是根据prepare的入参来判断是音频录制、视频录制还是音视频录制
await videoRecorder.prepare(videoConfig).then(() => {
console.info('prepare success');
}, this.failureCallback).catch(this.catchCallback);
// 包含视频的录制需要调用getInputSurface接口,并将返回值surfaceID传递给camera相关接口
await videoRecorder.getInputSurface().then((surface) => {
console.info('getInputSurface success');
surfaceID = surface; // surfaceID给camera的createVideoOutput()作为其中的一个入参
}, this.failureCallback).catch(this.catchCallback);
// 视频录制依赖相机相关接口,以下需要先调用相机起流接口后才能继续执行,具体的相机接口调用请参考sample用例
// 视频录制启动接口
await videoRecorder.start().then(() => {
console.info('start success');
}, this.failureCallback).catch(this.catchCallback);
// 调用pause接口时需要暂停camera出流
await videoRecorder.pause().then(() => {
console.info('pause success');
}, this.failureCallback).catch(this.catchCallback);
// 调用resume接口时需要恢复camera出流
await videoRecorder.resume().then(() => {
console.info('resume success');
}, this.failureCallback).catch(this.catchCallback);
// 停止camera出流后,停止视频录制
await videoRecorder.stop().then(() => {
console.info('stop success');
}, this.failureCallback).catch(this.catchCallback);
// 重置录制相关配置
await videoRecorder.reset().then(() => {
console.info('reset success');
}, this.failureCallback).catch(this.catchCallback);
// 关闭监听回调,如果用户不自行调用off接口,在调用release后,设置的回调接口也会无效
videoRecorder.off('stateChange');
videoRecorder.off('error');
// 释放视频录制相关资源并释放camera对象相关资源
await videoRecorder.release().then(() => {
console.info('release success');
}, this.failureCallback).catch(this.catchCallback);
// 相关对象置null
videoRecorder = undefined;
surfaceID = undefined;
}
}
```
### 音视频录制APP
音视频录制APP案例包含:创建实例、设置录制参数、获取输入surface、开始录制、暂停录制、恢复录制、停止录制、释放资源等流程。
详细代码可参考:[AVRecorderDemo]([multimedia_player_framework: Implementation of media playback and recording | 媒体播放和录制功能实现 - Gitee.com](https://gitee.com/openharmony/multimedia_player_framework/tree/master/test/appdemo/AVRecorderDemo))
因为 它太大了无法显示 source diff 。你可以改为 查看blob
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册