新增对mp2音视频编码格式的支持

This commit is contained in:
xia-chu 2026-03-09 21:19:05 +08:00
parent 5d4a266873
commit bb903fddcd
12 changed files with 1176 additions and 3 deletions

218
ext-codec/MP2A.cpp Normal file
View File

@ -0,0 +1,218 @@
/*
* 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 "MP2A.h"
#include "MP2ARtp.h"
#include "Extension/Factory.h"
#include "Extension/CommonRtmp.h"
#include "Rtsp/Rtsp.h"
using namespace std;
using namespace toolkit;
namespace mediakit {
// ======================== MpegAudioFrameInfo ========================
// MPEG Audio 版本表
// MPEG Audio version table
// Index: version_bits (2 bits from header)
// 00 = MPEG 2.5, 01 = reserved, 10 = MPEG 2, 11 = MPEG 1
static const int s_mpeg_version[] = { 3, 0, 2, 1 }; // 3=MPEG2.5, 0=reserved, 2=MPEG2, 1=MPEG1
// Layer 表: 00=reserved, 01=III, 10=II, 11=I
static const int s_mpeg_layer[] = { 0, 3, 2, 1 };
// MPEG-1 比特率表 (kbps)
// bitrate_index: 0-15, layer: 1-3
static const int s_bitrate_mpeg1[][16] = {
// Layer I
{ 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0 },
// Layer II
{ 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0 },
// Layer III
{ 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0 },
};
// MPEG-2/2.5 比特率表 (kbps)
static const int s_bitrate_mpeg2[][16] = {
// Layer I
{ 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0 },
// Layer II / III
{ 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 },
};
// 采样率表 (Hz)
// Index: [version_index][samplerate_index]
static const int s_sample_rate[][4] = {
{ 44100, 48000, 32000, 0 }, // MPEG-1
{ 22050, 24000, 16000, 0 }, // MPEG-2
{ 11025, 12000, 8000, 0 }, // MPEG-2.5
};
bool MpegAudioFrameInfo::parse(const uint8_t *data, size_t size, MpegAudioFrameInfo &info) {
if (size < 4) {
return false;
}
// 检查同步字 0xFFE0 (11 bits all 1)
if (data[0] != 0xFF || (data[1] & 0xE0) != 0xE0) {
return false;
}
int version_bits = (data[1] >> 3) & 0x03;
int layer_bits = (data[1] >> 1) & 0x03;
// int protection = !(data[1] & 0x01);
int bitrate_index = (data[2] >> 4) & 0x0F;
int samplerate_index = (data[2] >> 2) & 0x03;
int padding = (data[2] >> 1) & 0x01;
int channel_mode = (data[3] >> 6) & 0x03;
int ver = s_mpeg_version[version_bits];
int layer = s_mpeg_layer[layer_bits];
if (ver == 0 || layer == 0 || samplerate_index == 3 || bitrate_index == 0 || bitrate_index == 15) {
return false;
}
int ver_index = ver - 1; // 0=MPEG1, 1=MPEG2, 2=MPEG2.5
int sr = s_sample_rate[ver_index][samplerate_index];
if (sr == 0) {
return false;
}
int bitrate = 0;
if (ver == 1) {
// MPEG-1
bitrate = s_bitrate_mpeg1[layer - 1][bitrate_index];
} else {
// MPEG-2 / MPEG-2.5
if (layer == 1) {
bitrate = s_bitrate_mpeg2[0][bitrate_index];
} else {
bitrate = s_bitrate_mpeg2[1][bitrate_index];
}
}
info.version = ver;
info.layer = layer;
info.bitrate = bitrate;
info.sample_rate = sr;
info.channels = (channel_mode == 3) ? 1 : 2; // 3=mono, 其他=stereo
// 计算每帧的采样数和帧大小
if (layer == 1) {
// Layer I: 384 samples
info.samples_per_frame = 384;
info.frame_size = (12 * bitrate * 1000 / sr + padding) * 4;
} else if (layer == 2) {
// Layer II: 1152 samples
info.samples_per_frame = 1152;
info.frame_size = 144 * bitrate * 1000 / sr + padding;
} else {
// Layer III
if (ver == 1) {
info.samples_per_frame = 1152;
info.frame_size = 144 * bitrate * 1000 / sr + padding;
} else {
info.samples_per_frame = 576;
info.frame_size = 72 * bitrate * 1000 / sr + padding;
}
}
return true;
}
// ======================== MP2ATrack ========================
bool MP2ATrack::inputFrame(const Frame::Ptr &frame) {
if (!_info_parsed) {
auto data = (const uint8_t *)frame->data() + frame->prefixSize();
auto size = frame->size() - frame->prefixSize();
MpegAudioFrameInfo info;
if (MpegAudioFrameInfo::parse(data, size, info)) {
_sample_rate = info.sample_rate;
_channels = info.channels;
_info_parsed = true;
}
}
return AudioTrackImp::inputFrame(frame);
}
Sdp::Ptr MP2ATrack::getSdp(uint8_t pt) const {
// RFC 2250/3551: MPA 的 RTP 时钟频率固定为 90000而不是音频采样率
// RFC 2250/3551: MPA RTP clock rate is fixed at 90000, not the audio sample rate
class MP2ASdp : public Sdp {
public:
// 注意Sdp 基类构造必须传入 90000 作为 sample_rate
MP2ASdp(uint8_t payload_type, int channels, int bitrate)
: Sdp(90000, payload_type) {
_printer << "m=audio 0 RTP/AVP " << (int)payload_type << "\r\n";
if (bitrate) {
_printer << "b=AS:" << bitrate << "\r\n";
}
_printer << "a=rtpmap:" << (int)payload_type << " MPA/90000/" << channels << "\r\n";
}
std::string getSdp() const override { return _printer; }
private:
toolkit::_StrPrinter _printer;
};
return std::make_shared<MP2ASdp>(pt, getAudioChannel(), getBitRate() >> 10);
}
Track::Ptr MP2ATrack::clone() const {
return std::make_shared<MP2ATrack>(*this);
}
namespace {
CodecId getCodec() {
return CodecMP2A;
}
Track::Ptr getTrackByCodecId(int sample_rate, int channels, int sample_bit) {
return std::make_shared<MP2ATrack>(sample_rate, channels);
}
Track::Ptr getTrackBySdp(const SdpTrack::Ptr &track) {
return std::make_shared<MP2ATrack>(track->_samplerate, track->_channel);
}
RtpCodec::Ptr getRtpEncoderByCodecId(uint8_t pt) {
return std::make_shared<MP2ARtpEncoder>();
}
RtpCodec::Ptr getRtpDecoderByCodecId() {
return std::make_shared<MP2ARtpDecoder>();
}
RtmpCodec::Ptr getRtmpEncoderByTrack(const Track::Ptr &track) {
return std::make_shared<CommonRtmpEncoder>(track);
}
RtmpCodec::Ptr getRtmpDecoderByTrack(const Track::Ptr &track) {
return std::make_shared<CommonRtmpDecoder>(track);
}
Frame::Ptr getFrameFromPtr(const char *data, size_t bytes, uint64_t dts, uint64_t pts) {
return std::make_shared<MP2AFrameNoCacheAble>((char *)data, bytes, dts, pts);
}
} // namespace
CodecPlugin mp2a_plugin = { getCodec,
getTrackByCodecId,
getTrackBySdp,
getRtpEncoderByCodecId,
getRtpDecoderByCodecId,
getRtmpEncoderByTrack,
getRtmpDecoderByTrack,
getFrameFromPtr };
} // namespace mediakit

90
ext-codec/MP2A.h Normal file
View File

@ -0,0 +1,90 @@
/*
* 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.
*/
#ifndef ZLMEDIAKIT_MP2A_H
#define ZLMEDIAKIT_MP2A_H
#include "Extension/Frame.h"
#include "Extension/Track.h"
namespace mediakit {
/**
* MPEG-1/2 Audio (Layer I/II)
* MPEG-1/2 Audio (Layer I/II) frame helper class template
*/
template <typename Parent>
class MP2AFrameHelper : public Parent {
public:
using Ptr = std::shared_ptr<MP2AFrameHelper>;
template <typename... ARGS>
MP2AFrameHelper(ARGS &&...args)
: Parent(std::forward<ARGS>(args)...) {
this->_codec_id = CodecMP2A;
}
bool keyFrame() const override { return false; }
bool configFrame() const override { return false; }
};
/// MPEG-1/2 Audio 帧类
using MP2AFrame = MP2AFrameHelper<FrameImp>;
using MP2AFrameNoCacheAble = MP2AFrameHelper<FrameFromPtr>;
// MPEG Audio 帧头解析工具
// MPEG Audio frame header parsing utility
struct MpegAudioFrameInfo {
int version = 0; // 1: MPEG-1, 2: MPEG-2, 3: MPEG-2.5
int layer = 0; // 1: Layer I, 2: Layer II, 3: Layer III
int bitrate = 0; // kbps
int sample_rate = 0; // Hz
int channels = 0; // 1: mono, 2: stereo
int frame_size = 0; // bytes per frame
int samples_per_frame = 0;
/**
* MPEG Audio sync word
* Parse frame header info from MPEG Audio sync word
* @param data 4
* @param size
* @return
*/
static bool parse(const uint8_t *data, size_t size, MpegAudioFrameInfo &info);
};
/**
* MPEG-1/2 Audio (Layer I/II) Track
* CodecMP2A
*/
class MP2ATrack : public AudioTrackImp {
public:
using Ptr = std::shared_ptr<MP2ATrack>;
MP2ATrack(int sample_rate = 44100, int channels = 2)
: AudioTrackImp(CodecMP2A, sample_rate, channels, 16) {}
bool inputFrame(const Frame::Ptr &frame) override;
private:
/**
* RFC 2250/3551 MPA RTP 90000
* RFC 2250/3551 specifies MPA RTP clock rate is fixed at 90000
*/
Sdp::Ptr getSdp(uint8_t payload_type) const override;
Track::Ptr clone() const override;
private:
bool _info_parsed = false;
};
} // namespace mediakit
#endif // ZLMEDIAKIT_MP2A_H

