js对flv提取h264、aac音视频流
FLV提取里面的h264视频流
FLV和MP4支持的编码

流媒体和媒体文件的区别
流媒体是指将一连串的多媒体资料压缩后,经过互联网分段发送资料,在互联网上即时传输影音以供观赏的一种技术与过程,此技术使得资料数据包得以像流水一样发送,如果不使用此技术,就必须在使用前下载整个媒体文件。flv属于流媒体格式,所以很适合做低延时的直播
对比hls和mp4
相对于mp4,flv更加灵活体积更小,mp4不是流媒体需要索引表才可以正常播放
相对于hls,flv可以做到延时更低,因为hls需要发起多次http短连接请求播放,而flv可以通过http长连接结合ReadableStream做到更小切片的播放。
** ps:下面的图片很多是采用别人的,我也忘记备注来源了 **
1.flv的协议结构
FLV文件由FLV header和FLV body组成,FLV body由一系列的FLV tags组成,如下图所示:

tag又可以分成三类:audio,video,script,分别代表音频流,视频流,脚本流,而每个tag又由tag header和tag data组成。每个Tag前面还包含了Previous Tag Size字段,表示前面一个Tag的大小。整个FLV文件的详细的组成如下图所示:

下面是一个flv视频的hex编码:
flv header

这里前面9个字节为flv的header
0x46:ASCII编码里的"F"
0x4c: ASCII编码里的"L"
0x56: ASCII编码里的"V"
0x01: FLV的版本号
0x05: 对应二进制为0000 0101,意思为包含视频和音频
0x 00 00 00 09: 表示flv body的起始字节位置
flv的body:

****这里flv body的前4个字节总是0
tag的结构
tag Header

type:0x12 (18为元数据tag,9为视频tag,8为音频tag,占1个字节)
dataSize:0x0001AC = 428 (tagbody的长度,占3个字节)
timeStamp: 0x00000000 = 0 (tag对应的时间戳,其中最后一个字节代表高位,一共4个字节)
streamId:0x000000 = 0 (一直为0)
tag Data
video data构成

视频Tag也用开始的第1个字节包含视频数据的参数信息,从第2个字节为视频流数据。结构如下图所示

第1个字节的前4位表示帧类型,各个取值的含义如下:

后4位表示视频编码类型,各个取值的含义如下:

| 字节位置 | 描述 |
|---|---|
| 1 | 视频参数信息,帧类型和编码类型(如上图) |
| 2 | 该video tag data的类型,0为AVC packet type, 1为NALU,这里AVC packet type包含了该视频下面的一下公共信息,NALU则是h264的基本构成。2为结束标志 |
| 3~5 | composition time,AVC时,全0,无意义 |
看下截图的数据:
0x17:1-keyframe 7-avc
0x00:AVC sequence header -- AVC packet type
0x000000: composition time,AVC时,全0,无意义
- AVC sequence header数据结构(video data第6个字节开始)
| 字节位置 | 描述 | 截图数据 |
|---|---|---|
| 6 | configurationVersion 配置版本 | 0x01 |
| 7 | AVCProfileIndication AVC配置文件指示 | 0x64 |
| 8 | profileCompatibility 配置文件兼容性 | 0x00 |
| 9 | AVCLevelIndication AVC级别 | 0x1e |
| 10 | lengthSizeMinusOne FLV中NALU包长数据所使用的字节数,(lengthSizeMinusOne & 3)+1,实际测试时发现总为ff,计算结果为4 | (0xff & 3) + 1 = 4 |
| 11 | numOfSequenceParameterSets (E1 -- SPS 的个数,numOfSequenceParameterSets & 0x1F) | 0xe1 & 0x1f = 1 |
| 12~13 | sequenceParameterSetLength SPS 的长度,2个字节 | 0x001a=26 |
| 14~14+sequenceParameterSetLength | SPS 数据 | 0x27 ... 0x92 |
| 14+sequenceParameterSetLength+1 | PPS 的个数,实际测试时发现总为01 | 0x01 |
| 14+sequenceParameterSetLength+2 | pictureParameterSetLength PPS 的长度 | 0x0004=4 |
| 14+sequenceParameterSetLength+3 ~ dataEnd | PPS 数据 | 0x28ee3cb0 |
分析截图数据中,比较重要的只有lengthSizeMinusOne = 4字节,这里需要存起来因为下面的NALU解析时需要用到。
- NALU数据结构
NALU的小知识

