mirror of
https://gitee.com/xia-chu/ZLMediaKit.git
synced 2026-05-06 10:57:50 +08:00
275 lines
8.8 KiB
C++
275 lines
8.8 KiB
C++
/*
|
||
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
|
||
*
|
||
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
|
||
*
|
||
* Use of this source code is governed by MIT-like license that can be found in the
|
||
* LICENSE file in the root of the source tree. All contributing project authors
|
||
* may be found in the AUTHORS file in the root of the source tree.
|
||
*/
|
||
|
||
#include "MP2VRtp.h"
|
||
#include "Common/config.h"
|
||
|
||
namespace mediakit {
|
||
|
||
// ======================== MP2VRtpDecoder ========================
|
||
|
||
MP2VRtpDecoder::MP2VRtpDecoder() {
|
||
obtainFrame();
|
||
}
|
||
|
||
void MP2VRtpDecoder::obtainFrame() {
|
||
_frame = FrameImp::create<MP2VFrame>();
|
||
}
|
||
|
||
bool MP2VRtpDecoder::inputRtp(const RtpPacket::Ptr &rtp, bool key_pos) {
|
||
auto seq = rtp->getSeq();
|
||
auto last_gop_dropped = _gop_dropped;
|
||
bool is_gop_start = decodeRtp(rtp);
|
||
if (!_gop_dropped && seq != (uint16_t)(_last_seq + 1) && _last_seq) {
|
||
_gop_dropped = true;
|
||
WarnL << "start drop mp2v gop, last seq:" << _last_seq << ", rtp:\r\n" << rtp->dumpString();
|
||
}
|
||
_last_seq = seq;
|
||
return is_gop_start && !last_gop_dropped;
|
||
}
|
||
|
||
/**
|
||
* RFC 2250 MPEG Video-specific header (4 bytes):
|
||
*
|
||
* 0 1 2 3
|
||
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||
* | MBZ |T| TR |AN|N|S|B|E| P | | BFC | | FFC |
|
||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||
* FBV FFV
|
||
*
|
||
* T: MPEG-2 specific header extension present (1 bit)
|
||
* TR: Temporal Reference (10 bits)
|
||
* AN: Active N bit (1 bit)
|
||
* N: New picture header (1 bit)
|
||
* S: Sequence-header-present (1 bit)
|
||
* B: Beginning-of-slice (1 bit)
|
||
* E: End-of-slice (1 bit)
|
||
* P: Picture-Type (3 bits): I(1), P(2), B(3), D(4)
|
||
* FBV: full_pel_backward_vector (1 bit)
|
||
* BFC: backward_f_code (3 bits)
|
||
* FFV: full_pel_forward_vector (1 bit)
|
||
* FFC: forward_f_code (3 bits)
|
||
*/
|
||
bool MP2VRtpDecoder::decodeRtp(const RtpPacket::Ptr &rtp) {
|
||
auto payload_size = rtp->getPayloadSize();
|
||
if (payload_size <= (ssize_t)kMP2VHeaderSize) {
|
||
// 负载太小,不包含有效数据
|
||
return false;
|
||
}
|
||
auto payload = rtp->getPayload();
|
||
auto stamp = rtp->getStampMS();
|
||
auto seq = rtp->getSeq();
|
||
|
||
// 解析 RFC 2250 MPEG Video-specific header
|
||
bool t_bit = (payload[0] >> 2) & 0x01;
|
||
// uint16_t temporal_ref = ((payload[0] & 0x03) << 8) | payload[1];
|
||
// bool seq_header_present = (payload[2] >> 5) & 0x01;
|
||
// bool begin_of_slice = (payload[2] >> 4) & 0x01;
|
||
// bool end_of_slice = (payload[2] >> 3) & 0x01;
|
||
uint8_t picture_type = (payload[2] & 0x07);
|
||
|
||
// 如果 T bit 置位,还有 4 字节的 MPEG-2 扩展头需要跳过
|
||
size_t header_size = kMP2VHeaderSize + (t_bit ? 4 : 0);
|
||
if (payload_size <= (ssize_t)header_size) {
|
||
return false;
|
||
}
|
||
|
||
auto es_data = payload + header_size;
|
||
auto es_size = payload_size - header_size;
|
||
|
||
// 检查是否为新帧(时间戳变化)
|
||
if (!_frame->_buffer.empty() && stamp != _frame->_pts) {
|
||
// 时间戳变化,输出上一帧
|
||
outputFrame(rtp);
|
||
}
|
||
|
||
if (_frame->_buffer.empty()) {
|
||
// 新帧开始
|
||
_frame->_pts = stamp;
|
||
_drop_flag = false;
|
||
_picture_type = picture_type;
|
||
}
|
||
|
||
if (_drop_flag) {
|
||
return false;
|
||
}
|
||
|
||
// 检测 seq 不连续,丢弃当前帧
|
||
if (!_frame->_buffer.empty() && seq != (uint16_t)(_last_seq + 1) && _last_seq) {
|
||
_drop_flag = true;
|
||
_frame->_buffer.clear();
|
||
return false;
|
||
}
|
||
|
||
// 追加 ES 数据
|
||
_frame->_buffer.append((char *)es_data, es_size);
|
||
|
||
// RTP mark bit 标识帧结束
|
||
if (rtp->getHeader()->mark) {
|
||
outputFrame(rtp);
|
||
return _picture_type == 1; // I-Picture
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
void MP2VRtpDecoder::outputFrame(const RtpPacket::Ptr &rtp) {
|
||
if (_frame->_buffer.empty()) {
|
||
return;
|
||
}
|
||
|
||
// 生成 DTS(MPEG-2 有 B 帧,PTS 和 DTS 不一定相同)
|
||
_dts_generator.getDts(_frame->_pts, _frame->_dts);
|
||
|
||
bool is_key = _frame->keyFrame();
|
||
if (is_key && _gop_dropped) {
|
||
_gop_dropped = false;
|
||
InfoL << "new mp2v gop received, rtp:\r\n" << rtp->dumpString();
|
||
}
|
||
if (!_gop_dropped) {
|
||
RtpCodec::inputFrame(_frame);
|
||
}
|
||
obtainFrame();
|
||
}
|
||
|
||
// ======================== MP2VRtpEncoder ========================
|
||
|
||
bool MP2VRtpEncoder::hasSequenceHeader(const uint8_t *data, size_t size) {
|
||
// 查找 sequence header start code: 00 00 01 B3
|
||
for (size_t i = 0; i + 3 < size; ++i) {
|
||
if (data[i] == 0x00 && data[i + 1] == 0x00 && data[i + 2] == 0x01 && data[i + 3] == 0xB3) {
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
void MP2VRtpEncoder::parsePictureInfo(const uint8_t *data, size_t size) {
|
||
_temporal_ref = 0;
|
||
_picture_type = 0;
|
||
_fbv = 0;
|
||
_bfc = 0;
|
||
_ffv = 0;
|
||
_ffc = 0;
|
||
_has_seq_header = hasSequenceHeader(data, size);
|
||
|
||
// 查找 picture start code: 00 00 01 00
|
||
for (size_t i = 0; i + 5 < size; ++i) {
|
||
if (data[i] == 0x00 && data[i + 1] == 0x00 && data[i + 2] == 0x01 && data[i + 3] == 0x00) {
|
||
// temporal_reference: 10 bits, picture_coding_type: 3 bits
|
||
_temporal_ref = (data[i + 4] << 2) | ((data[i + 5] >> 6) & 0x03);
|
||
_picture_type = (data[i + 5] >> 3) & 0x07;
|
||
|
||
// 解析 motion vector codes (vbv_delay 之后)
|
||
// picture header: temporal_reference(10) + picture_coding_type(3) + vbv_delay(16)
|
||
if (i + 8 < size) {
|
||
uint8_t extra_byte = data[i + 8];
|
||
if (_picture_type == 2 /* P */ || _picture_type == 3 /* B */) {
|
||
// full_pel_forward_vector(1) + forward_f_code(3)
|
||
_ffv = (extra_byte >> 2) & 0x01;
|
||
_ffc = ((extra_byte & 0x03) << 1);
|
||
if (i + 9 < size) {
|
||
_ffc |= (data[i + 9] >> 7) & 0x01;
|
||
}
|
||
}
|
||
if (_picture_type == 3 /* B */) {
|
||
// full_pel_backward_vector(1) + backward_f_code(3) 紧跟在 forward 之后
|
||
if (i + 9 < size) {
|
||
_fbv = (data[i + 9] >> 6) & 0x01;
|
||
_bfc = (data[i + 9] >> 3) & 0x07;
|
||
}
|
||
}
|
||
}
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
void MP2VRtpEncoder::buildMpvHeader(uint8_t *buf, const uint8_t *data, size_t size,
|
||
bool is_begin_of_slice, bool is_end_of_slice) {
|
||
// RFC 2250 Section 3.4
|
||
// Byte 0: MBZ(5) + T(1) + TR high 2 bits
|
||
// T = 0 (不发送 MPEG-2 扩展头)
|
||
buf[0] = (_temporal_ref >> 8) & 0x03;
|
||
|
||
// Byte 1: TR low 8 bits
|
||
buf[1] = _temporal_ref & 0xFF;
|
||
|
||
// Byte 2: AN(1) + N(1) + S(1) + B(1) + E(1) + P(3)
|
||
uint8_t byte2 = 0;
|
||
// AN = 0, N = 0
|
||
if (_has_seq_header) {
|
||
byte2 |= 0x20; // S bit
|
||
}
|
||
if (is_begin_of_slice) {
|
||
byte2 |= 0x10; // B bit
|
||
}
|
||
if (is_end_of_slice) {
|
||
byte2 |= 0x08; // E bit
|
||
}
|
||
byte2 |= (_picture_type & 0x07);
|
||
buf[2] = byte2;
|
||
|
||
// Byte 3: FBV(1) + BFC(3) + FFV(1) + FFC(3)
|
||
buf[3] = ((_fbv & 0x01) << 7) | ((_bfc & 0x07) << 4) | ((_ffv & 0x01) << 3) | (_ffc & 0x07);
|
||
}
|
||
|
||
bool MP2VRtpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||
auto ptr = (const uint8_t *)frame->data() + frame->prefixSize();
|
||
auto size = frame->size() - frame->prefixSize();
|
||
if (size == 0) {
|
||
return false;
|
||
}
|
||
|
||
// 解析帧信息(picture type, temporal reference 等)
|
||
parsePictureInfo(ptr, size);
|
||
|
||
bool is_key = frame->keyFrame();
|
||
auto max_payload = getRtpInfo().getMaxSize() - kMP2VHeaderSize;
|
||
size_t offset = 0;
|
||
|
||
while (offset < size) {
|
||
bool is_first = (offset == 0);
|
||
size_t payload_size;
|
||
bool is_last;
|
||
|
||
if (size - offset <= max_payload) {
|
||
payload_size = size - offset;
|
||
is_last = true;
|
||
} else {
|
||
payload_size = max_payload;
|
||
is_last = false;
|
||
}
|
||
|
||
// 构建 MPEG Video-specific header
|
||
uint8_t mpv_header[kMP2VHeaderSize];
|
||
buildMpvHeader(mpv_header, ptr + offset, payload_size, is_first, is_last);
|
||
|
||
// 创建 RTP 包:MPEG header + ES data
|
||
auto rtp = getRtpInfo().makeRtp(TrackVideo, nullptr, kMP2VHeaderSize + payload_size, is_last, frame->pts());
|
||
auto rtp_payload = rtp->getPayload();
|
||
|
||
// 写入 MPEG Video-specific header
|
||
memcpy(rtp_payload, mpv_header, kMP2VHeaderSize);
|
||
// 写入 ES 数据
|
||
memcpy(rtp_payload + kMP2VHeaderSize, ptr + offset, payload_size);
|
||
|
||
// 输入到 RTP 环形缓存
|
||
RtpCodec::inputRtp(rtp, is_key && is_first);
|
||
|
||
offset += payload_size;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
} // namespace mediakit
|