websocket flv 客户端解封包

Posted qianbo_insist

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了websocket flv 客户端解封包相关的知识,希望对你有一定的参考价值。

目的

在GB28181 传输过程中,为了达到最大效率的结果,只制作webscoket传输和flv传输,本身flv是可以在web端展示的,如果是h265等等编码,可能在web端解码效率不够的情况下,我们需要客户端来连接服务器,并且

websocket 对比http协议

webscoket协议确实只是http协议的升级,问题就是websocket协议本身加入了字节协议,也就是数据帧的概念,这个协议推出很有必要,为什么呢?
前端html使用javascript程序员本身很难对tcp协议理解,http协议大家都能理解,文本协议,所以他以\\r\\n\\r\\n来结束协议。websocket的特点除了长链接,还有可以传输二进制的特点,注意http协议本身可以传输二进制,如
1、图像
2、视频
以content-length来标识数据长度,也可以断电续传。http协议本身也是一个非常值得研究的协议。
以Range:byte=来 标识

Range:byte=1000- 代表从1000字节开始传输

2、flv 解封包

这样,websocket 的帧头字节数标识可以让websocket 协议获取单帧二进制内容

2.1 flv 数据结构定义

下面定义数据结构,我们应该都知道8 和 9 的含义,8代表音频,9代表视频,和rtmp协议是一致的,实际上,rtmp协议本身内部是flv格式封装

#define FLV_TAG_AUDIO 0x08
#define FLV_TAG_VIDEO 0x09

#define FLV_VIDEO_KEY_FLAG   0x10
#define FLV_VIDEO_INTER_FLAG 0x20

#define FLV_VIDEO_AVC_SEQHDR 0x00
#define FLV_VIDEO_AVC_NALU   0x01

#define FLV_VIDEO_H264_CODEC 0x07
#define FLV_VIDEO_H265_CODEC 0x0c
#define FLV_VIDEO_AV1_CODEC  0x0d
#define FLV_VIDEO_VP8_CODEC  0x0e
#define FLV_VIDEO_VP9_CODEC  0x0f

#define FLV_AUDIO_OPUS_CODEC  0x90
#define FLV_AUDIO_AAC_CODEC   0xa0

/* offsets for packed values */
#define FLV_AUDIO_SAMPLESSIZE_OFFSET 1
#define FLV_AUDIO_SAMPLERATE_OFFSET  2
#define FLV_AUDIO_CODECID_OFFSET     4

enum 
    FLV_MONO = 0,
    FLV_STEREO = 1,
;

enum 
    FLV_SAMPLESSIZE_8BIT = 0,
    FLV_SAMPLESSIZE_16BIT = 1 << FLV_AUDIO_SAMPLESSIZE_OFFSET,
;

enum 
    FLV_SAMPLERATE_SPECIAL = 0, /**< signifies 5512Hz and 8000Hz in the case of NELLYMOSER */
    FLV_SAMPLERATE_11025HZ = 1 << FLV_AUDIO_SAMPLERATE_OFFSET,
    FLV_SAMPLERATE_22050HZ = 2 << FLV_AUDIO_SAMPLERATE_OFFSET,
    FLV_SAMPLERATE_44100HZ = 3 << FLV_AUDIO_SAMPLERATE_OFFSET,
;
typedef enum 
    MEDIA_CODEC_UNKOWN = 0,
    MEDIA_CODEC_H264 = 1,
    MEDIA_CODEC_H265,
    MEDIA_CODEC_VP8,
    MEDIA_CODEC_VP9,
    MEDIA_CODEC_AAC = 100,
    MEDIA_CODEC_OPUS,
    MEDIA_CODEC_MP3
 MEDIA_CODEC_TYPE;

typedef enum 
    MEDIA_FORMAT_UNKOWN = 0,
    MEDIA_FORMAT_RAW,
    MEDIA_FORMAT_FLV,
    MEDIA_FORMAT_MPEGTS,
 MEDIA_FORMAT_TYPE;
typedef enum 
    MEDIA_UNKOWN_TYPE = 0,
    MEDIA_VIDEO_TYPE = 1,
    MEDIA_AUDIO_TYPE,
    MEDIA_METADATA_TYPE
 MEDIA_PKT_TYPE;

typedef int(*av_callback)(uint8_t* pkt_ptr, int len);

#define FLV_RET_NEED_MORE    1

#define FLV_HEADER_LEN     9
#define FLV_TAG_PRE_SIZE   4
#define FLV_TAG_HEADER_LEN 11

