From bb903fddcdf324bdda577b61cf573f8b724c91ec Mon Sep 17 00:00:00 2001 From: xia-chu <771730766@qq.com> Date: Mon, 9 Mar 2026 21:19:05 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=AF=B9mp2=E9=9F=B3?= =?UTF-8?q?=E8=A7=86=E9=A2=91=E7=BC=96=E7=A0=81=E6=A0=BC=E5=BC=8F=E7=9A=84?= =?UTF-8?q?=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ext-codec/MP2A.cpp | 218 ++++++++++++++++++++++++++++++ ext-codec/MP2A.h | 90 +++++++++++++ ext-codec/MP2ARtp.cpp | 175 ++++++++++++++++++++++++ ext-codec/MP2ARtp.h | 87 ++++++++++++ ext-codec/MP2V.cpp | 116 ++++++++++++++++ ext-codec/MP2V.h | 97 ++++++++++++++ ext-codec/MP2VRtp.cpp | 274 ++++++++++++++++++++++++++++++++++++++ ext-codec/MP2VRtp.h | 112 ++++++++++++++++ src/Extension/Factory.cpp | 2 + src/Extension/Frame.h | 4 +- src/Extension/Track.h | 2 +- src/Rtsp/Rtsp.h | 2 +- 12 files changed, 1176 insertions(+), 3 deletions(-) create mode 100644 ext-codec/MP2A.cpp create mode 100644 ext-codec/MP2A.h create mode 100644 ext-codec/MP2ARtp.cpp create mode 100644 ext-codec/MP2ARtp.h create mode 100644 ext-codec/MP2V.cpp create mode 100644 ext-codec/MP2V.h create mode 100644 ext-codec/MP2VRtp.cpp create mode 100644 ext-codec/MP2VRtp.h diff --git a/ext-codec/MP2A.cpp b/ext-codec/MP2A.cpp new file mode 100644 index 00000000..31029842 --- /dev/null +++ b/ext-codec/MP2A.cpp @@ -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(pt, getAudioChannel(), getBitRate() >> 10); +} + +Track::Ptr MP2ATrack::clone() const { + return std::make_shared(*this); +} + +namespace { + +CodecId getCodec() { + return CodecMP2A; +} + +Track::Ptr getTrackByCodecId(int sample_rate, int channels, int sample_bit) { + return std::make_shared(sample_rate, channels); +} + +Track::Ptr getTrackBySdp(const SdpTrack::Ptr &track) { + return std::make_shared(track->_samplerate, track->_channel); +} + +RtpCodec::Ptr getRtpEncoderByCodecId(uint8_t pt) { + return std::make_shared(); +} + +RtpCodec::Ptr getRtpDecoderByCodecId() { + return std::make_shared(); +} + +RtmpCodec::Ptr getRtmpEncoderByTrack(const Track::Ptr &track) { + return std::make_shared(track); +} + +RtmpCodec::Ptr getRtmpDecoderByTrack(const Track::Ptr &track) { + return std::make_shared(track); +} + +Frame::Ptr getFrameFromPtr(const char *data, size_t bytes, uint64_t dts, uint64_t pts) { + return std::make_shared((char *)data, bytes, dts, pts); +} + +} // namespace + +CodecPlugin mp2a_plugin = { getCodec, + getTrackByCodecId, + getTrackBySdp, + getRtpEncoderByCodecId, + getRtpDecoderByCodecId, + getRtmpEncoderByTrack, + getRtmpDecoderByTrack, + getFrameFromPtr }; + +} // namespace mediakit diff --git a/ext-codec/MP2A.h b/ext-codec/MP2A.h new file mode 100644 index 00000000..a3c6c1ec --- /dev/null +++ b/ext-codec/MP2A.h @@ -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 +class MP2AFrameHelper : public Parent { +public: + using Ptr = std::shared_ptr; + + template + MP2AFrameHelper(ARGS &&...args) + : Parent(std::forward(args)...) { + this->_codec_id = CodecMP2A; + } + + bool keyFrame() const override { return false; } + bool configFrame() const override { return false; } +}; + +/// MPEG-1/2 Audio 帧类 +using MP2AFrame = MP2AFrameHelper; +using MP2AFrameNoCacheAble = MP2AFrameHelper; + +// 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(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 diff --git a/ext-codec/MP2ARtp.cpp b/ext-codec/MP2ARtp.cpp new file mode 100644 index 00000000..63a56874 --- /dev/null +++ b/ext-codec/MP2ARtp.cpp @@ -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(); +} + +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 diff --git a/ext-codec/MP2ARtp.h b/ext-codec/MP2ARtp.h new file mode 100644 index 00000000..71c04324 --- /dev/null +++ b/ext-codec/MP2ARtp.h @@ -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; + + /** + * 输入 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(); + + /** + * 输入 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 diff --git a/ext-codec/MP2V.cpp b/ext-codec/MP2V.cpp new file mode 100644 index 00000000..6263a7a3 --- /dev/null +++ b/ext-codec/MP2V.cpp @@ -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(pt, *this); +} + +namespace { + +CodecId getCodec() { + return CodecMP2V; +} + +Track::Ptr getTrackByCodecId(int sample_rate, int channels, int sample_bit) { + return std::make_shared(); +} + +Track::Ptr getTrackBySdp(const SdpTrack::Ptr &track) { + return std::make_shared(); +} + +RtpCodec::Ptr getRtpEncoderByCodecId(uint8_t pt) { + return std::make_shared(); +} + +RtpCodec::Ptr getRtpDecoderByCodecId() { + return std::make_shared(); +} + +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((char *)data, bytes, dts, pts, 0); +} + +} // namespace + +CodecPlugin mp2v_plugin = { getCodec, + getTrackByCodecId, + getTrackBySdp, + getRtpEncoderByCodecId, + getRtpDecoderByCodecId, + getRtmpEncoderByTrack, + getRtmpDecoderByTrack, + getFrameFromPtr }; + +} // namespace mediakit diff --git a/ext-codec/MP2V.h b/ext-codec/MP2V.h new file mode 100644 index 00000000..3c8ce287 --- /dev/null +++ b/ext-codec/MP2V.h @@ -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 +class MP2VFrameHelper : public Parent { +public: + using Ptr = std::shared_ptr; + + template + MP2VFrameHelper(ARGS &&...args) + : Parent(std::forward(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; +using MP2VFrameNoCacheAble = MP2VFrameHelper; + +/** + * MPEG-2 Video Track + */ +class MP2VTrack : public VideoTrackImp { +public: + using Ptr = std::shared_ptr; + + MP2VTrack() : VideoTrackImp(CodecMP2V) {} + + Track::Ptr clone() const override { return std::make_shared(*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 diff --git a/ext-codec/MP2VRtp.cpp b/ext-codec/MP2VRtp.cpp new file mode 100644 index 00000000..afada09d --- /dev/null +++ b/ext-codec/MP2VRtp.cpp @@ -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(); +} + +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 diff --git a/ext-codec/MP2VRtp.h b/ext-codec/MP2VRtp.h new file mode 100644 index 00000000..8fd98581 --- /dev/null +++ b/ext-codec/MP2VRtp.h @@ -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(); + + /** + * 输入 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; + + /** + * 输入 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 diff --git a/src/Extension/Factory.cpp b/src/Extension/Factory.cpp index 4bec196b..80affb79 100644 --- a/src/Extension/Factory.cpp +++ b/src/Extension/Factory.cpp @@ -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()); diff --git a/src/Extension/Frame.h b/src/Extension/Frame.h index 99d665fe..d78cbe18 100644 --- a/src/Extension/Frame.h +++ b/src/Extension/Frame.h @@ -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, diff --git a/src/Extension/Track.h b/src/Extension/Track.h index f999d72b..a9c3531b 100644 --- a/src/Extension/Track.h +++ b/src/Extension/Track.h @@ -195,7 +195,7 @@ public: _fps = fps; } - VideoTrackImp(CodecId codec_id) { + VideoTrackImp(CodecId codec_id) { _codec_id = codec_id; _fps = 30; } diff --git a/src/Rtsp/Rtsp.h b/src/Rtsp/Rtsp.h index f94664c2..dbb494c1 100644 --- a/src/Rtsp/Rtsp.h +++ b/src/Rtsp/Rtsp.h @@ -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)