欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

ZLmediakit关于mk_media_input_h264()输入时间戳的问题

程序员文章站 2022-07-02 08:34:26
...

在zlmediakit的mk_media_input_h264()函数中,我们输入pts和dts如果为0,则代码会自己生成时间戳,但是这个时间戳不平滑,输出视频有卡顿现象,如果输入原始视频流的时间戳,需要将时间戳转化为时间基为{1,1000}才可行。
看代码:

API_EXPORT void API_CALL mk_media_input_h264(mk_media ctx, void *data, int len, uint32_t dts, uint32_t pts) {
    assert(ctx && data && len > 0);
    MediaHelper::Ptr *obj = (MediaHelper::Ptr *) ctx;
    (*obj)->getChannel()->inputH264((char *) data, len, dts, pts);
}
void DevChannel::inputH264(const char *data, int len, uint32_t dts, uint32_t pts) {
//作者在这里自己生成时间戳,单位是毫秒,所以我们传入的也是毫秒
    if(dts == 0){
        dts = (uint32_t)_aTicker[0].elapsedTime();
    }
    if(pts == 0){
        pts = dts;
    }

    //由于rtmp/hls/mp4需要缓存时间戳相同的帧,
    //所以使用FrameNoCacheAble类型的帧反而会在转换成FrameCacheAble时多次内存拷贝
    //在此处只拷贝一次,性能开销更低
    auto frame = FrameImp::create<H264Frame>();
    frame->_dts = dts;
    frame->_pts = pts;
    frame->_buffer.assign(data, len);
    frame->_prefix_size = prefixSize(data,len);
    inputFrame(frame);
}

具体代码在H264RtpEncoder中有体现:


/**
 * 264 rtp打包类
 */
class H264RtpEncoder : public H264RtpDecoder ,public RtpInfo{
public:
    typedef std::shared_ptr<H264RtpEncoder> Ptr;

    /**
     * @param ssrc ssrc
     * @param mtu mtu大小
     * @param sample_rate 采样率,强制为90000
     * @param pt pt类型
     * @param interleaved rtsp interleaved
     */
    H264RtpEncoder(uint32_t ssrc,
                   uint32_t mtu = 1400,
                   uint32_t sample_rate = 90000,//记得这个值
                   uint8_t pt = 96,
                   uint8_t interleaved = TrackVideo * 2);
    ~H264RtpEncoder() {}

    /**
     * 输入264帧
     * @param frame 帧数据,必须
     */
    void inputFrame(const Frame::Ptr &frame) override;

private:
    void makeH264Rtp(const void *pData, size_t uiLen, bool bMark,  bool gop_pos, uint32_t uiStamp);
};



H264RtpEncoder::H264RtpEncoder(uint32_t ssrc, uint32_t mtu, uint32_t sample_rate, uint8_t pt, uint8_t interleaved)
        : RtpInfo(ssrc, mtu, sample_rate, pt, interleaved) {
}

void H264RtpEncoder::inputFrame(const Frame::Ptr &frame) {
    auto ptr = frame->data() + frame->prefixSize();
    auto len = frame->size() - frame->prefixSize();
    //frame内部的pts就是我们传入的pts
    auto pts = frame->pts();
    auto nal_type = H264_TYPE(ptr[0]);
    auto packet_size = getMaxSize() - 2;

    //末尾5bit为nalu type,固定为28(FU-A)
    auto fu_char_0 = (ptr[0] & (~0x1F)) | 28;
    auto fu_char_1 = nal_type;
    FuFlags *fu_flags = (FuFlags *) (&fu_char_1);
    fu_flags->start_bit = 1;

    //超过MTU则按照FU-A模式打包
    if (len > packet_size + 1) {
        size_t offset = 1;
        while (!fu_flags->end_bit) {
            if (!fu_flags->start_bit && len <= offset + packet_size) {
                //FU-A end
                packet_size = len - offset;
                fu_flags->end_bit = 1;
            }

            //传入nullptr先不做payload的内存拷贝
            auto rtp = makeRtp(getTrackType(), nullptr, packet_size + 2, fu_flags->end_bit, pts);
            //rtp payload 负载部分
            uint8_t *payload = rtp->getPayload();
            //FU-A 第1个字节
            payload[0] = fu_char_0;
            //FU-A 第2个字节
            payload[1] = fu_char_1;
            //H264 数据
            memcpy(payload + 2, (uint8_t *) ptr + offset, packet_size);
            //输入到rtp环形缓存
            RtpCodec::inputRtp(rtp, fu_flags->start_bit && nal_type == H264Frame::NAL_IDR);

            offset += packet_size;
            fu_flags->start_bit = 0;
        }
    } else {
        //如果帧长度不超过mtu, 则按照Single NAL unit packet per H.264 方式打包
        makeH264Rtp(ptr, len, false, false, pts);
    }
}

void H264RtpEncoder::makeH264Rtp(const void* data, size_t len, bool mark, bool gop_pos, uint32_t uiStamp) {
//makeRtp()这个函数内部会生成pts
    RtpCodec::inputRtp(makeRtp(getTrackType(), data, len, mark, uiStamp), gop_pos);
}

//具体代码如下:
RtpPacket::Ptr RtpInfo::makeRtp(TrackType type, const void* data, size_t len, bool mark, uint32_t stamp) {
    uint16_t payload_len = (uint16_t) (len + RtpPacket::kRtpHeaderSize);
    auto rtp = RtpPacket::create();
    rtp->setCapacity(payload_len + RtpPacket::kRtpTcpHeaderSize);
    rtp->setSize(payload_len + RtpPacket::kRtpTcpHeaderSize);
    rtp->sample_rate = _sample_rate;
    rtp->type = type;

    //rtsp over tcp 头
    auto ptr = (uint8_t *) rtp->data();
    ptr[0] = '$';
    ptr[1] = _interleaved;
    ptr[2] = payload_len >> 8;
    ptr[3] = payload_len & 0xFF;

    //rtp头
    auto header = rtp->getHeader();
    header->version = RtpPacket::kRtpVersion;
    header->padding = 0;
    header->ext = 0;
    header->csrc = 0;
    header->mark = mark;
    header->pt = _pt;
    header->seq = htons(_seq++);
    //stamp就是当时你传入的时间戳,从这里可以看出你传入的必须是毫秒
    header->stamp = htonl(uint64_t(stamp) * _sample_rate / 1000);
    header->ssrc = htonl(_ssrc);

    //有效负载
    if (data) {
        memcpy(&ptr[RtpPacket::kRtpHeaderSize + RtpPacket::kRtpTcpHeaderSize], data, len);
    }
    return rtp;
}

另外,rtsp服务器输出的时间戳在打开视频的时候不一定是从0开始的,所以不能用ffmpeg录像,http是从0开始的。

作为一个记录吧