175
ext-codec/MP2ARtp.cpp Normal file
View File

@ -0,0 +1,175 @@
/*
* 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 "MP2ARtp.h"
namespace mediakit {
// ======================== MP2ARtpEncoder ========================
void MP2ARtpEncoder::outputRtp(const char *data, size_t len, size_t frag_offset, bool mark, uint64_t stamp) {
// RFC 2250 Section 3.5:
// 4 bytes MPEG Audio-specific header + ES data
auto rtp = getRtpInfo().makeRtp(TrackAudio, nullptr, len + kMP2AHeaderSize, mark, stamp);
auto payload = rtp->getPayload();
// MPEG Audio-specific header
// MBZ (16 bits) = 0
payload[0] = 0;
payload[1] = 0;
// Frag_offset (16 bits)
payload[2] = (frag_offset >> 8) & 0xFF;
payload[3] = frag_offset & 0xFF;
// ES data
memcpy(payload + kMP2AHeaderSize, data, len);
RtpCodec::inputRtp(std::move(rtp), false);
}
bool MP2ARtpEncoder::inputFrame(const Frame::Ptr &frame) {
auto data = (const uint8_t *)frame->data() + frame->prefixSize();
auto total_size = (size_t)(frame->size() - frame->prefixSize());
if (total_size <= 0) {
return false;
}
auto max_payload = getRtpInfo().getMaxSize() - kMP2AHeaderSize;
auto base_dts = frame->dts();
// TS demux 可能一次回调多个完整的 MPEG Audio 帧(一个 PES 包),
// 需要逐帧解析并独立打 RTP 包,否则 FFmpeg 等接收端会因为分片
// 导致 RTP payload 不以 sync word 开头而报 "Header missing"。
size_t pos = 0;
int frame_index = 0;
while (pos + 4 <= total_size) {
// 检查 MPEG Audio sync word
if (data[pos] != 0xFF || (data[pos + 1] & 0xE0) != 0xE0) {
// 跳过无效字节,寻找下一个 sync word
++pos;
continue;
}
// 解析帧头获取帧大小
MpegAudioFrameInfo info;
if (!MpegAudioFrameInfo::parse(data + pos, total_size - pos, info) || info.frame_size <= 0) {
++pos;
continue;
}
size_t frame_size = (size_t)info.frame_size;
if (pos + frame_size > total_size) {
// 不完整的帧,打包剩余数据
frame_size = total_size - pos;
}
// 计算当前帧的时间戳偏移(毫秒)
// 每帧 samples_per_frame 个采样点,采样率 info.sample_rate
uint64_t stamp = base_dts;
if (frame_index > 0 && info.sample_rate > 0) {
stamp += (uint64_t)frame_index * info.samples_per_frame * 1000 / info.sample_rate;
}
// 对单个 MPEG Audio 帧打 RTP 包
auto ptr = (const char *)(data + pos);
size_t remain = frame_size;
size_t frag_offset = 0;
while (remain > 0) {
if (remain <= max_payload) {
outputRtp(ptr, remain, frag_offset, true, stamp);
break;
}
outputRtp(ptr, max_payload, frag_offset, false, stamp);
ptr += max_payload;
remain -= max_payload;
frag_offset += max_payload;
}
pos += frame_size;
++frame_index;
}
return true;
}
// ======================== MP2ARtpDecoder ========================
MP2ARtpDecoder::MP2ARtpDecoder() {
obtainFrame();
}
void MP2ARtpDecoder::obtainFrame() {
_frame = FrameImp::create<MP2AFrame>();
}
void MP2ARtpDecoder::flushData() {
if (_frame->_buffer.empty()) {
return;
}
RtpCodec::inputFrame(_frame);
obtainFrame();
}
bool MP2ARtpDecoder::inputRtp(const RtpPacket::Ptr &rtp, bool key_pos) {
auto payload_size = rtp->getPayloadSize();
if (payload_size <= (ssize_t)kMP2AHeaderSize) {
// 负载太小,没有有效 ES 数据
return false;
}
auto payload = rtp->getPayload();
auto stamp = rtp->getStamp();
auto seq = rtp->getSeq();
// 解析 MPEG Audio-specific header (RFC 2250 Section 3.5)
// MBZ (16 bits) + Frag_offset (16 bits)
uint16_t frag_offset = (payload[2] << 8) | payload[3];
auto es_data = payload + kMP2AHeaderSize;
auto es_size = payload_size - kMP2AHeaderSize;
if (frag_offset == 0) {
// frag_offset == 0 表示这是一个新帧(或完整帧)的开始
// 先输出之前缓存的帧(如果有)
flushData();
// 使用 90kHz 时间戳转换为毫秒
_frame->_dts = rtp->getStampMS();
_frame->_pts = _frame->_dts;
} else if (_frame->_buffer.empty()) {
// frag_offset != 0 但 buffer 为空,说明丢了第一个分片包,丢弃
_last_seq = seq;
_last_stamp = stamp;
return false;
} else if (seq != (uint16_t)(_last_seq + 1)) {
// 分片包 seq 不连续,丢包了,丢弃当前帧
WarnL << "mp2a rtp packet loss:" << _last_seq << " -> " << seq;
_frame->_buffer.clear();
_last_seq = seq;
_last_stamp = stamp;
return false;
}
_last_seq = seq;
_last_stamp = stamp;
// 追加 ES 数据
_frame->_buffer.append((char *)es_data, es_size);
// mark bit 表示帧的最后一个 RTP 包,立即输出
if (rtp->getHeader()->mark) {
flushData();
}
return false;
}
} // namespace mediakit

87
ext-codec/MP2ARtp.h Normal file
View File

@ -0,0 +1,87 @@
/*
* 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.
*/
#ifndef ZLMEDIAKIT_MP2ARTP_H
#define ZLMEDIAKIT_MP2ARTP_H
#include "MP2A.h"
#include "Rtsp/RtpCodec.h"
namespace mediakit {
// RFC 2250 Section 3.5 MPEG Audio-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 | Frag_offset |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
// MBZ: Must Be Zero (16 bits)
// Frag_offset: Byte offset into the audio frame for the data in this packet (16 bits)
static constexpr size_t kMP2AHeaderSize = 4;
/**
* MP2A (MPEG-1/2 Audio Layer I/II) RTP
* RFC 2250 Section 3.5
*/
class MP2ARtpEncoder : public RtpCodec {
public:
using Ptr = std::shared_ptr<MP2ARtpEncoder>;
/**
* MPEG Audio RTP
* @param frame
*/
bool inputFrame(const Frame::Ptr &frame) override;
private:
/**
* RTP
* @param data ES
* @param len
* @param frag_offset
* @param mark
* @param stamp (ms)
*/
void outputRtp(const char *data, size_t len, size_t frag_offset, bool mark, uint64_t stamp);
};
/**
* MP2A (MPEG-1/2 Audio Layer I/II) RTP
* RFC 2250 Section 3.5
*/
class MP2ARtpDecoder : public RtpCodec {
public:
using Ptr = std::shared_ptr<MP2ARtpDecoder>;
MP2ARtpDecoder();
/**
* MPEG Audio RTP
* @param rtp rtp
* @param key_pos
*/
bool inputRtp(const RtpPacket::Ptr &rtp, bool key_pos = false) override;
private:
void obtainFrame();
void flushData();
private:
uint16_t _last_seq = 0;
uint32_t _last_stamp = 0;
FrameImp::Ptr _frame;
};
} // namespace mediakit
#endif // ZLMEDIAKIT_MP2ARTP_H

