h264视频流,aac音频流(g711a转码)推送至rtmp服务器

这篇博客是写给新手的,我就不介绍rtmp流的格式了,因为是直接使用rtmp的库的,rtmp的格式对我的程序没有什么影响。对于h264视频流和aac音频格式简要的介绍一下。

h264视频格式:

下图是h264的视频序列,但是这样描述的不太好(对我而言)

h264视频码流

sps pps I 帧 B帧 ……P帧
sps pps I 帧 B帧 ……P帧
sps pps I 帧 B帧 ……P帧

就像上面一样,只不过B帧和P帧之间可能有其他的不同数目的B帧和P帧,但是一定是I帧开始,P帧结束,然后再来一个类似的结构,然后就是区分不同的帧的类型了。
首先说一下我的rtmp封包的处理原则(所谓的处理原则其实就是rtmp包体前封装不同的数据)对于未列举的数据帧不太重要,就不用考虑其封包规则

  • 对于每个NAL块的标识头都去掉,因为这部分信息对rtmp流服务器没用
  • 对sps、pps单独处理,把sps、pps封成一个rtmp包发送出去,这部分需要添加AVCDecodeConfigurationRecord,这个的具体含义,有兴趣的可以去查查。
  • 对于I帧区别对待,因为I帧是关健帧(如果I帧丢失,I帧后面的都要丢掉,直到下一个I帧,但是这部分我们不用管)
  • 其他的数据就按照普通数据的处理
知道了结构我们就可以对H264视频流进行封包了
  • 首先对RTMPserver初始化。
int RTMP264_Connect(const char *push_url)
{
    //由于摄像头的timestamp是一直在累加,需要每次得到相对时间戳
    //分配与初始化
    rtmp = RTMP_Alloc();
    RTMP_Init(rtmp);

    //设置URL
    if (RTMP_SetupURL(rtmp, push_url) < 0)
    {
        //log(LOG_ERR, "RTMP_SetupURL() failed!");
        RTMP_Free(rtmp);
        return -1;
    }

    //设置可写,即发布流,这个函数必须在连接前使用,否则无效
    RTMP_EnableWrite(rtmp);

    //连接服务器
    if (RTMP_Connect(rtmp, NULL) < 0)
    {
        //log(LOG_ERR, "RTMP_Connect() failed!");
        RTMP_Free(rtmp);
        return -1;    
    }

    //连接流
    if (RTMP_ConnectStream(rtmp, 0) < 0)
    {
        //log(LOG_ERR, "RTMP_ConnectStream() failed!");
        RTMP_Close(rtmp);
        RTMP_Free(rtmp);
        return -1;
    }
    return 0;
}
  • 初始化之后对h264数据封包(代码基本固定)
int SendVideoSpsPps(unsigned char *sps, int sps_len, unsigned char *pps, int pps_len, int timestamp, const char *push_url)
{
    RTMPPacket *packet = NULL; //rtmp packet
    unsigned char *body = NULL;
    int i;
    packet = (RTMPPacket *)malloc(RTMP_HEAD_SIZE + 1024);
    memset(packet, 0, RTMP_HEAD_SIZE + 1024);
    packet->m_body = (char *)packet + RTMP_HEAD_SIZE;
    body = (unsigned char *)packet->m_body;
    if(sps[2] == 0x00)
    {
        sps += 4;
        sps_len -= 4;
    } else if (sps[2] == 0x01)
    {
        sps += 3;
        sps_len -= 3;
    }
    if(pps[2] == 0x00)
    {
        pps += 4;
        pps_len -= 4;
    } else if (pps[2] == 0x01)
    {
        pps += 3;
        pps_len -= 3;
    }


    i = 0;
    body[i++] = 0x17;
    body[i++] = 0x00; 
    body[i++] = 0x00;
    body[i++] = 0x00;
    body[i++] = 0x00;

    // AVCDecodeConfigurationRecord
    body[i++] = 0x01;
    body[i++] = sps[1];
    body[i++] = sps[2];
    body[i++] = sps[3];
    body[i++] = 0xff;

    //sps
    body[i++] = 0xe1;
    body[i++] = (sps_len >> 8) & 0xff;
    body[i++] = sps_len & 0xff;
    memcpy(&body[i], sps, sps_len);
    i += sps_len;

    //pps
    body[i++] = 0x01;
    body[i++] = (pps_len >> 8) & 0xff;
    body[i++] = (pps_len) & 0xff;
    memcpy(&body[i], pps, pps_len);
    i += pps_len;

    packet->m_packetType = RTMP_PACKET_TYPE_VIDEO;
    packet->m_nBodySize = i;
    packet->m_nChannel = 0x04;
    packet->m_nTimeStamp = timestamp & 0xffffff;
    packet->m_hasAbsTimestamp = 0;
    packet->m_headerType = RTMP_PACKET_SIZE_MEDIUM;
    packet->m_nInfoField2 = rtmp->m_stream_id;

    if (!RTMP_IsConnected(rtmp))
    {
        RTMP264_Connect(push_url);
        printf("sps pps trying to connect to rtmp server...\n");
    }
    if (!RTMP_IsConnected(rtmp))
    {
        RTMP264_Connect(push_url);
        printf("sps pps trying to connect to rtmp server...\n");
    }
    if (!RTMP_IsConnected(rtmp))
    {
        RTMP264_Connect(push_url);
        printf("sps pps trying to connect to rtmp server...\n");
    }
    //调用发送接口
    int nRet = RTMP_SendPacket(rtmp, packet, true);
    free(packet);

    return nRet;
}
  • 处理普通视频数据