| 类型 | 描述 |
|---|---|
| SPS | 序列参数集,SPS中保存了⼀组编码视频序列(Coded video sequence)的全局参数 |
| PPS | 图像参数集,对应的是⼀个序列中某⼀幅图像或者某⼏幅图像的参数 |
| I帧 | 帧内编码帧,可独⽴解码⽣成完整的图⽚ |
| P帧 | 前向预测编码帧,需要参考其前⾯的⼀个I 或者B 来⽣成⼀张完整的图⽚ |
| B帧 | 双向预测内插编码帧,则要参考其前⼀个I或者P帧及其后⾯的⼀个P帧来⽣成⼀张完整的图⽚ |
下面是第二个video tag的截图:

第二个字节为0x01,说明下面是NALU包,一个tag可以包含多个NALU(h264的NALU之间需要用0X000000或0x00000000作为间隔,不过flv内是不包含的)
第3~5字节为composition time,可以忽略不记
所以由第6个字节开始,从第一个video tag的AVC sequence header可以得知每个NALU的数据长度由起始的4个字节描述。
所以第一个NALU的数据长度为:0x0000001A = 26byte
数据为:0x276400 ... 92
这里其中第一个字节的前5位为该NAL包的类型
0x27 & 0x1f = 7
NAl的类型对照表:
#define NALU_TYPE_SLICE 1
#define NALU_TYPE_DPA 2
#define NALU_TYPE_DPB 3
#define NALU_TYPE_DPC 4
#define NALU_TYPE_IDR 5
#define NALU_TYPE_SEI 6
#define NALU_TYPE_SPS 7
#define NALU_TYPE_PPS 8
#define NALU_TYPE_AUD 9
#define NALU_TYPE_EOSEQ 10
#define NALU_TYPE_EOSTREAM 11
#define NALU_TYPE_FILL 12
一个NALU结束后的4个字节为下个NALU的长度,以此下去。
代码实现抽取NALU:
uint8Array // 以获得的flv数据,下面只是针对video tag的解析,不是完整代码
let idx
dataLeng = (uint8Array[idx++] << 0x10) + (uint8Array[idx++] << 0x08) + uint8Array[idx++];
timeStamp = (uint8Array[idx + 3] << 24) + (uint8Array[idx++] << 16) + (uint8Array[idx++] << 8) + uint8Array[idx++]
idx+= (1 + 3)
const dataStartIdx = idx // data起始idx
videoTotalTime += timeStamp
const isIKeyframe = (uint8Array[idx] & 0xf0) === 16 // 是否为关键帧
const codeId = (uint8Array[idx++] & 0x0f) // 视频编码类型(7为avc)
const isAVCSequenceHeader = uint8Array[idx++] === 0 // 是否为avc头部,只有一个
if (isAVCSequenceHeader) {
const compositionTime = 0 // AVC时,全0,无意义(直接跳过3个字节)
idx+=3
const configurationVersion = uint8Array[idx++] // 配置版本
const AVCProfileIndication = uint8Array[idx++] // AVC配置文件指示
const profileCompatibility = uint8Array[idx++] // 配置文件兼容性
const AVCLevelIndication = uint8Array[idx++] // AVC等级指示
const lengthSizeMinusOne = (uint8Array[idx++] & 3) + 1 //FLV中NALU包长数据所使用的字节数,(lengthSizeMinusOne & 3)+1,实际测试时发现总为ff,计算结果为4
const numOfSequenceParameterSets = uint8Array[idx++] & 0x1f // 01 -- SPS 的个数,numOfSequenceParameterSets & 0x1F
const sequenceParameterSetLength = (uint8Array[idx++] << 8) + uint8Array[idx++] // SPS 的长度,2个字节
videoArr.push(this.concatenate(Uint8Array, [new Uint8Array([0,0,0,1]), uint8Array.slice(idx, idx + sequenceParameterSetLength)]))
idx += sequenceParameterSetLength
const numOfPictureParameterSets = uint8Array[idx++] // PPS 的个数,实际测试时发现总为E1
const pictureParameterSetLength = (uint8Array[idx++] << 8) + uint8Array[idx++] // PPS 的长度
videoArr.push(this.concatenate(Uint8Array, [new Uint8Array([0,0,0,1]), uint8Array.slice(idx, idx + pictureParameterSetLength)]))
idx += pictureParameterSetLength
videoConfig = {
compositionTime,
configurationVersion,
AVCProfileIndication,
profileCompatibility,
AVCLevelIndication,
lengthSizeMinusOne,
}
} else { // 非头部tag
const compositionTime = (uint8Array[idx++] << 16) + (uint8Array[idx++] << 8) + uint8Array[idx++]
// header得到的lengthSizeMinusOne
while(dataLeng + dataStartIdx > idx) {
let i = 1
let naluLength = 0
while(i <= videoConfig.lengthSizeMinusOne) {
naluLength += (uint8Array[idx++] << ((videoConfig.lengthSizeMinusOne - i) * 8))
i++
}
videoArr.push(this.concatenate(Uint8Array, [new Uint8Array([0,0,0,1]), uint8Array.slice(idx, idx + naluLength)]))
idx += naluLength
}
}
idx += 4 // preTagSize
audio data构成