116
ext-codec/MP2V.cpp Normal file
View File

@ -0,0 +1,116 @@
/*
* 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 "MP2V.h"
#include "MP2VRtp.h"
#include "Extension/Factory.h"
#include "Rtsp/Rtsp.h"
using namespace std;
using namespace toolkit;
namespace mediakit {
// MPEG-2 sequence header 帧率表 (ISO 13818-2 Table 6-4)
// MPEG-2 sequence header frame rate table
static const float s_mp2v_frame_rate_table[] = {
0, // 0000 forbidden
24000.0 / 1001, // 0001 23.976
24.0, // 0010
25.0, // 0011
30000.0 / 1001, // 0100 29.97
30.0, // 0101
50.0, // 0110
60000.0 / 1001, // 0111 59.94
60.0, // 1000
};
void MP2VTrack::parseSequenceHeader(const uint8_t *data, size_t size) {
// 查找 sequence header start code: 00 00 01 B3
// Look for sequence header start code: 00 00 01 B3
for (size_t i = 0; i + 7 < size; ++i) {
if (data[i] == 0x00 && data[i + 1] == 0x00 && data[i + 2] == 0x01 && data[i + 3] == 0xB3) {
// sequence_header() 结构:
// horizontal_size_value: 12 bits
// vertical_size_value: 12 bits
// aspect_ratio_information: 4 bits
// frame_rate_code: 4 bits
_width = (data[i + 4] << 4) | ((data[i + 5] >> 4) & 0x0F);
_height = ((data[i + 5] & 0x0F) << 8) | data[i + 6];
uint8_t frame_rate_code = data[i + 7] & 0x0F;
if (frame_rate_code > 0 && frame_rate_code <= 8) {
_fps = s_mp2v_frame_rate_table[frame_rate_code];
}
_seq_header_parsed = true;
return;
}
}
}
bool MP2VTrack::inputFrame(const Frame::Ptr &frame) {
if (!_seq_header_parsed) {
parseSequenceHeader((const uint8_t *)frame->data() + frame->prefixSize(),
frame->size() - frame->prefixSize());
}
return VideoTrackImp::inputFrame(frame);
}
Sdp::Ptr MP2VTrack::getSdp(uint8_t pt) const {
return std::make_shared<DefaultSdp>(pt, *this);
}
namespace {
CodecId getCodec() {
return CodecMP2V;
}
Track::Ptr getTrackByCodecId(int sample_rate, int channels, int sample_bit) {
return std::make_shared<MP2VTrack>();
}
Track::Ptr getTrackBySdp(const SdpTrack::Ptr &track) {
return std::make_shared<MP2VTrack>();
}
RtpCodec::Ptr getRtpEncoderByCodecId(uint8_t pt) {
return std::make_shared<MP2VRtpEncoder>();
}
RtpCodec::Ptr getRtpDecoderByCodecId() {
return std::make_shared<MP2VRtpDecoder>();
}
RtmpCodec::Ptr getRtmpEncoderByTrack(const Track::Ptr &track) {
WarnL << "Unsupported MP2V rtmp encoder";
return nullptr;
}
RtmpCodec::Ptr getRtmpDecoderByTrack(const Track::Ptr &track) {
WarnL << "Unsupported MP2V rtmp decoder";
return nullptr;
}
Frame::Ptr getFrameFromPtr(const char *data, size_t bytes, uint64_t dts, uint64_t pts) {
return std::make_shared<MP2VFrameNoCacheAble>((char *)data, bytes, dts, pts, 0);
}
} // namespace
CodecPlugin mp2v_plugin = { getCodec,
getTrackByCodecId,
getTrackBySdp,
getRtpEncoderByCodecId,
getRtpDecoderByCodecId,
getRtmpEncoderByTrack,
getRtmpDecoderByTrack,
getFrameFromPtr };
} // namespace mediakit

97
ext-codec/MP2V.h Normal file
View File

@ -0,0 +1,97 @@
/*
* 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.
*/
#ifndef ZLMEDIAKIT_MP2V_H
#define ZLMEDIAKIT_MP2V_H
#include "Extension/Frame.h"
#include "Extension/Track.h"
namespace mediakit {
/**
* MPEG-2 Video
* MPEG-2 Video frame helper class template
*/
template <typename Parent>
class MP2VFrameHelper : public Parent {
public:
using Ptr = std::shared_ptr<MP2VFrameHelper>;
template <typename... ARGS>
MP2VFrameHelper(ARGS &&...args)
: Parent(std::forward<ARGS>(args)...) {
this->_codec_id = CodecMP2V;
}
/**
* MPEG-2 : 00 00 01 00 (picture_start_code)
* I帧判断picture_coding_type == 1 (I-Picture)
* picture_coding_type picture header 11-12 bit ( temporal_reference )
*
* MPEG-2 video start code: 00 00 01 00 (picture_start_code)
* I-frame detection: picture_coding_type == 1 (I-Picture)
*/
bool keyFrame() const override {
auto data = (const uint8_t *)this->data() + this->prefixSize();
auto size = this->size() - this->prefixSize();
return isMP2VKeyFrame(data, size);
}
bool configFrame() const override { return false; }
static bool isMP2VKeyFrame(const uint8_t *data, size_t size) {
// 查找 picture start code (00 00 01 00),然后检查 picture_coding_type
// Look for picture start code (00 00 01 00), then check picture_coding_type
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) {
// picture header: temporal_reference(10bits) + picture_coding_type(3bits)
// picture_coding_type: 001 = I, 010 = P, 011 = B
uint8_t picture_coding_type = (data[i + 5] >> 3) & 0x07;
return picture_coding_type == 1;
}
}
return false;
}
};
/// MPEG-2 Video 帧类
using MP2VFrame = MP2VFrameHelper<FrameImp>;
using MP2VFrameNoCacheAble = MP2VFrameHelper<FrameFromPtr>;
/**
* MPEG-2 Video Track
*/
class MP2VTrack : public VideoTrackImp {
public:
using Ptr = std::shared_ptr<MP2VTrack>;
MP2VTrack() : VideoTrackImp(CodecMP2V) {}
Track::Ptr clone() const override { return std::make_shared<MP2VTrack>(*this); }
bool inputFrame(const Frame::Ptr &frame) override;
private:
Sdp::Ptr getSdp(uint8_t payload_type) const override;
/**
* sequence header
* Parse width, height and fps from sequence header
*/
void parseSequenceHeader(const uint8_t *data, size_t size);
private:
bool _seq_header_parsed = false;
};
} // namespace mediakit
#endif // ZLMEDIAKIT_MP2V_H

