【鸿蒙开发】第二十一章 Media媒体服务(二)--- 音频播放和录制
1 AVPlayer音频播放
使用AVPlayer
可以实现端到端播放原始媒体资源,本开发指导将以完整地播放一首音乐作为示例,向开发者讲解AVPlayer音频播放相关功能。
以下指导仅介绍如何实现媒体资源播放,如果要实现后台播放或熄屏播放,需要使用AVSession(媒体会话)
和申请长时任务
,避免播放被系统强制中断。
播放的全流程包含:创建AVPlayer
,设置播放资源
,设置播放参数(音量/倍速/焦点模式)
,播放控制(播放/暂停/跳转/停止)
,重置
,销毁资源
。
在进行应用开发的过程中,开发者可以通过AVPlayer
的state
属性主动获取当前状态或使用on(‘stateChange’)方法
监听状态变化。如果应用在音频播放器处于错误状态时执行操作,系统可能会抛出异常或生成其他未定义的行为。
当播放处于prepared / playing / paused / completed
状态时,播放引擎处于工作状态,这需要占用系统较多的运行内存。当客户端暂时不使用播放器时,调用reset()
或release()
回收内存资源,做好资源利用。
1.1 开发步骤及注意事项
-
创建实例
createAVPlayer()
,AVPlayer
初始化idle状态
。 -
设置业务需要的监听事件,搭配全流程场景使用。支持的监听事件包括:
-
设置资源:设置属性url,AVPlayer进入initialized状态。
说明:
下面代码示例中的url仅作示意使用,开发者需根据实际情况,确认资源有效性并设置:
1.如果使用本地资源播放,必须确认资源文件可用,并使用应用沙箱路径访问对应资源,参考获取应用文件路径。应用沙箱的介绍及如何向应用沙箱推送文件,请参考文件管理。
2.如果使用网络播放路径,需声明权限:ohos.permission.INTERNET。
3.如果使用ResourceManager.getRawFd打开HAP资源文件描述符,使用方法可参考ResourceManager API参考。
4.需要使用支持的播放格式与协议。
- 准备播放:调用prepare(),AVPlayer进入prepared状态,此时可以获取duration,设置音量。
- 音频播控:播放play(),暂停pause(),跳转seek(),停止stop() 等操作。
- (可选)更换资源:调用reset()重置资源,AVPlayer重新进入idle状态,允许更换资源url。
- 退出播放:调用release()销毁实例,AVPlayer进入released状态,退出播放。
1.2 完整示例
参考以下示例,完整地播放一首音乐。
import media from '@ohos.multimedia.media';
import fs from '@ohos.file.fs';
import common from '@ohos.app.ability.common';
import { BusinessError } from '@ohos.base';export class AVPlayerDemo {private count: number = 0;private isSeek: boolean = true; // 用于区分模式是否支持seek操作private fileSize: number = -1;private fd: number = 0;// 注册avplayer回调函数setAVPlayerCallback(avPlayer: media.AVPlayer) {// seek操作结果回调函数avPlayer.on('seekDone', (seekDoneTime: number) => {console.info(`AVPlayer seek succeeded, seek time is ${seekDoneTime}`);})// error回调监听函数,当avPlayer在操作过程中出现错误时调用 reset接口触发重置流程avPlayer.on('error', (err: BusinessError) => {console.error(`Invoke avPlayer failed, code is ${err.code}, message is ${err.message}`);avPlayer.reset(); // 调用reset重置资源,触发idle状态})// 状态机变化回调函数avPlayer.on('stateChange', async (state: string, reason: media.StateChangeReason) => {switch (state) {case 'idle': // 成功调用reset接口后触发该状态机上报console.info('AVPlayer state idle called.');avPlayer.release(); // 调用release接口销毁实例对象break;case 'initialized': // avplayer 设置播放源后触发该状态上报console.info('AVPlayer state initialized called.');avPlayer.prepare();break;case 'prepared': // prepare调用成功后上报该状态机console.info('AVPlayer state prepared called.');avPlayer.play(); // 调用播放接口开始播放break;case 'playing': // play成功调用后触发该状态机上报console.info('AVPlayer state playing called.');if (this.count !== 0) {if (this.isSeek) {console.info('AVPlayer start to seek.');avPlayer.seek(avPlayer.duration); //seek到音频末尾} else {// 当播放模式不支持seek操作时继续播放到结尾console.info('AVPlayer wait to play end.');}} else {avPlayer.pause(); // 调用暂停接口暂停播放}this.count++;break;case 'paused': // pause成功调用后触发该状态机上报console.info('AVPlayer state paused called.');avPlayer.play(); // 再次播放接口开始播放break;case 'completed': // 播放结束后触发该状态机上报console.info('AVPlayer state completed called.');avPlayer.stop(); //调用播放结束接口break;case 'stopped': // stop接口成功调用后触发该状态机上报console.info('AVPlayer state stopped called.');avPlayer.reset(); // 调用reset接口初始化avplayer状态break;case 'released':console.info('AVPlayer state released called.');break;default:console.info('AVPlayer state unknown called.');break;}})}// 以下demo为使用fs文件系统打开沙箱地址获取媒体文件地址并通过url属性进行播放示例async avPlayerUrlDemo() {// 创建avPlayer实例对象let avPlayer: media.AVPlayer = await media.createAVPlayer();// 创建状态机变化回调函数this.setAVPlayerCallback(avPlayer);let fdPath = 'fd://';// 通过UIAbilityContext获取沙箱地址filesDir,以Stage模型为例let context = getContext(this) as common.UIAbilityContext;let pathDir = context.filesDir;let path = pathDir + '/01.mp3';// 打开相应的资源文件地址获取fd,并为url赋值触发initialized状态机上报let file = await fs.open(path);fdPath = fdPath + '' + file.fd;this.isSeek = true; // 支持seek操作avPlayer.url = fdPath;}// 以下demo为使用资源管理接口获取打包在HAP内的媒体资源文件并通过fdSrc属性进行播放示例async avPlayerFdSrcDemo() {// 创建avPlayer实例对象let avPlayer: media.AVPlayer = await media.createAVPlayer();// 创建状态机变化回调函数this.setAVPlayerCallback(avPlayer);// 通过UIAbilityContext的resourceManager成员的getRawFd接口获取媒体资源播放地址// 返回类型为{fd,offset,length},fd为HAP包fd地址,offset为媒体资源偏移量,length为播放长度let context = getContext(this) as common.UIAbilityContext;let fileDescriptor = await context.resourceManager.getRawFd('01.mp3');let avFileDescriptor: media.AVFileDescriptor ={ fd: fileDescriptor.fd, offset: fileDescriptor.offset, length: fileDescriptor.length };this.isSeek = true; // 支持seek操作// 为fdSrc赋值触发initialized状态机上报avPlayer.fdSrc = avFileDescriptor;}// 以下demo为使用fs文件系统打开沙箱地址获取媒体文件地址并通过dataSrc属性进行播放(seek模式)示例async avPlayerDataSrcSeekDemo() {// 创建avPlayer实例对象let avPlayer: media.AVPlayer = await media.createAVPlayer();// 创建状态机变化回调函数this.setAVPlayerCallback(avPlayer);// dataSrc播放模式的的播放源地址,当播放为Seek模式时fileSize为播放文件的具体大小,下面会对fileSize赋值let src: media.AVDataSrcDescriptor = {fileSize: -1,callback: (buf: ArrayBuffer, length: number, pos: number | undefined) => {let num = 0;if (buf == undefined || length == undefined || pos == undefined) {return -1;}num = fs.readSync(this.fd, buf, { offset: pos, length: length });if (num > 0 && (this.fileSize >= pos)) {return num;}return -1;}}let context = getContext(this) as common.UIAbilityContext;// 通过UIAbilityContext获取沙箱地址filesDir,以Stage模型为例let pathDir = context.filesDir;let path = pathDir + '/01.mp3';await fs.open(path).then((file: fs.File) => {this.fd = file.fd;})// 获取播放文件的大小this.fileSize = fs.statSync(path).size;src.fileSize = this.fileSize;this.isSeek = true; // 支持seek操作avPlayer.dataSrc = src;}// 以下demo为使用fs文件系统打开沙箱地址获取媒体文件地址并通过dataSrc属性进行播放(No seek模式)示例async avPlayerDataSrcNoSeekDemo() {// 创建avPlayer实例对象let avPlayer: media.AVPlayer = await media.createAVPlayer();// 创建状态机变化回调函数this.setAVPlayerCallback(avPlayer);let context = getContext(this) as common.UIAbilityContext;let src: media.AVDataSrcDescriptor = {fileSize: -1,callback: (buf: ArrayBuffer, length: number) => {let num = 0;if (buf == undefined || length == undefined) {return -1;}num = fs.readSync(this.fd, buf);if (num > 0) {return num;}return -1;}}// 通过UIAbilityContext获取沙箱地址filesDir,以Stage模型为例let pathDir = context.filesDir;let path = pathDir + '/01.mp3';await fs.open(path).then((file: fs.File) => {this.fd = file.fd;})this.isSeek = false; // 不支持seek操作avPlayer.dataSrc = src;}// 以下demo为通过url设置网络地址来实现播放直播码流的demoasync avPlayerLiveDemo() {// 创建avPlayer实例对象let avPlayer: media.AVPlayer = await media.createAVPlayer();// 创建状态机变化回调函数this.setAVPlayerCallback(avPlayer);this.isSeek = false; // 不支持seek操作avPlayer.url = 'http://xxx.xxx.xxx.xxx:xx/xx/index.m3u8';}
}
2 SoundPool音频播放
使用SoundPool(音频池)
提供的接口,可以实现低时延短音播放
。
当应用开发时,经常需要使用一些急促简短的音效(如相机快门音效、系统通知音效等)
,此时建议调用SoundPool
,实现一次加载,多次低时延播放。
SoundPool
当前支持播放1MB以下的音频资源
,大小超过1MB的长音频将截取1MB大小数据进行播放。
本开发指导将以SoundPool
进行一次低时延播放音频的过程为例,向开发者讲解如何使用SoundPool
。详细的API声明请参考SoundPool API参考。
过程包括:创建SoundPool实例
,加载音频资源(包括资源的解封装与解码:解码格式参考音频解码支持)
,设置播放参数(循环模式/播放优先级等)
,播放控制(播放/停止)
,释放资源
。
在应用开发过程中,开发者应通过监听方法检查当前播放状态并按照一定顺序调用接口,执行对应操作,否则系统可能会抛出异常或生成其他未定义的行为。具体顺序可参考下列开发步骤及对应说明。
2.1 开发步骤及注意事项
- 调用
createSoundPool
方法创建SoundPool实例。
import media from '@ohos.multimedia.media';
import audio from '@ohos.multimedia.audio';
import { BusinessError } from '@ohos.base';let soundPool: media.SoundPool;
let audioRendererInfo: audio.AudioRendererInfo = {usage : audio.StreamUsage.STREAM_USAGE_MUSIC,rendererFlags : 0
}media.createSoundPool(5, audioRendererInfo).then((soundpool_: media.SoundPool) => {if (soundpool_ != null) {soundPool = soundpool_;console.info('create SoundPool success');} else {console.error('create SoundPool fail');}
}).catch((error: BusinessError) => {console.error(`soundpool catchCallback, error message:${error.message}`);
});
- 调用
load()
方法进行音频资源加载。 可以传入uri
或fd
加载资源,此处使用传入uri
的方式为例,更多方法请参考API文档。
import { BusinessError } from '@ohos.base';
import fs from '@ohos.file.fs';let soundID: number;
let uri: string;
async function load() {await fs.open('/test_01.mp3', fs.OpenMode.READ_ONLY).then((file: fs.File) => {console.info("file fd: " + file.fd);uri = 'fd://' + (file.fd).toString()}); // '/test_01.mp3' 作为样例,使用时需要传入文件对应路径。soundPool.load(uri).then((soundId: number) => {console.info('soundPool load uri success');soundID = soundId;}).catch((err: BusinessError) => {console.error('soundPool load failed and catch error is ' + err.message);})
}
- 调用
on(‘loadComplete’)
方法,用于监听“资源加载完成”。
soundPool.on('loadComplete', (soundId: number) => {console.info('loadComplete, soundId: ' + soundId);
});
- 调用
on(‘playFinished’)
方法,用于监听“播放完成”。
soundPool.on('playFinished', () => {console.info("receive play finished message");
});
- 调用
on(‘error’)
方法,设置错误类型监听。
soundPool.on('error', (error) => {console.info('error happened,message is :' + error.message);
});
- 配置播放参数
PlayParameters
,并调用play
方法播放音频。多次调用play
播放同一个soundID
,只会播放一次。
let soundID: number;
let streamID: number;
let playParameters: media.PlayParameters = {loop: 0, // 循环0次rate: 2, // 2倍速leftVolume: 0.5, // range = 0.0-1.0rightVolume: 0.5, // range = 0.0-1.0priority: 0, // 最低优先级}
soundPool.play(soundID, playParameters, (error, streamId: number) => {if (error) {console.info(`play sound Error: errCode is ${error.code}, errMessage is ${error.message}`)} else {streamID = streamId;console.info('play success soundid:' + streamId);}
});
- 调用
setLoop()
方法设置循环次数。
import { BusinessError } from '@ohos.base';let streamID: number;
soundPool.setLoop(streamID, 1).then(() => {console.info('setLoop success streamID:' + streamID);
}).catch((err: BusinessError) => {console.error('soundpool setLoop failed and catch error is ' + err.message);
});
- 调用
setPriority()
方法设置优先级。
let streamID: number;
soundPool.setPriority(streamID, 1);
- 调用
setVolume()
方法设置音量。
import { BusinessError } from '@ohos.base';let streamID: number;
// 先调用play方法获取到对应资源的streamIDsoundPool.setVolume(streamID, 0.5, 0.5).then(() => {console.info('setVolume success');
}).catch((err: BusinessError) => {console.error('soundpool setVolume failed and catch error is ' + err.message);
});
- 调用
stop()
方法终止指定流的播放。
import { BusinessError } from '@ohos.base';let streamID: number;
//先调用play方法给拿到对应的streamIDsoundPool.stop(streamID).then(() => {console.info('stop success');
}).catch((err: BusinessError) => {console.error('soundpool load stop and catch error is ' + err.message);
});
- 调用
unload()
方法卸载音频资源。
import { BusinessError } from '@ohos.base';let soundID: number;
// 先调用load方法获取到对应资源的soundIDsoundPool.unload(soundID).then(() => {console.info('unload success');
}).catch((err: BusinessError) => {console.error('soundpool unload failed and catch error is ' + err.message);
});
- 调用
off(‘loadComplete’)
方法注销加载完成监听。
soundPool.off('loadComplete');
- 调用
off(‘playFinished’)
方法注销播放完成监听。
soundPool.off('playFinished');
- 调用
off(‘error’)
方法注销错误错误类型监听。
soundPool.off('error');
- 调用
release()
方法释放SoundPool
实例。
import { BusinessError } from '@ohos.base';soundPool.release().then(() => {console.info('release success');
}).catch((err: BusinessError) => {console.error('soundpool release failed and catch error is ' + err.message);
});
2.2 完整示例
下面展示了使用SoundPool
进行低时延播放的完整示例代码。
import audio from '@ohos.multimedia.audio';
import media from '@ohos.multimedia.media';
import fs from '@ohos.file.fs'let soundPool: media.SoundPool;
let streamId: number = 0;
let soundId: number = 0;
let audioRendererInfo: audio.AudioRendererInfo = {usage: audio.StreamUsage.STREAM_USAGE_MUSIC,rendererFlags: 1
}
let PlayParameters: media.PlayParameters = {loop: 3, // 循环4次rate: audio.AudioRendererRate.RENDER_RATE_NORMAL, // 正常倍速leftVolume: 0.5, // range = 0.0-1.0rightVolume: 0.5, // range = 0.0-1.0priority: 0, // 最低优先级
}
let uri: string = "";
async function create() {//创建soundPool实例soundPool = await media.createSoundPool(5, audioRendererInfo);//注册监听loadCallback();finishPlayCallback();setErrorCallback();// 加载音频资源await fs.open('/test_01.mp3', fs.OpenMode.READ_ONLY).then((file: fs.File) => {console.info("file fd: " + file.fd);uri = 'fd://' + (file.fd).toString()}); // '/test_01.mp3' 作为样例,使用时需要传入文件对应路径。soundId = await soundPool.load(uri);
}
async function loadCallback() {// 加载完成回调soundPool.on('loadComplete', (soundId_: number) => {console.info('loadComplete, soundId: ' + soundId_);})
}
//设置播放完成监听
async function finishPlayCallback() {// 播放完成回调soundPool.on('playFinished', () => {console.info("recive play finished message");// 可进行下次播放})
}
//设置错误类型监听
function setErrorCallback() {soundPool.on('error', (error) => {console.info('error happened,message is :' + error.message);})
}
async function PlaySoundPool() {// 开始播放,这边play也可带播放播放的参数PlayParametersstreamId = await soundPool.play(soundId);// 设置循环播放次数soundPool.setLoop(streamId, 2); // 播放3次// 设置对应流的优先级soundPool.setPriority(streamId, 1);// 设置音量soundPool.setVolume(streamId, 0.5, 0.5);
}
async function release() {// 终止指定流的播放soundPool.stop(streamId);// 卸载音频资源await soundPool.unload(soundId);//关闭监听setOffCallback();// 释放SoundPoolawait soundPool.release();
}
//关闭监听
function setOffCallback() {soundPool.off('loadComplete');soundPool.off('playFinished');soundPool.off('error');
}
3 AVRecorder音频录制
使用AVRecorder
可以实现音频录制功能,本开发指导将以“开始录制-暂停录制-恢复录制-停止录制
”的一次流程为示例,向开发者讲解AVRecorder音频录制相关功能。
在进行应用开发的过程中,开发者可以通过AVRecorder
的state属性
,主动获取当前状态或使用on(‘stateChange’)
方法监听状态变化。开发过程中应该严格遵循状态机要求,例如只能在started状态
下调用pause()
接口,只能在paused状态
下调用resume()
接口。
3.1 开发步骤及注意事项
详细的API说明请参考AVRecorder API参考。
创建AVRecorder实例
,实例创建完成进入idle状态
。
说明:
需要在avRecorder完成赋值(即“avRecorder = recorder; ”运行完成)后,再进行剩余操作。
import media from '@ohos.multimedia.media';
import { BusinessError } from '@ohos.base';let avRecorder: media.AVRecorder;
media.createAVRecorder().then((recorder: media.AVRecorder) => {avRecorder = recorder;
}, (error: BusinessError) => {console.error(`createAVRecorder failed`);
})
- 设置业务需要的监听事件,监听
状态变化
及错误上报
。
import { BusinessError } from '@ohos.base';// 状态上报回调函数
avRecorder.on('stateChange', (state: media.AVRecorderState, reason: media.StateChangeReason) => {console.log(`current state is ${state}`);// 用户可以在此补充状态发生切换后想要进行的动作
})// 错误上报回调函数
avRecorder.on('error', (err: BusinessError) => {console.error(`avRecorder failed, code is ${err.code}, message is ${err.message}`);
})
- 配置音频录制参数,调用
prepare()
接口,此时进入prepared状态
。
说明: 配置参数需要注意:
prepare接口的入参avConfig中仅设置音频相关的配置参数,如示例代码所示。 如果只需要录制音频,请不要设置视频相关配置参数;如果需要录制视频,可以参考视频录制开发指导进行开发。直接设置视频相关参数会导致后续步骤报错。
需要使用支持的录制规格。
录制输出的url地址(即示例里avConfig中的url),形式为fd://xx (fd number)。需要基础文件操作接口(ohos.file.fs)实现应用文件访问能力,获取方式参考应用文件访问与管理。
import media from '@ohos.multimedia.media';
import { BusinessError } from '@ohos.base';let avProfile: media.AVRecorderProfile = {audioBitrate: 100000, // 音频比特率audioChannels: 2, // 音频声道数audioCodec: media.CodecMimeType.AUDIO_AAC, // 音频编码格式,当前只支持aacaudioSampleRate: 48000, // 音频采样率fileFormat: media.ContainerFormatType.CFT_MPEG_4A, // 封装格式,当前只支持m4a
}
let avConfig: media.AVRecorderConfig = {audioSourceType: media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC, // 音频输入源,这里设置为麦克风profile: avProfile,url: 'fd://35', // 参考应用文件访问与管理中的开发示例获取创建的音频文件fd填入此处
}
avRecorder.prepare(avConfig).then(() => {console.log('Invoke prepare succeeded.');
}, (err: BusinessError) => {console.error(`Invoke prepare failed, code is ${err.code}, message is ${err.message}`);
})
- 开始录制,调用
start()
接口,此时进入started状态
。
// 开始录制
avRecorder.start();
- 暂停录制,调用
pause()
接口,此时进入paused状态
。
// 暂停录制
avRecorder.pause();
- 恢复录制,调用
resume()
接口,此时再次进入started状态
。
// 恢复录制
avRecorder.resume();
- 停止录制,调用
stop()
接口,此时进入stopped状态
。
// 停止录制
avRecorder.stop();
- 重置资源,调用
reset()
重新进入idle状态
,允许重新配置录制参数
。
// 重置资源
avRecorder.reset();
- 销毁实例,调用
release()
进入released状态
,退出录制
。
// 销毁实例
avRecorder.release();
3.2 完整示例
参考以下示例,完成“开始录制-暂停录制-恢复录制-停止录制”的完整流程。
import media from '@ohos.multimedia.media';
import { BusinessError } from '@ohos.base';export class AudioRecorderDemo {private avRecorder: media.AVRecorder | undefined = undefined;private avProfile: media.AVRecorderProfile = {audioBitrate: 100000, // 音频比特率audioChannels: 2, // 音频声道数audioCodec: media.CodecMimeType.AUDIO_AAC, // 音频编码格式,当前只支持aacaudioSampleRate: 48000, // 音频采样率fileFormat: media.ContainerFormatType.CFT_MPEG_4A, // 封装格式,当前只支持m4a};private avConfig: media.AVRecorderConfig = {audioSourceType: media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC, // 音频输入源,这里设置为麦克风profile: this.avProfile,url: 'fd://35', // 参考应用文件访问与管理开发示例新建并读写一个文件};// 注册audioRecorder回调函数setAudioRecorderCallback() {if (this.avRecorder != undefined) {// 状态机变化回调函数this.avRecorder.on('stateChange', (state: media.AVRecorderState, reason: media.StateChangeReason) => {console.log(`AudioRecorder current state is ${state}`);})// 错误上报回调函数this.avRecorder.on('error', (err: BusinessError) => {console.error(`AudioRecorder failed, code is ${err.code}, message is ${err.message}`);})}}// 开始录制对应的流程async startRecordingProcess() {if (this.avRecorder != undefined) {await this.avRecorder.release();this.avRecorder = undefined;}// 1.创建录制实例this.avRecorder = await media.createAVRecorder();this.setAudioRecorderCallback();// 2.获取录制文件fd赋予avConfig里的url;参考FilePicker文档// 3.配置录制参数完成准备工作await this.avRecorder.prepare(this.avConfig);// 4.开始录制await this.avRecorder.start();}// 暂停录制对应的流程async pauseRecordingProcess() {if (this.avRecorder != undefined && this.avRecorder.state === 'started') { // 仅在started状态下调用pause为合理状态切换await this.avRecorder.pause();}}// 恢复录制对应的流程async resumeRecordingProcess() {if (this.avRecorder != undefined && this.avRecorder.state === 'paused') { // 仅在paused状态下调用resume为合理状态切换await this.avRecorder.resume();}}// 停止录制对应的流程async stopRecordingProcess() {if (this.avRecorder != undefined) {// 1. 停止录制if (this.avRecorder.state === 'started'|| this.avRecorder.state === 'paused') { // 仅在started或者paused状态下调用stop为合理状态切换await this.avRecorder.stop();}// 2.重置await this.avRecorder.reset();// 3.释放录制实例await this.avRecorder.release();this.avRecorder = undefined;// 4.关闭录制文件fd}}// 一个完整的【开始录制-暂停录制-恢复录制-停止录制】示例async audioRecorderDemo() {await this.startRecordingProcess(); // 开始录制// 用户此处可以自行设置录制时长,例如通过设置休眠阻止代码执行await this.pauseRecordingProcess(); //暂停录制await this.resumeRecordingProcess(); // 恢复录制await this.stopRecordingProcess(); // 停止录制}
}
4 音视频元数据获取
使用AVMetadataExtractor
可以实现从原始媒体资源
中获取元信息
,本开发指导将以获取一个音频资源的元信息作为示例,向开发者讲解AVMetadataExtractor
元信息相关功能。视频资源的元信息获取流程与音频类似,由于视频没有专辑封面,所以无法获取视频资源的专辑封面。
获取音频资源的元信息的全流程包含:创建AVMetadataExtractor
,设置资源
,获取元信息
,获取专辑封面
,销毁资源
。
4.1 开发步骤及注意事项
详细的API说明请参考AVMetadataExtractor API参考。
-
使用createAVMetadataExtractor()创建实例。
-
设置资源:用户可以根据需要选择设置属性fdSrc(表示文件描述符), 或者设置属性dataSrc(表示dataSource描述符)。
说明:
开发者需根据实际情况,确认资源有效性并设置:
1.如果设置fdSrc,可以使用ResourceManager.getRawFd打开HAP资源文件描述符,使用方法可参考ResourceManager API参考。
2.如果设置dataSrc,必须正确设置dataSrc中的callback属性,确保callback被调用时能正确读取到对应资源,使用应用沙箱路径访问对应资源,参考获取应用文件路径。应用沙箱的介绍及如何向应用沙箱推送文件,请参考文件管理。
-
获取元信息:调用fetchMetadata(),可以获取到一个AVMetadata对象,通过访问该对象的各个属性,可以获取到元信息。
-
(可选)获取专辑封面:调用fetchAlbumCover(),可以获取到专辑封面。
-
释放资源:调用release()销毁实例,释放资源。
4.2 完整示例
参考以下示例,设置文件描述符,获取一个音频的元信息和专辑封面。
import media from '@ohos.multimedia.media'
import image from '@ohos.multimedia.image'
import type common from '@ohos.app.ability.common';
import fs from '@ohos.file.fs';const TAG = 'MetadataDemo'
@Entry
@Component
struct Index {@State message: string = 'Hello World'// pixelMap对象声明,用于图片显示@State pixelMap: image.PixelMap | undefined = undefined;build() {Row() {Column() {Text(this.message).fontSize(50).fontWeight(FontWeight.Bold)Button() {Text('TestButton').fontSize(30).fontWeight(FontWeight.Bold)}.type(ButtonType.Capsule).margin({top: 20}).backgroundColor('#0D9FFB').width('60%').height('5%').onClick(() => {// 设置fdSrc, 获取音频元信息和专辑封面(异步接口以Callback形式调用)this.testFetchMetadataFromFdSrcByCallback()// 设置fdSrc, 获取音频元信息和专辑封面(异步接口以Promise形式调用)this.testFetchMetadataFromFdSrcByPromise()// 设置dataSrc, 获取音频元信息和专辑封面this.testFetchMetadataFromDataSrc()})Image(this.pixelMap).width(300).height(300).margin({top: 20})}.width('100%')}.height('100%')}// 在以下demo中,使用资源管理接口获取打包在HAP内的媒体资源文件,通过设置fdSrc属性,获取音频元信息并打印,// 获取音频专辑封面并通过Image控件显示在屏幕上。该demo以Callback形式进行异步接口调用async testFetchMetadataFromFdSrcByCallback() {// 创建AVMetadataExtractor对象let avMetadataExtractor: media.AVMetadataExtractor = await media.createAVMetadataExtractor()// 设置fdSrcavMetadataExtractor.fdSrc = await getContext(this).resourceManager.getRawFd('cover.mp3');// 获取元信息(callback模式)avMetadataExtractor.fetchMetadata((error, metadata) => {if (error) {console.error(TAG, `fetchMetadata callback failed, err = ${JSON.stringify(error)}`)return}console.info(TAG, `fetchMetadata callback success, genre: ${metadata.genre}`)})//获取专辑封面(callback模式)avMetadataExtractor.fetchAlbumCover((err, pixelMap) => {if (err) {console.error(TAG, `fetchAlbumCover callback failed, err = ${JSON.stringify(err)}`)return}this.pixelMap = pixelMap// 释放资源(callback模式)avMetadataExtractor.release((error) => {if (error) {console.error(TAG, `release failed, err = ${JSON.stringify(error)}`)return}console.info(TAG, `release success.`)})})}// 在以下demo中,使用资源管理接口获取打包在HAP内的媒体资源文件,通过设置fdSrc属性,获取音频元信息并打印,// 获取音频专辑封面并通过Image控件显示在屏幕上。该demo以Promise形式进行异步接口调用async testFetchMetadataFromFdSrcByPromise() {// 创建AVMetadataExtractor对象let avMetadataExtractor: media.AVMetadataExtractor = await media.createAVMetadataExtractor()// 设置fdSrcavMetadataExtractor.fdSrc = await getContext(this).resourceManager.getRawFd('cover.mp3');// 获取元信息(promise模式)let metadata = await avMetadataExtractor.fetchMetadata()console.info(TAG, `get meta data, hasAudio: ${metadata.hasAudio}`)// 获取专辑封面(promise模式)this.pixelMap = await avMetadataExtractor.fetchAlbumCover()// 释放资源(promise模式)avMetadataExtractor.release()console.info(TAG, `release success.`)}// 在以下demo中,使用fs文件系统打开沙箱地址获取媒体文件地址,设置dataSrc属性,获取音频元信息并打印,// 获取音频专辑封面并通过Image控件显示在屏幕上。async testFetchMetadataFromDataSrc() {let context = getContext(this) as common.UIAbilityContext// 通过UIAbilityContext获取沙箱地址filesDir(以Stage模型为例)let filePath: string = context.filesDir + '/cover.mp3';let fd: number = fs.openSync(filePath, 0o0).fd;let fileSize: number = fs.statSync(filePath).size;// 设置dataSrc描述符,通过callback从文件中获取资源,写入buffer中let dataSrc: media.AVDataSrcDescriptor = {fileSize: fileSize,callback: (buffer, len, pos) => {if (buffer == undefined || len == undefined || pos == undefined) {console.error(TAG, `dataSrc callback param invalid`)return -1}class Option {offset: number | undefined = 0;length: number | undefined = len;position: number | undefined = pos;}let options = new Option();let num = fs.readSync(fd, buffer, options)console.info(TAG, 'readAt end, num: ' + num)if (num > 0 && fileSize >= pos) {return num;}return -1;}}// 创建AVMetadataExtractor对象let avMetadataExtractor = await media.createAVMetadataExtractor()// 设置dataSrcavMetadataExtractor.dataSrc = dataSrc;// 获取元信息(promise模式)let metadata = await avMetadataExtractor.fetchMetadata()console.info(TAG, `get meta data, mimeType: ${metadata.mimeType}`)// 获取专辑封面(promise模式)this.pixelMap = await avMetadataExtractor.fetchAlbumCover()// 释放资源(promise模式)avMetadataExtractor.release()console.info(TAG, `release data source success.`)}
}
参考文献:
[1]OpenHarmoney应用开发文档
相关文章:
【鸿蒙开发】第二十一章 Media媒体服务(二)--- 音频播放和录制
1 AVPlayer音频播放 使用AVPlayer可以实现端到端播放原始媒体资源,本开发指导将以完整地播放一首音乐作为示例,向开发者讲解AVPlayer音频播放相关功能。 以下指导仅介绍如何实现媒体资源播放,如果要实现后台播放或熄屏播放,需要…...
网络安全从入门到精通(特别篇I):Windows安全事件应急响应之Windows应急响应基础必备技能
Windows应急 询问攻击情况范围 事件发生时的状况或安全设备告警等,能帮助应急处置人员快速分析确定事件类型,方便前期准备。 通用排查思路 入侵肯定会留下痕迹,另外重点强调的是不要一上来就各种查查查,问清楚谁在什么时间发现的主机异常情况,异常的现象是什么,受害用…...
基于SpringBoot+Mybatis框架的私人影院预约系统(附源码,包含数据库文件)
基于SpringBootMybatis框架的私人影院预约系统,附源码,包含数据库文件。 非常完整的一个项目,希望能对大家有帮助哈。 本系统的完整源码以及数据库文件都在文章结尾处,大家自行获取即可。 项目简介 该项目设计了基于SpringBoo…...
【SERVERLESS】AWS Lambda上实操
通过Serverless的发展历程及带给我们的挑战,引出我们改如何改变思路,化繁为简,趋利避害,更好的利用其优势,来释放企业效能,为创造带来无限可能。 一 Serverless概述 无服务器计算近年来与云原生计算都是在…...
IDEA2023 开发环境配置
目录 1. 关闭IDEA自动更新1.2 IDEA 新版样式切换 2. Maven配置2.1本地仓库优先加载2.2 maven.config配置文件中 3. 全局配置JDK4. 配置文件编码:UTF-85. 开启自动编译(全局配置)6. 开启自动导包7. 开启鼠标悬浮(提示文档信息)8. 设…...
YOLOV5 + 双目相机实现三维测距(新版本)
文章目录 YOLOV5 双目相机实现三维测距(新版本)1. 项目流程2. 测距原理3. 操作步骤和代码解析4. 实时检测5. 训练6. 源码下载 YOLOV5 双目相机实现三维测距(新版本) 本文主要是对此篇文章做一些改进,以及解释读者在…...
【计算机网络】(一)计算机网络概述
文章目录 【计算机网络】(一)计算机网络概述前言1.1 计算机网络在信息时代中的作用1.2 互联网概述1.2.1 网络的网络1.2.2 互联网基础结构发展的三个阶段1.2.3 互联网标准化工作 1.3 互联网的组成1.3.1 互联网的边缘部分1.3.2 互联网的核心部分 1.4 计算机…...
前端npm常用命令总结
npm(Node Package Manager)是Node.js的包管理器,用于安装和管理Node.js的依赖库。以下是一份npm命令的总结文档,涵盖了常用的npm命令及其功能: 包相关的 安装和卸载包 npm install :安装指定名称的包。n…...
[尚硅谷flink] 检查点笔记
在Flink中,有一套完整的容错机制来保证故障后的恢复,其中最重要的就是检查点。 文章目录 11.1 检查点11.1.1 检查点的保存1)周期性的触发保存2)保存的时间点3)保存的具体流程 11.1.2 从检查点恢复状态11.1.3 检查点算法…...
JVM虚拟机(五)强引用、软引用、弱引用、虚引用
目录 一、强引用二、软引用三、弱引用四、虚引用五、总结 引文: 在 Java 中一共存在 4 种引用:强、软、弱、虚。它们主要指的是,在进行垃圾回收的时候,对于不同的引用垃圾回收的情况是不一样的。下面我们就一起来看一下这 4 种引用…...
(最新)itext7 freemarker动态模板转pdf
1.引入依赖 <!--PDF导出POM--> <dependency><groupId>com.itextpdf</groupId><artifactId>itext7-core</artifactId><version>8.0.3</version><type>pom</type> </dependency> <dependency><grou…...
solidworks electrical 2D和3D有什么区别
SolidWorks Electrical 是一款专为电气设计开发的软件工具,它提供了两种主要的工作环境:2D电气设计和3D电气集成设计。两者在功能和应用场景上存在显著的区别: SolidWorks Electrical 2D 设计 特点与用途: SolidWorks Electrica…...
4.2、ipex-llm(原bigdl-llm)进行语音识别
ipex-llm环境配置及模型下载 由于需要处理音频文件,还需要安装用于音频分析的 librosa 软件包。 pip install librosa下载音频文件 !wget -O audio_en.mp3 https://datasets-server.huggingface.co/assets/common_voice/--/en/train/5/audio/audio.mp3 !wget -O a…...
上海亚商投顾:创业板指低开低走 黄金、家电股逆势大涨
上海亚商投顾前言:无惧大盘涨跌,解密龙虎榜资金,跟踪一线游资和机构资金动向,识别短期热点和强势个股。 一.市场情绪 沪指4月12日震荡调整,创业板指尾盘跌超1%。黄金板块延续强势,莱绅通灵9连板࿰…...
AIGC革新浪潮:大语言模型如何优化企业运营
在当今快速发展的商业环境中,企业对于有效管理知识资产的需求日益增长。知识管理作为企业核心竞争力的关键组成部分,对于提高决策质量、增强创新能力和优化运营流程起着至关重要的作用。随着数字化转型的推进,企业对知识管理系统提出了新的要…...
Golang基础-12
Go语言基础 介绍 目录操作 创建 删除 重命名 遍历目录 修改权限 文件操作 创建 打开关闭 删除 重命名 修改权限 读文件 写文件 文件定位 拷贝 测试 单元测试 基准测试 示例 介绍 本文介绍Go语言中目录操作(创建目录、删除目录、重命名、遍历…...
python递归统计文件夹下pdf文件的数量
python递归统计文件夹下pdf文件的数量 import os from docx import Documentdef count_all_pages(root_dir):total_pages 0# 遍历文件夹for dirpath, dirnames, filenames in os.walk(root_dir):for filename in filenames:# if filename.endswith(.docx) or filename.endswit…...
Kafka 硬件和操作系统
目录 一. 前言 二. Kafka 硬件和操作系统(Hardware and OS) 2.1. 操作系统(OS) 2.2. 磁盘和文件系统(Disks and Filesystem) 一. 前言 Kafka 是 I/O 密集型而非计算密集型的框架,所以对 CP…...
Kolla-ansible部署OpenStack集群
0. OpenStack 部署 系统要求 单机部署最低配置: 2张网卡8G内存40G硬盘空间 主机系统: CentOS Stream 9Debian Bullseye (11)openEuler 22.03 LTSRocky Linux 9- Ubuntu Jammy (22.04) 官方不再支持CentOS 7作为主机系统,我这里使用的是R…...
SHARE 203S PRO:倾斜摄影相机在地灾救援中的应用
在地质灾害的紧急关头,救援队伍面临的首要任务是迅速而准确地掌握灾区的地理信息。这时,倾斜摄影相机成为了救援测绘的利器。SHARE 203S PRO,这款由深圳赛尔智控科技有限公司研发的五镜头倾斜摄影相机,以其卓越的性能和功能&#…...
MATLAB算法实战应用案例精讲-【数模应用】中介效应分析(补充篇)(附R语言和python代码实现)
目录 前言 几个高频面试题目 中介效应分析与路径分析的区别 1.中介效应分析 2.路径分析 注意事项...
Day96:云上攻防-云原生篇Docker安全系统内核版本漏洞CDK自动利用容器逃逸
目录 云原生-Docker安全-容器逃逸&系统内核漏洞 云原生-Docker安全-容器逃逸&docker版本漏洞 CVE-2019-5736 runC容器逃逸(需要管理员配合触发) CVE-2020-15257 containerd逃逸(启动容器时有前提参数) 云原生-Docker安全-容器逃逸&CDK自动化 知识点࿱…...
python botos s3 aws
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html AWS是亚马逊的云服务,其提供了非常丰富的套件,以及支持多种语言的SDK/API。本文针对其S3云储存服务的Python SDK(boto3)的使用进行介绍。 …...
python画神经网络图
代码1(画神经网络连接图) from math import cos, sin, atan import matplotlib.pyplot as plt # 注意这里并没有用到这个networkx这个库,完全是根据matploblib这个库来画的。 class Neuron():def __init__(self, x, y,radius,nameNone):self.x xself.y …...
Bash 编程精粹:从新手到高手的全面指南之逻辑控制
在 Unix 和 Linux 系统中,Bash(Bourne-Again Shell)是一种广泛使用的 shell,提供了强大的脚本编程能力。本文将详细介绍 Bash 脚本中的逻辑控制结构,包括条件判断、分支选择、循环控制以及退出控制等内容。 条件判断&…...
自动化运维(三十)Ansible 实战之自定义插件
Ansible 自定义插件允许你扩展其功能,以满足特定的自动化需求。Ansible 支持多种类型的插件开发,如动态库存、查找、回调、过滤器、变量等。这里我们将通过实例,介绍如何开发、部署和使用一个自定义插件。 开发自定义查找插件 查找插件用于在 Ansible 任务中动态获取数据。…...
实习僧网站的实习岗位信息分析
目录 背景描述数据说明数据集来源问题描述分析目标以及导入模块1. 数据导入2. 数据基本信息和基本处理3. 数据处理3.1 新建data_clean数据框3.2 数值型数据处理3.2.1 “auth_capital”(注册资本)3.2.2 “day_per_week”(每周工作天数…...
C语言中局部变量和全局变量是否可以重名?为什么?
可以重名 在C语言中, 局部变量指的是定义在函数内的变量, 全局变量指的是定义在函数外的变量 他们在程序中的使用方法是不同的, 当重名时, 局部变量在其所在的作用域内具有更高的优先级, 会覆盖或者说隐藏同名的全局变量 具体来说: 局部变量的生命周期只在函数内部,如果出了…...
小程序中配置scss
找到:project.config.json 文件 setting 模块下添加: "useCompilerPlugins": ["sass","其他的样式类型"] 配置完成后,重启开发工具,并新建文件 结果:...
ZYNQ-Vitis(SDK)裸机开发之(四)PS端MIO和EMIO的使用
目录 一、ZYNQ中MIO和EMIO简介 二、Vivado中搭建block design 1.配置PS端MIO: 2.配置PS端EMIO: 三、Vitis中新建工程进行GPIO控制 1. GPIO操作头文件gpio_hdl.h: 2.GPIO操作源文件gpio_hdl.c: 3.main函数进行调用 例程开发…...
新人怎么做跨境电商/sem和seo的区别
mysql 查询某个库里表的数量 在mysql中有个数据库information_schema下的表tables记录了所有数据库中所有的表相关信息 TABLE_SCHEMA 数据库名称 SELECT COUNT( * ) FROM information_schema.tables WHERE TABLE_SCHEMA 库名 原文链接:http://www.cnblogs.com/liu…...
贵州省住房和城乡建设局网站首页/今日最新国际新闻
个人站长的win2003服务器配置与安全- Discuz实例,适合菜鸟教程!现在用vps的站长越来越多了,用vps就需要装系统,一般vps系统用的比较多的有linux与windouws。目前使用win系统做服务器的大型网站还是有蛮多的。例如易名中国,西部数码…...
南通物流网站建设/网络营销文案实例
URL即:统一资源定位符 (Uniform Resource Locator, URL) 完整的URL由这几个部分构成: scheme://host:port/path?query#fragment scheme:通信协议 常用的http,ftp,maito等 host:主机 服务器(计算机)域名系统 (DNS) 主机名或 IP 地址。 port:端口号 整数,…...
天河网站建设设计/网络推广员是干嘛的
数据科学(Data Science)作为一门新兴的学科,很多概念的定义其实没有特别明确。这点跟国内喜欢提的大数据很像。大数据这个概念总让我感觉有点无所适从,因为我实在说不清大数据的明确定义和具体范围。 国外一个做数据服务的网站mango-solutions将从事数据…...
休闲食品网站模板/新东方
函数式编程在前端权限管理中的应用 解决什么问题 本文主要是自己在实际业务开发中的一些总结,写出来希望与大家一起探讨。 首先介绍一下业务背景: 我们开发的是一套2B的企业培训SaaS系统,企业可以在平台上用直播的方式对自己的员工进行培训。…...
做外贸生意用哪个网站/seo关键词推广价格
用户故事可能是捕获产品功能的最流行的敏捷技术:使用用户故事很容易。但讲述有效的故事可能很难。以下十个提示可帮助您创建好故事。 1位用户先到先得 顾名思义,用户故事描述了客户或用户如何使用该产品; 从用户的角度讲述它。此外,用户故事…...