libRTMP 推流FLV文件

效果图如下:


861656723893_.pic.jpg

首先就是介绍一下项目中用到的工具 推流工具FLV文件分析工具

这里必须吐槽一下好用的流媒体分析工具都是windos端上的,MAC OS 上能用的工具少之又少,希望自己有一天也有实力造出来一些好用的工具提供给大家;

image.png

预览推流的工具 Railgun Cast;
FLV流解析工具 QHFlvParserMan
进制在线转换工具 https://tool.oschina.net/hexconvert;
转码推流 FFmpeg

视频截图中的人物有点马赛克的的感觉,后来文章写完发现是错误的 ffmpeg命令生成错误编码的flv导致的;

本文的分析视频文件 编码格式是 视频 h.263 + 音频 mp3的格式撰写的;

1.获取flv 文件

 ffmpeg -i /Users/pengchao/Documents/unknown.mp4  /Users/pengchao/Documents/download.flv

保证编码格式不变使用该命令

 ffmpeg -i /Users/pengchao/Documents/unknown.mp4 -c copy /Users/pengchao/Documents/download.flv

2.开启直播服务器

打开软件 Railgun Cast 获取推流地址 rtmp://192.168.0.100:57139/railgun/test;

3.解析FLV

在讲代码之前还是按照惯例对流协议进行一下讲解;

image.png

在文章 FLV格式解析 中,我们了解到一个FLV文件是有 FLV HeaderFLV Body 组成的,FLV Body 又由 Video Tag ,Audio Tag ,Script Tag三种Tag 组成, 更详细的解析可以看这篇文章,加深对FLV 格式的了解FLV格式解析;

重点、重点、重点
Video Tag ,Audio Tag ,Script Tag 都是由Tag Header 组成,三者的Tag Body 结构不一样;
重点、重点、重点

3.1 FLV Header

FLV Header占9个字节; 分别代表的含义如下;

image.png

通过Xcode 查看内存中文件中的16进制,前9个bytes 是46 4C 56 01 05 00 00 00 09,通过换算后,正是 FLV Header

通过 QHFlvParserMan工具,也可以看到FLV Header9 byte的内容 并佐证其中代表的含义;

image.png

3.2 FLV TAG

参考FLV 格式的定义,FLV Header后面 应该是一个 FLV TAG ,第一个字节0x12,通过换算后,这是一个 Scripty Tag 类型的TAG
Script Tag又通常被称为MetadataTag,会放一些关于FLV视频和音频的元数据信息如:duration、width、height等。通常该类型Tag会跟在FileHeader后面作为第一个Tag出现,而且只有一个。
在本demo 中 script Tag 可以不推到服务器不影响解码,因此 Script Tag 我们跳过;

FLV Common TAG Header
3.1 FLV TAG【 Script Tag 】

在拿到了 Script Tag 的长度后,移动指针,指向下一个 TAG, 获取第一个字节为0x12, 用 10进制表示18,说明这是一个Script Tag,(由于 三种 FLV Tag 的格式不一样,所以针对不同的FLV TAG需要单独解析),参考 Script Tag Body 的格式。11 bytesFLV TAG header格式一致,后3bytes00 01 A4 表示Script Tag 的长度 ,用10进制表示 420,因此Script tag的length是 420, 就知道了指针移动多少距离可以找到下一个TAG

参考 QHFlvParserMan 工具,佐证我们格式分析的是否正确;

xxx
3.2 FLV TAG【Audio Tag】

指针接着移动 420 + 4 的长度,420 是上一个Script TAG的长度 ,第二个TAG 的第一个bytes 是 0x08 ,表示这是一个Audio Tag,由于 TAG Header都是固定的11 位,所以分析起来和 Script Tag一样,后面紧跟着是3bytes DataSize3bytes timeStamp1bytes extend TimeStamp3bytes streamID

重点、重点、重点 Audio Tag Body格式参考下图
找到 FLV TAG Header 的11 个字节后 08 00 01 A2 00 00 00 00 00 00 00,紧跟着 第一个字节是 0x2F用二进制表示 00101111 ;

第1、4位bit 0010 表示 内部音频编码格式是mp3
第5、6位bit 11 编码采样率 44k-HZ;
第7位1音频采样深度 16Bit
第8位1 声道数 2;
通过ffprobe查看 flv 的信息 和上面的信息是 一致的 (Audio 类型,mp3,双声道,44100hz);

Duration: 00:00:30.10, start: 0.000000, bitrate: 2007 kb/s
  Stream #0:0: Audio: mp3, 44100 Hz, stereo, fltp, 128 kb/s
  Stream #0:1: Video: flv1, yuv420p, 720x1280, 200 kb/s, 30 fps, 30 tbr, 1k tb

