一、RTSP推流服务器端概述
RTSP是一种网络控制协议,用于控制实时数据的传输,如音频和视频。它允许客户端与流媒体服务器进行交互,以控制和接收实时的音视频流。RTSP的设计目标是提供一种标准化的协议,使得音视频流能够在客户端和服务器之间进行控制和传输。
二、处理步骤
当RTSP推流服务器端接收到流后,会进行以下处理步骤:
- 连接管理:
服务器会监听特定的端口(如554端口),等待客户端的连接请求。
当接收到客户端的连接请求时,服务器会建立TCP连接,并初始化RTSP会话。 - 指令区分:
服务器会解析客户端发送的RTSP请求报文,区分出控制指令和数据指令。
控制指令包括DESCRIBE、SETUP、PLAY、PAUSE、TEARDOWN等,用于控制媒体流的播放、暂停、停止等操作。
数据指令则通常与RTP/RTCP协议相关,用于传输实际的音视频数据。 - RTP/RTCP处理:
对于数据指令,服务器会进一步区分出RTP和RTCP报文。
RTP报文用于传输音视频数据,而RTCP报文则用于传输控制信息,如发送者报告和接收者报告。 - 音视频数据处理:
对于RTP报文中的音视频数据,服务器会进行解码和重组。
视频数据需要按照序列号进行排序,以处理可能出现的乱序情况。
排序后的视频数据会进行解码,并合成视频帧以供播放或存储。 - 反馈与质量控制:
服务器会利用RTCP报文中的发送者报告和接收者报告来监控音视频流的质量。
根据这些报告,服务器可以调整传输速率、编码方式等参数,以优化音视频流的传输效果。
三、指令与数据区分
在RTSP推流过程中,服务器需要区分RTSP控制指令和数据指令。这通常通过解析请求报文的首部行和实体主体来实现。
- 控制指令:RTSP请求报文的首部行会包含方法名称(如DESCRIBE、SETUP等)和URL地址等信息。服务器会根据这些信息来识别并处理控制指令。
- 数据指令:与RTP/RTCP协议相关的数据指令则通常通过特定的端口和传输协议进行传输。服务器会监听这些端口,并解析接收到的RTP/RTCP报文来处理音视频数据和控制信息。
四、乱序处理与视频帧合成
对于RTP报文中的音视频数据,服务器需要处理可能出现的乱序情况。这通常通过以下方式实现:
- 序列号排序:服务器会按照RTP报文中的序列号对音视频数据进行排序。序列号是一个递增的整数,用于标识每个数据包的顺序。
- 缓冲与重组:排序后的音视频数据会被放入缓冲区中进行重组。对于视频数据,服务器会根据帧头和帧尾信息来识别并提取完整的视频帧。
- 解码与播放:重组后的视频帧会进行解码处理,并转换为可供播放或存储的格式。解码后的视频帧可以被发送到播放器进行播放,或者存储到文件系统中以供后续使用。
五、总结
RTSP推流服务器端在接收到流后,会进行连接管理、指令区分、RTP/RTCP处理、音视频数据处理以及反馈与质量控制等步骤。在处理过程中,服务器需要区分RTSP控制指令和数据指令,并对音视频数据进行排序、缓冲、重组和解码处理。对于视频数据,服务器还需要进行视频帧的合成和播放控制等操作。这些处理步骤共同确保了音视频流的实时传输和高质量播放。
六、项目介绍
整个同屏流程是基于jmdns发现协议和rtsp推流实现,本文分析同屏服务端app的源码。
- jmdns服务注册,使得该服务能够被网络上的其他设备通过mDNS协议发现
1.MainActivity.java -> initViews()
BaseApp.getInstance().getAirPlayerController().init(this);
2.ExplayerController.java -> init(Context context)
BoxService.startService(context,deviceName);
3.BoxService.java -> startService(Context context,String deviceName)
Intent intent = new Intent(context, BoxService.class);
context.getApplicationContext().startService(intent);
-> onCreate()
boxServer = new ExBoxServer(deviceName);
boxServer.start();
4.ExBoxServer.java -> run()
mBonjour = new BonjourExplay(name, port);
mBonjour.start();
5.Bonjour.java -> start()
mHandler.obtainMessage(MSG_START).sendToTarget();
-> internalRefresh()
JmDNS dns = mAddr2Dns.get(addr);
if (dns == null) {
ServiceInfo info = onCreateServiceInfo(addr.getAddress(), name, port);
dns= JmDNS.create(addr);
dns.registerService(info);
mAddr2Dns.put(addr, dns);
}
- 初始化rtsp接收服务端,并设置音频和视频帧接收回调
1.MainActivity.java -> initViews()
PlayerManager.getInstance().init(context);
2.PlayerManager.java -> init(Context context)
loadLibrariesOnce();
->loadLibrariesOnce()
System.loadLibrary("media_server");
3.native-lib.cpp -> JNI_OnLoad(JavaVM *vm, void * /* reserved */)
cb.Mcast_Audio_Init = onNotifyMirrorAudioCodecInfo;
cb.Mcast_Audio_Process = onMirrorAudioData;
cb.Mcast_Audio_destroy = onStopAudioPlayer;
cb.Mcast_Video_Play = onStartMirrorPlayer;
cb.Mcast_Video_Process = onMirrorVideoData;
cb.Mcast_Video_Stop = onStopMirrorPlayer;
startRtspServer(&cb);
4.RtspServer.cpp -> startRtspServer(mcast_callbacks_t *cb)
mk_rtsp_server_start(8554, 0);
mk_events events = {
.on_mk_media_changed = on_mk_media_changed,
.on_mk_media_publish = on_mk_media_publish,
.on_mk_media_play = on_mk_media_play,
.on_mk_media_not_found = on_mk_media_not_found,
.on_mk_media_no_reader = on_mk_media_no_reader,
.on_mk_http_request = on_mk_http_request,
.on_mk_http_access = on_mk_http_access,
.on_mk_http_before_access = on_mk_http_before_access,
.on_mk_rtsp_get_realm = on_mk_rtsp_get_realm,
.on_mk_rtsp_auth = on_mk_rtsp_auth,
.on_mk_record_mp4 = on_mk_record_mp4,
.on_mk_shell_login = on_mk_shell_login,
.on_mk_flow_report = on_mk_flow_report
};
mk_events_listen(&events);
3.视频帧接收跟踪分析
1.RtpSession.cpp -> onRecv(const Buffer::Ptr &data)
收到推流数据
RtpSplitter::input(data->data(), data->size());
2.HttpRequestSplitter.cpp -> input(const char *data,size_t len)
_content_len = onRecvHeader(header_ptr, header_size);
3.RtspSplitter.cpp -> onRecvHeader(const char *data, size_t len)
分离出rtcp(与RTP紧密配合,共同实现流媒体传输的监控和管理)、rtp音视频数据和rtsp指令数据,将rtcp,rtp数据进行下一步处理
onRtpPacket(data, len);
4.RtspSession.cpp -> onRtpPacket(const char *data, size_t len)
handleOneRtp(track_idx, _sdp_track[track_idx]->_type, _sdp_track[track_idx]->_samplerate, (uint8_t *) data + RtpPacket::kRtpTcpHeaderSize, len - RtpPacket::kRtpTcpHeaderSize);
继续分离rtcp和rtp数据,将rtp数据进行下一步处理
5.RtpReceiver.h -> handleOneRtp(int index, TrackType type, int sample_rate, uint8_t *ptr, size_t len)
_track[index].inputRtp(type, sample_rate, ptr, len).operator bool()
6.RtpReceiver.cpp -> inputRtp(TrackType type, int sample_rate, uint8_t *ptr, size_t len)
sortPacket(rtp->getSeq(), rtp);
7.RtpReceiver.h -> sortPacket(SEQ seq, T packet)
该方法进行数据包排序后再继续下一步处理。由于数据包可能不会按顺序到达,这里判断数据包的序列号是否是连续,不是连续就缓存起来,直到接收到正确的数据包再输出到下一步,如果一直收不到就输出缓存的数据包,并更新序列号判断。
->output(SEQ seq, T packet)
->_cb(seq, std::move(packet));
8.RtpReceiver.cpp -> setOnSort([this](uint16_t seq, RtpPacket::Ptr packet)
onRtpSorted(std::move(packet));
->onRtpSorted(RtpPacket::Ptr rtp)
_on_sorted(std::move(rtp));
9.RtpReceiver.h -> RtpMultiReceiver()
onRtpSorted(std::move(rtp), index);
10.RtspSession.cpp -> onRtpSorted(RtpPacket::Ptr rtp, int track_idx)
_push_src->onWrite(std::move(rtp), false);
11.RtspMediaSourceImp.cpp -> onWrite(RtpPacket::Ptr rtp, bool keyPos)
PacketCache<RtpPacket>::inputPacket(stamp, is_video, std::move(rtp), keyPos);
12.RtspDemuxer.cpp -> RtspDemuxer::inputRtp(const RtpPacket::Ptr &rtp)
_video_rtp_decoder->inputRtp(rtp, true);
13.H264Rtp.cpp -> H264RtpDecoder::inputRtp(const RtpPacket::Ptr &rtp, bool key_pos)
decodeRtp(rtp);
->H264RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtp)
mergeFu(rtp, frame, payload_size, stamp, seq)
->H264RtpDecoder::mergeFu(const RtpPacket::Ptr &rtp, const uint8_t *ptr, ssize_t size, uint64_t stamp, uint16_t seq)
该方法进行数据包合并帧的动作,由于单个视频帧数据会大于单个数据包,所以需要将多个数据包合并成一个完整的视频帧再进行下一步操作
outputFrame(rtp, _frame);
->H264RtpDecoder::outputFrame(const RtpPacket::Ptr &rtp, const H264Frame::Ptr &frame)
RtpCodec::inputFrame(frame);
14.Frame.h -> inputFrame(const Frame::Ptr &frame)
pr.second->inputFrame(frame)
15. RtspServer.cpp -> on_mk_video_frame_out(void *user_data, mk_frame frame)
rtsp_cb->Mcast_Video_Process(playerId, (uint8_t*)mk_frame_get_data(frame), mk_frame_get_data_size(frame),
mk_frame_get_pts(frame), 0, 1920, 1080);
16.native-lib.cpp -> onMirrorVideoData(uint32_t playerId, const uint8_t *p_src, uint32_t size, int64_t ptsValue,int rota,int width,int height)
ret = env->CallStaticIntMethod(g_miracastNativeMethod.id,
g_miracastNativeMethod.method_onMirrorVideoData,
playerId, jbarray, size, ptsValue,rota,width,height);
17.ExPlayerManager.java -> onMirrorVideoData(int playerId, byte[] p_src, int size, long ptsValue)
player.onEventListener.onMirrorVideoData(player, p_src, size, ptsValue);
18.MainActivity.java -> onMirrorVideoData(ExPlayer player, byte[] p_src, int size, long ptsValue)
videoView.getVideoPlayer().putEncodeFrame(p_src, 0, size, ptsValue);
视频帧数据加入队列中,后面另开子线程取数据进行播放