jv4-demuxer
Advanced tools
Comparing version 1.0.2 to 1.0.3
{ | ||
"name": "jv4-demuxer", | ||
"version": "1.0.2", | ||
"version": "1.0.3", | ||
"description": "", | ||
@@ -15,4 +15,5 @@ "type": "module", | ||
"afsm": "^2.2.3", | ||
"eventemitter3": "^4.0.7" | ||
"eventemitter3": "^4.0.7", | ||
"oput": "^1.2.2" | ||
} | ||
} |
import { EventEmitter } from "eventemitter3"; | ||
import Oput from "oput"; | ||
export const enum DemuxEvent { | ||
@@ -6,19 +7,40 @@ AUDIO_ENCODER_CONFIG_CHANGED = "audio-encoder-config-changed", | ||
} | ||
export const enum DemuxMode { | ||
PULL, | ||
PUSH | ||
} | ||
export interface Source { | ||
oput?: Oput; | ||
read<T extends number | Uint8Array>(need: T): Promise<Uint8Array>; | ||
} | ||
export abstract class BaseDemuxer extends EventEmitter { | ||
constructor( | ||
public source: { | ||
read<T extends number | Uint8Array>(need: T): Promise<Uint8Array>; | ||
} | ||
public mode: DemuxMode = DemuxMode.PULL, | ||
public source?: Source | ||
) { | ||
super(); | ||
if (source) { | ||
if (mode == DemuxMode.PULL) { | ||
this.startPull(source); | ||
} else { | ||
source.oput = new Oput(this.demux()); | ||
} | ||
} | ||
} | ||
audioReadable: ReadableStream<EncodedAudioChunkInit> = new ReadableStream({ | ||
pull: async (controller) => controller.enqueue(await this.pullAudio()), | ||
}); | ||
videoReadable: ReadableStream<EncodedVideoChunkInit> = new ReadableStream({ | ||
pull: async (controller) => controller.enqueue(await this.pullVideo()), | ||
}); | ||
audioReadable?: ReadableStream<EncodedAudioChunkInit>; | ||
videoReadable?: ReadableStream<EncodedVideoChunkInit>; | ||
audioEncoderConfig?: AudioEncoderConfig; | ||
videoEncoderConfig?: VideoEncoderConfig; | ||
abstract pull(): Promise<void>; | ||
startPull(source: Source) { | ||
this.mode = DemuxMode.PULL; | ||
this.source = source; | ||
this.audioReadable = new ReadableStream({ | ||
pull: async (controller) => controller.enqueue(await this.pullAudio()), | ||
}); | ||
this.videoReadable = new ReadableStream({ | ||
pull: async (controller) => controller.enqueue(await this.pullVideo()), | ||
}); | ||
} | ||
abstract demux(): Generator<number, void, Uint8Array>; | ||
gotAudio?: (data: EncodedAudioChunkInit) => void; | ||
@@ -25,0 +47,0 @@ gotVideo?: (data: EncodedVideoChunkInit) => void; |
199
src/flv.ts
@@ -1,2 +0,2 @@ | ||
import { BaseDemuxer, DemuxEvent } from "./base"; | ||
import { BaseDemuxer, DemuxEvent, DemuxMode } from "./base"; | ||
@@ -13,3 +13,5 @@ export interface FlvTag { | ||
header?: Uint8Array; | ||
async readTag(): Promise<{ | ||
tmp8 = new Uint8Array(4); | ||
dv = new DataView(this.tmp8.buffer); | ||
async pullTag(): Promise<{ | ||
type: number; | ||
@@ -20,24 +22,14 @@ data: Uint8Array; | ||
const t = new Uint8Array(15); //复用15个字节,前面4个字节是上一个tag的长度,跳过 | ||
const tmp8 = new Uint8Array(4); | ||
const dv = new DataView(tmp8.buffer); | ||
this.readTag = async () => { | ||
await this.source.read(t); | ||
this.pullTag = async () => { | ||
await this.source!.read(t); | ||
const type = t[4]; //tag类型,8是音频,9是视频,18是script | ||
tmp8[0] = 0; //首位置空,上一次读取可能会有残留数据 | ||
tmp8.set(t.subarray(5, 8), 1); | ||
const length = dv.getUint32(0); //大端方式读取长度 | ||
tmp8.set(t.subarray(8, 11), 1); | ||
let timestamp = dv.getUint32(0); //大端方式读取时间戳 | ||
if (timestamp === 0xffffff) { | ||
//扩展时间戳 | ||
tmp8[0] = t[11]; //最高位 | ||
timestamp = dv.getUint32(0); | ||
} | ||
const data = await this.source.read(length); | ||
const length = this.readLength(t.subarray(5, 8)); | ||
const timestamp = this.readTimestamp(t.subarray(8, 11)); | ||
const data = await this.source!.read(length); | ||
return { type, data: data.slice(), timestamp }; | ||
}; | ||
console.time("flv"); | ||
await this.source.read(9).then((data) => { | ||
await this.source!.read(9).then((data) => { | ||
this.header = data; | ||
console.log(data) | ||
console.log(data); | ||
if ( | ||
@@ -52,67 +44,120 @@ data[0] != "F".charCodeAt(0) || | ||
}); | ||
return this.readTag(); | ||
return this.pullTag(); | ||
} | ||
async pull(): Promise<void> { | ||
const value = await this.readTag(); | ||
if (value) { | ||
switch (value.type) { | ||
case 8: | ||
if (!this.audioEncoderConfig) { | ||
this.audioEncoderConfig = { | ||
codec: | ||
{ 10: "aac", 7: "pcma", 8: "pcmu" }[value.data[0] >> 4] || | ||
"unknown", | ||
numberOfChannels: 1, | ||
sampleRate: 44100, | ||
}; | ||
//TODO: parse audio config | ||
if (this.audioEncoderConfig.codec == "aac") { | ||
} | ||
} | ||
readTag(data: Uint8Array) { | ||
const type = data[0]; //tag类型,8是音频,9是视频,18是script | ||
const length = this.readLength(data.subarray(1, 4)); | ||
const timestamp = this.readTimestamp(data.subarray(4, 8)); | ||
this.gotTag(type, data.subarray(11, 11 + length), timestamp); | ||
} | ||
gotTag(type: number, data: Uint8Array, timestamp: number) { | ||
switch (type) { | ||
case 8: | ||
if (!this.audioEncoderConfig) { | ||
this.audioEncoderConfig = { | ||
codec: | ||
{ 10: "aac", 7: "pcma", 8: "pcmu" }[data[0] >> 4] || | ||
"unknown", | ||
numberOfChannels: 1, | ||
sampleRate: 44100, | ||
}; | ||
//TODO: parse audio config | ||
if (this.audioEncoderConfig.codec == "aac") { | ||
if (value.data[1] == 0x00) { | ||
this.emit( | ||
DemuxEvent.AUDIO_ENCODER_CONFIG_CHANGED, | ||
value.data.subarray(2) | ||
); | ||
return this.pull(); | ||
} | ||
} | ||
return this.gotAudio!({ | ||
type: "key", | ||
data: | ||
this.audioEncoderConfig.codec == "aac" | ||
? value.data.subarray(2) | ||
: value.data.subarray(1), | ||
timestamp: value.timestamp, | ||
duration: 0, | ||
}); | ||
case 9: | ||
if (!this.videoEncoderConfig) { | ||
this.videoEncoderConfig = { | ||
codec: | ||
{ 7: "avc", 12: "hevc" }[value.data[0] & 0xf] || "unknown", | ||
width: 0, | ||
height: 0, | ||
}; | ||
//TODO: parse video config | ||
} | ||
if (value.data[1] == 0x00) { | ||
this.emit( | ||
DemuxEvent.VIDEO_ENCODER_CONFIG_CHANGED, | ||
value.data.subarray(5) | ||
); | ||
} | ||
if (this.audioEncoderConfig.codec == "aac" && data[1] == 0x00) { | ||
this.emit( | ||
DemuxEvent.AUDIO_ENCODER_CONFIG_CHANGED, | ||
data.subarray(2) | ||
); | ||
if (this.mode == DemuxMode.PULL) | ||
return this.pull(); | ||
} | ||
return this.gotVideo!({ | ||
type: value.data[0] >> 4 == 1 ? "key" : "delta", | ||
data: value.data.subarray(5), | ||
timestamp: value.timestamp, | ||
duration: 0, | ||
}); | ||
default: | ||
else return; | ||
} | ||
return this.gotAudio?.({ | ||
type: "key", | ||
data: | ||
this.audioEncoderConfig.codec == "aac" | ||
? data.subarray(2) | ||
: data.subarray(1), | ||
timestamp: timestamp, | ||
duration: 0, | ||
}); | ||
case 9: | ||
if (!this.videoEncoderConfig) { | ||
this.videoEncoderConfig = { | ||
codec: | ||
{ 7: "avc", 12: "hevc" }[data[0] & 0xf] || "unknown", | ||
width: 0, | ||
height: 0, | ||
}; | ||
//TODO: parse video config | ||
} | ||
if (data[1] == 0x00) { | ||
this.emit( | ||
DemuxEvent.VIDEO_ENCODER_CONFIG_CHANGED, | ||
data.subarray(5) | ||
); | ||
if (this.mode == DemuxMode.PULL) | ||
return this.pull(); | ||
else return; | ||
} | ||
return this.gotVideo?.({ | ||
type: data[0] >> 4 == 1 ? "key" : "delta", | ||
data: data.subarray(5), | ||
timestamp: timestamp, | ||
duration: 0, | ||
}); | ||
default: | ||
if (this.mode == DemuxMode.PULL) | ||
return this.pull(); | ||
} | ||
} | ||
} | ||
async pull(): Promise<void> { | ||
const value = await this.pullTag(); | ||
if (value) { | ||
return this.gotTag(value.type, value.data, value.timestamp); | ||
} | ||
} | ||
readLength(data: Uint8Array) { | ||
this.tmp8[0] = 0; //首位置空,上一次读取可能会有残留数据 | ||
this.tmp8.set(data, 1); | ||
return this.dv.getUint32(0); //大端方式读取长度 | ||
} | ||
readTimestamp(data: Uint8Array) { | ||
this.tmp8.set(data.subarray(0, 3), 1); | ||
let timestamp = this.dv.getUint32(0); //大端方式读取时间戳 | ||
if (timestamp === 0xffffff) { | ||
//扩展时间戳 | ||
this.tmp8[0] = data[3]; //最高位 | ||
timestamp = this.dv.getUint32(0); | ||
} | ||
return timestamp; | ||
} | ||
readHead(data: Uint8Array) { | ||
console.time("flv"); | ||
this.header = data; | ||
console.log(data); | ||
if ( | ||
data[0] != "F".charCodeAt(0) || | ||
data[1] != "L".charCodeAt(0) || | ||
data[2] != "V".charCodeAt(0) | ||
) { | ||
throw new Error("not flv"); | ||
} | ||
console.timeEnd("flv"); | ||
} | ||
*demux(): Generator<number, void, Uint8Array> { | ||
this.readHead(yield 13); | ||
while (true) { | ||
let data = yield 11; | ||
const type = data[0]; //tag类型,8是音频,9是视频,18是script | ||
const length = this.readLength(data.subarray(1, 4)); | ||
const timestamp = this.readTimestamp(data.subarray(4, 8)); | ||
data = yield length; | ||
this.gotTag(type, data.slice(), timestamp); | ||
yield 4; | ||
} | ||
} | ||
} |
8098
221
3
+ Addedoput@^1.2.2
+ Addedoput@1.2.2(transitive)