iOS直播(基础篇)-rtmp

首先我们获得h264的流,在监听里,我们通过参数可以获得RTMP包 IStreamPacket,调用getData()方法直接获得包数据 放入IOBuffer。以下是提取并修改数据存成h264文件的步骤

  1. 添加监听 IStreamListener
  2. 通过IOBuffer的put函数将每次获得的包数据放入新的IObuffer
  3. 在流结束时将IOBuffer存成文件
  4. 用工具,如UltraEdit打开文件,查看里面的数据并分析
  5. 根据分析结果修改程序,提取h264视频文件所需的数据并存储

1.RTMP协议
RTMP协议封包由一个包头和一个包体组成,包头可以是4种长度的任意一种:12, 8, 4, 1 byte(s).完整的RTMP包头应该是12bytes,包含了时间戳,AMFSize,AMFType,StreamID信息, 8字节的包头只纪录了时间戳,AMFSize,AMFType,其他字节的包头纪录信息依次类推 。包体最大长度默认为128字节,通过chunkSize可改变包体最大长度,通常当一段AFM数据超过128字节后,超过128的部分就放到了其他的RTMP封包中,包头为一个字节.完整的12字节RTMP包头每个字节的含义:
用途

大小(Byte)

含义

Head_Type

1

包头

TiMMER

3

时间戳

AMFSize

3

数据大小

AMFType

1

数据类型

StreamID

4

流ID

1.1 Head_Type第一个字节Head_Type的前两个Bit决定了包头的长度.它可以用掩码0xC0进行"与"计算: Head_Type的前两个Bit和长度对应关系:
Bits

Header Length

00

12 bytes

01

8 bytes

10

4 bytes

11

1 byte

Head_Type的后面6个Bit和StreamID决定了ChannelID。 StreamID和ChannelID对应关系:StreamID=(ChannelID-4)/5+1 参考red5
ChannelID

Use

02

Ping 和ByteRead通道

03

Invoke通道 我们的connect() publish()和自字写的NetConnection.Call() 数据都是在这个通道的

04

Audio和Vidio通道

05 06 07

服务器保留,经观察FMS2用这些Channel也用来发送音频或视频数据

例如在rtmp包里面经常看到的0xC2,就表示一字节的包头,channel=2.1.2 TiMMER TiMMER占3个字节纪录的是时间戳,音视频流的时间戳是统一排的。可分为绝对时间戳和相对时间戳。fms对于同一个流,发布的时间戳接受的时间戳是有区别的publish时间戳,采用相对时间戳,时间戳值等于当前媒体包的绝对时间戳与上个媒体包的绝对时间戳之间的差距,也就是说音视频时间戳在一个时间轴上面.单位毫秒。play时间戳,相对时间戳,时间戳值等于当前媒体包的绝对时间戳与上个同类型媒体包的绝对时间戳之间的差距,也就是说音视频时间戳分别为单独的时间轴,单位毫秒。flv格式文件时间戳,绝对时间戳,时间戳长度3个字节。超过0xFFFFFF后时间戳值等于TimeStamp &0xFFFFFF。flv格式文件影片总时间长度保存在onMetaData的duration属性里面,长度为8个字节,是一个翻转的double类型。1.3 AMFSizeAMFSize占三个字节,这个长度是AMF长度,可超过RTMP包的最大长度128字节。如果超过了128字节,那么由多个后续RTMP封包组合,每个后续RTMP封包的头只占一个字节。一般就是以0xC?开头。1.4 AMFType****AMFSize占三个字节,这个长度是AMF长度,可超过RTMP包的最大长度128字节。AMFType是包的类型
0×01

Chunk Size

changes the chunk size for packets

0×02

Unknown

0×03

Bytes Read

send every x bytes read by both sides

0×04

Ping

ping is a stream control message, has subtypes

0×05

Server BW

the servers downstream bw

0×06

Client BW

the clients upstream bw

0×07

Unknown

0×08

Audio Data

packet containing audio

0×09

Video Data

packet containing video data

0x0A-0x0E

Unknown

0x0F

FLEX_STREAM_SEND

TYPE_FLEX_STREAM_SEND

0x10

FLEX_SHARED_OBJECT

TYPE_FLEX_SHARED_OBJECT

0x11

FLEX_MESSAGE

TYPE_FLEX_MESSAGE

0×12

Notify

an invoke which does not expect a reply

0×13

Shared Object

has subtypes

0×14

Invoke

like remoting call, used for stream actions too.

0×16

StreamData

这是FMS3出来后新增的数据类型,这种类型数据中包含AudioData和VideoData

1.6 StreamIDStreamID是音视频流的ID,如果AMFType!=0x08或!=0x09那么 StreamID为0。ChannelID 和StreamID之间的计算公式:StreamID=(ChannelID-4)/5+1 参考red5例如当ChannelID为2、3、4时StreamID都为1当ChannelID为9的时候StreamID为2

