UNPKG

node-media-server

Version:

A Node.js implementation of RTMP Server

313 lines (287 loc) 9.7 kB
// @ts-check // // Created by Chen Mingliang on 23/12/01. // illuspas@msn.com // Copyright (c) 2023 Nodemedia. All rights reserved. // const AMF = require("./amf.js"); const logger = require("../core/logger.js"); const AVPacket = require("../core/avpacket.js"); const FLV_MEDIA_TYPE_AUDIO = 8; const FLV_MEDIA_TYPE_VIDEO = 9; const FLV_MEDIA_TYPE_SCRIPT = 18; const FLV_PARSE_INIT = 0; const FLV_PARSE_HEAD = 1; const FLV_PARSE_TAGS = 2; const FLV_PARSE_PREV = 3; const FLV_FRAME_KEY = 1;///< key frame (for AVC, a seekable frame) const FLV_FRAME_INTER = 2; ///< inter frame (for AVC, a non-seekable frame) const FLV_FRAME_DISP_INTER = 3; ///< disposable inter frame (H.263 only) const FLV_FRAME_GENERATED_KEY = 4;///< generated key frame (reserved for server use only) const FLV_FRAME_VIDEO_INFO_CMD = 5; ///< video info/command frame const FLV_AVC_SEQUENCE_HEADER = 0; const FLV_AVC_NALU = 1; const FLV_AVC_END_OF_SEQUENCE = 2; const FLV_CODECID_PCM = 0; const FLV_CODECID_ADPCM = 1; const FLV_CODECID_MP3 = 2; const FLV_CODECID_PCM_LE = 3; const FLV_CODECID_NELLYMOSER_16KHZ_MONO = 4; const FLV_CODECID_NELLYMOSER_8KHZ_MONO = 5; const FLV_CODECID_NELLYMOSER = 6; const FLV_CODECID_PCM_ALAW = 7; const FLV_CODECID_PCM_MULAW = 8; const FLV_CODECID_ExHeader = 9; const FLV_CODECID_AAC = 10; const FLV_CODECID_SPEEX = 11; const FLV_CODECID_H263 = 2; const FLV_CODECID_SCREEN = 3; const FLV_CODECID_VP6 = 4; const FLV_CODECID_VP6A = 5; const FLV_CODECID_SCREEN2 = 6; const FLV_CODECID_H264 = 7; const FLV_CODECID_REALH263 = 8; const FLV_CODECID_MPEG4 = 9; const FOURCC_AV1 = Buffer.from("av01"); const FOURCC_VP9 = Buffer.from("vp09"); const FOURCC_HEVC = Buffer.from("hvc1"); const FOURCC_AC3 = Buffer.from("ac-3"); const FOURCC_EAC3 = Buffer.from("ec-3"); const FOURCC_OPUS = Buffer.from("Opus"); const FOURCC_MP3 = Buffer.from(".mp3"); const FOURCC_FLAC = Buffer.from("fLaC"); const FOURCC_AAC = Buffer.from("mp4a"); const VideoPacketTypeSequenceStart = 0; const VideoPacketTypeCodedFrames = 1; const VideoPacketTypeSequenceEnd = 2; const VideoPacketTypeCodedFramesX = 3; const VideoPacketTypeMetadata = 4; const VideoPacketTypeMPEG2TSSequenceStart = 5; const AudioPacketTypeSequenceStart = 0; const AudioPacketTypeCodedFrames = 1; const AudioPacketTypeSequenceEnd = 2; const AudioPacketTypeMultichannelConfig = 4; const AudioPacketTypeMultitrack = 5; const AudioPacketTypModEx = 7; /** * @class */ class Flv { constructor() { this.parserBuffer = Buffer.alloc(13); this.parserState = FLV_PARSE_INIT; this.parserHeaderBytes = 0; this.parserTagBytes = 0; this.parserTagType = 0; this.parserTagSize = 0; this.parserTagTime = 0; this.parserTagCapacity = 1024 * 1024; this.parserTagData = Buffer.alloc(this.parserTagCapacity); this.parserPreviousBytes = 0; } /** * @abstract * @param {AVPacket} avpacket */ onPacketCallback = (avpacket) => { }; /** * @param {Buffer} buffer * @returns {string | null} error */ parserData = (buffer) => { let s = buffer.length; let n = 0; let p = 0; while (s > 0) { switch (this.parserState) { case FLV_PARSE_INIT: n = 13 - this.parserHeaderBytes; n = n <= s ? n : s; buffer.copy(this.parserBuffer, this.parserHeaderBytes, p, p + n); this.parserHeaderBytes += n; s -= n; p += n; if (this.parserHeaderBytes === 13) { this.parserState = FLV_PARSE_HEAD; this.parserHeaderBytes = 0; } break; case FLV_PARSE_HEAD: n = 11 - this.parserHeaderBytes; n = n <= s ? n : s; buffer.copy(this.parserBuffer, this.parserHeaderBytes, p, p + n); this.parserHeaderBytes += n; s -= n; p += n; if (this.parserHeaderBytes === 11) { this.parserState = FLV_PARSE_TAGS; this.parserHeaderBytes = 0; this.parserTagType = this.parserBuffer[0]; this.parserTagSize = this.parserBuffer.readUintBE(1, 3); this.parserTagTime = (this.parserBuffer[4] << 16) | (this.parserBuffer[5] << 8) | this.parserBuffer[6] | (this.parserBuffer[7] << 24); logger.trace(`parser tag type=${this.parserTagType} time=${this.parserTagTime} size=${this.parserTagSize} `); } break; case FLV_PARSE_TAGS: this.parserTagAlloc(this.parserTagSize); n = this.parserTagSize - this.parserTagBytes; n = n <= s ? n : s; buffer.copy(this.parserTagData, this.parserTagBytes, p, p + n); this.parserTagBytes += n; s -= n; p += n; if (this.parserTagBytes === this.parserTagSize) { this.parserState = FLV_PARSE_PREV; this.parserTagBytes = 0; } break; case FLV_PARSE_PREV: n = 4 - this.parserPreviousBytes; n = n <= s ? n : s; buffer.copy(this.parserBuffer, this.parserPreviousBytes, p, p + n); this.parserPreviousBytes += n; s -= n; p += n; if (this.parserPreviousBytes === 4) { this.parserState = FLV_PARSE_HEAD; this.parserPreviousBytes = 0; const parserPreviousNSize = this.parserBuffer.readUint32BE(); if (parserPreviousNSize === this.parserTagSize + 11) { let packet = Flv.parserTag(this.parserTagType, this.parserTagTime, this.parserTagSize, this.parserTagData); this.onPacketCallback(packet); } else { return "flv tag parser error"; } } break; } } return null; }; /** * @param {number} size */ parserTagAlloc = (size) => { if (this.parserTagCapacity < size) { this.parserTagCapacity = size * 2; const newBuffer = Buffer.alloc(this.parserTagCapacity); this.parserTagData.copy(newBuffer); this.parserTagData = newBuffer; } }; /** * @param {boolean} hasAudio * @param {boolean} hasVideo * @returns {Buffer} */ static createHeader = (hasAudio, hasVideo) => { const buffer = Buffer.from([0x46, 0x4c, 0x56, 0x01, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00]); if (hasAudio) { buffer[4] |= 4; } if (hasVideo) { buffer[4] |= 1; } return buffer; }; /** * @param {AVPacket} avpacket * @returns {Buffer} */ static createMessage = (avpacket) => { const buffer = Buffer.alloc(11 + avpacket.size + 4); buffer[0] = avpacket.codec_type; buffer.writeUintBE(avpacket.size, 1, 3); buffer[4] = (avpacket.dts >> 16) & 0xFF; buffer[5] = (avpacket.dts >> 8) & 0xFF; buffer[6] = avpacket.dts & 0xFF; buffer[7] = (avpacket.dts >> 24) & 0xFF; avpacket.data.copy(buffer, 11, 0, avpacket.size); buffer.writeUint32BE(11 + avpacket.size, 11 + avpacket.size); return buffer; }; /** * @param {number} type * @param {number} time * @param {number} size * @param {Buffer} data * @returns {AVPacket} */ static parserTag = (type, time, size, data) => { let packet = new AVPacket(); packet.codec_type = type; packet.pts = time; packet.dts = time; packet.size = size; packet.data = data; if (type === FLV_MEDIA_TYPE_AUDIO) { const soundFormat = data[0] >> 4; packet.codec_id = soundFormat; packet.flags = 1; if (soundFormat !== FLV_CODECID_ExHeader) { if (soundFormat === FLV_CODECID_AAC) { if (data[1] === 0) { packet.flags = 0; } } } else { const audioPacketType = data[0] & 0x0f; if(audioPacketType === AudioPacketTypeSequenceStart) { packet.flags = 0; } } } else if (type === FLV_MEDIA_TYPE_VIDEO) { const frameType = data[0] >> 4 & 0b0111; const codecID = data[0] & 0x0f; const isExHeader = (data[0] >> 4 & 0b1000) !== 0; if (isExHeader) { const VideoPacketType = data[0] & 0x0f; const fourCC = data.subarray(1, 5); if (fourCC.compare(FOURCC_AV1) === 0 || fourCC.compare(FOURCC_VP9) === 0 || fourCC.compare(FOURCC_HEVC) === 0) { packet.codec_id = fourCC.readUint32BE(); if (VideoPacketType === VideoPacketTypeSequenceStart) { packet.flags = 2; } else if (VideoPacketType === VideoPacketTypeCodedFrames || VideoPacketType === VideoPacketTypeCodedFramesX) { if (frameType === FLV_FRAME_KEY) { packet.flags = 3; } else { packet.flags = 4; } } else if (VideoPacketType === VideoPacketTypeMetadata) { // const hdrMetadata = AMF.parseScriptData(packet.data.buffer, 5, packet.size); // logger.debug(`hdrMetadata:${JSON.stringify(hdrMetadata)}`); packet.flags = 6; } if (fourCC.compare(FOURCC_HEVC) === 0) { if (VideoPacketType === VideoPacketTypeCodedFrames) { const cts = data.readUintBE(5, 3); packet.pts = packet.dts + cts; } } } } else { const cts = data.readUintBE(2, 3); const VideoPacketType = data[1]; packet.codec_id = codecID; packet.pts = packet.dts + cts; packet.flags = 4; if (codecID === FLV_CODECID_H264) { if (VideoPacketType === FLV_AVC_SEQUENCE_HEADER) { packet.flags = 2; } else { if (frameType === FLV_FRAME_KEY) { packet.flags = 3; } else { packet.flags = 4; } } } } } else if (type === FLV_MEDIA_TYPE_SCRIPT) { packet.flags = 5; } return packet; }; } module.exports = Flv;