前两个字节为公共头部
| 字节位置 | 描述 |
|---|---|
| 1 | 音频参数 |
| 2 | AACPacketType 0为AudioSpecificConfig, 1为AACframeData |
音频参数数据结构
| 位 | 描述 | 截图数据分析 |
|---|---|---|
| 1~4 | format编码类型 | 0xAF&0xF0=10 |
| 5~6 | rate采样率 | (0xAF&0x0c)>>2=3 |
| 7 | sampleSize采样精度 | (0xAF & 0x02) >> 1=1 |
| 8 | audiotype音频类型 | 0xAF&0x01=1 |




第二个字节为0x00,所以下面为AudioSpecificConfig数据,因为AudioSpecificConfig只出现一次,所以需要记录起来。
AudioSpecificConfig的数据可以由第3、4个字节获取。
具体数据结构如下:
| 位 | 字段 | 描述 |
|---|---|---|
| 1~5 | audioObjectType | 编码结构类型 |
| 6~9 | samplingFrequencyIndex | 音频采样率索引值,44100对应值4 |
| 10~13 | channelConfiguration | 音频输出声道 |
| 14 | frameLengthFlag | 标志位,用于表明IMDCT窗口长度,0 |
| 15 | dependsOnCoreCoder | 标志位,表明是否依赖于corecoder,0 |
| 16 | extensionFlag | 延时标志位 |
- flv存储的AAC数据为AAC为es数据流,不能直接播放,如果想要播放需要在每个es流前面加上ADTS头部,所以一个完整可播放的AAC为:

这里ADTS由adts_fixed_header和adts_variable_header组成
其一为固定头信息,紧接着是可变头信息。固定头信息中的数据每一帧都相同,而可变头信息则在帧与帧之间可变
adts_fixed_header:
| 字段 | 描述 | 长度(bits) |
|---|---|---|
| syncword | 同步头 总是0xFFF, all bits must be 1,代表着一个ADTS帧的开始 | 12 |
| ID | MPEG标识符,0标识MPEG-4,1标识MPEG-2 | 1 |
| Layer | always: '00' | 2 |
| protection_absent | 表示是否误码校验。Warning, set to 1 if there is no CRC and 0 if there is CRC | 1 |
| profile | 表示使用哪个级别的AAC,如01 Low Complexity(LC)--- AAC LC。有些芯片只支持AAC LC,值等于 Audio Object Type的值减1 | 2 |
| sampling_frequency_index | 表示使用的采样率下标 | 4 |
| private bit | 0 | 1 |
| channel_configuration | 表示声道数,比如2表示立体声双声道 | 3 |
| original | 0 | 1 |
| home | 0 | 1 |
adts_variable_header:
| 字段 | 描述 | 长度(bits) |
|---|---|---|
| copyright_id_bit | 0 | 1 |
| copyright_id_start | 0 | 1 |
| aac_frame_length | 一个ADTS帧的长度包括ADTS头和AAC原始流 | 13 |
| adts_buffer_fullness | 0x7FF 说明是码率可变的码流 | 11 |
| number_of_raw_data_blocks_in_frame | 00 | 2 |
第二个audio data里的AACPacketType都会为1,所以只要便利所有的audio tag,给每个es流前面加上ADTS头部就可以了
实现代码:
uint8Array // 以获得的flv数据,下面只是针对audio tag的解析,不是完整代码
let idx
dataLeng = (uint8Array[idx++] << 0x10) + (uint8Array[idx++] << 0x08) + uint8Array[idx++];
timeStamp = (uint8Array[idx + 3] << 24) + (uint8Array[idx++] << 16) + (uint8Array[idx++] << 8) + uint8Array[idx++]
idx += (1 + 3)
const audioDataEndIdx = idx + dataLeng
const info = uint8Array[idx++]
const format = info & 0xF0 // 编码类型
const rate = (info & 0x0c) >> 2 // 采样率
const sampleSize = (info & 0x02) >> 1 // 采样精度
const audiotype = (info & 0x01) // 音频类型
const isAudioSpecificConfig = !uint8Array[idx++]
if (isAudioSpecificConfig) {
audioSpecificConfig = this.getAudioSpecificConfig(uint8Array[idx++], uint8Array[idx++])
idx = audioDataEndIdx
} else {
const adtsLen = dataLeng - 2 + 7
let ADTS = new Uint8Array(7)
ADTS[0] = 0xff // syncword:0xfff 高8bits
ADTS[1] = 0xf0 // syncword:0xfff 低4bits
ADTS[1] |= (0 << 3) // MPEG Version:0 for MPEG-4,1 for MPEG-2 1bit
ADTS[1] |= (0 << 1) // Layer:0 2bits
ADTS[1] |= 1 // protection absent:1 1bit
ADTS[2] = (audioSpecificConfig.audioObjectType - 1) << 6 // profile:audio_object_type - 1 2bits
ADTS[2] |= (audioSpecificConfig.samplingFrequencyIndex & 0x0f) << 2 // sampling frequency index:sampling_frequency_index 4bits
ADTS[2] |= (0 << 1) // private bit:0 1bit
ADTS[2] |= (audioSpecificConfig.channelConfiguration & 0x04) >> 2 // channel configuration:channel_config 高1bit
ADTS[3] = (audioSpecificConfig.channelConfiguration & 0x03) << 6 // channel configuration:channel_config 低2bits
ADTS[3] |= (0 << 5) // original:0 1bit
ADTS[3] |= (0 << 4) // home:0 1bit
ADTS[3] |= (0 << 3) // copyright id bit:0 1bit
ADTS[3] |= (0 << 2) // copyright id start:0 1bit
ADTS[3] |= (adtsLen & 0x1800) >> 11 // frame length:value 高2bits
ADTS[4] = (adtsLen & 0x7f8) >> 3 // frame length:value 中间8bits
ADTS[5] = (adtsLen & 0x7) << 5 // frame length:value 低3bits
ADTS[5] |= 0x1f // buffer fullness:0x7ff 高5bits
ADTS[6] = 0xfc
audioArr.push(this.concatenate(Uint8Array, [ADTS, uint8Array.slice(idx, audioDataEndIdx)]))
idx = audioDataEndIdx
}
idx += 4
Metadata Tag
主要是描述该flv的信息,例如宽高,时长等等。所处位置为第一个tag
播放h264和aac