struct webso_flv_data

    uint8_t* pkt = 0;
    int len;
    MEDIA_PKT_TYPE av_type_ = MEDIA_UNKOWN_TYPE;
    MEDIA_CODEC_TYPE codec_type_ = MEDIA_CODEC_UNKOWN;
    MEDIA_FORMAT_TYPE fmt_type_ = MEDIA_FORMAT_UNKOWN;
    int64_t dts_ = 0;
    int64_t pts_ = 0;
    bool is_key_frame_ = false;
    bool is_seq_hdr_ = false;
    uint32_t ssrc = 0;

;

封装class

class flv_demuxer

public:
    flv_demuxer(av_callback cb);
    ~flv_demuxer();

public:
    int input_packet(webso_flv_data* pkt_ptr);
    bool has_video() return has_video_;
    bool has_audio() return has_audio_;

private:
    int handle_packet(webso_flv_data* data);

private:
    av_format_callback* callback_ = nullptr;
    uint8_t* buffer_;
    uint32_t ssrc;
    bool has_video_ = false;
    bool has_audio_ = false;

private:
    bool flv_header_ready_ = false;
    bool tag_header_ready_ = false;

private:
    uint8_t tag_type_ = 0;
    uint32_t tag_data_size_ = 0;
    uint32_t tag_timestamp_ = 0;
;
#include "flv_demux.hpp"
//#include "logger.hpp"
#include "byte_stream.hpp"


flv_demuxer::flv_demuxer(av_callback cb):callback_(cb)



flv_demuxer::~flv_demuxer()



int flv_demuxer::handle_packet(webso_flv_data* data) 
    uint8_t* p = NULL;
    uint32_t ts_delta = 0;
    int plen = 0;
    if (!flv_header_ready_) 

        if (data->len < (FLV_HEADER_LEN + FLV_TAG_PRE_SIZE)) 
            return FLV_RET_NEED_MORE;
        

        p = (uint8_t*)data->pkt;
        plen = data->len;

        if ((p[0] != 'F') || (p[1] != 'L') || (p[2] != 'V')) 
            return -1;
        
        if ((p[4] & 0x01) == 0x01) 
            has_video_ = true;
        
        if ((p[4] & 0x04) == 0x04) 
            has_audio_ = true;
        

        if ((p[5] != 0) || (p[6] != 0) || (p[7] != 0) || (p[8] != 9)) 
            return -1;
        
        p += FLV_HEADER_LEN + FLV_TAG_PRE_SIZE;
        plen -= FLV_HEADER_LEN + FLV_TAG_PRE_SIZE;
        flv_header_ready_ = true;
    

    if (!tag_header_ready_) 
        if (plen < FLV_TAG_HEADER_LEN) 
            return FLV_RET_NEED_MORE;
        
        //p = (uint8_t*)buffer_.data();
        tag_type_ = p[0];
        p++;
        tag_data_size_ = read_3bytes(p);
        p += 3;
        tag_timestamp_ = read_3bytes(p);
        p += 3;
        tag_timestamp_ |= ((uint32_t)p[0]) << 24;

        tag_header_ready_ = true;
        p += FLV_TAG_HEADER_LEN;
        plen -= FLV_TAG_HEADER_LEN;
    

    if (plen < tag_data_size_ + FLV_TAG_PRE_SIZE)
        return FLV_RET_NEED_MORE;
    bool is_ready = true;
    int header_len = 0;

    data->fmt_type_ = MEDIA_FORMAT_RAW;
    data->ssrc = ssrc;
    if (tag_type_ == FLV_TAG_AUDIO) 
        header_len = 2;
        output_pkt_ptr->av_type_ = MEDIA_AUDIO_TYPE;

        if ((p[0] & 0xf0) == FLV_AUDIO_AAC_CODEC) 
            output_pkt_ptr->codec_type_ = MEDIA_CODEC_AAC;
         else if ((p[0] & 0xf0) == FLV_AUDIO_OPUS_CODEC) 
            output_pkt_ptr->codec_type_ = MEDIA_CODEC_OPUS;
         else 
            is_ready = false;
            printf("does not suport audio codec type:0x%02x", p[0]);
            return -1;
        
        if (p[1] == 0x00) 
            output_pkt_ptr->is_seq_hdr_ = true;
         else 
            output_pkt_ptr->is_key_frame_ = false;
            output_pkt_ptr->is_seq_hdr_   = false;
        
     
    else if (tag_type_ == FLV_TAG_VIDEO) 
        header_len = 2 + 3;
        data->av_type_ = MEDIA_VIDEO_TYPE;
        if ((p[0]&0x0f) == FLV_VIDEO_H264_CODEC) 
            data->codec_type_ = MEDIA_CODEC_H264;
         else if ((p[0]&0x0f) == FLV_VIDEO_H265_CODEC) 
            data->codec_type_ = MEDIA_CODEC_H265;
         else if ((p[0]&0x0f) == FLV_VIDEO_VP8_CODEC) 
            data->codec_type_ = MEDIA_CODEC_VP8;
         else if ((p[0]&0x0f) == FLV_VIDEO_VP9_CODEC) 
            data->codec_type_ = MEDIA_CODEC_VP9;
         else 
            is_ready = false;
            printf("does not support codec type:0x%02x.", p[0]);
            return -1;
        

        if ((p[0] & 0xf0) == FLV_VIDEO_KEY_FLAG) 
            if (p[1] == 0x00) 
                data->is_seq_hdr_ = true;
             else if (p[1] == 0x01) 
                data->is_seq_hdr_   = false;
                data->is_key_frame_ = true;
             else 
                is_ready = false;
                printf("unkown key byte:0x%02x.", p[1]);
                return -1;
            
        
        ts_delta = read_3bytes(p + 2);
     
    else 
        is_ready = false;
        tag_header_ready_ = false;
        printf("does not suport tag type:0x%02x", tag_type_);
        return 0;
    

    int ret = 0;
    if (is_ready && ((int)tag_data_size_ > header_len)) 
        data->dts_ = tag_timestamp_;
        data->pts_ = tag_timestamp_ + ts_delta;

        p += header_len;
        int len = tag_data_size_ - header_len;
        if(callback_!=NULL)
	        ret = callback_(p,len);
    
    tag_header_ready_ = false;
    return ret;