int send_rtmp_video(unsigned char* buf, int len, int timestamp, const char *push_url)
{
    int type;
    long timeoffset;
    RTMPPacket *packet;
    unsigned char *body;

    timeoffset = timestamp;

    //去掉帧界定符
    if(buf[2] == 0x00)
    {
        buf += 4;
        len -= 4;
    } else if (buf[2] == 0x01)
    {
        buf += 3;
        len -= 3;
    }
    type = buf[0]&0x1f;

    packet = (RTMPPacket *)malloc(RTMP_HEAD_SIZE + len + 9);
    memset(packet, 0, RTMP_HEAD_SIZE);

    packet->m_body = (char *)packet + RTMP_HEAD_SIZE;
    packet->m_nBodySize = len +9;

    // send video packet
    body = (unsigned char *)packet->m_body;
    memset(body, 0, len + 9);

    // key frame
    body[0] = 0x27;
    //if (type == NAL_SLICE_IDR) type = 5是关键帧
    // 在此处还可以判断是否是SEI帧,如果是丢掉,否则播放的时候会报错(虽然不影响播放效果)
    if (type == 5)
    {
        body[0] = 0x17;
    }else if (type == 6){
        free(packet);
        return 0;
    }

    body[1] = 0x01; //nal unit
    body[2] = 0x00;
    body[3] = 0x00;
    body[4] = 0x00;

    body[5] = (len >> 24) & 0xff;
    body[6] = (len >> 16) & 0xff;
    body[7] = (len >>  8) & 0xff;
    body[8] = (len) & 0xff;

    // copy data
    memcpy(&body[9], buf, len);

    packet->m_hasAbsTimestamp = 0;
    packet->m_packetType = RTMP_PACKET_TYPE_VIDEO;
    packet->m_nInfoField2 = rtmp->m_stream_id;
    packet->m_nChannel = 0x04;
    packet->m_headerType = RTMP_PACKET_SIZE_LARGE;
    packet->m_nTimeStamp = timeoffset & 0xffffff;

    // 调用发送接口
    if (!RTMP_IsConnected(rtmp))
    {
        RTMP264_Connect(push_url);
        printf("video normal data trying to connect to rtmp server...\n");
    }
    if (!RTMP_IsConnected(rtmp))
    {
        RTMP264_Connect(push_url);
        printf("video normal data trying to connect to rtmp server...\n");
    }
    if (!RTMP_IsConnected(rtmp))
    {
        RTMP264_Connect(push_url);
        printf("video normal data trying to connect to rtmp server...\n");
        if (!RTMP_IsConnected(*rtmp))
            return -1;
    }
    int nRet = RTMP_SendPacket(rtmp, packet, true);
    free(packet);
    return nRet;
}
接下来是针对音频数据了

aac发送须知

  • 发送音频包之前要先发送AudioDecoderSpecificInfo
  • 音频包分隔单元前7个字节是没有用的,直接去掉
  • 在封装到rtmp流时,发送AudioDecoderSpecificInfo时,rtmp包主体前要添加0xAF 00,普通数据包体前添加0xAF 01,我就当时没注意,搞了好长时间都没声音,还是同事发现的。
  • 发送aac AudioDecoderSpecificInfo