音频 Audio Tag Data参考如下图的格式

音频 Audio Tag Data

QHFlvParserMan分析工具获得的数据,可以作证我们格式分析的正确性;

Audio Tag Data

3.2 FLV TAG【Video Tag】

在拿到了上一个 Video Tag 的长度后,移动指针 418个长度,指向下一个TAG, 获取第一个字节,0x09 用10进制表示09 ,这是Video Tag
Video Tag Header 也是11 byte 字节表示的,09 00 EC 14 00 00 19 00 00 00 00

找到找到Header 后的第一个字节 是 0x12 二进制表示为 00010010; 参考下图 VideoTag Data 部分的解释
第1到4bit 0001表示 这是一个关键帧;
第5到6bit 0010 表示这个是 h.263 ;

震惊、震惊、震惊
,竟然碰到了h.263 ,这是真的吗 、;通过 ffprobe -show_streams download.flv命令,我再次确认了一下 flv 文件的Video Stream 的编码格式,发现这竟然是真的(发现原因是我生成 flv 的 ffmpeg 命令没有copy原来的编码方式)

index=1
codec_name=flv1
codec_long_name=FLV / Sorenson Spark / Sorenson H.263 (Flash Video)

这里我调整了一下 生成flv 的命令,保证生成的FLV 文件的编码格式和 原始MP4文件的编码格式不变,音频为AAC ,视频为 H.264

 ffmpeg -i /Users/pengchao/Documents/unknown.mp4 -c copy  /Users/pengchao/Documents/download.flv

编码方式和 MP4 的文件 一致了

  Stream #0:0: Video: h264 (High), yuv420p(tv, smpte170m/smpte432/bt709, progressive), 720x1280 [SAR 1:1 DAR 9:16], 1531 kb/s, 28.24 fps, 30 tbr, 1k tbn
  Stream #0:1: Audio: aac (LC), 44100 Hz, stereo, fltp, 128 kb/s

Video Tag Data 部分的解释 如下:

Video Tag Data 部分的解释

image.png

4.发送音频包和视频包

本来只是想写一下libRTMP 怎么用的,结果写着写着,变成了格式分析了,那先这样吧;

RTMP_Alloc():为结构体“RTMP”分配内存。
RTMP_Init():初始化结构体“RTMP”中的成员变量。
RTMP_SetupURL():设置输入的RTMP连接的URL。
RTMP_EnableWrite():发布流的时候必须要使用。如果不使用则代表接收流。
RTMP_Connect():建立RTMP连接,创建一个RTMP协议规范中的NetConnection。
RTMP_ConnectStream():创建一个RTMP协议规范中的NetStream。
Delay:发布流过程中的延时,保证按正常播放速度发送数据。
RTMP_SendPacket():发送一个RTMP数据RTMPPacket。
RTMP_Close():关闭RTMP连接。
RTMP_Free():释放结构体“RTMP”。

具体解析FLV 文件的流程可以参考如下demo源码;整体流程就是,初始化RTMP上下文,配置必要的参数, 解析FLV文件,找到FLV Header,然后找到FLV Tag,解析FLV Tag ,然后将其封装成 RTMP Packet,继续找到下一个FLV Tag 解析并封装成RTMP Packet,如此循环,直到结束;

源码如下:

int push_packet(char* infile,char* rtmp_server_path){
    RTMP *rtmp = NULL;
    //RTMPPacket *packet = NULL;
    uint32_t start_time = 0;
    uint32_t now_time = 0;
    //上一帧时间戳
    long pre_frame_time = 0;
    long lasttime = 0;
    //下一帧是否是关键帧
    int bNextIsKey = 1;
    uint32_t preTagSize = 0;
    uint32_t type = 0;
    uint32_t datalength = 0;
    uint32_t timestamp = 0;
    uint32_t streamid = 0;
    FILE* fp = NULL;
    fp = fopen(infile,"rb");
    if(!fp) {
        RTMP_LogPrintf("Open File Error\n");
        return -1;
    }

    //为结构体“RTMP”分配内存
    rtmp = RTMP_Alloc();
    if(!rtmp){
        RTMP_LogPrintf("RTMP_Alloc failed\n");
        return -1;
    }
    //初始化结构体“RTMP”中的成员变量
    RTMP_Init(rtmp);
    rtmp->Link.timeout = 5;
    if(!RTMP_SetupURL(rtmp, rtmp_server_path)){
        RTMP_Log(RTMP_LOGERROR,"SetupURL Err\n");
        RTMP_Free(rtmp);
        return -1;
    }
    //发布流的时候必须要使用。如果不使用则代表接收流。
    RTMP_EnableWrite(rtmp);
    //建立RTMP连接,创建一个RTMP协议规范中的NetConnection
    if (!RTMP_Connect(rtmp,NULL)){
                   RTMP_Log(RTMP_LOGERROR,"Connect Err\n");
                   RTMP_Free(rtmp);
                   return -1;
    }
    //创建一个RTMP协议规范中的NetStream
     if (!RTMP_ConnectStream(rtmp,0)){
                   RTMP_Log(RTMP_LOGERROR,"ConnectStream Err\n");
                   RTMP_Close(rtmp);
                   RTMP_Free(rtmp);
                   return -1;
    }

    RTMPPacket *packet  = malloc(sizeof(RTMPPacket));
    RTMPPacket_Alloc(packet,1024*64);
    RTMPPacket_Reset(packet);
    packet->m_hasAbsTimestamp = 0;
    packet->m_nChannel = 0x04;
    packet->m_nInfoField2 = rtmp->m_stream_id;
    RTMP_LogPrintf("Start to send data ...\n");
    //跳过flv的9个头字节
    fseek(fp,9,SEEK_SET);
       //跳过previousTagSize所占的4个字节
    fseek(fp,4,SEEK_CUR);
    
    start_time=RTMP_GetTime();
    while(1)
    {
        //如果下一帧是关键帧,且上一帧的时间戳比现在的时间还长(外部时钟),说明推流速度过快了,可以延时下
        if((((now_time=RTMP_GetTime())-start_time)<(pre_frame_time)) && bNextIsKey)
        {
            //wait for 1 sec if the send process is too fast
             //这个机制不好,需要改进
             if(pre_frame_time>lasttime){
                  RTMP_LogPrintf("TimeStamp:%8lu ms\n",pre_frame_time);
                  lasttime=pre_frame_time;
             }
            sleep(0.1);//等待1秒
            continue;
        }
         //not quite the same as FLV spec
        if(!ReadU8(&type,fp))//读取type
            break;
        if(!ReadU24(&datalength,fp))//读取datalength的长度
             break;
        if(!ReadTime(×tamp,fp))//从flv的head读取时间戳
             break;
        if(!ReadU24(&streamid,fp))//读取streamid的类型
             break;
        if(type!=0x08&&type!=0x09){//如果不是音频或视频类型
            //jump over non_audio and non_video frame,
            //jump over next previousTagSizen at the same time
            printf("seek offset :%d",(datalength +4));
            fseek(fp,datalength+4,SEEK_CUR);//跳过脚本以及脚本后的previousTagSize
            continue;
        }
        //把flv的音频和视频数据写入到packet的body中
        if(fread(packet->m_body,1,datalength,fp)!=datalength)
            break;
        //packet header  大尺寸
        packet->m_headerType = RTMP_PACKET_SIZE_LARGE;
        packet->m_nTimeStamp = timestamp;//时间戳
        packet->m_packetType = type;//包类型
        packet->m_nBodySize  = datalength;//包大小
        pre_frame_time=timestamp;//把包读取到的时间戳赋值给上一帧时间戳变量
        if (!RTMP_IsConnected(rtmp)){//确认连接
             RTMP_Log(RTMP_LOGERROR,"rtmp is not connect\n");
             break;
        }
        RTMP_Log(RTMP_LOGINFO,"send packet\n");
        //发送一个RTMP数据RTMPPacket
       if (!RTMP_SendPacket(rtmp,packet,0)) {
             RTMP_Log(RTMP_LOGERROR,"Send Error\n");
             break;
       }
       if(!ReadU32(&preTagSize,fp))
               break;
       if(!PeekU8(&type,fp))//去读下一帧的类型
           break;
       if(type==0x09){//如果下一帧是视频
            if(fseek(fp,11,SEEK_CUR)!=0)
                 break;
            if(!PeekU8(&type,fp)){
                 break;
            }
            if(type==0x17)//如果视频帧是关键帧
                bNextIsKey=1;
            else
                bNextIsKey=0;
            fseek(fp,-11,SEEK_CUR);
       }
    }
   RTMP_LogPrintf("\nSend Data Over!\n");
        
   if(fp) {
      fclose(fp);
   }
   if (rtmp!=NULL){
      //关闭RTMP连接
      RTMP_Close(rtmp);
      //释放结构体RTMP
      RTMP_Free(rtmp);
      rtmp=NULL;
   }
   if (packet!=NULL){
      RTMPPacket_Free(packet);
      free(packet);
      packet=NULL;
   }
   return 0;
    
}


5.总结

源码地址 https://github.com/hunter858/OpenGL_Study/AVFoundation/libRTMP_demo

本文创作参考文章: 基于librtmp的推流实现

861656723893_.pic.jpg

你可能感兴趣的:(libRTMP 推流FLV文件)