2.RTMP包的数据部分分析
如果 AMFType = 0×09, 数据就是 Video Data
Video Data由多个video tag组成
一个video tag,包含的信息:SPS,PPS,访问单元分隔符,SEI,I帧包
首先我们来看下vedio tag
如果TAG包中的TagType==9时,就表示这个TAG是video.
StreamID之后的数据就表示是VideoTagHeader,VideoTagHeader结构如下:
Field

Type

Comment

Frame Type

UB [4]

Type of video frame. The following values are defined:1 = key frame (for AVC, a seekable frame)2 = inter frame (for AVC, a non-seekable frame)3 = disposable inter frame (H.263 only)4 = generated key frame (reserved for server use only)5 = video info/command frame

CodecID

UB [4]

Codec Identifier. The following values are defined:2 = Sorenson H.2633 = Screen video4 = On2 VP65 = On2 VP6 with alpha channel6 = Screen video version 27 = AVC

AVCPacketType

IF CodecID == 7UI8

The following values are defined:0 = AVC sequence header1 = AVC NALU2 = AVC end of sequence (lower level NALU sequence ender is not required or supported)

CompositionTime

IF CodecID == 7SI24

IF AVCPacketType == 1Composition time offsetELSE0See ISO 14496-12, 8.15.3 for an explanation of compositiontimes. The offset in an FLV file is always in milliseconds.

VideoTagHeader的头1个字节,也就是接跟着StreamID的1个字节包含着视频帧类型及视频CodecID最基本信息.表里列的十分清楚.
VideoTagHeader之后跟着的就是VIDEODATA数据了,也就是videopayload.当然就像音频AAC一样,这里也有特例就是如果视频的格式是AVC(H.264)的话,VideoTagHeader会多出4个字节的信息.
AVCPacketType 和CompositionTime。AVCPacketType表示接下来 VIDEODATA(AVCVIDEOPACKET)的内容:
IF AVCPacketType ==0 AVCDecoderConfigurationRecord(AVC sequence header)IF AVCPacketType == 1 One or more NALUs (Full frames are required)
AVCDecoderConfigurationRecord.包含着是H.264解码相关比较重要的sps和pps信息,再给AVC解码器送数据流之前一定要把sps和pps信息送出,否则的话解码器不能正常解码。而且在解码器stop之后再次start之前,如seek、快进快退状态切换等,都需要重新送一遍sps和pps的信息.AVCDecoderConfigurationRecord在FLV文件中一般情况也是出现1次,也就是第一个 video tag.


2.1 AVC sequence header分析

§ 17:1-keyframe 7-avc
§ 00:AVC sequence header -- AVC packet type
§ 00 00 00:composition time,AVC时,全0,无意义
因为AVC packet type=AVCsequence header,接下来就是AVCDecoderConfigurationRecord的内容
§ configurationVersion= 01
§ AVCProfileIndication= 42
§ profile_compatibility=00
§ AVCLevelIndication =1E
§ lengthSizeMinusOne =FF -- FLV中NALU包长数据所使用的字节数,(lengthSizeMinusOne & 3)+1,实际测试时发现总为ff,计算结果为4,下文还会提到这个数据
§ numOfSequenceParameterSets= E1 -- SPS的个数,numOfSequenceParameterSets & 0x1F,实际测试时发现总为E1,计算结果为1
§ sequenceParameterSetLength= 0x2E-- SPS的长度,2个字节,计算结果46
§ sequenceParameterSetNALUnits=6742 80 1E 96 54 0A 0F D8 0A 84 00 00 03 00 04 00 00 03 00 7B 80 00 08 00 00 0400 1F c6 38 C0 00 04 00 0 03 02 00 0F E3 1C 3B 42 44 D4-- SPS,为刚才计算的46个字节, SPS中包含了视频长、宽的信息
§ numOfPictureParameterSets= 01 -- PPS的个数,实际测试时发现总为E1,计算结果为1
§ pictureParameterSetLength= 0004-- PPS的长度
§ pictureParameterSetNALUnits=68ce 35 20 -- PPS

2.1 AVCNALU分析
接下来又是新的一包videotag数据了
§ 17:1-keyframe 7-avc
§ 01:AVC NALU
§ 00 00 00:composition time,AVC时,全0,无意义
因为AVCPacket type = AVCNALU,接下来就是一个或多个NALU
每个NALU包前面都有(lengthSizeMinusOne & 3)+1个字节的NAL包长度描述(前文提到的,还记得吗),前面计算结果为4个字节
§ 00 00 00 02:2 -- NALU length
§ 09 10:NAL包
这里插入一点NALU的小知识,每个NALU第一个字节的前5位标明的是该NAL包的类型,即NAL nal_unit_type

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

§ 09&0x1f=9,访问单元分隔符
前面我们解析的sps头字节为67,67&0x1f = 7,pps头字节为68,68&0x1f=8,正好能对应上。
§ 00 00 00 29:说明接下来的NAL包长度为41
06 00 11 80 00 af c8 00 00 03 00 00 03 00 00 af c8 00 00 03 00 00 40 010c 00 00 03 00 00 03 00 90 80 08 00 00 03 00 0880:06&0x1f=6 -- SEI
§ 00 00 0F 9F:接下来的NAL包长度
65 88 80……:65&0x1f=5 -- I帧数据
这包video tag分析到此结束了,下面会紧接着来一些该I帧对应的P帧数据,
前面说的I帧数据从65 88 80,到下图第一行的 5F 7E B0都是上一个video tag的内容,即前面说的65 88 80那个I帧的数据拉,27开始是新的一个video tag