274
ext-codec/MP2VRtp.cpp Normal file
View File

@ -0,0 +1,274 @@
/*
* 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;
}
// 生成 DTSMPEG-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

112
ext-codec/MP2VRtp.h Normal file
View File

@ -0,0 +1,112 @@
/*
* 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.
*/
#ifndef ZLMEDIAKIT_MP2VRTP_H
#define ZLMEDIAKIT_MP2VRTP_H
#include "MP2V.h"
#include "Common/Stamp.h"
#include "Rtsp/RtpCodec.h"
namespace mediakit {
// 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 |N|S|B|E| P | | BFC | | FFC |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// AN FBV FFV
static constexpr size_t kMP2VHeaderSize = 4;
/**
* MP2V (MPEG-2 Video) RTP
* MPEG-2 Video over RTP MP2V Frame
* RFC 2250
*/
class MP2VRtpDecoder : public RtpCodec {
public:
using Ptr = std::shared_ptr<MP2VRtpDecoder>;
MP2VRtpDecoder();
/**
* MPEG-2 Video RTP
* @param rtp rtp包
* @param key_pos
*/
bool inputRtp(const RtpPacket::Ptr &rtp, bool key_pos = true) override;
private:
bool decodeRtp(const RtpPacket::Ptr &rtp);
void outputFrame(const RtpPacket::Ptr &rtp);
void obtainFrame();
private:
bool _gop_dropped = true;
bool _drop_flag = false;
uint16_t _last_seq = 0;
uint8_t _picture_type = 0;
MP2VFrame::Ptr _frame;
DtsGenerator _dts_generator;
};
/**
* MP2V (MPEG-2 Video) RTP
* MPEG-2 Video RTP
* RFC 2250
*/
class MP2VRtpEncoder : public RtpCodec {
public:
using Ptr = std::shared_ptr<MP2VRtpEncoder>;
/**
* MPEG-2 Video
* @param frame
*/
bool inputFrame(const Frame::Ptr &frame) override;
private:
/**
* RFC 2250 MPEG Video-specific header
* @param buf 4
* @param data MPEG-2 ES
* @param size
* @param is_begin_of_slice slice
* @param is_end_of_slice slice
*/
void buildMpvHeader(uint8_t *buf, const uint8_t *data, size_t size,
bool is_begin_of_slice, bool is_end_of_slice);
/**
* picture type, temporal reference
*/
void parsePictureInfo(const uint8_t *data, size_t size);
/**
* sequence header
*/
bool hasSequenceHeader(const uint8_t *data, size_t size);
private:
uint16_t _temporal_ref = 0;
uint8_t _picture_type = 0;
uint8_t _fbv = 0;
uint8_t _bfc = 0;
uint8_t _ffv = 0;
uint8_t _ffc = 0;
bool _has_seq_header = false;
};
} // namespace mediakit
#endif // ZLMEDIAKIT_MP2VRTP_H