读写封装

在flv 格式容器读写的时候需要一些封装

double av_int2double(uint64_t i)

    union av_intfloat64 v;
    v.i = i;
    return v.f;


uint64_t av_double2int(double f)

    union av_intfloat64 v;
    v.f = f;
    return v.i;



uint64_t read_8bytes(const uint8_t* data) 
    uint64_t value = 0;
    uint8_t* output = (uint8_t*)&value;

    output[7] = *data++;
    output[6] = *data++;
    output[5] = *data++;
    output[4] = *data++;
    output[3] = *data++;
    output[2] = *data++;
    output[1] = *data++;
    output[0] = *data++;

    return value;


uint32_t read_4bytes(const uint8_t* data) 
    uint32_t value = 0;
    uint8_t* output = (uint8_t*)&value;

    output[3] = *data++;
    output[2] = *data++;
    output[1] = *data++;
    output[0] = *data++;

    return value;



uint32_t read_3bytes(const uint8_t* data) 
    uint32_t value = 0;
    uint8_t* output = (uint8_t*)&value;

    output[2] = *data++;
    output[1] = *data++;
    output[0] = *data++;

    return value;


uint16_t read_2bytes(const uint8_t* data) 
    uint16_t value = 0;
    uint8_t* output = (uint8_t*)&value;

    output[1] = *data++;
    output[0] = *data++;

    return value;


void write_8bytes(uint8_t* data, uint64_t value) 
    uint8_t* p = data;
    uint8_t* pp = (uint8_t*)&value;

    *p++ = pp[7];
    *p++ = pp[6];
    *p++ = pp[5];
    *p++ = pp[4];
    *p++ = pp[3];
    *p++ = pp[2];
    *p++ = pp[1];
    *p++ = pp[0];


void write_4bytes(uint8_t* data, uint32_t value) 
    uint8_t* p = data;
    uint8_t* pp = (uint8_t*)&value;

    *p++ = pp[3];
    *p++ = pp[2];
    *p++ = pp[1];
    *p++ = pp[0];


void write_2bytes_le(uint8_t* data, uint32_t value) 
    uint8_t* p = data;
    uint8_t* pp = (uint8_t*)&value;

    *p++ = pp[0];
    *p++ = pp[1];


void write_4bytes_le(uint8_t* data, uint32_t value) 
    uint8_t* p = data;
    uint8_t* pp = (uint8_t*)&value;

    *p++ = pp[0];
    *p++ = pp[1];
    *p++ = pp[2];
    *p++ = pp[3];


void write_3bytes(uint8_t* data, uint32_t value) 
    uint8_t* p = data;
    uint8_t* pp = (uint8_t*)&value;

    *p++ = pp[2];
    *p++ = pp[1];
    *p++ = pp[0];


void write_2bytes(uint8_t* data, uint16_t value) 
    uint8_t* p 直播服务器简单实现 http_flv和hls 内网直播桌面

封装静态路由解封

转载 iptables 禁用与 解封ip

以太网数据格式与封装解封

Oracle - 解封 Scott

k8s部署的vault ,自动解封(unseal)