开天辟地 HarmonyOS(鸿蒙) - 媒体: AVTranscoder(视频转码)
开天辟地 HarmonyOS(鸿蒙) - 媒体: AVTranscoder(视频转码)
示例如下:
pages\media\AVTranscoderDemo.ets
/*
* AVTranscoder - 视频转码
*
* 先创建 worker 线程文件
* 1、在 /src/main/ets/ 目录层级之下创建一个 .ets 文件,本例创建的文件是 /src/main/ets/workers/transcodeworker.ets
* 2、编辑 build-profile.json5 文件,并添加类似如下的内容
* {
* "apiType": "stageMode",
* "buildOption": {
* "sourceOption": {
* "workers": [
* './src/main/ets/workers/transcodeworker.ets'
* ]
* }
* }
* }
*/
import { TitleBar } from '../TitleBar'
import { sendableContextManager } from '@kit.AbilityKit';
import { ErrorEvent, MessageEvents, worker } from '@kit.ArkTS';
@Entry
@Component
struct AVTranscoderDemo {
build() {
Column({ space: 10 }) {
TitleBar()
Tabs() {
TabContent() { MySample1() }.tabBar('基础').align(Alignment.Top)
}
.scrollable(true)
.barMode(BarMode.Scrollable)
.layoutWeight(1)
}
}
}
@Component
struct MySample1 {
@State message:string = ""
// 创建 worker 线程做转码
async startWorker() {
// 通过指定的 worker 线程文件创建工作线程对象(注:此处的文件路径要省略 src/main/)
let threadWorker = new worker.ThreadWorker('entry/ets/workers/transcodeworker.ets');
// 接收到工作线程传递过来的消息后的回调
threadWorker.onmessage = (event: MessageEvents) => {
// e.data 就是工作线程传递过来的消息
this.message += `onmessage: ${event.data}\n`
if (event.data == 'finish') {
// 如果 worker 线程传来了 'finish' 则终止 worker 线程
threadWorker.terminate();
}
}
// 接收到工作线程传递过来的无法被反序列化的消息后的回调
threadWorker.onmessageerror = (event: MessageEvents) => {
this.message += `onmessageerror: ${event.data}\n`
}
// 工作线程运行中出现异常后的回调
threadWorker.onerror = (err: ErrorEvent) => {
this.message += `onerror: ${err.message}\n`
}
// 工作线程被销毁后的回调(code 为 0 代表正常退出,code 为 1 代表异常退出)
threadWorker.onexit = (code: number) => {
this.message += `onexit: ${code}\n`
}
// 向 worker 线程发送消息
let context = this.getUIContext().getHostContext();
if (context != undefined) {
const sendableContext: sendableContextManager.SendableContext = sendableContextManager.convertFromContext(context);
// 需要转码的文件的 rawfile 地址
let sourceFilePath = "video/mp4.mp4"
// 转码后的文件的沙箱地址
let transcodedFilePath = context.getApplicationContext().filesDir + "/" + "mp4_transcoded.mp4";
const sendableObject: MySendableObject = new MySendableObject(sendableContext, sourceFilePath, transcodedFilePath);
threadWorker.postMessageWithSharedSendable(sendableObject);
}
}
build() {
Column({space:10}) {
Text(this.message).fontSize(16)
Button("click me").fontSize(16).onClick(async () => {
await this.startWorker();
})
}
}
}
// 用于在线程之间传递的 @Sendable 对象
@Sendable
export class MySendableObject {
constructor(sendableContext: sendableContextManager.SendableContext, rawfilePath: string, transcodedFilePath: string) {
this.sendableContext = sendableContext;
this.rawfilePath = rawfilePath;
this.transcodedFilePath = transcodedFilePath;
}
// 用于封装 context 对象
private sendableContext: sendableContextManager.SendableContext;
// 需要转码的文件的 rawfile 地址
private rawfilePath: string;
// 转码后的文件的沙箱地址
private transcodedFilePath: string;
public getSendableContext() {
return this.sendableContext;
}
public getRawfilePath() {
return this.rawfilePath;
}
public getTranscodedFilePath() {
return this.transcodedFilePath;
}
}
\entry\src\main\ets\workers\transcodeworker.ets
/*
* 此 worker 线程用于演示视频转码
*/
import { ErrorEvent, MessageEvents, ThreadWorkerGlobalScope, worker } from '@kit.ArkTS';
import { media } from '@kit.MediaKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { fileIo as fs } from '@kit.CoreFileKit';
import { common, sendableContextManager } from '@kit.AbilityKit';
import { MySendableObject } from '../pages/media/AVTranscoderDemo';
const workerPort: ThreadWorkerGlobalScope = worker.workerPort;
workerPort.onmessage = async (event: MessageEvents) => {
console.info('workerPort onmessage');
const sendableObject: MySendableObject = event.data
const sendableContext: sendableContextManager.SendableContext = sendableObject.getSendableContext()
const context: common.Context = sendableContextManager.convertToContext(sendableContext)
// 需要转码的文件的 rawfile 地址
let rawfilePath = sendableObject.getRawfilePath()
// 转码后的文件的沙箱地址
let transcodedFilePath = sendableObject.getTranscodedFilePath()
await transcode(context, rawfilePath, transcodedFilePath);
workerPort.postMessage('finish');
};
workerPort.onmessageerror = (event: MessageEvents) => {
console.info('workerPort onmessageerror');
};
workerPort.onerror = (event: ErrorEvent) => {
console.info('workerPort onerror: ', event.message);
};
let transcoder: media.AVTranscoder | undefined
async function transcode(context: common.Context, rawfilePath: string, transcodedFilePath: string) {
try {
/*
* AVTranscoder - 视频转码器
* media.createAVTranscoder() - 创建 AVTranscoder 对象
* on('complete')/off('complete') - 转码完成时的回调
* on('error')/off('error') - 转码异常时的回调
* on('progressUpdate')/off('progressUpdate') - 转码进度发生变化时的回调
* fdSrc - 需要转码的文件的 AVFileDescriptor 对象
* fdDst - 转码后的文件的 fd number
* prepare() - 准备转码(传入一个 AVTranscoderConfig 对象)
* audioBitrate - 输出的音频码率,单位:bps,默认值:48kbps
* audioCodec - 输出的音频的编码格式(CodecMimeType 枚举),默认值为 media.CodecMimeType.AUDIO_AAC
* fileFormat - 输出的文件的封装格式(ContainerFormatType 枚举),目前只支持 media.ContainerFormatType.CFT_MPEG_4
* videoBitrate - 输出的视频码率,单位:bps
* videoCodec - 输出的视频的编码格式(CodecMimeType 枚举),默认值为 media.CodecMimeType.VIDEO_AVC
* videoFrameWidth - 输出的视频的宽,单位:px
* videoFrameHeight - 输出的视频的高,单位:px
* start() - 开始转码,需要 prepare() 成功之后才能开始转码
* pause() - 暂停转码
* resume() - 继续转码
* cancel() - 取消转码
* release() - 清理资源
*/
transcoder = await media.createAVTranscoder();
transcoder.on('complete', async () => {
console.info(`mytranscoder: complete`);
workerPort.postMessage('mytranscoder: complete');
await release()
})
transcoder.on('error', async (err: BusinessError) => {
console.info(`mytranscoder: error`, err.code, err.message);
workerPort.postMessage(`mytranscoder: error, ${err.code}, ${err.message}`);
await release()
})
transcoder.on('progressUpdate', (progress: number) => {
console.info(`mytranscoder: progressUpdate`, progress);
})
// 将需要转码的文件转为 AVFileDescriptor 对象,并赋值给 transcoder 的 fdSrc 属性
// 将 rawfile 中的视频文件转为 AVFileDescriptor 对象
let fileDescriptor: media.AVFileDescriptor = await context.resourceManager.getRawFd(rawfilePath);
// 将沙箱路径中的视频文件转为 AVFileDescriptor 对象
// let fileDescriptor: media.AVFileDescriptor = fs.openSync(filePath);
transcoder.fdSrc = fileDescriptor;
// 拿到转码后的文件的文件描述符,并赋值给 transcoder 的 fdDst 属性
let file = fs.openSync(transcodedFilePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE | fs.OpenMode.TRUNC);
transcoder.fdDst = file.fd;
let avConfig: media.AVTranscoderConfig = {
audioBitrate: 48_000,
audioCodec: media.CodecMimeType.AUDIO_AAC,
fileFormat: media.ContainerFormatType.CFT_MPEG_4,
videoBitrate: 1_000_000,
videoCodec: media.CodecMimeType.VIDEO_AVC,
videoFrameWidth: 960,
videoFrameHeight: 400,
};
await transcoder?.prepare(avConfig);
await transcoder?.start();
} catch (error) {
console.info(`mytranscoder: ${error}`);
workerPort.postMessage(`mytranscoder: ${error}`);
await release()
}
}
// 清理资源
async function release() {
transcoder?.off('complete')
transcoder?.off('error')
transcoder?.off('progressUpdate')
transcoder?.release()
}