View File

@ -33,6 +33,8 @@ REGISTER_CODEC(g711a_plugin)
REGISTER_CODEC(g711u_plugin);
REGISTER_CODEC(l16_plugin);
REGISTER_CODEC(mp3_plugin);
REGISTER_CODEC(mp2v_plugin);
REGISTER_CODEC(mp2a_plugin);
void Factory::registerPlugin(const CodecPlugin &plugin) {
InfoL << "Load codec: " << getCodecName(plugin.getCodec());

View File

@ -54,7 +54,9 @@ typedef enum {
XX(CodecG722, TrackAudio, 18, "G722", PSI_STREAM_AUDIO_G722, MOV_OBJECT_NONE) \
XX(CodecG723, TrackAudio, 19, "G723", PSI_STREAM_AUDIO_G723, MOV_OBJECT_NONE) \
XX(CodecG728, TrackAudio, 20, "G728", PSI_STREAM_RESERVED, MOV_OBJECT_NONE) \
XX(CodecG729, TrackAudio, 21, "G729", PSI_STREAM_AUDIO_G729, MOV_OBJECT_NONE)
XX(CodecG729, TrackAudio, 21, "G729", PSI_STREAM_AUDIO_G729, MOV_OBJECT_NONE) \
XX(CodecMP2V, TrackVideo, 22, "MPV", PSI_STREAM_MPEG2, MOV_OBJECT_MP2V) \
XX(CodecMP2A, TrackAudio, 23, "MPA", PSI_STREAM_AUDIO_MPEG1, MOV_OBJECT_MP3)
typedef enum {
CodecInvalid = -1,

View File

@ -195,7 +195,7 @@ public:
_fps = fps;
}
VideoTrackImp(CodecId codec_id) {
VideoTrackImp(CodecId codec_id) {
_codec_id = codec_id;
_fps = 30;
}

View File

@ -53,7 +53,7 @@ typedef enum {
XX(JPEG, TrackVideo, 26, 90000, 1, CodecJPEG) \
XX(nv, TrackVideo, 28, 90000, 1, CodecInvalid) \
XX(H261, TrackVideo, 31, 90000, 1, CodecInvalid) \
XX(MPV, TrackVideo, 32, 90000, 1, CodecInvalid) \
XX(MPV, TrackVideo, 32, 90000, 1, CodecMP2V) \
XX(MP2T, TrackVideo, 33, 90000, 1, CodecTS) \
XX(H263, TrackVideo, 34, 90000, 1, CodecInvalid)