mirror of
https://gitee.com/xia-chu/ZLMediaKit.git
synced 2026-05-06 10:57:50 +08:00
新增对mp2音视频编码格式的支持
This commit is contained in:
parent
5d4a266873
commit
bb903fddcd
218
ext-codec/MP2A.cpp
Normal file
218
ext-codec/MP2A.cpp
Normal 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
90
ext-codec/MP2A.h
Normal 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
175
ext-codec/MP2ARtp.cpp
Normal 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
87
ext-codec/MP2ARtp.h
Normal 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
116
ext-codec/MP2V.cpp
Normal 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
97
ext-codec/MP2V.h
Normal 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
274
ext-codec/MP2VRtp.cpp
Normal 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;
|
||||
}
|
||||
|
||||
// 生成 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
|
||||
112
ext-codec/MP2VRtp.h
Normal file
112
ext-codec/MP2VRtp.h
Normal 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
|
||||
@ -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());
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -195,7 +195,7 @@ public:
|
||||
_fps = fps;
|
||||
}
|
||||
|
||||
VideoTrackImp(CodecId codec_id) {
|
||||
VideoTrackImp(CodecId codec_id) {
|
||||
_codec_id = codec_id;
|
||||
_fps = 30;
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user