公司最近想承接一个通过智能手机实现视频双向通讯的功能。提前开始了技术预研究。为保证较小的延迟,和优质的视频功能,我们对手机采集的音频和视频都利用手机硬件提供的硬编码功能直接实现H264+AAC编码。封包采用目前视频网站普遍使用的FLV格式。然后通过开源的RtmpLib库,以RTMP协议发送给音视频分发服务器。从而实现延迟很小的高质量视频通讯。
作为这个实施方案的第一步,我们需要分别实现Android和IOS手机上的H264裸流封装为FLV格式。我主要负责IOS部分的实现,经过2天的努力,终于实现了目标。看到VLC播放成功我封装的视频,实在非常有成就感。下面我把这几天的努力,总结下。
FLV是一个二进制文件,简单来说,其是由一个文件头(FLV header)和很多tag组成(FLV body)。tag又可以分成三类:audio,video,script,分别代表音频流,视频流,脚本流,而每个tag又由tag header和tag data组成。用通俗语言介绍下我的理解。FLV是音视频的容器。有固定长度的FLV Head和FLV Body组成。FLV Body由里面不同的TAG块组成。主要有3种,META TAG,在最前面,告诉后面视频或音频的头信息,从而告诉播放器播放码率和视频尺寸。这些信息都在H264裸流或AAC裸流的头部,可以直接提取封装,不需要我们解码。后面就是Video TAG视频TAG块和Audio TAG音频TAG块组成。本文只涉及视频部分,音频部分类似,不在本文述说了。
知道这些基本知识,要封装还是不容易的。我们现在通过截图来具体讲解。下面是H264裸流文件构成。
h264是一个个NALU单元组成的,每个单元以00 00 01 或者 00 00 00 01分隔开来,每2个00 00 00 01之间就是一个NALU单元。我们实际上就是将一个个NALU单元封装进FLV文件。头两个NAL块封装的数据描述了该视频的码率和版本都信息。后面就是按照时间顺序的视频数据块了。后面我们需要分割后,循环拼装到FLV格式中的。在IOS中可以用二维数组来分割和保存这些NAL块。代码如下:
-(void)initH264File:(NSString *)path{
NSInteger fuck = 0;
NSData * reader = [NSData dataWithContentsOfFile:path];//H264裸数据
[reader getBytes:&fuck length:sizeof(fuck)];
Byte *contentByte = (Byte *)[reader bytes];
NSLog(@"fuck:%d byte len:%d",fuck,[reader length]);
int count_i=-1;
Byte kk;
for(int i=0;i<[reader length];i++){
if(contentByte[i+0]==0x00&&contentByte[i+1]==0x00&&contentByte[i+2]==0x00&&contentByte[i+3]==0x01){
i=i+3;
count_i++;
[self.VideoListArray addObject:[[NSMutableData alloc] init]];
}
else if(contentByte[i+0]==0x00&&contentByte[i+1]==0x00&&contentByte[i+2]==0x00){
i=i+2;
count_i++;
[self.VideoListArray addObject:[[NSMutableData alloc] init]];
}
else{
if(count_i>-1){
kk=contentByte[i];
[[self.VideoListArray objectAtIndex:count_i] appendBytes:&kk length:sizeof(kk)];
}
}
}
}
后面我们看下FLV格式,首先看下图:
绿色标注的前9个Byte是FLVHead头信息。前三个是固定的’F’,’L’,’V’。前3个bytes是文件类型,总是“FLV”,也就是(0x46 0x4C 0x56)。第4btye是版本号,目前一般是0x01。第5byte是流的信息,倒数第一bit是1表示有视频(0x01),倒数第三bit是1表示有音频(0x4),有视频又有音频就是0x01 | 0x04(0x05),其他都应该是0。最后4bytes表示FLV 头的长度,3+1+1+4 = 9。 IOS代码可以如下实现:
NSMutableData *writer = [[NSMutableData alloc] init];
Byte i1 = 0x46;
[writer appendBytes:&i1 length:sizeof(i1)];//1
i1 = 0x4C;
[writer appendBytes:&i1 length:sizeof(i1)];//2
i1 = 0x56;
[writer appendBytes:&i1 length:sizeof(i1)];//3
i1 = 0x01;
[writer appendBytes:&i1 length:sizeof(i1)];//4
i1 = 0x01;//0x01--代表只有视频,0x05--音频和视频混合,0x04--只有音频
[writer appendBytes:&i1 length:sizeof(i1)];//5
i1 = 0x00;
[writer appendBytes:&i1 length:sizeof(i1)];//6
i1 = 0x00;
[writer appendBytes:&i1 length:sizeof(i1)];//7
i1 = 0x00;
[writer appendBytes:&i1 length:sizeof(i1)];//8
i1 = 0x09;
[writer appendBytes:&i1 length:sizeof(i1)];//9
然后讲解下META TAG,如下图:
首先把音视频的配置信息封进metadata之后的tag,然后就可以封数据了。再说下元数据,flv header之后就是它了,再之后就是音视频配置信息,再后面就是音视频数据。
0x12前面的00 00 00 00 就是刚刚说的记录着上一个tag的长度的4bytes,这里因为前面没有tag,所以为0。
如果是视频数据,第一个byte记录video信息:
前4bits表示类型:(·1– keyframe·2 — inner frame·3 — disposable inner frame (h.263 only)·4 –generated keyframe)
后4bits表示解码器ID:(·6 — Screen video version ·7 — AVC(h.264))。
这部分就是把H264文件的都两个块封装进来,前面加上2byte的数据长度。具体代码如下:
//Meta Tag data
i1 = 0x17;
[writer appendBytes:&i1 length:sizeof(i1)];//18
i1 = 0x00;
[writer appendBytes:&i1 length:sizeof(i1)];//18
i1 = 0x00;
[writer appendBytes:&i1 length:sizeof(i1)];//18
i1 = 0x00;
[writer appendBytes:&i1 length:sizeof(i1)];//18
i1 = 0x00;
[writer appendBytes:&i1 length:sizeof(i1)];//18
//------------
i1 = 0x01;
[writer appendBytes:&i1 length:sizeof(i1)];//18
i1 = 0x42;
[writer appendBytes:&i1 length:sizeof(i1)];//18
i1 = 0x80;
[writer appendBytes:&i1 length:sizeof(i1)];//18
i1 = 0x0D;
[writer appendBytes:&i1 length:sizeof(i1)];//18
i1 = 0xFF;
[writer appendBytes:&i1 length:sizeof(i1)];//18
i1 = 0xE1;
[writer appendBytes:&i1 length:sizeof(i1)];//18
i1 = (Byte)(([[self.VideoListArray objectAtIndex:0] length]&0x0000FF00)>>8);
[writer appendBytes:&i1 length:sizeof(i1)];//17
i1 = (Byte)( [[self.VideoListArray objectAtIndex:0] length]&0x000000FF);
[writer appendBytes:&i1 length:sizeof(i1)];//18
[writer appendData:[self.VideoListArray objectAtIndex:0]];
i1 = 0x01;
[writer appendBytes:&i1 length:sizeof(i1)];//18
i1 = (Byte)(([[self.VideoListArray objectAtIndex:1] length]&0x0000FF00)>>8);
[writer appendBytes:&i1 length:sizeof(i1)];//17
i1 = (Byte)([[self.VideoListArray objectAtIndex:1] length]&0x000000FF);
[writer appendBytes:&i1 length:sizeof(i1)];//18
[writer appendData:[self.VideoListArray objectAtIndex:1]];
每个TAG的前4个Byte是上一个TAG的总长度,如下图:
视频TAG由固定的Head头,20个Byte和不固定的Body组成。数据是:一个byte的video信息+一个byte的AVCPacket type+3个bytes的无用数据(composition time,当AVC时无用,全是0)+ 4个bytes的NALU单元长度 + N个bytes的NALU数据,所以包头数据长度信息是刚才提到的信息的总和长度。要强调下,当音视频配置信息tag的时候,是没有4个bytes的NALU单元长度的。在这儿我们需要说明下,除了PreTAGHEAD之外,里面所有涉及到描述数据长度的位置,都是后面的数据区域。上图的0x06 0xf2的描述的长度是包括后续的9个描述字节的,NALU的单位长度的描述的是后面数据区的长度,这儿是0x06 0xe9.这儿尤其要注意。17 — 高4bits:1,keyframe。低4bits:7,代表AVC。后面一个byte 0x00,AVCPacket type,代表AVC sequence header。后3个bytes无意义,之后就是decoder configuration record的内容了。 图中绿色后面 00 00 00 28就是前面tag的总长度。当NALU第一个byte xx& 0x1f == 5的时候,说明该单元是一个I frame,关键帧。否则就是0x27.
后面讲下时间戳的设定,这是依据摄像头采样率考虑的,一般采样是11500,播放速度是25毫秒,1000/25=40.所以依次增加40就可以。上这部分代码:
int time_h=0;//初始时间戳
for(int j=2;j<[self.VideoListArray count];j++){
if(j==2){
fuck=metaFixLen+[[self.VideoListArray objectAtIndex:0] length]+[[self.VideoListArray objectAtIndex:1] length];
// NSLog(@"len:%d",fuck);
i1 = (Byte)((fuck&0x00FF0000)>>24);
[writer appendBytes:&i1 length:sizeof(i1)];//16
i1 = (Byte)((fuck&0x00FF0000)>>16);
[writer appendBytes:&i1 length:sizeof(i1)];//16
i1 = (Byte)((fuck&0x0000FF00)>>8);
[writer appendBytes:&i1 length:sizeof(i1)];//17
i1 = (Byte)(fuck&0x000000FF);
[writer appendBytes:&i1 length:sizeof(i1)];//18
}else{
fuck=videoTagFixLen+[[self.VideoListArray objectAtIndex:j-1] length];
// NSLog(@"len:%d",fuck);
i1 = (Byte)((fuck&0x00FF0000)>>24);
[writer appendBytes:&i1 length:sizeof(i1)];//16
i1 = (Byte)((fuck&0x00FF0000)>>16);
[writer appendBytes:&i1 length:sizeof(i1)];//16
i1 = (Byte)((fuck&0x0000FF00)>>8);
[writer appendBytes:&i1 length:sizeof(i1)];//17
i1 = (Byte)(fuck&0x000000FF);
[writer appendBytes:&i1 length:sizeof(i1)];//18
}
i1 = 0x09;
[writer appendBytes:&i1 length:sizeof(i1)];//18
fuck=[[self.VideoListArray objectAtIndex:j] length]+9;
i1 = (Byte)((fuck&0x00FF0000)>>16);
[writer appendBytes:&i1 length:sizeof(i1)];//16
i1 = (Byte)((fuck&0x0000FF00)>>8);
[writer appendBytes:&i1 length:sizeof(i1)];//17
i1 = (Byte)(fuck&0x000000FF);
[writer appendBytes:&i1 length:sizeof(i1)];//18
//----
//定义3个时间戳
i1 = (Byte)((time_h&0x00FF0000)>>16);
[writer appendBytes:&i1 length:sizeof(i1)];//18
i1 = (Byte)((time_h&0x0000FF00)>>8);
[writer appendBytes:&i1 length:sizeof(i1)];//18
i1 =(Byte)(time_h&0x000000FF);
[writer appendBytes:&i1 length:sizeof(i1)];//18
//备份时间戳
i1 = 0x00;
[writer appendBytes:&i1 length:sizeof(i1)];//18
//----
//定义3个3bytes是streamID
i1 = 0x00;
[writer appendBytes:&i1 length:sizeof(i1)];//18
i1 = 0x00;
[writer appendBytes:&i1 length:sizeof(i1)];//18
i1 = 0x00;
[writer appendBytes:&i1 length:sizeof(i1)];//18
//---------
Byte *contentByte = (Byte *)[[self.VideoListArray objectAtIndex:j] bytes];
if((contentByte[0]& 0x1f) == 5){
i1 = 0x17;
[writer appendBytes:&i1 length:sizeof(i1)];//18
}else{
i1 = 0x27;
[writer appendBytes:&i1 length:sizeof(i1)];//18
}
i1 = 0x01;
[writer appendBytes:&i1 length:sizeof(i1)];//
//-------
i1 = 0x00;
[writer appendBytes:&i1 length:sizeof(i1)];//18
i1 = 0x00;
[writer appendBytes:&i1 length:sizeof(i1)];//18
i1 = 0x00;
[writer appendBytes:&i1 length:sizeof(i1)];//18
//--------
fuck=[[self.VideoListArray objectAtIndex:j] length];
NSLog(@"len:%d",fuck);
i1 = (Byte)((fuck&0x00FF0000)>>24);
[writer appendBytes:&i1 length:sizeof(i1)];//16
i1 = (Byte)((fuck&0x00FF0000)>>16);
[writer appendBytes:&i1 length:sizeof(i1)];//16
i1 = (Byte)((fuck&0x0000FF00)>>8);
[writer appendBytes:&i1 length:sizeof(i1)];//17
i1 = (Byte)(fuck&0x000000FF);
[writer appendBytes:&i1 length:sizeof(i1)];//18
[writer appendData:[self.VideoListArray objectAtIndex:j]];
//---------
time_h=time_h+40;//采样率是11500时候,是40递增,加倍就是80递增
}//for
到此为止,我们就完成了所有封装了。本文全部代码如下:
//
// xukangwenViewController.m
// H264ToFlv
//
// Created by zhengyu xu on 14-2-26.
// Copyright (c) 2014年 smg. All rights reserved.
//
#import "xukangwenViewController.h"
//#import "rtmp.h"
//#import "log.h"
//#import "Rtmpdump.h"
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
//网络与本地字节转换
#define HTON16(x) ((x>>8&0xff)|(x<<8&0xff00))
#define HTON24(x) ((x>>16&0xff)|(x<<16&0xff0000)|x&0xff00)
#define HTON32(x) ((x>>24&0xff)|(x>>8&0xff00)|\
(x<<8&0xff0000)|(x<<24&0xff000000))
/**
class RtmpSend {
private:
id flv_route; // holds an NSString
//RTMP_XXX()返回0表示失败,1表示成功
RTMP*rtmp=NULL;//rtmp应用指针
RTMPPacket*packet=NULL;//rtmp包结构
//id rtmpurl="rtmp://172.28.125.74:1935/ios/test";//连接的URL
// id flvfile="test.flv";//读取的flv文件
public:
RtmpSend() {
flv_route = @"Test 11123";
}
RtmpSend(const char* initial_greeting_text) {
flv_route = [[NSString alloc] initWithUTF8String:initial_greeting_text];
}
void say_flv_route2(){
printf("test 111234\n");
}
int say_flv_route(char* rtmpurl,char* flvfile) {
if (!ZINIT())
{
printf("init socket err\n");
return -1;
}
/初始化//
RTMP_LogLevel lvl=RTMP_LOGINFO;
RTMP_LogSetLevel(lvl);//设置信息等级
//RTMP_LogSetOutput(FILE*fp);//设置信息输出文件
rtmp=RTMP_Alloc();//申请rtmp空间
RTMP_Init(rtmp);//初始化rtmp设置
rtmp->Link.timeout=5;//设置连接超时,单位秒,默认30秒
packet=new RTMPPacket();//创建包
RTMPPacket_Alloc(packet,1024*64);//给packet分配数据空间,要满足最长的帧,不知道可设大些
RTMPPacket_Reset(packet);//重置packet状态
RTMPPacket_Reset(packet);//重置packet状态
连接//
RTMP_SetupURL(rtmp,rtmpurl);//设置url
RTMP_EnableWrite(rtmp);//设置可写状态
if (!RTMP_Connect(rtmp,NULL))//连接服务器
{
printf("connect err\n");
ZCLEAR();
return -1;
}
if (!RTMP_ConnectStream(rtmp,0))//创建并发布流(取决于rtmp->Link.lFlags)
{
printf("ConnectStreamerr\n");
ZCLEAR();
return -1;
}
packet->m_hasAbsTimestamp = 0; //绝对时间戳
packet->m_nChannel = 0x04; //通道
packet->m_nInfoField2 = rtmp->m_stream_id;
FILE*fp=fopen(flvfile,"rb");
if (fp==NULL)
{
printf("open file:%s err\n",flvfile);
ZCLEAR();
return -1;
}
printf("rtmpurl:%s\nflvfile:%s\nsend data ...\n",rtmpurl,flvfile);
发送数据//
fseek(fp,9,SEEK_SET);//跳过前9个字节
fseek(fp,4,SEEK_CUR);//跳过4字节长度
long start=clock()-1000;
long perframetime=0;//上一帧时间戳
while(TRUE)
{
if((clock()-start)<perframetime)//发的太快就等一下
{
sleep(500);
continue;
}
int type=0;//类型
int datalength=0;//数据长度
int time=0;//时间戳
int streamid=0;//流ID
if(!Read8(type,fp))
break;
if(!Read24(datalength,fp))
break;
if(!ReadTime(time,fp))
break;
if(!Read24(streamid,fp))
break;
if (type!=0x08&&type!=0x09)
{
fseek(fp,datalength+4,SEEK_CUR);
continue;
}
if(fread(packet->m_body,1,datalength,fp)!=datalength)
break;
packet->m_headerType = RTMP_PACKET_SIZE_MEDIUM;
packet->m_nTimeStamp = time;
packet->m_packetType=type;
packet->m_nBodySize=datalength;
if (!RTMP_IsConnected(rtmp))
{
printf("rtmp is not connect\n");
break;
}
if (!RTMP_SendPacket(rtmp,packet,0))
{
printf("send err\n");
break;
}
int alldatalength=0;//该帧总长度
if(!Read32(alldatalength,fp))
break;
perframetime=time;
}
printf("send data over\n");
fclose(fp);
ZCLEAR();
return 0;
}
bool ZINIT()
{
// WORD version;
// WSADATA wsaData;
// version=MAKEWORD(2,2);
return TRUE;
}
void ZCLEAR()
{
//释放/
if (rtmp!=NULL)
{
RTMP_Close(rtmp);//断开连接
RTMP_Free(rtmp);//释放内存
rtmp=NULL;
}
if (packet!=NULL)
{
RTMPPacket_Free(packet);//释放内存
delete packet;
packet=NULL;
}
///
// WSACleanup();
}
bool Read8(int &i8,FILE*fp)
{
if(fread(&i8,1,1,fp)!=1)
return false;
return true;
}
bool Read16(int &i16,FILE*fp)
{
if(fread(&i16,2,1,fp)!=1)
return false;
i16=HTON16(i16);
return true;
}
bool Read24(int &i24,FILE*fp)
{
if(fread(&i24,3,1,fp)!=1)
return false;
i24=HTON24(i24);
return true;
}
bool Read32(int &i32,FILE*fp)
{
if(fread(&i32,4,1,fp)!=1)
return false;
i32=HTON32(i32);
return true;
}
bool Peek8(int &i8,FILE*fp)
{
if(fread(&i8,1,1,fp)!=1)
return false;
fseek(fp,-1,SEEK_CUR);
return true;
}
bool ReadTime(int &itime,FILE*fp)
{
int temp=0;
if(fread(&temp,4,1,fp)!=1)
return false;
itime=HTON24(temp);
itime|=(temp&0xff000000);
return true;
}
};
*/
@interface xukangwenViewController (){
//@private RtmpSend *RtmpSend1;
}
@end
@implementation xukangwenViewController
@synthesize VideoListArray;
@synthesize rPublish;
int first1=0;
int topTagLen=16;
int metaFixLen=27;
int first2=0;
int videoLen=0;
int videoTagFixLen=20;
- (id)init {
if (self = [super init]) {
// RtmpSend1= new RtmpSend();
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
if(NULL==self.VideoListArray){
self.VideoListArray=[[NSMutableArray alloc] init];
}else{
if([self.VideoListArray count]>0){
[self.VideoListArray removeAllObjects];
}
}
// Do any additional setup after loading the view, typically from a nib.
[self TestBitFile];
NSLog(@"test");
//[self setRTMP];
rPublish=[[RtmpLib alloc] init];
}
-(void)setRTMP{
NSString *documentPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *realPath = [documentPath stringByAppendingPathComponent:@"IOSencoder.flv"];
NSFileManager *fileManager=[NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:realPath]){//if 2
// NSString *string_content = @"rtmp://172.28.125.74:1935/ios/test";
// NSString *color_string = @"0xff0000";
// char *color_char = NULL;//"rtmp://172.28.125.74:1935/ios/test";//[color_string cStringUsingEncoding:NSASCIIStringEncoding];
// char *color_char2 = NULL;
// char *inStrg = (char *)[[string_content dataUsingEncoding : NSASCIIStringEncoding ] bytes];
// char *inStrg2 = (char *)[[realPath dataUsingEncoding : NSASCIIStringEncoding ] bytes];
// RtmpSend1->say_flv_route(inStrg, inStrg2);
// RtmpSend1->say_flv_route2();
}//if 2
}
-(void)initH264File:(NSString *)path{
NSInteger fuck = 0;
NSData * reader = [NSData dataWithContentsOfFile:path];//H264裸数据
[reader getBytes:&fuck length:sizeof(fuck)];
Byte *contentByte = (Byte *)[reader bytes];
NSLog(@"fuck:%d byte len:%d",fuck,[reader length]);
int count_i=-1;
Byte kk;
for(int i=0;i<[reader length];i++){
if(contentByte[i+0]==0x00&&contentByte[i+1]==0x00&&contentByte[i+2]==0x00&&contentByte[i+3]==0x01){
i=i+3;
count_i++;
[self.VideoListArray addObject:[[NSMutableData alloc] init]];
}
else if(contentByte[i+0]==0x00&&contentByte[i+1]==0x00&&contentByte[i+2]==0x00){
i=i+2;
count_i++;
[self.VideoListArray addObject:[[NSMutableData alloc] init]];
}
else{
if(count_i>-1){
kk=contentByte[i];
[[self.VideoListArray objectAtIndex:count_i] appendBytes:&kk length:sizeof(kk)];
}
}
}
}
-(NSMutableData *)getSecondFile:(NSString *)path{
NSMutableData *writer = [[NSMutableData alloc] init];
NSInteger fuck = 0;
NSData * reader = [NSData dataWithContentsOfFile:path];//H264裸数据
[reader getBytes:&fuck length:sizeof(fuck)];
Byte *contentByte = (Byte *)[reader bytes];
int count_i=0;
Byte kk;
for(int i=0;i<[reader length];i++){
if(contentByte[i+0]==0x00&&contentByte[i+1]==0x00&&contentByte[i+2]==0x00&&contentByte[i+3]==0x01){
i=i+3;
count_i++;
if(count_i==3){
break;
}
}else{
if(count_i<3&&count_i>1){
kk=contentByte[i];
[writer appendBytes:&kk length:sizeof(kk)];//7
first2++;
}
}
}
return writer;
}
-(NSMutableData *)getFirstFile:(NSString *)path{
NSMutableData *writer = [[NSMutableData alloc] init];
NSInteger fuck = 0;
NSData * reader = [NSData dataWithContentsOfFile:path];//H264裸数据
[reader getBytes:&fuck length:sizeof(fuck)];
Byte *contentByte = (Byte *)[reader bytes];
int count_i=0;
Byte kk;
for(int i=0;i<[reader length];i++){
if(contentByte[i+0]==0x00&&contentByte[i+1]==0x00&&contentByte[i+2]==0x00&&contentByte[i+3]==0x01){
i=i+3;
count_i++;
if(count_i==2){
break;
}
}else{
if(count_i<2){
kk=contentByte[i];
[writer appendBytes:&kk length:sizeof(kk)];//7
first1++;
}
}
}
return writer;
}
-(void)TestBitFile{
NSString *documentPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *realPath = [documentPath stringByAppendingPathComponent:@"IOSencoder.flv"];
NSString *realPath2 = [documentPath stringByAppendingPathComponent:@"encoder.h264"];
NSFileManager *fileManager=[NSFileManager defaultManager];
if (![fileManager fileExistsAtPath:realPath]){
[self initH264File:realPath2];
NSMutableData *writer = [[NSMutableData alloc] init];
Byte i1 = 0x46;
[writer appendBytes:&i1 length:sizeof(i1)];//1
i1 = 0x4C;
[writer appendBytes:&i1 length:sizeof(i1)];//2
i1 = 0x56;
[writer appendBytes:&i1 length:sizeof(i1)];//3
i1 = 0x01;
[writer appendBytes:&i1 length:sizeof(i1)];//4
i1 = 0x01;//0x01--代表只有视频,0x05--音频和视频混合,0x04--只有音频
[writer appendBytes:&i1 length:sizeof(i1)];//5
i1 = 0x00;
[writer appendBytes:&i1 length:sizeof(i1)];//6
i1 = 0x00;
[writer appendBytes:&i1 length:sizeof(i1)];//7
i1 = 0x00;
[writer appendBytes:&i1 length:sizeof(i1)];//8
i1 = 0x09;
[writer appendBytes:&i1 length:sizeof(i1)];//9
//-----------
//LAst TAG Head 4
i1 = 0x00;
[writer appendBytes:&i1 length:sizeof(i1)];//10
i1 = 0x00;
[writer appendBytes:&i1 length:sizeof(i1)];//11
i1 = 0x00;
[writer appendBytes:&i1 length:sizeof(i1)];//12
i1 = 0x00;
[writer appendBytes:&i1 length:sizeof(i1)];//13
/**
0x09前面的00 00 00 00 就是刚刚说的记录着上一个tag的长度的4bytes,这里因为前面没有tag,所以为0
*/
//TAG Head 11
/**
第1个byte为记录着tag的类型,音频(0x8),视频(0x9),脚本(0x12)
*/
i1 = 0x09;
[writer appendBytes:&i1 length:sizeof(i1)];//14
//----
//读取H264文件
int fuck=topTagLen+[[self.VideoListArray objectAtIndex:0] length]+[[self.VideoListArray objectAtIndex:1] length];
// NSLog(@"len:%d",fuck);
i1 = (Byte)((fuck&0x00FF0000)>>16);
[writer appendBytes:&i1 length:sizeof(i1)];//16
i1 = (Byte)((fuck&0x0000FF00)>>8);
[writer appendBytes:&i1 length:sizeof(i1)];//17
i1 = (Byte)(fuck&0x000000FF);
[writer appendBytes:&i1 length:sizeof(i1)];//18
//----
//定义3个时间戳
i1 = 0x00;
[writer appendBytes:&i1 length:sizeof(i1)];//18
i1 = 0x00;
[writer appendBytes:&i1 length:sizeof(i1)];//18
i1 = 0x00;
[writer appendBytes:&i1 length:sizeof(i1)];//18
//备份时间戳
i1 = 0x00;
[writer appendBytes:&i1 length:sizeof(i1)];//18
//----
//定义3个3bytes是streamID
i1 = 0x00;
[writer appendBytes:&i1 length:sizeof(i1)];//18
i1 = 0x00;
[writer appendBytes:&i1 length:sizeof(i1)];//18
i1 = 0x00;
[writer appendBytes:&i1 length:sizeof(i1)];//18
//Meta Tag data
i1 = 0x17;
[writer appendBytes:&i1 length:sizeof(i1)];//18
i1 = 0x00;
[writer appendBytes:&i1 length:sizeof(i1)];//18
i1 = 0x00;
[writer appendBytes:&i1 length:sizeof(i1)];//18
i1 = 0x00;
[writer appendBytes:&i1 length:sizeof(i1)];//18
i1 = 0x00;
[writer appendBytes:&i1 length:sizeof(i1)];//18
//------------
i1 = 0x01;
[writer appendBytes:&i1 length:sizeof(i1)];//18
i1 = 0x42;
[writer appendBytes:&i1 length:sizeof(i1)];//18
i1 = 0x80;
[writer appendBytes:&i1 length:sizeof(i1)];//18
i1 = 0x0D;
[writer appendBytes:&i1 length:sizeof(i1)];//18
i1 = 0xFF;
[writer appendBytes:&i1 length:sizeof(i1)];//18
i1 = 0xE1;
[writer appendBytes:&i1 length:sizeof(i1)];//18
i1 = (Byte)(([[self.VideoListArray objectAtIndex:0] length]&0x0000FF00)>>8);
[writer appendBytes:&i1 length:sizeof(i1)];//17
i1 = (Byte)( [[self.VideoListArray objectAtIndex:0] length]&0x000000FF);
[writer appendBytes:&i1 length:sizeof(i1)];//18
[writer appendData:[self.VideoListArray objectAtIndex:0]];
i1 = 0x01;
[writer appendBytes:&i1 length:sizeof(i1)];//18
i1 = (Byte)(([[self.VideoListArray objectAtIndex:1] length]&0x0000FF00)>>8);
[writer appendBytes:&i1 length:sizeof(i1)];//17
i1 = (Byte)([[self.VideoListArray objectAtIndex:1] length]&0x000000FF);
[writer appendBytes:&i1 length:sizeof(i1)];//18
[writer appendData:[self.VideoListArray objectAtIndex:1]];
//----------------
int time_h=0;//初始时间戳
for(int j=2;j<[self.VideoListArray count];j++){
if(j==2){
fuck=metaFixLen+[[self.VideoListArray objectAtIndex:0] length]+[[self.VideoListArray objectAtIndex:1] length];
// NSLog(@"len:%d",fuck);
i1 = (Byte)((fuck&0x00FF0000)>>24);
[writer appendBytes:&i1 length:sizeof(i1)];//16
i1 = (Byte)((fuck&0x00FF0000)>>16);
[writer appendBytes:&i1 length:sizeof(i1)];//16
i1 = (Byte)((fuck&0x0000FF00)>>8);
[writer appendBytes:&i1 length:sizeof(i1)];//17
i1 = (Byte)(fuck&0x000000FF);
[writer appendBytes:&i1 length:sizeof(i1)];//18
}else{
fuck=videoTagFixLen+[[self.VideoListArray objectAtIndex:j-1] length];
// NSLog(@"len:%d",fuck);
i1 = (Byte)((fuck&0x00FF0000)>>24);
[writer appendBytes:&i1 length:sizeof(i1)];//16
i1 = (Byte)((fuck&0x00FF0000)>>16);
[writer appendBytes:&i1 length:sizeof(i1)];//16
i1 = (Byte)((fuck&0x0000FF00)>>8);
[writer appendBytes:&i1 length:sizeof(i1)];//17
i1 = (Byte)(fuck&0x000000FF);
[writer appendBytes:&i1 length:sizeof(i1)];//18
}
i1 = 0x09;
[writer appendBytes:&i1 length:sizeof(i1)];//18
fuck=[[self.VideoListArray objectAtIndex:j] length]+9;
i1 = (Byte)((fuck&0x00FF0000)>>16);
[writer appendBytes:&i1 length:sizeof(i1)];//16
i1 = (Byte)((fuck&0x0000FF00)>>8);
[writer appendBytes:&i1 length:sizeof(i1)];//17
i1 = (Byte)(fuck&0x000000FF);
[writer appendBytes:&i1 length:sizeof(i1)];//18
//----
//定义3个时间戳
i1 = (Byte)((time_h&0x00FF0000)>>16);
[writer appendBytes:&i1 length:sizeof(i1)];//18
i1 = (Byte)((time_h&0x0000FF00)>>8);
[writer appendBytes:&i1 length:sizeof(i1)];//18
i1 =(Byte)(time_h&0x000000FF);
[writer appendBytes:&i1 length:sizeof(i1)];//18
//备份时间戳
i1 = 0x00;
[writer appendBytes:&i1 length:sizeof(i1)];//18
//----
//定义3个3bytes是streamID
i1 = 0x00;
[writer appendBytes:&i1 length:sizeof(i1)];//18
i1 = 0x00;
[writer appendBytes:&i1 length:sizeof(i1)];//18
i1 = 0x00;
[writer appendBytes:&i1 length:sizeof(i1)];//18
//---------
Byte *contentByte = (Byte *)[[self.VideoListArray objectAtIndex:j] bytes];
if((contentByte[0]& 0x1f) == 5){
i1 = 0x17;
[writer appendBytes:&i1 length:sizeof(i1)];//18
}else{
i1 = 0x27;
[writer appendBytes:&i1 length:sizeof(i1)];//18
}
i1 = 0x01;
[writer appendBytes:&i1 length:sizeof(i1)];//
//-------
i1 = 0x00;
[writer appendBytes:&i1 length:sizeof(i1)];//18
i1 = 0x00;
[writer appendBytes:&i1 length:sizeof(i1)];//18
i1 = 0x00;
[writer appendBytes:&i1 length:sizeof(i1)];//18
//--------
fuck=[[self.VideoListArray objectAtIndex:j] length];
NSLog(@"len:%d",fuck);
i1 = (Byte)((fuck&0x00FF0000)>>24);
[writer appendBytes:&i1 length:sizeof(i1)];//16
i1 = (Byte)((fuck&0x00FF0000)>>16);
[writer appendBytes:&i1 length:sizeof(i1)];//16
i1 = (Byte)((fuck&0x0000FF00)>>8);
[writer appendBytes:&i1 length:sizeof(i1)];//17
i1 = (Byte)(fuck&0x000000FF);
[writer appendBytes:&i1 length:sizeof(i1)];//18
[writer appendData:[self.VideoListArray objectAtIndex:j]];
//---------
time_h=time_h+40;//采样率是11500时候,是40递增,加倍就是80递增
}//for
//-----------------------
fuck=videoTagFixLen+[[self.VideoListArray objectAtIndex:([self.VideoListArray count]-1)] length];
// NSLog(@"len:%d",fuck);
i1 = (Byte)((fuck&0x00FF0000)>>24);
[writer appendBytes:&i1 length:sizeof(i1)];//16
i1 = (Byte)((fuck&0x00FF0000)>>16);
[writer appendBytes:&i1 length:sizeof(i1)];//16
i1 = (Byte)((fuck&0x0000FF00)>>8);
[writer appendBytes:&i1 length:sizeof(i1)];//17
i1 = (Byte)(fuck&0x000000FF);
[writer appendBytes:&i1 length:sizeof(i1)];//18
[writer writeToFile:realPath atomically:YES];
}
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end