int rtmp_sendaac_spec(unsigned char *spec_buf,int spec_len, int timestamp, const char *push_url)
{
    //if (NULL == spec_buf)
    //{
    //    printf("get spec_buf badly\n");
    //    return -1;
    //}
    RTMPPacket * packet;
    unsigned char * body;
    int len;

    len = spec_len;  /*spec data长度,一般是2*/

    packet = (RTMPPacket *)malloc(RTMP_HEAD_SIZE+len+2);
    memset(packet,0,RTMP_HEAD_SIZE);

    packet->m_body = (char *)packet + RTMP_HEAD_SIZE;
    body = (unsigned char *)packet->m_body;

    /*AF 00 + AAC RAW data*/
    body[0] = 0xAF;
    body[1] = 0x00;
    memcpy(&body[2],spec_buf,len); /*spec_buf是AAC sequence header数据*/
    //memcpy(&body[2], spec_buf, spec_len);

    packet->m_packetType = RTMP_PACKET_TYPE_AUDIO;
    packet->m_nBodySize = len + 2;
    packet->m_nChannel = 0x04;
    packet->m_hasAbsTimestamp = 0;
    packet->m_nTimeStamp = timestamp&0xffffff;
    packet->m_headerType = RTMP_PACKET_SIZE_LARGE;
    packet->m_nInfoField2 = rtmp->m_stream_id;

    /*调用发送接口*/
    if (!RTMP_IsConnected(rtmp))
    {
        RTMP264_Connect(push_url);
        printf("audio spec trying to connect to rtmp server...\n");
    }                          
    if (!RTMP_IsConnected(rtmp))
    {
        RTMP264_Connect(push_url);
        printf("audio spec trying to connect to rtmp server...\n");
    }                          
    if (!RTMP_IsConnected(rtmp))
    {
        RTMP264_Connect(push_url);
        printf("audio spec trying to connect to rtmp server...\n");
        if (!RTMP_IsConnected(*rtmp))
            return -1;
    }                          
    int nRet = RTMP_SendPacket(rtmp, packet, true);
    free(packet);
    return nRet;    
}

其中有必要解释一下spec_buf和spec_len就是AudioDecoderSpecificInfo
可以通过faacEncGetDecoderSpecificInfo得到。对于一般情况44100Hz双声道,这个值是0x1210

char *buf;
int len;
faacEncGetDecoderSpecificInfo(fh,&buf,&len);

memcpy(spec_buf,buf,len);
spec_len = len;

/*释放系统内存*/
free(buf);
  • 对于其他普通的音频数据
int send_aac_audio(unsigned char *buf, int len, int timestamp, const char *push_url)
{

    // 去掉前七个无用字节
    buf = buf + 7;
    len = len - 7;
    long timeoffset;
    RTMPPacket *packet;
    unsigned char *body;
    timeoffset = timestamp;
    packet = (RTMPPacket *)malloc(RTMP_HEAD_SIZE + len + 2);
    memset(packet, 0, RTMP_HEAD_SIZE);
    packet->m_body = (char *)packet + RTMP_HEAD_SIZE;
    body = (unsigned char *)packet->m_body;
    memset(body, 0, len + 2);
    body[0] = 0xAF;
    body[1] = 0x01;
    memcpy(&body[2], buf, len);

    packet->m_body = (char *)packet + RTMP_HEAD_SIZE;
    packet->m_nBodySize = len + 2;
    packet->m_hasAbsTimestamp = 0;
    packet->m_packetType = RTMP_PACKET_TYPE_AUDIO;
    packet->m_nInfoField2 = rtmp->m_stream_id;
    packet->m_nChannel = 0x04;
    packet->m_headerType = RTMP_PACKET_SIZE_MEDIUM;
    packet->m_nTimeStamp = timeoffset & 0xffffff;

    // 调用发送接口  
    if (!RTMP_IsConnected(rtmp))
    {
        RTMP264_Connect(push_url);
        printf(" audio trying to connect to rtmp server...\n");
    }                          
    if (!RTMP_IsConnected(rtmp))
    {
        RTMP264_Connect(push_url);
        printf(" audio trying to connect to rtmp server...\n");
    }                          
    if (!RTMP_IsConnected(rtmp))
    {
        RTMP264_Connect(push_url);
        printf(" audio trying to connect to rtmp server...\n");
    }                          
    int nRet = RTMP_SendPacket(rtmp, packet, true);
    free(packet);
    return nRet;
}

在此有必要说一下rtmp服务器不清楚是否支持线程安全,所以最好采用队列发送,另外如果出现一下错误,一定要记得检查一下时间戳的问题

  • ERROR: WriteN, RTMP send error 32 (136 bytes)
  • ERROR: WriteN, RTMP send error 32 (39 bytes)
  • ERROR: WriteN, RTMP send error 9 (42 bytes)

    音视频在一个队列里面发送也会出现时间戳的问题,音视频的时间戳相差时间不要太大,要不然就会报以上错误。

最后说一下g711a转码的问题,我是直接调用的转码库,大家可以搜一下,估计都比我的要好,因为我的库预测是内部人员写的,到现在还出现内存泄露的问题,估计到时候我还要改一下。
参考文章
  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值