§ 27:2-inter frame即P帧,7-codecid=AVC
§ 01:AVCPacket type = AVC NALU
§ 00 00 00:composition time,AVC时,全0,无意义
§ 00 00 00 02 09 30:跟上面分析的一样拉,2个字节的nal包,访问单元分隔符
§ 00 00 00 11:17字节的NAL包
§ 06 01 0c 00 00 80 0000 90 80 18 00 00 03 00 08 80:06&0x1f=6 --SEI
§ 00 00 0C 45: NAL包数据长度
§ 41 9A 02……: 41&0x1f=1 --P帧数据

3.H264视频文件格式
h264的NALU和NALU之间是由00 00 01(也可以是00 00 00 01)分隔开的,我们组成h264之后的格式为
1、00 00 00 01 SPS 0000 00 01 PPS 00 00 00 01访问单元分隔符 00 00 00 01 SEI 0000 00 01 I帧 00 00 00 01 P帧 00 00 00 01 P帧……(P帧数量不定)

其中的访问单元分隔符和SEI不是必须的

4.将获得的包数据存储成H264文件
通过以上我们清楚了H264文件的格式,也分析了现在获得的数据格式,我们需要对这些数据进行处理,得到H264视频要求的数据格式
1.当数据是AVC squence header(只有一次)的时候,提取sps,pps数据并加入 0000 01(也可以是00 00 00 01)隔开。

  1. 当数据是AVC NALU时,四个字节存储帧数据长度,后面紧跟着数据,根据长度计算帧数据长,提取数据,加上00 00 00 01,将每个帧数据隔开。

5.red5 数据处理代码
@Override
public void streamPublishStart(IBroadcastStreamstream) {
super.streamPublishStart(stream);
stream.addStreamListener(newIStreamListener() {

protected boolean bFirst = true;
@Override
public void packetReceived(IBroadcastStreamarg0, IStreamPacket arg1) {
IoBufferin = arg1.getData();
if(arg1.getDataType() == 0x09){
System.out.println("11111");
byte[] data = new byte[in.limit()];
in.get(data);
byte[] foredata = { 0, 0, 0, 1 };
ioBuffer3.put(data);
// buflimit3 += in.limit();
if( bFirst) {
//AVCsequence header
ioBuffer.put(foredata);
//获取sps
intspsnum = data[10]&0x1f;
intnumber_sps = 11;
intcount_sps = 1;
while (count_sps<=spsnum){
int spslen =(data[number_sps]&0x000000FF)<<8 |(data[number_sps+1]&0x000000FF);
number_sps += 2;
ioBuffer.put(data,number_sps, spslen);
ioBuffer.put(foredata);
number_sps += spslen;
count_sps ++;
}
//获取pps
intppsnum = data[number_sps]&0x1f;
intnumber_pps = number_sps+1;
intcount_pps = 1;
while (count_pps<=ppsnum){
int ppslen =(data[number_pps]&0x000000FF)<<8|data[number_pps+1]&0x000000FF;
number_pps += 2;
ioBuffer.put(data,number_pps,ppslen);
ioBuffer.put(foredata);
number_pps += ppslen;
count_pps ++;
}
bFirst =false;
} else {
//AVCNALU
int len =0;
int num =5;
ioBuffer.put(foredata);
while(num<data.length) {
len =(data[num]&0x000000FF)<<24|(data[num+1]&0x000000FF)<<16|(data[num+2]&0x000000FF)<<8|data[num+3]&0x000000FF;
num = num+4;
ioBuffer.put(data,num,len);
ioBuffer.put(foredata);
num = num + len;
}
}
System.out.println("22222");
}else if (arg1.getDataType() == 0x08) {
// 这存储处理音频数据 Audio data
}
}
});
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容

  • 一. 概述 苹果从iOS 8开始,开放了硬编码和硬解码的api,所以,从iOS 8开始,需要解码H.264视频时,...
    金山视频云阅读 9,828评论 1 25
  • 概述 Flash Video(简称FLV),是一种网络视频格式,用作流媒体格式,它的出现有效地解决了视频文件导入F...
    Damon_He阅读 2,186评论 1 1
  • ### YUV颜色空间 视频是由一帧一帧的数据连接而成,而一帧视频数据其实就是一张图片。 yuv是一种图片储存格式...
    天使君阅读 3,174评论 0 4
  • 视频格式封装——H264 转载自 http://blog.csdn.net/yangzhongxuan/artic...
    microchip阅读 2,296评论 0 1
  • 昨天,我们去白泉寺路线居然走错了,估计走远了十来里路,当我们来到那里的时候已经九点多了,大家都已经忙活开了,我也赶...
    时光若止_acf6阅读 121评论 0 1