Fragmented MP4文件格式
在Fragmented MP4文件中都有三个非常关键的boxes:‘moov’、‘moof’和‘mdat’。
(1)‘moov’(movie metadata box)
和普通MP4文件的‘moov’一样,包含了file-level的metadata信息,用来描述file。
(2)‘mdat’(media data box)
和普通MP4文件的‘mdat’一样,用于存放媒体数据,不同的是普通MP4文件只有一个‘mdat’box,而Fragmented MP4文件中,每个fragment都会有一个‘mdat’类型的box。
(3)‘moof’(movie fragment box)
该类型的box存放的是fragment-level的metadata信息,用于描述所在的fragment。该类型的box在普通的MP4文件中是不存在的,而在Fragmented MP4文件中,每个fragment都会有一个‘moof’类型的box。
一个‘moof’和一个‘mdat’组成Fragmented MP4文件的一个fragment,这个fragment包含一个video track或audio track,并且包含足够的metadata以保证这部分数据可以单独解码
js对flv提取h264、aac音视频流的更多相关文章
- FLV提取AAC音频单独播放并实现可视化的频谱
如上图,要实现对FLV直播流中音频的识别,并展示成一个音频相关的动态频谱. 一. 首先了解下什么是声音? 能量波,有频率有振幅,频率高低就是音调,振幅大小就是音量:采样率是对频率采样,采样精度是对幅度 ...
- FFmpeg命令行map参数选择音视频流
FFmpeg命令行map参数选择音视频流 介绍 -map参数告诉ffmpeg要从输入源中选择/拷贝哪个stream流到输出,可以从输入源中选择多个音视频流作为输出. 不加-map参数,ffmpeg默认 ...
- Realsense 提取彩色和深度视频流
一.简要介绍 关于realsense的介绍,网上很多,这里不再赘述,sdk及相关文档可参考realsense SDK,也可参考开发人员专区. 运行代码之前,要确保你已经安装好了realsense的DC ...
- ps流提取H264并解码播放
因为需要从海康ps流中提取H264数据并进行解码播放,才有了这篇文章.因为是视频编解码领域的纯入门新手,个别理解或者方法有误,需要自行判断,不过相关方法已经测试通过,对于 像我这样的新手还是有一定的借 ...
- [转]【流媒體】H264—MP4格式及在MP4文件中提取H264的SPS、PPS及码流
[流媒體]H264—MP4格式及在MP4文件中提取H264的SPS.PPS及码流 SkySeraph Apr 1st 2012 Email:skyseraph00@163.com 一.MP4格式基本 ...
- send/receive h264/aac file/data by rtp/rtsp over udp/tcp
一.安装一些必要的调试工具 1.vlc安装sudo apt-get install vlcsudo apt-get install vlc-nox 2.ffmpeg安装,带ffplay,ffplay依 ...
- JSFinder:一个在js文件中提取URL和子域名的脚本
JSFinder介绍 JSFinder是一款用作快速在网站的js文件中提取URL,子域名的脚本工具. 支持用法 简单爬取 深度爬取 批量指定URL/指定JS 其他参数 以往我们子域名多数使用爆破或DN ...
- librtmp接收flv流中提取h264码流:根据多个资料汇总
rtmpdump可以下载rtmp流并保存成flv文件.如果要对流中的音频或视频单独处理,需要根据flv协议分别提取.简单修改rtmpdump代码,增加相应功能.1 提取音频:rtmpdump程序在Do ...
- h264 aac 封装 flv
Part 1flvtag组成 FLV 文件结构由 FLVheader和FLVBody组成.(注意flv文件是大端格式的)FLV头组成(以c为例子,一字节对齐):FLVBody是由若干个Tag组成的: ...
随机推荐
- Java基础教程——字符流
字符流 字节流服务文本文件时,可能出现中文乱码.因为一个中文字符可能占用多个字节. 针对于非英语系的国家和地区,提供了一套方便读写方式--字符流. java.io.Reader java.io.Wri ...
- zk可用性测试
1.首先起3个zk: 2.观察主从情况: 3.连接集群观察心跳: 4.kill掉master: 可以看到客户端在重试几次后链接到了新的master,且seesionid没有改变. 5.观察现在的主从: ...
- LeetCode 030 Substring with Concatenation of All Words
题目要求:Substring with Concatenation of All Words You are given a string, S, and a list of words, L, th ...
- Django的静态文件的配置
静态文件配置 STATIC_URL = '/static/' # 静态文件配置 STATICFILES_DIRS = [ os.path.join(BASE_DIR,'static') ] # 暴露给 ...
- 冲刺随笔——Day_Ten
这个作业属于哪个课程 软件工程 (福州大学至诚学院 - 计算机工程系) 这个作业要求在哪里 团队作业第五次--Alpha冲刺 这个作业的目标 团队进行Alpha冲刺 作业正文 正文 其他参考文献 无 ...
- Spring Boot 统一返回结果及异常处理
在 Spring Boot 构建电商基础秒杀项目 (三) 通用的返回对象 & 异常处理 基础上优化.调整 一.通用类 1.1 通用的返回对象 public class CommonReturn ...
- PyQt学习随笔:Model/View中TableView视图数据项编辑结果及视图数据项的访问
按照<PyQt学习随笔:Model/View中设置视图数据项可编辑的方法>的方法支持视图数据可编辑后,编辑后的数据无需主动保存,PyQt会自动将界面变更的数据保存到对应的Model存储中, ...
- [GKCTF2020]CheckIN 注意了解多方面的东西
打开之后是这样的,没有发现反序列化函数,但是发现有一个@eval,想到了一句话,这是用base64进行传参首先传参phpinfo();看看,需要经过base64编码 http://e0cc90ac-d ...
- XPATH基本语法
1.XPATH与自动化之间的关系 1.XPATH是一门在XML文档中查找信息的语言.XPATH可用来在XML文档中对元素和属性进行遍历. 2.XPATH是用来选择"节点"的一种基于 ...
- dataframe 检查缺失值
s = df.isnull().any() #返回series形式,可以用enumerate打印s #true代表有空值 null_index = [] for i,j in enumerate(s) ...
