From 53f2bc8ca77bb107f0887932f69206d6218bf8ad Mon Sep 17 00:00:00 2001 From: lonelyhentxi Date: Mon, 24 Mar 2025 01:02:30 +0800 Subject: [PATCH] add codecs --- apps/mock/public/video/bear-vp9.webm | Bin 0 -> 67504 bytes apps/playground/src/index.html | 4 +- .../playground/src/media/base/audio_codecs.ts | 32 +++ apps/playground/src/media/base/errors.ts | 11 + .../playground/src/media/base/video_codecs.ts | 97 ++++++++ apps/playground/src/media/mkv/codecs/aac.ts | 113 +++++++++ apps/playground/src/media/mkv/codecs/avc.ts | 125 ++++++++++ apps/playground/src/media/mkv/codecs/hevc.ts | 144 +++++++++++ apps/playground/src/media/mkv/codecs/index.ts | 229 ++++++++++++++++++ apps/playground/src/media/mkv/model.ts | 6 +- biome.jsonc | 1 + package.json | 1 + packages/codecs/package.json | 19 ++ packages/codecs/src/index.ts | 0 pnpm-lock.yaml | 25 ++ 15 files changed, 802 insertions(+), 5 deletions(-) create mode 100644 apps/mock/public/video/bear-vp9.webm create mode 100644 apps/playground/src/media/base/audio_codecs.ts create mode 100644 apps/playground/src/media/base/errors.ts create mode 100644 apps/playground/src/media/base/video_codecs.ts create mode 100644 apps/playground/src/media/mkv/codecs/aac.ts create mode 100644 apps/playground/src/media/mkv/codecs/avc.ts create mode 100644 apps/playground/src/media/mkv/codecs/hevc.ts create mode 100644 apps/playground/src/media/mkv/codecs/index.ts create mode 100644 packages/codecs/package.json create mode 100644 packages/codecs/src/index.ts diff --git a/apps/mock/public/video/bear-vp9.webm b/apps/mock/public/video/bear-vp9.webm new file mode 100644 index 0000000000000000000000000000000000000000..4f497aeb344a805c11b8780e8acd2aac6adf4a63 GIT binary patch literal 67504 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 z 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 z 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 ob8n!HXj5&V98 - - + + \ No newline at end of file diff --git a/apps/playground/src/media/base/audio_codecs.ts b/apps/playground/src/media/base/audio_codecs.ts new file mode 100644 index 0000000..0ce538d --- /dev/null +++ b/apps/playground/src/media/base/audio_codecs.ts @@ -0,0 +1,32 @@ +export enum AudioCodec { + Unknown = 0, + AAC = 1, + MP3 = 2, + PCM = 3, + Vorbis = 4, + FLAC = 5, + AMR_NB = 6, + AMR_WB = 7, + PCM_MULAW = 8, + GSM_MS = 9, + PCM_S16BE = 10, + PCM_S24BE = 11, + Opus = 12, + EAC3 = 13, + PCM_ALAW = 14, + ALAC = 15, + AC3 = 16, + MpegHAudio = 17, + DTS = 18, + DTSXP2 = 19, + DTSE = 20, + AC4 = 21, + IAMF = 22, + PCM_S32BE = 23, + PCM_S32LE = 24, + PCM_S24LE = 25, + PCM_S16LE = 26, + PCM_F32BE = 27, + PCM_F32LE = 28, + MaxValue = PCM_F32LE, // Must equal the last "real" codec above. +} diff --git a/apps/playground/src/media/base/errors.ts b/apps/playground/src/media/base/errors.ts new file mode 100644 index 0000000..86459b5 --- /dev/null +++ b/apps/playground/src/media/base/errors.ts @@ -0,0 +1,11 @@ +export class UnsupportCodecError extends Error { + constructor(codec: string, context: string) { + super(`codec ${codec} is not supported in ${context} context`); + } +} + +export class ParseCodecPrivateError extends Error { + constructor(codec: string, detail: string) { + super(`code ${codec} private parse failed: ${detail}`); + } +} diff --git a/apps/playground/src/media/base/video_codecs.ts b/apps/playground/src/media/base/video_codecs.ts new file mode 100644 index 0000000..a226caa --- /dev/null +++ b/apps/playground/src/media/base/video_codecs.ts @@ -0,0 +1,97 @@ +export enum VideoCodec { + Unknown = 0, + H264 = 1, + VC1 = 2, + MPEG2 = 3, + MPEG4 = 4, + Theora = 5, + VP8 = 6, + VP9 = 7, + HEVC = 8, + DolbyVision = 9, + AV1 = 10, + MaxValue = AV1, // Must equal the last "real" codec above. +} + +export enum VideoCodecProfile { + VIDEO_CODEC_PROFILE_UNKNOWN = -1, + VIDEO_CODEC_PROFILE_MIN = VIDEO_CODEC_PROFILE_UNKNOWN, + H264PROFILE_MIN = 0, + H264PROFILE_BASELINE = H264PROFILE_MIN, + H264PROFILE_MAIN = 1, + H264PROFILE_EXTENDED = 2, + H264PROFILE_HIGH = 3, + H264PROFILE_HIGH10PROFILE = 4, + H264PROFILE_HIGH422PROFILE = 5, + H264PROFILE_HIGH444PREDICTIVEPROFILE = 6, + H264PROFILE_SCALABLEBASELINE = 7, + H264PROFILE_SCALABLEHIGH = 8, + H264PROFILE_STEREOHIGH = 9, + H264PROFILE_MULTIVIEWHIGH = 10, + H264PROFILE_MAX = H264PROFILE_MULTIVIEWHIGH, + VP8PROFILE_MIN = 11, + VP8PROFILE_ANY = VP8PROFILE_MIN, + VP8PROFILE_MAX = VP8PROFILE_ANY, + VP9PROFILE_MIN = 12, + VP9PROFILE_PROFILE0 = VP9PROFILE_MIN, + VP9PROFILE_PROFILE1 = 13, + VP9PROFILE_PROFILE2 = 14, + VP9PROFILE_PROFILE3 = 15, + VP9PROFILE_MAX = VP9PROFILE_PROFILE3, + HEVCPROFILE_MIN = 16, + HEVCPROFILE_MAIN = HEVCPROFILE_MIN, + HEVCPROFILE_MAIN10 = 17, + HEVCPROFILE_MAIN_STILL_PICTURE = 18, + HEVCPROFILE_MAX = HEVCPROFILE_MAIN_STILL_PICTURE, + DOLBYVISION_PROFILE0 = 19, + // Deprecated: DOLBYVISION_PROFILE4 = 20, + DOLBYVISION_PROFILE5 = 21, + DOLBYVISION_PROFILE7 = 22, + THEORAPROFILE_MIN = 23, + THEORAPROFILE_ANY = THEORAPROFILE_MIN, + THEORAPROFILE_MAX = THEORAPROFILE_ANY, + AV1PROFILE_MIN = 24, + AV1PROFILE_PROFILE_MAIN = AV1PROFILE_MIN, + AV1PROFILE_PROFILE_HIGH = 25, + AV1PROFILE_PROFILE_PRO = 26, + AV1PROFILE_MAX = AV1PROFILE_PROFILE_PRO, + DOLBYVISION_PROFILE8 = 27, + DOLBYVISION_PROFILE9 = 28, + HEVCPROFILE_EXT_MIN = 29, + HEVCPROFILE_REXT = HEVCPROFILE_EXT_MIN, + HEVCPROFILE_HIGH_THROUGHPUT = 30, + HEVCPROFILE_MULTIVIEW_MAIN = 31, + HEVCPROFILE_SCALABLE_MAIN = 32, + HEVCPROFILE_3D_MAIN = 33, + HEVCPROFILE_SCREEN_EXTENDED = 34, + HEVCPROFILE_SCALABLE_REXT = 35, + HEVCPROFILE_HIGH_THROUGHPUT_SCREEN_EXTENDED = 36, + HEVCPROFILE_EXT_MAX = HEVCPROFILE_HIGH_THROUGHPUT_SCREEN_EXTENDED, + VVCPROFILE_MIN = 37, + VVCPROFILE_MAIN10 = VVCPROFILE_MIN, + VVCPROFILE_MAIN12 = 38, + VVCPROFILE_MAIN12_INTRA = 39, + VVCPROIFLE_MULTILAYER_MAIN10 = 40, + VVCPROFILE_MAIN10_444 = 41, + VVCPROFILE_MAIN12_444 = 42, + VVCPROFILE_MAIN16_444 = 43, + VVCPROFILE_MAIN12_444_INTRA = 44, + VVCPROFILE_MAIN16_444_INTRA = 45, + VVCPROFILE_MULTILAYER_MAIN10_444 = 46, + VVCPROFILE_MAIN10_STILL_PICTURE = 47, + VVCPROFILE_MAIN12_STILL_PICTURE = 48, + VVCPROFILE_MAIN10_444_STILL_PICTURE = 49, + VVCPROFILE_MAIN12_444_STILL_PICTURE = 50, + VVCPROFILE_MAIN16_444_STILL_PICTURE = 51, + VVCPROFILE_MAX = VVCPROFILE_MAIN16_444_STILL_PICTURE, + VIDEO_CODEC_PROFILE_MAX = VVCPROFILE_MAIN16_444_STILL_PICTURE, +} + +export type VideoCodecLevel = number; // uint32 +export const NoVideoCodecLevel: VideoCodecLevel = 0; + +export type VideoCodecProfileLevel = { + codec: VideoCodec; + profile: VideoCodecProfile; + level: VideoCodecLevel; +}; diff --git a/apps/playground/src/media/mkv/codecs/aac.ts b/apps/playground/src/media/mkv/codecs/aac.ts new file mode 100644 index 0000000..8eed1b0 --- /dev/null +++ b/apps/playground/src/media/mkv/codecs/aac.ts @@ -0,0 +1,113 @@ +import { ParseCodecPrivateError } from '@/media/base/errors'; +import { ArkErrors, type } from 'arktype'; + +export const AAC_CODEC_TYPE = 'AAC'; + +export const AudioObjectTypeSchema = type('1 | 2 | 3 | 4 | 5 | 29 | 67'); + +export const SamplingFrequencyIndexSchema = type('1|2|3|4|5|6|7|8|9|10|11|12'); + +export const ChannelConfigurationSchema = type('1 | 2 | 3 | 4 | 5 | 6 | 7'); + +export const AudioSpecificConfigSchema = type({ + audioObjectType: AudioObjectTypeSchema, // AAC profiles: Main, LC, SSR, LTP, HE, HE v2 + samplingFrequencyIndex: SamplingFrequencyIndexSchema.optional(), // Sampling rate index + channelConfiguration: ChannelConfigurationSchema, // Channel config (1-7) + sbrPresent: type.boolean.optional(), // Optional: Indicates SBR presence + psPresent: type.boolean.optional(), // Optional: Indicates PS presence (for HE-AAC v2) +}); + +export type AudioSpecificConfigType = typeof AudioSpecificConfigSchema.infer; + +/** + * Parse AudioSpecificConfig from codec_private Uint8Array + * @param codecPrivate - Uint8Array containing codec_private data + * @returns Parsed AudioSpecificConfig or throws an error if invalid + */ +export function parseAudioSpecificConfig( + codecPrivate: Uint8Array +): AudioSpecificConfigType { + if (codecPrivate.length < 2) { + throw new ParseCodecPrivateError( + AAC_CODEC_TYPE, + 'codec_private data too short' + ); + } + + // Create a DataView for bit-level manipulation + const view = new DataView( + codecPrivate.buffer, + codecPrivate.byteOffset, + codecPrivate.byteLength + ); + let byteOffset = 0; + let bitOffset = 0; + + // Helper function to read specific number of bits + function readBits(bits: number): number { + let value = 0; + for (let i = 0; i < bits; i++) { + const byte = view.getUint8(byteOffset); + const bit = (byte >> (7 - bitOffset)) & 1; + value = (value << 1) | bit; + bitOffset++; + if (bitOffset === 8) { + bitOffset = 0; + byteOffset++; + } + } + return value; + } + + // Read 5 bits for audioObjectType + const audioObjectType = readBits(5); + + // Read 4 bits for samplingFrequencyIndex + const samplingFrequencyIndex = readBits(4); + + // Read 4 bits for channelConfiguration + const channelConfiguration = readBits(4); + + // Check for SBR/PS extension (if audioObjectType indicates HE-AAC) + let sbrPresent = false; + let psPresent = false; + if (audioObjectType === 5 || audioObjectType === 29) { + sbrPresent = true; + if (audioObjectType === 29) { + psPresent = true; // HE-AAC v2 includes Parametric Stereo + } + // Skip extension-specific bits if present (simplified here) + // In real cases, additional parsing may be needed + } + + // Construct the result object + const config: AudioSpecificConfigType = { + audioObjectType: + audioObjectType as AudioSpecificConfigType['audioObjectType'], + samplingFrequencyIndex: + samplingFrequencyIndex as AudioSpecificConfigType['samplingFrequencyIndex'], + channelConfiguration: + channelConfiguration as AudioSpecificConfigType['channelConfiguration'], + ...(sbrPresent && { sbrPresent }), + ...(psPresent && { psPresent }), + }; + + // Validate with arktype + const validation = AudioSpecificConfigSchema(config); + if (validation instanceof ArkErrors) { + const error = new ParseCodecPrivateError( + AAC_CODEC_TYPE, + 'Invalid AudioSpecificConfig' + ); + error.cause = validation; + throw error; + } + + return config; +} + +export function genCodecIdByAudioSpecificConfig( + config: AudioSpecificConfigType +) { + return `mp4a.40.${config.audioObjectType}`; +} diff --git a/apps/playground/src/media/mkv/codecs/avc.ts b/apps/playground/src/media/mkv/codecs/avc.ts new file mode 100644 index 0000000..bcb6108 --- /dev/null +++ b/apps/playground/src/media/mkv/codecs/avc.ts @@ -0,0 +1,125 @@ +import { ParseCodecPrivateError } from '@/media/base/errors'; +import { type } from 'arktype'; + +export const AVC_CODEC_TYPE = 'h264(AVC)'; + +export const AVCDecoderConfigurationRecordSchema = type({ + configurationVersion: type.number, // Configuration version, typically 1 + avcProfileIndication: type.number, // AVC profile + profileCompatibility: type.number, // Profile compatibility + avcLevelIndication: type.number, // AVC level + lengthSizeMinusOne: type.number, // NAL unit length field size minus 1 + sps: type + .instanceOf(Uint8Array) + .array() + .atLeastLength(1), // Sequence Parameter Sets (SPS) + pps: type + .instanceOf(Uint8Array) + .array() + .atLeastLength(1), // Picture Parameter Sets (PPS) +}); + +export type AVCDecoderConfigurationRecordType = + typeof AVCDecoderConfigurationRecordSchema.infer; + +/** + * Parse AVCDecoderConfigurationRecord from codec_private Uint8Array + * @param codecPrivate - Uint8Array containing codec_private data + * @returns Parsed AVCDecoderConfigurationRecord or throws an error if invalid + */ +export function parseAVCDecoderConfigurationRecord( + codecPrivate: Uint8Array +): AVCDecoderConfigurationRecordType { + let offset = 0; + + // Check if data length is sufficient + if (codecPrivate.length < 5) { + throw new ParseCodecPrivateError( + AVC_CODEC_TYPE, + 'Input data too short for AVCDecoderConfigurationRecord' + ); + } + + const configurationVersion = codecPrivate[offset++]; + const avcProfileIndication = codecPrivate[offset++]; + const profileCompatibility = codecPrivate[offset++]; + const avcLevelIndication = codecPrivate[offset++]; + + // Read lengthSizeMinusOne (first 6 bits are reserved, typically 0xFF, last 2 bits are the value) + const lengthSizeMinusOne = codecPrivate[offset++] & 0x03; + + // Read number of SPS (first 3 bits are reserved, typically 0xE0, last 5 bits are SPS count) + const numOfSPS = codecPrivate[offset++] & 0x1f; + const sps: Uint8Array[] = []; + + // Parse SPS + for (let i = 0; i < numOfSPS; i++) { + if (offset + 2 > codecPrivate.length) { + throw new ParseCodecPrivateError(AVC_CODEC_TYPE, 'Invalid SPS length'); + } + + const spsLength = (codecPrivate[offset] << 8) | codecPrivate[offset + 1]; + offset += 2; + + if (offset + spsLength > codecPrivate.length) { + throw new ParseCodecPrivateError( + AVC_CODEC_TYPE, + 'SPS data exceeds buffer length' + ); + } + + sps.push(codecPrivate.subarray(offset, offset + spsLength)); + offset += spsLength; + } + + // Read number of PPS + if (offset >= codecPrivate.length) { + throw new ParseCodecPrivateError(AVC_CODEC_TYPE, 'No space for PPS count'); + } + const numOfPPS = codecPrivate[offset++]; + const pps: Uint8Array[] = []; + + // Parse PPS + for (let i = 0; i < numOfPPS; i++) { + if (offset + 2 > codecPrivate.length) { + throw new ParseCodecPrivateError(AVC_CODEC_TYPE, 'Invalid PPS length'); + } + + const ppsLength = (codecPrivate[offset] << 8) | codecPrivate[offset + 1]; + offset += 2; + + if (offset + ppsLength > codecPrivate.length) { + throw new ParseCodecPrivateError( + AVC_CODEC_TYPE, + 'PPS data exceeds buffer length' + ); + } + + pps.push(codecPrivate.subarray(offset, offset + ppsLength)); + offset += ppsLength; + } + + return { + configurationVersion, + avcProfileIndication, + profileCompatibility, + avcLevelIndication, + lengthSizeMinusOne, + sps, + pps, + }; +} + +export function genCodecIdByAVCDecoderConfigurationRecord( + config: AVCDecoderConfigurationRecordType +): string { + const profileHex = config.avcProfileIndication.toString(16).padStart(2, '0'); + const profileCompatHex = config.profileCompatibility + .toString(16) + .padStart(2, '0'); + const levelHex = (config.avcLevelIndication / 10) + .toString(16) + .replace(/./g, '') + .padStart(2, '0'); + return `avc1.${profileHex}${profileCompatHex}${levelHex}`; +} diff --git a/apps/playground/src/media/mkv/codecs/hevc.ts b/apps/playground/src/media/mkv/codecs/hevc.ts new file mode 100644 index 0000000..31a7b7f --- /dev/null +++ b/apps/playground/src/media/mkv/codecs/hevc.ts @@ -0,0 +1,144 @@ +import { ParseCodecPrivateError } from '@/media/base/errors'; +import { ArkErrors, type } from 'arktype'; + +export const HEVC_CODEC_TYPE = 'h265(HEVC)'; + +export const HEVCDecoderConfigurationRecordArraySchema = type({ + arrayCompleteness: type.boolean, + reserved: type.number, + NALUnitType: type.number, + numNalus: type.number, + nalUnits: type.instanceOf(Uint8Array).array(), +}); + +export type HEVCDecoderConfigurationRecordArrayType = + typeof HEVCDecoderConfigurationRecordArraySchema.infer; + +// Define the schema for HEVCDecoderConfigurationRecord +export const HEVCDecoderConfigurationRecordSchema = type({ + configurationVersion: type.number, // Must be 1 + generalProfileSpace: type.number, + generalTierFlag: type.boolean, + generalProfileIdc: type.number, + generalProfileCompatibilityFlags: type.number, + generalConstraintIndicatorFlags: type.number.array().exactlyLength(6), // Fixed 6-byte array + generalLevelIdc: type.number, + reserved1: type.number, // 4 bits reserved, must be 1111 + minSpatialSegmentationIdc: type.number, + reserved2: type.number, // 6 bits reserved, must be 111111 + parallelismType: type.number, + chromaFormat: type.number, + bitDepthLumaMinus8: type.number, + bitDepthChromaMinus8: type.number, + avgFrameRate: type.number, + constantFrameRate: type.number, + numTemporalLayers: type.number, + temporalIdNested: type.boolean, + lengthSizeMinusOne: type.number, + numOfArrays: type.number, + arrays: HEVCDecoderConfigurationRecordArraySchema.array(), +}); + +export type HEVCDecoderConfigurationRecordType = + typeof HEVCDecoderConfigurationRecordSchema.infer; + +/** + * Parse HEVCDecoderConfigurationRecord from codec_private Uint8Array + * @param codecPrivate - Uint8Array containing codec_private data + * @returns Parsed HEVCDecoderConfigurationRecord or throws an error if invalid + */ +export function parseHEVCDecoderConfigurationRecord( + codecPrivate: Uint8Array +): HEVCDecoderConfigurationRecordType { + let offset = 0; + + // Read and validate basic fields + const config: HEVCDecoderConfigurationRecordType = { + configurationVersion: codecPrivate[offset++], + generalProfileSpace: codecPrivate[offset] >> 6, + generalTierFlag: Boolean(codecPrivate[offset] & 0x20), + generalProfileIdc: codecPrivate[offset++] & 0x1f, + generalProfileCompatibilityFlags: + (codecPrivate[offset] << 24) | + (codecPrivate[offset + 1] << 16) | + (codecPrivate[offset + 2] << 8) | + codecPrivate[offset + 3], + generalConstraintIndicatorFlags: Array.from( + codecPrivate.subarray(offset + 4, offset + 10) + ), + generalLevelIdc: codecPrivate[offset + 10], + reserved1: (codecPrivate[offset + 11] & 0xf0) >> 4, // 4 bits + minSpatialSegmentationIdc: + ((codecPrivate[offset + 11] & 0x0f) << 8) | codecPrivate[offset + 12], + reserved2: (codecPrivate[offset + 13] & 0xfc) >> 2, // 6 bits + parallelismType: codecPrivate[offset + 13] & 0x03, + chromaFormat: (codecPrivate[offset + 14] & 0xe0) >> 5, + bitDepthLumaMinus8: (codecPrivate[offset + 14] & 0x1c) >> 2, + bitDepthChromaMinus8: codecPrivate[offset + 14] & 0x03, + avgFrameRate: (codecPrivate[offset + 15] << 8) | codecPrivate[offset + 16], + constantFrameRate: (codecPrivate[offset + 17] & 0xc0) >> 6, + numTemporalLayers: (codecPrivate[offset + 17] & 0x38) >> 3, + temporalIdNested: Boolean(codecPrivate[offset + 17] & 0x04), + lengthSizeMinusOne: codecPrivate[offset + 17] & 0x03, + numOfArrays: codecPrivate[offset + 18], + arrays: [], + }; + offset += 19; + + // Parse NAL unit arrays + const arrays = config.arrays; + for (let i = 0; i < config.numOfArrays; i++) { + const array: HEVCDecoderConfigurationRecordArrayType = { + arrayCompleteness: Boolean(codecPrivate[offset] & 0x80), + reserved: (codecPrivate[offset] & 0x40) >> 6, + NALUnitType: codecPrivate[offset] & 0x3f, + numNalus: (codecPrivate[offset + 1] << 8) | codecPrivate[offset + 2], + nalUnits: [] as Uint8Array[], + }; + offset += 3; + + for (let j = 0; j < array.numNalus; j++) { + const nalUnitLength = + (codecPrivate[offset] << 8) | codecPrivate[offset + 1]; + offset += 2; + array.nalUnits.push( + codecPrivate.subarray(offset, offset + nalUnitLength) + ); + offset += nalUnitLength; + } + arrays.push(array); + } + + const result = { ...config, arrays }; + + // Validate using arktype + const validation = HEVCDecoderConfigurationRecordSchema(result); + if (validation instanceof ArkErrors) { + const error = new ParseCodecPrivateError( + HEVC_CODEC_TYPE, + 'Invalid HEVC configuration record' + ); + error.cause = validation; + throw error; + } + + return result; +} + +export function genCodecStringByHEVCDecoderConfigurationRecord( + config: HEVCDecoderConfigurationRecordType +) { + const profileSpace = + config.generalProfileSpace === 0 + ? '' + : String.fromCharCode(65 + config.generalProfileSpace - 1); + const profileIdcHex = config.generalProfileIdc.toString(16); + const tier = config.generalTierFlag ? '7' : '6'; + const levelMajor = Math.floor(config.generalLevelIdc / 30); + const levelMinor = + config.generalLevelIdc % 30 === 0 ? '0' : (config.generalLevelIdc % 30) / 3; + const levelStr = `L${config.generalLevelIdc.toString().padStart(3, '0')}`; + + const constraint = '00'; + return `hev1.${profileSpace}${profileIdcHex}.${tier}.${levelStr}.${constraint}`; +} diff --git a/apps/playground/src/media/mkv/codecs/index.ts b/apps/playground/src/media/mkv/codecs/index.ts new file mode 100644 index 0000000..bf8821b --- /dev/null +++ b/apps/playground/src/media/mkv/codecs/index.ts @@ -0,0 +1,229 @@ +import { AudioCodec } from '../../base/audio_codecs'; +import { UnsupportCodecError } from '../../base/errors'; +import { VideoCodec } from '../../base/video_codecs'; +import type { TrackEntryType } from '../schema'; +import { + genCodecIdByAudioSpecificConfig, + parseAudioSpecificConfig, +} from './aac'; +import { + genCodecIdByAVCDecoderConfigurationRecord, + parseAVCDecoderConfigurationRecord, +} from './avc'; + +export const VideoCodecId = { + VCM: 'V_MS/VFW/FOURCC', + UNCOMPRESSED: 'V_UNCOMPRESSED', + MPEG4_ISO_SP: 'V_MPEG4/ISO/SP', + MPEG4_ISO_ASP: 'V_MPEG4/ISO/ASP', + MPEG4_ISO_AP: 'V_MPEG4/ISO/AP', + MPEG4_MS_V3: 'V_MPEG4/MS/V3', + MPEG1: 'V_MPEG1', + MPEG2: 'V_MPEG2', + H264: 'V_MPEG4/ISO/AVC', + HEVC: 'V_MPEGH/ISO/HEVC', + AVS2: 'V_AVS2', + AVS3: 'V_AVS3', + RV10: 'V_REAL/RV10', + RV20: 'V_REAL/RV20', + RV30: 'V_REAL/RV30', + RV40: 'V_REAL/RV40', + QUICKTIME: 'V_QUICKTIME', + THEORA: 'V_THEORA', + PROPRES: 'V_PRORES', + VP8: 'V_VP8', + VP9: 'V_VP9', + FFV1: 'V_FFV1', + AV1: 'V_AV1', +} as const; + +export type VideoCodecIdType = + | `${(typeof VideoCodecId)[keyof typeof VideoCodecId]}` + | string; + +export const AudioCodecId = { + MPEG_L3: 'A_MPEG/L3', + MPEG_L2: 'A_MPEG/L2', + MPEG_L1: 'A_MPEG/L1', + PCM_INT_BIG: 'A_PCM/INT/BIG', + PCM_INT_LIT: 'A_PCM/INT/LIT', + PCM_FLOAT_IEEE: 'A_PCM/FLOAT/IEEE', + MPC: 'A_MPC', + AC3: 'A_AC3', + AC3_BSID9: 'A_AC3/BSID9', + AC3_BSID10: 'A_AC3/BSID10', + ALAC: 'A_ALAC', + DTS: 'A_DTS', + DTS_EXPRESS: 'A_DTS/EXPRESS', + DTS_LOSSLESS: 'A_DTS/LOSSLESS', + VORBIS: 'A_VORBIS', + OPUS: 'A_OPUS', + FLAC: 'A_FLAC', + EAC3: 'A_EAC3', + REAL_14_4: 'A_REAL/14_4', + REAL_28_8: 'A_REAL/28_8', + REAL_COOK: 'A_REAL/COOK', + REAL_SIPR: 'A_REAL/SIPR', + REAL_RALF: 'A_REAL/RALF', + REAL_ATRC: 'A_REAL/ATRC', + MS_ACM: 'A_MS/ACM', + AAC: 'A_AAC', + AAC_MPEG2_MAIN: 'A_AAC/MPEG2/MAIN', + AAC_MPEG2_LC: 'A_AAC/MPEG2/LC', + AAC_MPEG2_LC_SBR: 'A_AAC/MPEG2/LC/SBR', + AAC_MPEG2_SSR: 'A_AAC/MPEG2/SSR', + AAC_MPEG4_MAIN: 'A_AAC/MPEG4/MAIN', + AAC_MPEG4_LC: 'A_AAC/MPEG4/LC', + AAC_MPEG4_SBR: 'A_AAC/MPEG4/LC/SBR', + AAC_MPEG4_SSR: 'A_AAC/MPEG4/SSR', + AAC_MPEG4_LTP: 'A_AAC/MPEG4/LTP', + QUICKTIME: 'A_QUICKTIME', + QDMC: 'A_QUICKTIME/QDMC', + QDM2: 'A_QUICKTIME/QDM2', + TTA1: 'A_TTA1', + WAVEPACK4: 'A_WAVPACK4', + ATRAC: 'A_ATRAC/AT1', +} as const; + +export type AudioCodecIdType = + | `${(typeof AudioCodecId)[keyof typeof AudioCodecId]}` + | string; + +export const SubtitleCodecId = { + UTF8: 'S_TEXT/UTF8', + SSA: 'S_TEXT/SSA', + ASS: 'S_TEXT/ASS', + WEBVTT: 'S_TEXT/WEBVTT', + BMP: 'S_IMAGE/BMP', + DVBSUB: 'S_DVBSUB', + VOBSUB: 'S_VOBSUB', + HDMV_PGS: 'S_HDMV/PGS', + HDMV_TEXTST: 'S_HDMV/TEXTST', + KATE: 'S_KATE', + ARIBSUB: 'S_ARIBSUB', +} as const; + +export type SubtitleCodecIdType = + | `${(typeof SubtitleCodecId)[keyof typeof SubtitleCodecId]}` + | string; + +export function videoCodecIdToWebCodecsVideoDecoder( + track: TrackEntryType +): [VideoCodec, string] { + const codecId = track.CodecID; + const codecPrivate = track.CodecPrivate; + switch (codecId) { + case VideoCodecId.HEVC: + return [VideoCodec.HEVC, 'hevc']; + case VideoCodecId.VP9: + return [VideoCodec.VP9, 'vp09']; + case VideoCodecId.AV1: + return [VideoCodec.AV1, 'av1']; + case VideoCodecId.H264: + if (!codecPrivate) { + throw new UnsupportCodecError( + 'h264(without codec_private profile)', + 'web codecs audio decoder' + ); + } + return [ + VideoCodec.H264, + genCodecIdByAVCDecoderConfigurationRecord( + parseAVCDecoderConfigurationRecord(codecPrivate) + ), + ]; + case VideoCodecId.THEORA: + return [VideoCodec.Theora, 'theora']; + case VideoCodecId.VP8: + return [VideoCodec.VP8, 'vp8']; + case VideoCodecId.MPEG4_ISO_SP: + return [VideoCodec.MPEG4, 'mp4v.01.3']; + case VideoCodecId.MPEG4_ISO_ASP: + return [VideoCodec.MPEG4, 'mp4v.20.9']; + case VideoCodecId.MPEG4_ISO_AP: + return [VideoCodec.MPEG4, 'mp4v.20.9']; + default: + throw new UnsupportCodecError(codecId, 'web codecs video decoder'); + } +} + +export function videoCodecIdToWebCodecsAudioDecoder( + track: TrackEntryType +): [AudioCodec, string] { + const codecId = track.CodecID; + const codecPrivate = track.CodecPrivate; + const bitDepth = track.Audio?.BitDepth; + switch (track.CodecID) { + case AudioCodecId.AAC_MPEG4_MAIN: + case AudioCodecId.AAC_MPEG2_MAIN: + return [AudioCodec.AAC, 'mp4a.40.1']; + case AudioCodecId.AAC_MPEG2_LC: + case AudioCodecId.AAC_MPEG4_LC: + return [AudioCodec.AAC, 'mp4a.40.2']; + case AudioCodecId.AAC_MPEG2_SSR: + case AudioCodecId.AAC_MPEG4_SSR: + return [AudioCodec.AAC, 'mp4a.40.3']; + case AudioCodecId.AAC_MPEG4_LTP: + return [AudioCodec.AAC, 'mp4a.40.4']; + case AudioCodecId.AAC_MPEG2_LC_SBR: + case AudioCodecId.AAC_MPEG4_SBR: + return [AudioCodec.AAC, 'mp4a.40.5']; + case AudioCodecId.AAC: + return [ + AudioCodec.AAC, + codecPrivate + ? genCodecIdByAudioSpecificConfig( + parseAudioSpecificConfig(codecPrivate) + ) + : 'mp4a.40.2', + ]; + case AudioCodecId.AC3: + case AudioCodecId.AC3_BSID9: + return [AudioCodec.AC3, 'ac-3']; + case AudioCodecId.EAC3: + case AudioCodecId.AC3_BSID10: + return [AudioCodec.EAC3, 'ec-3']; + case AudioCodecId.MPEG_L3: + return [AudioCodec.MP3, 'mp3']; + case AudioCodecId.VORBIS: + return [AudioCodec.Vorbis, 'vorbis']; + case AudioCodecId.FLAC: + return [AudioCodec.FLAC, 'flac']; + case AudioCodecId.OPUS: + return [AudioCodec.Opus, 'opus']; + case AudioCodecId.ALAC: + return [AudioCodec.ALAC, 'alac']; + case AudioCodecId.PCM_INT_BIG: + if (bitDepth === 16) { + return [AudioCodec.PCM_S16BE, 'pcm-s16be']; + } + if (bitDepth === 24) { + return [AudioCodec.PCM_S24BE, 'pcm-s24be']; + } + if (bitDepth === 32) { + return [AudioCodec.PCM_S32BE, 'pcm-s32be']; + } + throw new UnsupportCodecError( + `${codecId}(${bitDepth}b)`, + 'web codecs audio decoder' + ); + case AudioCodecId.PCM_INT_LIT: + if (bitDepth === 16) { + return [AudioCodec.PCM_S16LE, 'pcm-s16le']; + } + if (bitDepth === 24) { + return [AudioCodec.PCM_S24LE, 'pcm-s24le']; + } + if (bitDepth === 32) { + return [AudioCodec.PCM_S32LE, 'pcm-s32le']; + } + throw new UnsupportCodecError( + `${codecId}(${bitDepth}b)`, + 'web codecs audio decoder' + ); + case AudioCodecId.PCM_FLOAT_IEEE: + return [AudioCodec.PCM_F32LE, 'pcm-f32le']; + default: + throw new UnsupportCodecError(codecId, 'web codecs audio decoder'); + } +} diff --git a/apps/playground/src/media/mkv/model.ts b/apps/playground/src/media/mkv/model.ts index b2ea239..0017e9d 100644 --- a/apps/playground/src/media/mkv/model.ts +++ b/apps/playground/src/media/mkv/model.ts @@ -8,8 +8,8 @@ import { type EbmlSegmentTagType, EbmlTagIdEnum, EbmlTagPosition, - EbmlTagsTagType, - EbmlTagTagType, + type EbmlTagsTagType, + type EbmlTagTagType, type EbmlTagType, type EbmlTrackEntryTagType, type EbmlTracksTagType, @@ -28,7 +28,7 @@ import { SeekHeadSchema, type SeekHeadType, TagSchema, - TagType, + type TagType, TrackEntrySchema, type TrackEntryType, } from './schema'; diff --git a/biome.jsonc b/biome.jsonc index e02fabf..2e7d645 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -6,6 +6,7 @@ "linter": { "rules": { "style": { + "useSingleCaseStatement": "off", "noParameterProperties": "off", "noNonNullAssertion": "off" }, diff --git a/package.json b/package.json index e159c6a..c3aa175 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "@types/lodash-es": "^4.17.12", "arktype": "^2.1.10", "lodash-es": "^4.17.21", + "media-codecs": "^2.0.2", "mnemonist": "^0.40.3", "rxjs": "^7.8.2", "type-fest": "^4.37.0" diff --git a/packages/codecs/package.json b/packages/codecs/package.json new file mode 100644 index 0000000..7aa368c --- /dev/null +++ b/packages/codecs/package.json @@ -0,0 +1,19 @@ +{ + "name": "konoplayer-codecs", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "build": "rsbuild build", + "dev": "rsbuild dev", + "preview": "rsbuild preview" + }, + "dependencies": { + "konoebml": "0.1.2-rc.5", + "lit": "^3.2.1" + }, + "devDependencies": { + "@rsbuild/core": "^1.2.14", + "typescript": "^5.8.2" + } +} \ No newline at end of file diff --git a/packages/codecs/src/index.ts b/packages/codecs/src/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eb98867..1df4d20 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,9 @@ importers: lodash-es: specifier: ^4.17.21 version: 4.17.21 + media-codecs: + specifier: ^2.0.2 + version: 2.0.2 mnemonist: specifier: ^0.40.3 version: 0.40.3 @@ -108,6 +111,22 @@ importers: specifier: ^2.9.93 version: 2.9.94 + packages/codecs: + dependencies: + konoebml: + specifier: 0.1.2-rc.5 + version: 0.1.2-rc.5(arktype@2.1.10) + lit: + specifier: ^3.2.1 + version: 3.2.1 + devDependencies: + '@rsbuild/core': + specifier: ^1.2.14 + version: 1.2.15 + typescript: + specifier: ^5.8.2 + version: 5.8.2 + packages: '@angular-devkit/core@19.1.8': @@ -1923,6 +1942,10 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + media-codecs@2.0.2: + resolution: {integrity: sha512-D7ygdW7j5yqkDJ9kX5H4UU2iC/fsreU2Vy49GxbN6OUeYso6U2QG6QgcwSn75eFUoM6ttVuTLAOt4qxQvLWdFw==} + engines: {node: '>=22.0.0', npm: '>=10.5.1', snowdev: '>=2.2.x'} + media-typer@0.3.0: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} @@ -4550,6 +4573,8 @@ snapshots: math-intrinsics@1.1.0: {} + media-codecs@2.0.2: {} + media-typer@0.3.0: {} media-typer@1.1.0: {}