diff --git a/packages/av-cliper/src/combinator.ts b/packages/av-cliper/src/combinator.ts index 197b6966..7d0240f7 100644 --- a/packages/av-cliper/src/combinator.ts +++ b/packages/av-cliper/src/combinator.ts @@ -1,7 +1,7 @@ -import { Log, EventTool, file2stream, recodemux } from '@webav/internal-utils'; -import { OffscreenSprite } from './sprite/offscreen-sprite'; +import { EventTool, file2stream, Log, recodemux } from '@webav/internal-utils'; import { sleep } from './av-utils'; import { DEFAULT_AUDIO_CONF } from './clips'; +import { OffscreenSprite } from './sprite/offscreen-sprite'; export interface ICombinatorOpts { width?: number; @@ -10,6 +10,8 @@ export interface ICombinatorOpts { fps?: number; bgColor?: string; videoCodec?: string; + audioCodec?: 'opus' | 'aac'; + opusConfig?: object; /** * false 合成的视频文件中排除音轨 */ @@ -66,6 +68,8 @@ export class Combinator { static async isSupported( args: { videoCodec?: string; + audioCodec?: string; + hardwareAcceleration?: HardwarePreference; width?: number; height?: number; bitrate?: number; @@ -82,6 +86,7 @@ export class Combinator { (( await self.VideoEncoder.isConfigSupported({ codec: args.videoCodec ?? 'avc1.42E032', + hardwareAcceleration: args.hardwareAcceleration ?? 'no-preference', width: args.width ?? 1920, height: args.height ?? 1080, bitrate: args.bitrate ?? 7e6, @@ -90,7 +95,7 @@ export class Combinator { false) && ( await self.AudioEncoder.isConfigSupported({ - codec: DEFAULT_AUDIO_CONF.codec, + codec: args.audioCodec ?? DEFAULT_AUDIO_CONF.codec, sampleRate: DEFAULT_AUDIO_CONF.sampleRate, numberOfChannels: DEFAULT_AUDIO_CONF.channelCount, }) @@ -139,6 +144,8 @@ export class Combinator { width: 0, height: 0, videoCodec: 'avc1.42E032', + audioCodec: 'aac', + opusConfig: {}, audio: true, bitrate: 5e6, fps: 30, @@ -177,8 +184,17 @@ export class Combinator { } #startRecodeMux(duration: number) { - const { fps, width, height, videoCodec, bitrate, audio, metaDataTags } = - this.#opts; + const { + fps, + width, + height, + videoCodec, + audioCodec, + opusConfig, + bitrate, + audio, + metaDataTags, + } = this.#opts; const recodeMuxer = recodemux({ video: this.#hasVideoTrack ? { @@ -195,7 +211,8 @@ export class Combinator { audio === false ? null : { - codec: 'aac', + codec: audioCodec, + opusConfig: opusConfig, sampleRate: DEFAULT_AUDIO_CONF.sampleRate, channelCount: DEFAULT_AUDIO_CONF.channelCount, }, diff --git a/packages/internal-utils/src/recodemux.ts b/packages/internal-utils/src/recodemux.ts index 6c30965c..c093ea55 100644 --- a/packages/internal-utils/src/recodemux.ts +++ b/packages/internal-utils/src/recodemux.ts @@ -29,6 +29,7 @@ interface IRecodeMuxOpts { */ audio: { codec: 'opus' | 'aac'; + opusConfig: object; sampleRate: number; channelCount: number; } | null; @@ -376,17 +377,42 @@ function createVideoEncoder( return encoder; } +//codec info map entry +interface AudioCodecInfoEntry { + type: string; + codecString: string; + code: number; +} + +//codec info map to type, codec string and codec code +const codecInfoMap: Map = new Map([ + [ + 'aac', + { + type: 'mp4a', + codecString: 'mp4a.40.2', + code: 0x40, + } as AudioCodecInfoEntry, + ], + [ + 'opus', + { type: 'Opus', codecString: 'opus', code: 0xad } as AudioCodecInfoEntry, + ], +]); + function encodeAudioTrack( audioOpts: NonNullable, mp4File: MP4File, avSyncEvtTool: EventTool void>>, ): AudioEncoder { + const codecInfoMapEntry = codecInfoMap.get(audioOpts.codec)!; const audioTrackOpts = { timescale: 1e6, samplerate: audioOpts.sampleRate, channel_count: audioOpts.channelCount, hdlr: 'soun', - type: audioOpts.codec === 'aac' ? 'mp4a' : 'Opus', + //map codec to type + type: codecInfoMapEntry.type, name: 'Track created with WebAV', }; @@ -403,7 +429,9 @@ function encodeAudioTrack( }); const encoderConf = { - codec: audioOpts.codec === 'aac' ? 'mp4a.40.2' : 'opus', + //map codec to AudioEncoder codec + codec: codecInfoMapEntry.codecString, + opus: audioOpts.opusConfig, sampleRate: audioOpts.sampleRate, numberOfChannels: audioOpts.channelCount, bitrate: 128_000, @@ -424,9 +452,13 @@ function encodeAudioTrack( if (trackId === -1) { // 某些设备不会输出 description const desc = meta?.decoderConfig?.description; + trackId = mp4File.addTrack({ ...audioTrackOpts, - description: desc == null ? undefined : createESDSBox(desc), + description: + desc == null + ? undefined + : createESDSBox(desc, codecInfoMapEntry.code), }); avSyncEvtTool.emit('AudioReady'); Log.info('AudioEncoder, audio track ready, trackId:', trackId); @@ -449,9 +481,13 @@ function encodeAudioTrack( * 创建 ESDS 盒子(MPEG-4 Elementary Stream Descriptor) * ESDS 盒子用于描述 MPEG-4 的流信息,如编解码器类型、流类型、最大比特率、平均比特率等 * @param config - 配置信息,可以是 `ArrayBuffer` 或 `ArrayBufferView` 类型 + * @param codecConfig The audio codec code for m4a and opus * @return 返回一个 ESDS box */ -function createESDSBox(config: ArrayBuffer | ArrayBufferView) { +function createESDSBox( + config: ArrayBuffer | ArrayBufferView, + codecCode: number, +) { const configlen = config.byteLength; const buf = new Uint8Array([ 0x00, // version 0 @@ -468,7 +504,7 @@ function createESDSBox(config: ArrayBuffer | ArrayBufferView) { 0x04, // descriptor_type 0x12 + configlen, // length - 0x40, // codec : mpeg4_audio + codecCode, // codec : mpeg4_audio 0x15, // stream_type 0x00, 0x00,