feat: remove the hand-written rtsp client

ZoneMinder carried two rtsp implementations: ffmpeg, which every Type=Ffmpeg
monitor uses, and a hand-written one behind Type=Remote with Protocol=rtsp --
its own rtsp state machine, rtp transport, rtcp control and sdp parser, some
3200 lines. ffmpeg handles rtsp well enough now that a second implementation
earns nothing, and it was already marked deprecated in the interface.

A monitor still configured that way now logs an error naming the exact Source
Path to move to, built by RtspUrlFromRemote with the credentials stripped, so
the message is actionable rather than only a refusal. That helper and its tests
stay; they describe the migration, which still needs describing.

Type=Remote keeps its http protocol. mjpeg streams and snapshot-jpeg cameras
are what that capture method is for and they are unaffected.

Removed: zm_remote_camera_rtsp, zm_rtsp, zm_rtp, zm_rtp_ctrl, zm_rtp_data,
zm_rtp_source and zm_sdp. zm_rtsp_auth stays -- it is included by
zm_remote_camera.h and so is shared with the http camera. Everything named
zm_rtsp_server_* is the restreaming server and is unrelated.

Verified nothing outside the cluster referenced any of it: RtspThread,
RtpSource, RtpCtrlThread, RtpDataThread and SessionDescriptor had no callers
elsewhere in the tree, and RemoteCameraRtsp was named only by zm_monitor.

Build clean; suite 147 cases / 12531 assertions. An rtsp-over-ffmpeg monitor
still captures on hardware: vaapi negotiated, 646 frames into a recorded event,
no errors.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Y6FieTwEXuLhhR4e2yiax
This commit is contained in:
Isaac ConnorandClaude Opus 5 committed 2026-10-10 09:32:17 -04:00
1 parent f22d6d9e4d
commit d14dfd8efb
16 files changed
+8 -3266

No files matched your search

-7
View File
@@ -58,12 +58,6 @@ set(ZM_BIN_SRC_FILES
zm_remote_camera.cpp
zm_remote_camera_http.cpp
zm_remote_camera_nvsocket.cpp
zm_remote_camera_rtsp.cpp
zm_rtp.cpp
zm_rtp_ctrl.cpp
zm_rtp_data.cpp
zm_rtp_source.cpp
zm_rtsp.cpp
zm_rtsp_auth.cpp
zm_rtsp_server_stream_source.cpp
zm_rtsp_server_stream_adts_source.cpp
@@ -71,7 +65,6 @@ set(ZM_BIN_SRC_FILES
zm_rtsp_server_stream_av1_source.cpp
zm_rtsp_server_stream_audio_source.cpp
zm_rtsp_server_stream_video_source.cpp
zm_sdp.cpp
zm_server.cpp
zm_signal.cpp
zm_stream.cpp
+8 -23
View File
@@ -29,7 +29,6 @@
#include "zm_remote_camera.h"
#include "zm_remote_camera_http.h"
#include "zm_remote_camera_nvsocket.h"
#include "zm_remote_camera_rtsp.h"
#include "zm_signal.h"
#include "zm_stream_socket.h"
#include "zm_time.h"
@@ -830,28 +829,14 @@ void Monitor::LoadCamera() {
record_audio
);
} else if (protocol == "rtsp") {
Warning("Monitor %u (%s): the Remote/RTSP capture method is deprecated as of 1.40 and "
"will be removed in 1.41. Change this monitor to Type 'Ffmpeg' with Source Path %s",
id, name.c_str(),
remove_authentication(RtspUrlFromRemote(host, port, path, user, pass)).c_str());
camera = zm::make_unique<RemoteCameraRtsp>(this,
method,
host, // Host
port, // Port
path, // Path
user,
pass,
camera_width,
camera_height,
rtsp_describe,
colours,
brightness,
contrast,
hue,
colour,
purpose == CAPTURE,
record_audio
);
// The hand-written rtsp client this used is gone. ffmpeg speaks rtsp
// better than it did and is what every other rtsp monitor already uses,
// so there is nothing left for a second implementation to do. Name the
// exact Source Path to switch to rather than only saying no.
Error("Monitor %u (%s): the Remote/RTSP capture method has been removed. "
"Change this monitor to Type 'Ffmpeg' with Source Path %s",
id, name.c_str(),
remove_authentication(RtspUrlFromRemote(host, port, path, user, pass)).c_str());
} else {
Error("Unexpected remote camera protocol '%s'", protocol.c_str());
}
-293
View File
@@ -1,293 +0,0 @@
//
// ZoneMinder Remote Camera Class Implementation, $Date$, $Revision$
// Copyright (C) 2001-2008 Philip Coombes
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
#include "zm_remote_camera_rtsp.h"
#include "zm_config.h"
#include "zm_monitor.h"
#include "zm_packet.h"
#include "zm_signal.h"
RemoteCameraRtsp::RemoteCameraRtsp(
const Monitor *monitor,
const std::string &p_method,
const std::string &p_host,
const std::string &p_port,
const std::string &p_path,
const std::string &p_user,
const std::string &p_pass,
int p_width,
int p_height,
bool p_rtsp_describe,
int p_colours,
int p_brightness,
int p_contrast,
int p_hue,
int p_colour,
bool p_capture,
bool p_record_audio ) :
RemoteCamera(
monitor, "rtsp",
p_host, p_port, p_path,
p_user, p_pass,
p_width, p_height, p_colours,
p_brightness, p_contrast, p_hue, p_colour,
p_capture, p_record_audio),
rtsp_describe(p_rtsp_describe),
user(p_user),
pass(p_pass),
frameCount(0) {
if ( p_method == "rtpUni" )
method = RtspThread::RTP_UNICAST;
else if ( p_method == "rtpMulti" )
method = RtspThread::RTP_MULTICAST;
else if ( p_method == "rtpRtsp" )
method = RtspThread::RTP_RTSP;
else if ( p_method == "rtpRtspHttp" )
method = RtspThread::RTP_RTSP_HTTP;
else
Fatal("Unrecognised method '%s' when creating RTSP camera %d", p_method.c_str(), monitor->Id());
if ( capture ) {
Initialise();
}
/* Has to be located inside the constructor so other components such as zma will receive correct colours and subpixel order */
if ( zm_is_rgb32(pixelFormat) ) {
subpixelorder = ZM_SUBPIX_ORDER_RGBA;
imagePixFormat = AV_PIX_FMT_RGBA;
pixelFormat = AV_PIX_FMT_RGBA;
} else if ( zm_is_rgb24(pixelFormat) ) {
subpixelorder = ZM_SUBPIX_ORDER_RGB;
imagePixFormat = AV_PIX_FMT_RGB24;
pixelFormat = AV_PIX_FMT_RGB24;
} else if ( pixelFormat == AV_PIX_FMT_GRAY8 ) {
subpixelorder = ZM_SUBPIX_ORDER_NONE;
imagePixFormat = AV_PIX_FMT_GRAY8;
pixelFormat = AV_PIX_FMT_GRAY8;
} else {
Panic("Unexpected pixel format %d (%s); legacy colours=%d subpixelorder=%d",
pixelFormat, zm_get_pix_fmt_name(pixelFormat), colours, subpixelorder);
}
} // end RemoteCameraRtsp::RemoteCameraRtsp(...)
RemoteCameraRtsp::~RemoteCameraRtsp() {
if ( mVideoCodecContext ) {
//avcodec_close(mVideoCodecContext);
avcodec_free_context(&mVideoCodecContext);
mVideoCodecContext = nullptr; // Freed by avformat_free_context in the destructor of RtspThread class
}
// Is allocated in RTSPThread and is free there as well
mFormatContext = nullptr;
if ( capture ) {
Terminate();
}
}
void RemoteCameraRtsp::Initialise() {
RemoteCamera::Initialise();
int max_size = width*height*colours;
// This allocates a buffer able to hold a raw fframe, which is a little artbitrary. Might be nice to get some
// decent data on how large a buffer is really needed. I think in ffmpeg there are now some functions to do that.
buffer.size(max_size);
FFMPEGInit();
}
void RemoteCameraRtsp::Terminate() {
Disconnect();
}
int RemoteCameraRtsp::Connect() {
rtspThread = zm::make_unique<RtspThread>(monitor->Id(), method, protocol, host, port, path, user, pass, rtsp_describe);
return 0;
}
int RemoteCameraRtsp::Disconnect() {
if (rtspThread) {
rtspThread->Stop();
rtspThread.reset();
}
return 0;
}
int RemoteCameraRtsp::PrimeCapture() {
if (rtspThread) Disconnect();
Connect();
Debug(2, "Waiting for sources");
for (int i = 100; i && !zm_terminate && !rtspThread->hasSources(); i--) {
std::this_thread::sleep_for(Microseconds(10000));
}
if (!rtspThread->hasSources()) {
Error("No RTSP sources");
return -1;
}
Debug(2, "Got sources");
mFormatContext = rtspThread->getFormatContext();
// Find first video stream present
mVideoStreamId = -1;
mAudioStreamId = -1;
// Find the first video stream.
for ( unsigned int i = 0; i < mFormatContext->nb_streams; i++ ) {
if ( is_video_stream(mFormatContext->streams[i]) ) {
if ( mVideoStreamId == -1 ) {
mVideoStreamId = i;
mVideoStream = mFormatContext->streams[i];
mVideoStream->time_base = AV_TIME_BASE_Q;
continue;
} else {
Debug(2, "Have another video stream.");
}
#if 0
} else if ( is_audio_stream(mFormatContext->streams[i]) ) {
if ( mAudioStreamId == -1 ) {
mAudioStreamId = i;
mAudioStream = mFormatContext->streams[i];
} else {
Debug(2, "Have another audio stream.");
}
#endif
} else {
Debug(1, "Have unknown codec type in stream %d", i);
}
} // end foreach stream
if ( mVideoStreamId == -1 ) {
Error("Unable to locate video stream");
return -1;
}
if ( mAudioStreamId == -1 )
Debug(3, "Unable to locate audio stream");
// Get a pointer to the codec context for the video stream
mVideoCodecContext = avcodec_alloc_context3(nullptr);
avcodec_parameters_to_context(mVideoCodecContext, mFormatContext->streams[mVideoStreamId]->codecpar);
// Find the decoder for the video stream
const AVCodec *codec = avcodec_find_decoder(mVideoCodecContext->codec_id);
if ( codec == nullptr ) {
Error("Unable to locate codec %d decoder", mVideoCodecContext->codec_id);
return -1;
}
// Open codec
if ( avcodec_open2(mVideoCodecContext, codec, nullptr) < 0 ) {
Error("Can't open codec");
return -1;
}
int pSize = av_image_get_buffer_size(imagePixFormat, width, height, 1);
if ( (unsigned int)pSize != imagesize ) {
Error("Image size mismatch. Required: %d Available: %llu", pSize, imagesize);
return -1;
}
return 1;
} // end PrimeCapture
int RemoteCameraRtsp::PreCapture() {
if (!rtspThread || rtspThread->IsStopped())
return -1;
if ( !rtspThread->hasSources() ) {
Error("Cannot precapture, no RTP sources");
return -1;
}
return 1;
}
int RemoteCameraRtsp::Capture(std::shared_ptr<ZMPacket> &zm_packet) {
int frameComplete = false;
AVPacket *packet = zm_packet->packet.get();
while (!frameComplete) {
buffer.clear();
if (!rtspThread || rtspThread->IsStopped() || zm_terminate)
return -1;
if (rtspThread->getFrame(buffer)) {
Debug(3, "Read frame %d bytes", buffer.size());
Hexdump(4, buffer.head(), 16);
if ( !buffer.size() )
return -1;
if ( mVideoCodecContext->codec_id == AV_CODEC_ID_H264 ) {
// SPS and PPS frames should be saved and appended to IDR frames
int nalType = (buffer.head()[3] & 0x1f);
// SPS The SPS NAL unit contains parameters that apply to a series of consecutive coded video pictures
if ( nalType == 1 ) {
} else if ( nalType == 7 ) {
lastSps = buffer;
continue;
} else if ( nalType == 8 ) {
// PPS The PPS NAL unit contains parameters that apply to the decoding of one or more individual pictures inside a coded video sequence
lastPps = buffer;
continue;
} else if ( nalType == 5 ) {
packet->flags |= AV_PKT_FLAG_KEY;
zm_packet->keyframe = 1;
// IDR
buffer += lastSps;
buffer += lastPps;
} else {
Debug(2, "Unknown nalType %d", nalType);
}
} else {
Debug(3, "Not an h264 packet");
}
//while ( (!frameComplete) && (buffer.size() > 0) ) {
if ( buffer.size() > 0 ) {
packet->data = (uint8_t*)av_malloc(buffer.size());
memcpy(packet->data, buffer.head(), buffer.size());
//packet->data = buffer.head();
packet->size = buffer.size();
bytes += packet->size;
buffer -= packet->size;
struct timeval now;
gettimeofday(&now, nullptr);
packet->pts = packet->dts = now.tv_sec*1000000+now.tv_usec;
zm_packet->codec_type = mVideoCodecContext->codec_type;
zm_packet->stream = mVideoStream;
frameComplete = true;
Debug(2, "Frame: %d - %d/%d", frameCount, packet->size, buffer.size());
}
} /* getFrame() */
} // end while true
return 1;
} // end int RemoteCameraRtsp::Capture(ZMPacket &packet)
int RemoteCameraRtsp::PostCapture() {
return 1;
}
-89
View File
@@ -1,89 +0,0 @@
//
// ZoneMinder Remote RTSP Camera Class Interface, $Date$, $Revision$
// Copyright (C) 2001-2008 Philip Coombes
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
#ifndef ZM_REMOTE_CAMERA_RTSP_H
#define ZM_REMOTE_CAMERA_RTSP_H
#include "zm_ffmpeg.h"
#include "zm_remote_camera.h"
#include "zm_rtsp.h"
//
// Class representing 'rtsp' cameras, i.e. those which are
// accessed over a network connection using rtsp protocol
// (Real Time Streaming Protocol)
//
class RemoteCameraRtsp : public RemoteCamera {
protected:
struct sockaddr_in rtsp_sa;
struct sockaddr_in rtcp_sa;
int rtsp_sd;
int rtp_sd;
int rtcp_sd;
bool rtsp_describe;
const std::string user;
const std::string pass;
Buffer buffer;
Buffer lastSps;
Buffer lastPps;
RtspThread::RtspMethod method;
std::unique_ptr<RtspThread> rtspThread;
int frameCount;
_AVPIXELFORMAT imagePixFormat;
public:
RemoteCameraRtsp(
const Monitor *monitor,
const std::string &method,
const std::string &host,
const std::string &port,
const std::string &path,
const std::string &user,
const std::string &pass,
int p_width,
int p_height,
bool p_rtsp_describe,
int p_colours,
int p_brightness,
int p_contrast,
int p_hue,
int p_colour,
bool p_capture,
bool p_record_audio);
~RemoteCameraRtsp();
void Initialise() override;
void Terminate() override;
int Connect() override;
int Disconnect() override;
int PrimeCapture() override;
int PreCapture() override;
int Capture(std::shared_ptr <ZMPacket> &p) override;
int PostCapture() override;
int Close() override { return 0; };
};
#endif // ZM_REMOTE_CAMERA_RTSP_H
-22
View File
@@ -1,22 +0,0 @@
//
// ZoneMinder RTP/RTCP Class Implementation, $Date$, $Revision$
// Copyright (C) 2001-2008 Philip Coombes
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
#include "zm_rtp.h"
// Blank
-25
View File
@@ -1,25 +0,0 @@
//
// ZoneMinder RTP/RTCP Class Interface, $Date$, $Revision$
// Copyright (C) 2001-2008 Philip Coombes
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
#ifndef ZM_RTP_H
#define ZM_RTP_H
#define RTP_VERSION 2
#endif // ZM_RTP_H
-334
View File
@@ -1,334 +0,0 @@
//
// ZoneMinder RTCP Class Implementation, $Date$, $Revision$
// Copyright (C) 2001-2008 Philip Coombes
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
#include "zm_rtp_ctrl.h"
#include "zm_config.h"
#include "zm_rtp.h"
#include "zm_rtsp.h"
RtpCtrlThread::RtpCtrlThread(RtspThread &rtspThread, RtpSource &rtpSource)
: mRtspThread(rtspThread), mRtpSource(rtpSource), mTerminate(false) {
mThread = std::thread(&RtpCtrlThread::Run, this);
}
RtpCtrlThread::~RtpCtrlThread() {
Stop();
if (mThread.joinable())
mThread.join();
}
int RtpCtrlThread::recvPacket( const unsigned char *packet, ssize_t packetLen ) {
const RtcpPacket *rtcpPacket;
rtcpPacket = (RtcpPacket *)packet;
int consumed = 0;
//printf( "C: " );
//for ( int i = 0; i < packetLen; i++ )
//printf( "%02x ", (unsigned char)packet[i] );
//printf( "\n" );
int ver = rtcpPacket->header.version;
int count = rtcpPacket->header.count;
int pt = rtcpPacket->header.pt;
int len = ntohs(rtcpPacket->header.lenN);
Debug( 5, "RTCP Ver: %d Count: %d Pt: %d len: %d", ver, count, pt, len);
switch( pt ) {
case RTCP_SR : {
uint32_t ssrc = ntohl(rtcpPacket->body.sr.ssrcN);
Debug( 5, "RTCP Got SR (%x)", ssrc );
if ( mRtpSource.getSsrc() ) {
if ( ssrc != mRtpSource.getSsrc() ) {
Warning( "Discarding packet for unrecognised ssrc %x", ssrc );
return( -1 );
}
} else if ( ssrc ) {
mRtpSource.setSsrc( ssrc );
}
if ( len > 1 ) {
//printf( "NTPts:%d.%d, RTPts:%d\n", $ntptsmsb, $ntptslsb, $rtpts );
uint32_t ntptsmsb = ntohl(rtcpPacket->body.sr.ntpSecN);
uint32_t ntptslsb = ntohl(rtcpPacket->body.sr.ntpFracN);
//printf( "NTPts:%x.%04x, RTPts:%x\n", $ntptsmsb, $ntptslsb, $rtpts );
//printf( "Pkts:$sendpkts, Octs:$sendocts\n" );
uint32_t rtpTime = ntohl(rtcpPacket->body.sr.rtpTsN);
mRtpSource.updateRtcpData( ntptsmsb, ntptslsb, rtpTime );
}
break;
}
case RTCP_SDES : {
ssize_t contentLen = packetLen - sizeof(rtcpPacket->header);
while ( contentLen ) {
Debug( 5, "RTCP CL: %zd", contentLen );
uint32_t ssrc = ntohl(rtcpPacket->body.sdes.srcN);
Debug( 5, "RTCP Got SDES (%x), %d items", ssrc, count );
if ( mRtpSource.getSsrc() && (ssrc != mRtpSource.getSsrc()) ) {
Warning( "Discarding packet for unrecognised ssrc %x", ssrc );
return( -1 );
}
unsigned char *sdesPtr = (unsigned char *)&rtcpPacket->body.sdes.item;
for ( int i = 0; i < count; i++ ) {
RtcpSdesItem *item = (RtcpSdesItem *)sdesPtr;
Debug( 5, "RTCP Item length %d", item->len );
switch( item->type ) {
case RTCP_SDES_CNAME : {
std::string cname( item->data, item->len );
Debug( 5, "RTCP Got CNAME %s", cname.c_str() );
break;
}
case RTCP_SDES_END :
case RTCP_SDES_NAME :
case RTCP_SDES_EMAIL :
case RTCP_SDES_PHONE :
case RTCP_SDES_LOC :
case RTCP_SDES_TOOL :
case RTCP_SDES_NOTE :
case RTCP_SDES_PRIV :
default :
Error( "Received unexpected SDES item type %d, ignoring", item->type );
return -1;
}
int paddedLen = 4+2+item->len+1; // Add null byte
paddedLen = (((paddedLen-1)/4)+1)*4; // Round to nearest multiple of 4
Debug(5, "RTCP PL:%d", paddedLen);
sdesPtr += paddedLen;
contentLen = ( paddedLen <= contentLen ) ? ( contentLen - paddedLen ) : 0;
}
} // end whiel contentLen
break;
}
case RTCP_BYE :
Debug(5, "RTCP Got BYE");
Stop();
break;
case RTCP_APP :
// Ignoring as per RFC 3550
Debug(5, "Received RTCP_APP packet, ignoring.");
break;
case RTCP_RR :
Error("Received RTCP_RR packet.");
return -1;
default :
// Ignore unknown packet types. Some cameras do this by design.
Debug(5, "Received unexpected packet type %d, ignoring", pt);
break;
}
consumed = sizeof(uint32_t)*(len+1);
return consumed;
}
int RtpCtrlThread::generateRr( const unsigned char *packet, ssize_t packetLen ) {
RtcpPacket *rtcpPacket = (RtcpPacket *)packet;
int byteLen = sizeof(rtcpPacket->header)+sizeof(rtcpPacket->body.rr)+sizeof(rtcpPacket->body.rr.rr[0]);
int wordLen = ((byteLen-1)/sizeof(uint32_t))+1;
rtcpPacket->header.version = RTP_VERSION;
rtcpPacket->header.p = 0;
rtcpPacket->header.pt = RTCP_RR;
rtcpPacket->header.count = 1;
rtcpPacket->header.lenN = htons(wordLen-1);
mRtpSource.updateRtcpStats();
Debug(5, "Ssrc = %d Ssrc_1 = %d Last Seq = %d Jitter = %d Last SR = %d",
mRtspThread.getSsrc()+1,
mRtpSource.getSsrc(),
mRtpSource.getMaxSeq(),
mRtpSource.getJitter(),
mRtpSource.getLastSrTimestamp()
);
rtcpPacket->body.rr.ssrcN = htonl(mRtspThread.getSsrc()+1);
rtcpPacket->body.rr.rr[0].ssrcN = htonl(mRtpSource.getSsrc());
rtcpPacket->body.rr.rr[0].lost = mRtpSource.getLostPackets();
rtcpPacket->body.rr.rr[0].fraction = mRtpSource.getLostFraction();
rtcpPacket->body.rr.rr[0].lastSeqN = htonl(mRtpSource.getMaxSeq());
rtcpPacket->body.rr.rr[0].jitterN = htonl(mRtpSource.getJitter());
rtcpPacket->body.rr.rr[0].lsrN = htonl(mRtpSource.getLastSrTimestamp());
rtcpPacket->body.rr.rr[0].dlsrN = 0;
return wordLen*sizeof(uint32_t);
} // end RtpCtrlThread::generateRr
int RtpCtrlThread::generateSdes( const unsigned char *packet, ssize_t packetLen ) {
RtcpPacket *rtcpPacket = (RtcpPacket *)packet;
const std::string &cname = mRtpSource.getCname();
int byteLen = sizeof(rtcpPacket->header)+sizeof(rtcpPacket->body.sdes)+sizeof(rtcpPacket->body.sdes.item[0])+cname.size();
int wordLen = ((byteLen-1)/sizeof(uint32_t))+1;
rtcpPacket->header.version = RTP_VERSION;
rtcpPacket->header.p = 0;
rtcpPacket->header.pt = RTCP_SDES;
rtcpPacket->header.count = 1;
rtcpPacket->header.lenN = htons(wordLen-1);
rtcpPacket->body.sdes.srcN = htonl(mRtpSource.getSsrc()+1);
rtcpPacket->body.sdes.item[0].type = RTCP_SDES_CNAME;
rtcpPacket->body.sdes.item[0].len = cname.size();
memcpy( rtcpPacket->body.sdes.item[0].data, cname.data(), cname.size() );
return wordLen*sizeof(uint32_t);
} // end RtpCtrlThread::generateSdes
int RtpCtrlThread::generateBye( const unsigned char *packet, ssize_t packetLen ) {
RtcpPacket *rtcpPacket = (RtcpPacket *)packet;
int byteLen = sizeof(rtcpPacket->header)+sizeof(rtcpPacket->body.bye)+sizeof(rtcpPacket->body.bye.srcN[0]);
int wordLen = ((byteLen-1)/sizeof(uint32_t))+1;
rtcpPacket->header.version = RTP_VERSION;
rtcpPacket->header.p = 0;
rtcpPacket->header.pt = RTCP_BYE;
rtcpPacket->header.count = 1;
rtcpPacket->header.lenN = htons(wordLen-1);
rtcpPacket->body.bye.srcN[0] = htonl(mRtpSource.getSsrc());
return wordLen*sizeof(uint32_t);
} // end RtpCtrolThread::generateBye
int RtpCtrlThread::recvPackets( unsigned char *buffer, ssize_t nBytes ) {
unsigned char *bufferPtr = buffer;
// u_int32 len; /* length of compound RTCP packet in words */
// rtcp_t *r; /* RTCP header */
// rtcp_t *end; /* end of compound RTCP packet */
// if ((*(u_int16 *)r & RTCP_VALID_MASK) != RTCP_VALID_VALUE) {
// /* something wrong with packet format */
// }
// end = (rtcp_t *)((u_int32 *)r + len);
// do r = (rtcp_t *)((u_int32 *)r + r->common.length + 1);
// while (r < end && r->common.version == 2);
// if (r != end) {
// /* something wrong with packet format */
// }
while ( nBytes > 0 ) {
int consumed = recvPacket( bufferPtr, nBytes );
if ( consumed <= 0 )
break;
bufferPtr += consumed;
nBytes -= consumed;
}
return nBytes;
}
void RtpCtrlThread::Run() {
if (mRtpSource.getLocalCtrlPort()<=1024) {
Debug(2, "Not starting control thread");
return;
}
Debug( 2, "Starting control thread %x on port %d", mRtpSource.getSsrc(), mRtpSource.getLocalCtrlPort() );
zm::SockAddrInet localAddr, remoteAddr;
bool sendReports;
zm::UdpInetSocket rtpCtrlServer;
if ( mRtpSource.getLocalHost() != "" ) {
if ( !rtpCtrlServer.bind( mRtpSource.getLocalHost().c_str(), mRtpSource.getLocalCtrlPort() ) )
Fatal( "Failed to bind RTCP server" );
sendReports = false;
Debug( 3, "Bound to %s:%d", mRtpSource.getLocalHost().c_str(), mRtpSource.getLocalCtrlPort() );
} else {
if ( !rtpCtrlServer.bind( mRtspThread.getAddressFamily() == AF_INET6 ? "::" : "0.0.0.0", mRtpSource.getLocalCtrlPort() ) )
Fatal( "Failed to bind RTCP server" );
Debug( 3, "Bound to %s:%d", mRtpSource.getLocalHost().c_str(), mRtpSource.getLocalCtrlPort() );
if ( !rtpCtrlServer.connect( mRtpSource.getRemoteHost().c_str(), mRtpSource.getRemoteCtrlPort() ) )
Fatal( "Failed to connect RTCP server" );
Debug( 3, "Connected to %s:%d", mRtpSource.getRemoteHost().c_str(), mRtpSource.getRemoteCtrlPort() );
sendReports = true;
}
// The only reason I can think of why we would have a timeout period is so that we can regularly send RR packets.
// Why 10 seconds? If anything I think this should be whatever timeout value was given in the DESCRIBE response
zm::Select select(Seconds(10));
select.addReader( &rtpCtrlServer );
unsigned char buffer[ZM_NETWORK_BUFSIZ];
TimePoint last_receive = std::chrono::steady_clock::now();
bool timeout = false; // used as a flag that we had a timeout, and then sent an RR to see if we wake back up. Real timeout will happen when this is true.
while (!mTerminate && (select.wait() >= 0)) {
TimePoint now = std::chrono::steady_clock::now();
zm::Select::CommsList readable = select.getReadable();
if ( readable.size() == 0 ) {
if ( ! timeout ) {
// With this code here, we will send an SDES and RR packet every 10 seconds
ssize_t nBytes;
unsigned char *bufferPtr = buffer;
bufferPtr += generateRr( bufferPtr, sizeof(buffer)-(bufferPtr-buffer) );
bufferPtr += generateSdes( bufferPtr, sizeof(buffer)-(bufferPtr-buffer) );
Debug(3, "Preventing timeout by sending %zd bytes on sd %d. Time since last receive: %.2f s",
bufferPtr - buffer, rtpCtrlServer.getWriteDesc(), FPSeconds(now - last_receive).count());
if ( (nBytes = rtpCtrlServer.send(buffer, bufferPtr-buffer)) < 0 )
Error("Unable to send: %s", strerror(errno));
timeout = true;
continue;
} else {
Debug(1, "RTCP timed out. Time since last receive: %.2f s", FPSeconds(now - last_receive).count());
continue;
//break;
}
} else {
timeout = false;
last_receive = std::chrono::steady_clock::now();
}
for (zm::Select::CommsList::iterator iter = readable.begin(); iter != readable.end(); ++iter ) {
if ( zm::UdpInetSocket *socket = dynamic_cast<zm::UdpInetSocket *>(*iter) ) {
ssize_t nBytes = socket->recv( buffer, sizeof(buffer) );
Debug( 4, "Read %zd bytes on sd %d", nBytes, socket->getReadDesc() );
if ( nBytes ) {
recvPackets( buffer, nBytes );
if ( sendReports ) {
unsigned char *bufferPtr = buffer;
bufferPtr += generateRr( bufferPtr, sizeof(buffer)-(bufferPtr-buffer) );
bufferPtr += generateSdes( bufferPtr, sizeof(buffer)-(bufferPtr-buffer) );
Debug(3, "Sending %zd bytes on sd %d", bufferPtr-buffer, rtpCtrlServer.getWriteDesc());
if ( (nBytes = rtpCtrlServer.send( buffer, bufferPtr-buffer )) < 0 )
Error("Unable to send: %s", strerror(errno));
//Debug( 4, "Sent %d bytes on sd %d", nBytes, rtpCtrlServer.getWriteDesc() );
}
} else {
// Here is another case of not receiving some data causing us to terminate... why? Sometimes there are pauses in the interwebs.
Stop();
break;
}
} else {
Panic("Barfed");
} // end if socket
} // end foeach comms iterator
}
rtpCtrlServer.close();
mRtspThread.Stop();
}
-145
View File
@@ -1,145 +0,0 @@
//
// ZoneMinder RTCP Class Interface, $Date$, $Revision$
// Copyright (C) 2001-2008 Philip Coombes
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
#ifndef ZM_RTP_CTRL_H
#define ZM_RTP_CTRL_H
#include <atomic>
#include <sys/types.h>
#include <thread>
// Defined in ffmpeg rtp.h
//#define RTP_MAX_SDES 255 // maximum text length for SDES
// Big-endian mask for version, padding bit and packet type pair
#define RTCP_VALID_MASK (0xc000 | 0x2000 | 0xfe)
#define RTCP_VALID_VALUE ((RTP_VERSION << 14) | RTCP_SR)
class RtspThread;
class RtpSource;
class RtpCtrlThread {
friend class RtspThread;
private:
typedef enum {
RTCP_SR = 200,
RTCP_RR = 201,
RTCP_SDES = 202,
RTCP_BYE = 203,
RTCP_APP = 204
} RtcpType;
typedef enum {
RTCP_SDES_END = 0,
RTCP_SDES_CNAME = 1,
RTCP_SDES_NAME = 2,
RTCP_SDES_EMAIL = 3,
RTCP_SDES_PHONE = 4,
RTCP_SDES_LOC = 5,
RTCP_SDES_TOOL = 6,
RTCP_SDES_NOTE = 7,
RTCP_SDES_PRIV = 8
} RtcpSdesType;
struct RtcpCommonHeader {
uint8_t count:5; // varies by packet type
uint8_t p:1; // padding flag
uint8_t version:2; // protocol version
uint8_t pt; // RTCP packet type
uint16_t lenN; // pkt len in words, w/o this word, network order
};
// Reception report block
struct RtcpRr {
uint32_t ssrcN; // data source being reported
int32_t lost:24; // cumul. no. pkts lost (signed!)
uint32_t fraction:8; // fraction lost since last SR/RR
uint32_t lastSeqN; // extended last seq. no. received, network order
uint32_t jitterN; // interarrival jitter, network order
uint32_t lsrN; // last SR packet from this source, network order
uint32_t dlsrN; // delay since last SR packet, network order
};
// SDES item
struct RtcpSdesItem {
uint8_t type; // type of item (rtcp_sdes_type_t)
uint8_t len; // length of item (in octets)
char data[]; // text, not null-terminated
};
// RTCP packet
struct RtcpPacket {
RtcpCommonHeader header; // common header
union {
// Sender Report (SR)
struct Sr {
uint32_t ssrcN; // sender generating this report, network order
uint32_t ntpSecN; // NTP timestamp, network order
uint32_t ntpFracN;
uint32_t rtpTsN; // RTP timestamp, network order
uint32_t pSentN; // packets sent, network order
uint32_t oSentN; // octets sent, network order
RtcpRr rr[]; // variable-length list
} sr;
// Reception Report (RR)
struct Rr {
uint32_t ssrcN; // receiver generating this report
RtcpRr rr[]; // variable-length list
} rr;
// source description (SDES)
struct Sdes {
uint32_t srcN; // first SSRC/CSRC
RtcpSdesItem item[]; // list of SDES items
} sdes;
// BYE
struct {
uint32_t srcN[]; // list of sources
// can't express trailing text for reason (what does this mean? it's not even english!)
} bye;
} body;
};
private:
RtspThread &mRtspThread;
RtpSource &mRtpSource;
int mPort;
std::atomic<bool> mTerminate;
std::thread mThread;
private:
int recvPacket( const unsigned char *packet, ssize_t packetLen );
int generateRr( const unsigned char *packet, ssize_t packetLen );
int generateSdes( const unsigned char *packet, ssize_t packetLen );
int generateBye( const unsigned char *packet, ssize_t packetLen );
int recvPackets( unsigned char *buffer, ssize_t nBytes );
void Run();
public:
RtpCtrlThread( RtspThread &rtspThread, RtpSource &rtpSource );
~RtpCtrlThread();
void Stop() { mTerminate = true; }
};
#endif // ZM_RTP_CTRL_H
-111
View File
@@ -1,111 +0,0 @@
//
// ZoneMinder RTP Data Class Implementation, $Date$, $Revision$
// Copyright (C) 2001-2008 Philip Coombes
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
#include "zm_rtp_data.h"
#include "zm_config.h"
#include "zm_rtsp.h"
#include "zm_signal.h"
RtpDataThread::RtpDataThread(RtspThread &rtspThread, RtpSource &rtpSource) :
mRtspThread(rtspThread), mRtpSource(rtpSource), mTerminate(false) {
mThread = std::thread(&RtpDataThread::Run, this);
}
RtpDataThread::~RtpDataThread() {
Stop();
if (mThread.joinable())
mThread.join();
}
bool RtpDataThread::recvPacket(const unsigned char *packet, size_t packetLen) {
const RtpDataHeader *rtpHeader;
rtpHeader = (RtpDataHeader *)packet;
Debug(5, "Ver: %d P: %d Pt: %d Mk: %d Seq: %d T/S: %x SSRC: %x",
rtpHeader->version,
rtpHeader->p,
rtpHeader->pt,
rtpHeader->m,
ntohs(rtpHeader->seqN),
ntohl(rtpHeader->timestampN),
ntohl(rtpHeader->ssrcN));
//unsigned short seq = ntohs(rtpHeader->seqN);
uint32_t ssrc = ntohl(rtpHeader->ssrcN);
if ( mRtpSource.getSsrc() && (ssrc != mRtpSource.getSsrc()) ) {
Warning("Discarding packet for unrecognised ssrc %x", ssrc);
return false;
}
return mRtpSource.handlePacket(packet, packetLen);
}
void RtpDataThread::Run() {
if (mRtpSource.getLocalDataPort()<=1024) {
Debug(2, "Not starting data thread");
return;
}
Debug(2, "Starting data thread %d on port %d",
mRtpSource.getSsrc(), mRtpSource.getLocalDataPort());
zm::SockAddrInet localAddr;
zm::UdpInetServer rtpDataSocket;
if ( mRtpSource.getLocalHost() != "" ) {
if ( !rtpDataSocket.bind(mRtpSource.getLocalHost().c_str(), mRtpSource.getLocalDataPort()) )
Fatal("Failed to bind RTP server at %s:%d", mRtpSource.getLocalHost().c_str(), mRtpSource.getLocalDataPort());
} else {
if ( !rtpDataSocket.bind(
mRtspThread.getAddressFamily() == AF_INET6 ? "::" : "0.0.0.0",
mRtpSource.getLocalDataPort() ) )
Fatal("Failed to bind RTP server at %s:%d",
(mRtspThread.getAddressFamily() == AF_INET6 ? "::" : "0.0.0.0"), mRtpSource.getLocalDataPort());
}
Debug(3, "Bound to %s:%d", mRtpSource.getLocalHost().c_str(), mRtpSource.getLocalDataPort());
zm::Select select(Seconds(3));
select.addReader(&rtpDataSocket);
unsigned char buffer[ZM_NETWORK_BUFSIZ];
while ( !zm_terminate && !mTerminate && (select.wait() >= 0) ) {
zm::Select::CommsList readable = select.getReadable();
if ( readable.size() == 0 ) {
Error("RTP timed out");
Stop();
break;
}
for (zm::Select::CommsList::iterator iter = readable.begin(); iter != readable.end(); ++iter ) {
if ( zm::UdpInetServer *socket = dynamic_cast<zm::UdpInetServer *>(*iter) ) {
ssize_t nBytes = socket->recv(buffer, sizeof(buffer));
Debug(4, "Got %zd bytes on sd %d", nBytes, socket->getReadDesc());
if ( nBytes ) {
recvPacket(buffer, nBytes);
} else {
Stop();
break;
}
} else {
Panic("Barfed");
}
} // end foreach commsList
}
rtpDataSocket.close();
mRtspThread.Stop();
}
-64
View File
@@ -1,64 +0,0 @@
//
// ZoneMinder RTP Data Class Interface, $Date$, $Revision$
// Copyright (C) 2001-2008 Philip Coombes
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
#ifndef ZM_RTP_DATA_H
#define ZM_RTP_DATA_H
#include "zm_define.h"
#include <atomic>
#include <thread>
class RtspThread;
class RtpSource;
struct RtpDataHeader {
uint8_t cc:4; // CSRC count
uint8_t x:1; // header extension flag
uint8_t p:1; // padding flag
uint8_t version:2; // protocol version
uint8_t pt:7; // payload type
uint8_t m:1; // marker bit
uint16_t seqN; // sequence number, network order
uint32_t timestampN; // timestamp, network order
uint32_t ssrcN; // synchronization source, network order
uint32_t csrc[]; // optional CSRC list
};
class RtpDataThread {
friend class RtspThread;
private:
RtspThread &mRtspThread;
RtpSource &mRtpSource;
std::atomic<bool> mTerminate;
std::thread mThread;
private:
bool recvPacket( const unsigned char *packet, size_t packetLen );
void Run();
public:
RtpDataThread( RtspThread &rtspThread, RtpSource &rtpSource );
~RtpDataThread();
void Stop() { mTerminate = true; }
};
#endif // ZM_RTP_DATA_H
-388
View File
@@ -1,388 +0,0 @@
//
// ZoneMinder RTP Source Class Implementation, $Date$, $Revision$
// Copyright (C) 2001-2008 Philip Coombes
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
#include "zm_rtp_source.h"
#include "zm_time.h"
#include "zm_rtp_data.h"
#include "zm_utils.h"
#include <arpa/inet.h>
#include <unistd.h>
RtpSource::RtpSource(
int id,
const std::string &localHost,
int localPortBase,
const std::string &remoteHost,
int remotePortBase,
uint32_t ssrc,
uint16_t seq,
uint32_t rtpClock,
uint32_t rtpTime,
_AVCODECID codecId ) :
mId(id),
mSsrc(ssrc),
mLocalHost(localHost),
mRemoteHost(remoteHost),
mRtpClock(rtpClock),
mCodecId(codecId),
mFrame(65536),
mFrameCount(0),
mFrameGood(true),
prevM(false),
mFrameReady(false),
mFrameProcessed(false),
mTerminate(false) {
char hostname[256] = "";
gethostname(hostname, sizeof(hostname));
mCname = stringtf("zm-%d@%s", mId, hostname);
Debug(3, "RTP CName = %s", mCname.c_str());
init(seq);
mMaxSeq = seq - 1;
mProbation = MIN_SEQUENTIAL;
mLocalPortChans[0] = localPortBase;
mLocalPortChans[1] = localPortBase+1;
mRemotePortChans[0] = remotePortBase;
mRemotePortChans[1] = remotePortBase+1;
mRtpFactor = mRtpClock;
mBaseTimeReal = std::chrono::system_clock::now();
mBaseTimeNtp = {};
mBaseTimeRtp = rtpTime;
mLastSrTimeReal = {};
mLastSrTimeNtp = {};
mLastSrTimeRtp = 0;
mLastSrTimeNtpSecs = 0;
mLastSrTimeNtpFrac = 0;
mExpectedPackets = 0;
mLostPackets = 0;
mLostFraction = 0;
if ( mCodecId != AV_CODEC_ID_H264 && mCodecId != AV_CODEC_ID_MPEG4 )
Warning("The device is using a codec (%d) that may not be supported. Do not be surprised if things don't work.", mCodecId);
}
RtpSource::~RtpSource() {
mTerminate = true;
mFrameReadyCv.notify_all();
mFrameProcessedCv.notify_all();
}
void RtpSource::init(uint16_t seq) {
Debug(3, "Initialising sequence");
mBaseSeq = seq;
mMaxSeq = seq;
mBadSeq = RTP_SEQ_MOD + 1; // so seq == mBadSeq is false
mCycles = 0;
mReceivedPackets = 0;
mReceivedPrior = 0;
mExpectedPrior = 0;
// other initialization
mJitter = 0;
mTransit = 0;
}
bool RtpSource::updateSeq(uint16_t seq) {
uint16_t uDelta = seq - mMaxSeq;
// Source is not valid until MIN_SEQUENTIAL packets with
// sequential sequence numbers have been received.
Debug(5, "Seq: %d", seq);
if ( mProbation) {
// packet is in sequence
if ( seq == mMaxSeq + 1 ) {
Debug(3, "Sequence in probation %d, in sequence", mProbation);
mProbation--;
mMaxSeq = seq;
if ( mProbation == 0 ) {
init(seq);
mReceivedPackets++;
return true;
}
} else {
Warning("Sequence in probation %d, out of sequence", mProbation);
mProbation = MIN_SEQUENTIAL - 1;
mMaxSeq = seq;
return false;
}
return true;
} else if ( uDelta < MAX_DROPOUT ) {
if ( uDelta == 1 ) {
Debug(4, "Packet in sequence, gap %d", uDelta);
} else {
Warning("Packet in sequence, gap %d", uDelta);
}
// in order, with permissible gap
if ( seq < mMaxSeq ) {
// Sequence number wrapped - count another 64K cycle.
mCycles += RTP_SEQ_MOD;
}
mMaxSeq = seq;
} else if ( uDelta <= RTP_SEQ_MOD - MAX_MISORDER ) {
Warning("Packet out of sequence, gap %d", uDelta);
// the sequence number made a very large jump
if ( seq == mBadSeq ) {
Debug(3, "Restarting sequence");
// Two sequential packets -- assume that the other side
// restarted without telling us so just re-sync
// (i.e., pretend this was the first packet).
init(seq);
} else {
mBadSeq = (seq + 1) & (RTP_SEQ_MOD-1);
return false;
}
} else {
Warning("Packet duplicate or reordered, gap %d", uDelta);
// duplicate or reordered packet
return false;
}
mReceivedPackets++;
return( uDelta==1?true:false );
}
void RtpSource::updateJitter( const RtpDataHeader *header ) {
if (mRtpFactor > 0) {
SystemTimePoint now = std::chrono::system_clock::now();
FPSeconds time_diff = std::chrono::duration_cast<FPSeconds>(now - mBaseTimeReal);
uint32_t localTimeRtp = mBaseTimeRtp + static_cast<uint32>(time_diff.count() * mRtpFactor);
uint32_t packetTransit = localTimeRtp - ntohl(header->timestampN);
Debug(5,
"Delta rtp = %.6f\n Local RTP time = %x Packet RTP time = %x Packet transit RTP time = %x",
time_diff.count(),
localTimeRtp,
ntohl(header->timestampN),
packetTransit);
if ( mTransit > 0 ) {
// Jitter
int d = packetTransit - mTransit;
Debug(5, "Jitter D = %d", d);
if ( d < 0 )
d = -d;
//mJitter += (1./16.) * ((double)d - mJitter);
mJitter += d - ((mJitter + 8) >> 4);
}
mTransit = packetTransit;
} else {
mJitter = 0;
}
Debug(5, "RTP Jitter: %d", mJitter);
}
void RtpSource::updateRtcpData(
uint32_t ntpTimeSecs,
uint32_t ntpTimeFrac,
uint32_t rtpTime) {
timeval ntpTime = zm::chrono::duration_cast<timeval>(
Seconds(ntpTimeSecs) + Microseconds((Microseconds::period::den * (ntpTimeFrac >> 16)) / (1 << 16)));
Debug(5, "ntpTime: %jd.%06jd, rtpTime: %x", static_cast<intmax_t>(ntpTime.tv_sec),
static_cast<intmax_t>(ntpTime.tv_usec), rtpTime);
if ( mBaseTimeNtp.tv_sec == 0 ) {
mBaseTimeReal = std::chrono::system_clock::now();
mBaseTimeNtp = ntpTime;
mBaseTimeRtp = rtpTime;
} else if ( !mRtpClock ) {
Debug(5, "lastSrNtpTime: %jd.%06jd, rtpTime: %x"
"ntpTime: %jd.%06jd, rtpTime: %x",
static_cast<intmax_t>(mLastSrTimeNtp.tv_sec), static_cast<intmax_t>(mLastSrTimeNtp.tv_usec), rtpTime,
static_cast<intmax_t>(ntpTime.tv_sec), static_cast<intmax_t>(ntpTime.tv_usec), rtpTime);
FPSeconds diffNtpTime =
zm::chrono::duration_cast<Microseconds>(ntpTime) - zm::chrono::duration_cast<Microseconds>(mBaseTimeNtp);
uint32_t diffRtpTime = rtpTime - mBaseTimeRtp;
if (diffNtpTime.count() > 0.0) {
mRtpFactor = static_cast<uint32>(diffRtpTime / diffNtpTime.count());
}
Debug( 5, "NTP-diff: %.6f RTP-diff: %d RTPfactor: %d",
diffNtpTime.count(), diffRtpTime, mRtpFactor);
}
mLastSrTimeNtpSecs = ntpTimeSecs;
mLastSrTimeNtpFrac = ntpTimeFrac;
mLastSrTimeNtp = ntpTime;
mLastSrTimeRtp = rtpTime;
}
void RtpSource::updateRtcpStats() {
uint32_t extendedMax = mCycles + mMaxSeq;
mExpectedPackets = extendedMax - mBaseSeq + 1;
// The number of packets lost is defined to be the number of packets
// expected less the number of packets actually received:
mLostPackets = mExpectedPackets - mReceivedPackets;
uint32_t expectedInterval = mExpectedPackets - mExpectedPrior;
mExpectedPrior = mExpectedPackets;
uint32_t receivedInterval = mReceivedPackets - mReceivedPrior;
mReceivedPrior = mReceivedPackets;
int32_t lostInterval = expectedInterval - receivedInterval;
if ( expectedInterval == 0 || lostInterval <= 0 )
mLostFraction = 0;
else
mLostFraction = (lostInterval << 8) / expectedInterval;
Debug(5,
"Expected packets = %d\n Lost packets = %d\n Expected interval = %d\n Received interval = %d\n Lost interval = %d\n Lost fraction = %d\n",
mExpectedPackets,
mLostPackets,
expectedInterval,
receivedInterval,
lostInterval,
mLostFraction);
}
bool RtpSource::handlePacket(const unsigned char *packet, size_t packetLen) {
const RtpDataHeader *rtpHeader;
rtpHeader = (RtpDataHeader *)packet;
int rtpHeaderSize = 12 + rtpHeader->cc * 4;
// No need to check for nal type as non fragmented packets already have 001 start sequence appended
bool h264FragmentEnd = (mCodecId == AV_CODEC_ID_H264) && (packet[rtpHeaderSize+1] & 0x40);
// M stands for Marker, it is the 8th bit
// The interpretation of the marker is defined by a profile. It is intended
// to allow significant events such as frame boundaries to be marked in the
// packet stream. A profile may define additional marker bits or specify
// that there is no marker bit by changing the number of bits in the payload type field.
bool thisM = rtpHeader->m || h264FragmentEnd;
if ( updateSeq(ntohs(rtpHeader->seqN)) ) {
Hexdump(4, packet+rtpHeaderSize, 16);
if ( mFrameGood ) {
int extraHeader = 0;
if ( mCodecId == AV_CODEC_ID_H264 ) {
int nalType = (packet[rtpHeaderSize] & 0x1f);
Debug(3, "Have H264 frame: nal type is %d", nalType);
switch (nalType) {
case 24: // STAP-A
extraHeader = 2;
break;
case 25: // STAP-B
case 26: // MTAP-16
case 27: // MTAP-24
extraHeader = 3;
break;
// FU-A and FU-B
case 28:
case 29:
// Is this NAL the first NAL in fragmentation sequence
if ( packet[rtpHeaderSize+1] & 0x80 ) {
// Now we will form new header of frame
mFrame.append( "\x0\x0\x1\x0", 4 );
// Reconstruct NAL header from FU headers
*(mFrame+3) = (packet[rtpHeaderSize+1] & 0x1f) |
(packet[rtpHeaderSize] & 0xe0);
}
extraHeader = 2;
break;
default:
Debug(3, "Unhandled nalType %d", nalType);
}
// Append NAL frame start code
if ( !mFrame.size() )
mFrame.append("\x0\x0\x1", 3);
} // end if H264
mFrame.append(packet+rtpHeaderSize+extraHeader,
packetLen-rtpHeaderSize-extraHeader);
} else {
Debug(3, "NOT H264 frame: type is %d", mCodecId);
}
Hexdump(4, mFrame.head(), 16);
if ( thisM ) {
if ( mFrameGood ) {
Debug(3, "Got new frame %d, %d bytes", mFrameCount, mFrame.size());
{
std::lock_guard<std::mutex> lck(mFrameReadyMutex);
mFrameReady = true;
}
mFrameReadyCv.notify_all();
{
std::unique_lock<std::mutex> lck(mFrameProcessedMutex);
mFrameProcessedCv.wait(lck, [&] { return mFrameProcessed || mTerminate; });
mFrameProcessed = false;
}
if (mTerminate)
return false;
mFrameCount++;
} else {
Warning("Discarding incomplete frame %d, %d bytes", mFrameCount, mFrame.size());
}
mFrame.clear();
}
} else {
if ( mFrame.size() ) {
Warning("Discarding partial frame %d, %d bytes", mFrameCount, mFrame.size());
} else {
Warning("Discarding frame %d", mFrameCount);
}
mFrameGood = false;
mFrame.clear();
}
if ( thisM ) {
mFrameGood = true;
prevM = true;
} else
prevM = false;
updateJitter(rtpHeader);
return true;
}
bool RtpSource::getFrame(Buffer &buffer) {
{
std::unique_lock<std::mutex> lck(mFrameReadyMutex);
mFrameReadyCv.wait(lck, [&] { return mFrameReady || mTerminate; });
mFrameReady = false;
}
if (mTerminate)
return false;
buffer = mFrame;
{
std::lock_guard<std::mutex> lck(mFrameProcessedMutex);
mFrameProcessed = true;
}
mFrameProcessedCv.notify_all();
Debug(4, "Copied %d bytes", buffer.size());
return true;
}
-178
View File
@@ -1,178 +0,0 @@
//
// ZoneMinder RTP Source Class Interface, $Date$, $Revision$
// Copyright (C) 2001-2008 Philip Coombes
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
#ifndef ZM_RTP_SOURCE_H
#define ZM_RTP_SOURCE_H
#include "zm_buffer.h"
#include "zm_config.h"
#include "zm_define.h"
#include "zm_ffmpeg.h"
#include "zm_time.h"
#include <condition_variable>
#include <mutex>
#include <string>
#include <sys/time.h>
struct RtpDataHeader;
class RtpSource {
public:
typedef enum { EMPTY, FILLING, READY } FrameState;
private:
static const int RTP_SEQ_MOD = 1<<16;
static const int MAX_DROPOUT = 3000;
static const int MAX_MISORDER = 100;
static const int MIN_SEQUENTIAL = 2;
private:
// Identity
int mId; // General id (usually monitor id)
std::string mCname; // Canonical name, for SDES
// RTP/RTCP fields
uint32_t mSsrc;
uint16_t mMaxSeq; // highest seq. number seen
uint32_t mCycles; // shifted count of seq. number cycles
uint32_t mBaseSeq; // base seq number
uint32_t mBadSeq; // last 'bad' seq number + 1
uint32_t mProbation; // sequ. packets till source is valid
uint32_t mReceivedPackets; // packets received
uint32_t mExpectedPrior; // packet expected at last interval
uint32_t mReceivedPrior; // packet received at last interval
uint32_t mTransit; // relative trans time for prev pkt
uint32_t mJitter; // estimated jitter
// Ports/Channels
std::string mLocalHost;
int mLocalPortChans[2];
std::string mRemoteHost;
int mRemotePortChans[2];
// Time keys
uint32_t mRtpClock;
uint32_t mRtpFactor;
SystemTimePoint mBaseTimeReal;
struct timeval mBaseTimeNtp;
uint32_t mBaseTimeRtp;
struct timeval mLastSrTimeReal;
uint32_t mLastSrTimeNtpSecs;
uint32_t mLastSrTimeNtpFrac;
struct timeval mLastSrTimeNtp;
uint32_t mLastSrTimeRtp;
// Stats, intermittently updated
uint32_t mExpectedPackets;
uint32_t mLostPackets;
uint8_t mLostFraction;
_AVCODECID mCodecId;
Buffer mFrame;
int mFrameCount;
bool mFrameGood;
bool prevM;
bool mFrameReady;
std::condition_variable mFrameReadyCv;
std::mutex mFrameReadyMutex;
bool mFrameProcessed;
std::condition_variable mFrameProcessedCv;
std::mutex mFrameProcessedMutex;
bool mTerminate;
private:
void init(uint16_t seq);
public:
RtpSource( int id, const std::string &localHost, int localPortBase, const std::string &remoteHost, int remotePortBase, uint32_t ssrc, uint16_t seq, uint32_t rtpClock, uint32_t rtpTime, _AVCODECID codecId );
~RtpSource();
bool updateSeq( uint16_t seq );
void updateJitter( const RtpDataHeader *header );
void updateRtcpData( uint32_t ntpTimeSecs, uint32_t ntpTimeFrac, uint32_t rtpTime );
void updateRtcpStats();
bool handlePacket( const unsigned char *packet, size_t packetLen );
uint32_t getSsrc() const {
return( mSsrc );
}
void setSsrc( uint32_t ssrc ) {
mSsrc = ssrc;
}
bool getFrame( Buffer &buffer );
const std::string &getCname() const {
return( mCname );
}
const std::string &getLocalHost() const {
return( mLocalHost );
}
int getLocalDataPort() const {
return( mLocalPortChans[0] );
}
int getLocalCtrlPort() const {
return( mLocalPortChans[1] );
}
const std::string &getRemoteHost() const {
return( mRemoteHost );
}
int getRemoteDataPort() const {
return( mRemotePortChans[0] );
}
int getRemoteCtrlPort() const {
return( mRemotePortChans[1] );
}
uint32_t getMaxSeq() const {
return( mCycles + mMaxSeq );
}
uint32_t getExpectedPackets() const {
return( mExpectedPackets );
}
uint32_t getLostPackets() const {
return( mLostPackets );
}
uint8_t getLostFraction() const {
return( mLostFraction );
}
uint32_t getJitter() const {
return( mJitter >> 4 );
}
uint32_t getLastSrTimestamp() const {
return( ((mLastSrTimeNtpSecs&0xffff)<<16)|(mLastSrTimeNtpFrac>>16) );
}
};
#endif // ZM_RTP_SOURCE_H
-794
View File
@@ -1,794 +0,0 @@
//
// ZoneMinder RTSP Class Implementation, $Date$, $Revision$
// Copyright (C) 2001-2008 Philip Coombes
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
#include "zm_rtsp.h"
#include "zm_config.h"
#include "zm_rtp_data.h"
#include "zm_rtp_ctrl.h"
#include "zm_db.h"
#include <algorithm>
int RtspThread::smMinDataPort = 0;
int RtspThread::smMaxDataPort = 0;
RtspThread::PortSet RtspThread::smAssignedPorts;
bool RtspThread::sendCommand(std::string message) {
if ( mNeedAuth ) {
StringVector parts = Split(message, " ");
if ( parts.size() > 1 )
message += mAuthenticator->getAuthHeader(parts[0], parts[1]);
}
message += stringtf("User-Agent: ZoneMinder/%s\r\n", ZM_VERSION);
message += stringtf("CSeq: %d\r\n\r\n", ++mSeq);
Debug(2, "Sending RTSP message: %s", message.c_str());
if ( mMethod == RTP_RTSP_HTTP ) {
message = Base64Encode(message);
Debug(2, "Sending encoded RTSP message: %s", message.c_str());
if ( mRtspSocket2.send(message.c_str(), message.size()) != (int)message.length() ) {
Error("Unable to send message '%s': %s", message.c_str(), strerror(errno));
return false;
}
} else {
if ( mRtspSocket.send(message.c_str(), message.size()) != (int)message.length() ) {
Error("Unable to send message '%s': %s", message.c_str(), strerror(errno));
return false;
}
}
return true;
}
bool RtspThread::recvResponse(std::string &response) {
if ( mRtspSocket.recv(response) < 0 )
Error("Recv failed; %s", strerror(errno));
Debug(2, "Received RTSP response: %s (%zd bytes)", response.c_str(), response.size());
float respVer = 0;
respCode = -1;
char respText[ZM_NETWORK_BUFSIZ];
static_assert(ZM_NETWORK_BUFSIZ == 32768, "respText field width must be ZM_NETWORK_BUFSIZ-1");
if ( sscanf(response.c_str(), "RTSP/%f %3d %32767[^\r\n]\r\n", &respVer, &respCode, respText) != 3 ) {
if ( !response.empty() && isalnum(response[0]) ) {
Error("Response parse failure in '%s'", response.c_str());
} else {
Error("Response parse failure, %zd bytes follow", response.size());
if ( response.size() )
Hexdump(Logger::ERROR, response.data(), std::min(int(response.size()), 16));
}
return false;
}
if ( respCode == 401 ) {
Debug(2, "Got 401 access denied response code, check WWW-Authenticate header and retry");
mAuthenticator->checkAuthResponse(response);
mNeedAuth = true;
return false;
} else if ( respCode != 200 ) {
Error("Unexpected response code %d, text is '%s'", respCode, respText);
return false;
}
return true;
} // end RtspThread::recvResponse
int RtspThread::requestPorts() {
if ( !smMinDataPort ) {
//FIXME Why not load specifically by Id? This will get ineffeicient with a lot of monitors
std::string sql = "SELECT `Id` FROM `Monitors` WHERE `Function` != 'None' AND `Type` = 'Remote' AND `Protocol` = 'rtsp' AND `Method` = 'rtpUni' ORDER BY `Id` ASC";
MYSQL_RES *result = zmDbFetch(sql);
int nMonitors = 0;
int position = 0;
if (result) {
nMonitors = mysql_num_rows(result);
if ( nMonitors ) {
for ( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++ ) {
int id = atoi(dbrow[0]);
if ( mId == id ) {
position = i;
break;
}
}
}
mysql_free_result(result);
}
if (!nMonitors) {
// Minor hack for testing when not strictly enabled
nMonitors = 1;
position = 0;
}
int portRange = int(((config.max_rtp_port-config.min_rtp_port)+1)/nMonitors);
smMinDataPort = config.min_rtp_port + (position * portRange);
smMaxDataPort = smMinDataPort + portRange - 1;
Debug(2, "Assigned RTP port range is %d-%d", smMinDataPort, smMaxDataPort);
}
for ( int i = smMinDataPort; i <= smMaxDataPort; i++ ) {
PortSet::const_iterator iter = smAssignedPorts.find(i);
if ( iter == smAssignedPorts.end() ) {
smAssignedPorts.insert(i);
return i;
}
}
Panic("Can assign RTP port, no ports left in pool");
return -1;
}
void RtspThread::releasePorts(int port) {
if ( port > 0 )
smAssignedPorts.erase(port);
}
RtspThread::RtspThread(
int id,
RtspMethod method,
const std::string &protocol,
const std::string &host,
const std::string &port,
const std::string &path,
const std::string &user,
const std::string &pass,
bool rtsp_describe) :
mId(id),
mMethod(method),
mProtocol(protocol),
mHost(host),
mPort(port),
mPath(path),
mRtspDescribe(rtsp_describe),
mSessDesc(0),
mFormatContext(0),
mSeq(0),
mSession(0),
mSsrc(0),
mDist(UNDEFINED),
mRtpTime(0),
mTerminate(false) {
mUrl = mProtocol+"://"+mHost+":"+mPort;
if ( !mPath.empty() ) {
if ( mPath[0] == '/' )
mUrl += mPath;
else
mUrl += '/'+mPath;
}
mSsrc = rand();
Debug(2, "RTSP Local SSRC is %x, url is %s", mSsrc, mUrl.c_str());
if ( mMethod == RTP_RTSP_HTTP )
mHttpSession = stringtf("%d", rand());
mNeedAuth = false;
if ( user.length() > 0 && pass.length() > 0 ) {
Debug(2, "# of auth parts 2");
mAuthenticator = new zm::Authenticator(user, pass);
} else if( user.length() > 0 ) {
Debug(2, "# of auth parts 1");
mAuthenticator = new zm::Authenticator(user, "");
} else {
Debug(2, "# of auth parts 0");
mAuthenticator = new zm::Authenticator("", "");
}
mThread = std::thread(&RtspThread::Run, this);
}
RtspThread::~RtspThread() {
Stop();
if (mThread.joinable())
mThread.join();
if ( mFormatContext ) {
avformat_free_context(mFormatContext);
mFormatContext = nullptr;
}
if ( mSessDesc ) {
delete mSessDesc;
mSessDesc = nullptr;
}
delete mAuthenticator;
mAuthenticator = nullptr;
}
void RtspThread::Run() {
std::string message;
std::string response;
response.reserve(ZM_NETWORK_BUFSIZ);
if ( !mRtspSocket.connect(mHost.c_str(), mPort.c_str()) )
Fatal("Unable to connect RTSP socket");
//Select select( 0.25 );
//select.addReader( &mRtspSocket );
//while ( select.wait() )
//{
//mRtspSocket.recv( response );
//Debug( 4, "Drained %d bytes from RTSP socket", response.size() );
//}
bool authTried = false;
if ( mMethod == RTP_RTSP_HTTP ) {
if ( !mRtspSocket2.connect(mHost.c_str(), mPort.c_str()) )
Fatal("Unable to connect auxiliary RTSP/HTTP socket");
//Select select( 0.25 );
//select.addReader( &mRtspSocket2 );
//while ( select.wait() )
//{
//mRtspSocket2.recv( response );
//Debug( 4, "Drained %d bytes from HTTP socket", response.size() );
//}
//possibly retry sending the message for authentication
int respCode = -1;
char respText[256];
do {
message = "GET "+mPath+" HTTP/1.0\r\n";
message += "X-SessionCookie: "+mHttpSession+"\r\n";
if ( mNeedAuth ) {
message += mAuthenticator->getAuthHeader("GET", mPath);
authTried = true;
}
message += "Accept: application/x-rtsp-tunnelled\r\n\r\n";
Debug(2, "Sending HTTP message: %s", message.c_str());
if ( mRtspSocket.send(message.c_str(), message.size()) != (int)message.length() ) {
Error("Unable to send message '%s': %s", message.c_str(), strerror(errno));
return;
}
if ( mRtspSocket.recv(response) < 0 ) {
Error("Recv failed; %s", strerror(errno));
return;
}
Debug(2, "Received HTTP response: %s (%zd bytes)", response.c_str(), response.size());
float respVer = 0;
respCode = -1;
if ( sscanf(response.c_str(), "HTTP/%f %3d %255[^\r\n]\r\n", &respVer, &respCode, respText) != 3 ) {
if ( isalnum(response[0]) ) {
Error("Response parse failure in '%s'", response.c_str());
} else {
Error("Response parse failure, %zd bytes follow", response.size());
if ( response.size() )
Hexdump(Logger::ERROR, response.data(), std::min(int(response.size()), 16));
}
return;
}
// If Server requests authentication, check WWW-Authenticate header and fill required fields
// for requested authentication method
if ( respCode == 401 && !authTried ) {
mNeedAuth = true;
mAuthenticator->checkAuthResponse(response);
Debug(2, "Processed 401 response");
mRtspSocket.close();
if ( !mRtspSocket.connect(mHost.c_str(), mPort.c_str()) )
Fatal("Unable to reconnect RTSP socket");
Debug(2, "connection should be reopened now");
}
} while (respCode == 401 && !authTried);
if ( respCode != 200 ) {
Error("Unexpected response code %d, text is '%s'", respCode, respText);
return;
}
message = "POST "+mPath+" HTTP/1.0\r\n";
message += "X-SessionCookie: "+mHttpSession+"\r\n";
if ( mNeedAuth )
message += mAuthenticator->getAuthHeader("POST", mPath);
message += "Content-Length: 32767\r\n";
message += "Content-Type: application/x-rtsp-tunnelled\r\n";
message += "\r\n";
Debug(2, "Sending HTTP message: %s", message.c_str());
if ( mRtspSocket2.send(message.c_str(), message.size()) != (int)message.length() ) {
Error("Unable to send message '%s': %s", message.c_str(), strerror(errno));
return;
}
} // end if ( mMethod == RTP_RTSP_HTTP )
std::string localHost = "";
int localPorts[2] = { 0, 0 };
// Request supported RTSP commands by the server
message = "OPTIONS "+mUrl+" RTSP/1.0\r\n";
if ( !sendCommand(message) )
return;
// A negative return here may indicate auth failure, but we will have setup the auth mechanisms so we need to retry.
if ( !recvResponse(response) ) {
if ( mNeedAuth ) {
Debug(2, "Resending OPTIONS due to possible auth requirement");
if ( !sendCommand(message) )
return;
if ( !recvResponse(response) )
return;
} else {
return;
}
} // end if failed response maybe due to auth
char publicLine[256] = "";
StringVector lines = Split(response, "\r\n");
for ( size_t i = 0; i < lines.size(); i++ )
sscanf(lines[i].c_str(), "Public: %255[^\r\n]\r\n", publicLine);
// Check if the server supports the GET_PARAMETER command
// If yes, it is likely that the server will request this command as a keepalive message
bool sendKeepalive = false;
if ( publicLine[0] && strstr(publicLine, "GET_PARAMETER") )
sendKeepalive = true;
message = "DESCRIBE "+mUrl+" RTSP/1.0\r\n";
bool res;
do {
if ( mNeedAuth )
authTried = true;
sendCommand(message);
// FIXME Why sleep 1?
std::this_thread::sleep_for(Microseconds(10));
res = recvResponse(response);
if ( !res && respCode==401 )
mNeedAuth = true;
} while (!res && respCode==401 && !authTried);
const std::string endOfHeaders = "\r\n\r\n";
size_t sdpStart = response.find(endOfHeaders);
if ( sdpStart == std::string::npos )
return;
if ( mRtspDescribe ) {
std::string DescHeader = response.substr(0, sdpStart);
Debug(1, "Processing DESCRIBE response header '%s'", DescHeader.c_str());
lines = Split(DescHeader, "\r\n");
for ( size_t i = 0; i < lines.size(); i++ ) {
// If the device sends us a url value for Content-Base in the response header, we should use that instead
if ( ( lines[i].size() > 13 ) && ( lines[i].substr( 0, 13 ) == "Content-Base:" ) ) {
mUrl = TrimSpaces(lines[i].substr(13));
Info("Received new Content-Base in DESCRIBE response header. Updated device Url to: '%s'", mUrl.c_str() );
break;
}
} // end foreach line
} // end if mRtspDescribe
sdpStart += endOfHeaders.length();
std::string sdp = response.substr(sdpStart);
Debug(1, "Processing SDP '%s'", sdp.c_str());
try {
mSessDesc = new SessionDescriptor( mUrl, sdp );
mFormatContext = mSessDesc->generateFormatContext();
} catch ( const Exception &e ) {
Error("%s", e.getMessage().c_str());
return;
}
#if 0
// New method using ffmpeg native functions
std::string authUrl = mUrl;
if ( !mAuth.empty() )
authUrl.insert( authUrl.find( "://" )+3, mAuth+"@" );
if ( av_open_input_file( &mFormatContext, authUrl.c_str(), nullptr, 0, nullptr ) != 0 ) {
Error( "Unable to open input '%s'", authUrl.c_str() );
return( -1 );
}
#endif
uint32_t rtpClock = 0;
std::string trackUrl = mUrl;
std::string controlUrl;
_AVCODECID codecId = AV_CODEC_ID_NONE;
if ( mFormatContext->nb_streams >= 1 ) {
for ( unsigned int i = 0; i < mFormatContext->nb_streams; i++ ) {
SessionDescriptor::MediaDescriptor *mediaDesc = mSessDesc->getStream(i);
if (mFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
// Check if control Url is absolute or relative
controlUrl = mediaDesc->getControlUrl();
if (trackUrl != controlUrl) {
if ( *trackUrl.rbegin() != '/' ) {
trackUrl += "/" + controlUrl;
} else {
trackUrl += controlUrl;
}
}
rtpClock = mediaDesc->getClock();
codecId = mFormatContext->streams[i]->codecpar->codec_id;
break;
} // end if is video
} // end foreach stream
} // end if have stream
switch ( mMethod ) {
case RTP_UNICAST :
localPorts[0] = requestPorts();
localPorts[1] = localPorts[0]+1;
message = "SETUP "+trackUrl+" RTSP/1.0\r\nTransport: RTP/AVP;unicast;client_port="
+stringtf("%d", localPorts[0] )+"-"+stringtf( "%d", localPorts[1])+"\r\n";
break;
case RTP_MULTICAST :
message = "SETUP "+trackUrl+" RTSP/1.0\r\nTransport: RTP/AVP;multicast\r\n";
break;
case RTP_RTSP :
case RTP_RTSP_HTTP :
message = "SETUP "+trackUrl+" RTSP/1.0\r\nTransport: RTP/AVP/TCP;unicast\r\n";
break;
default:
Panic("Got unexpected method %d", mMethod);
break;
}
if ( !sendCommand(message) )
return;
if ( !recvResponse(response) )
return;
lines = Split(response, "\r\n");
std::string session;
Seconds timeout = Seconds(0);
char transport[256] = "";
for ( size_t i = 0; i < lines.size(); i++ ) {
if ( ( lines[i].size() > 8 ) && ( lines[i].substr(0, 8) == "Session:" ) ) {
StringVector sessionLine = Split(lines[i].substr(9), ";");
session = TrimSpaces(sessionLine[0]);
if ( sessionLine.size() == 2 ) {
int32 timeout_val = 0;
sscanf(TrimSpaces(sessionLine[1]).c_str(), "timeout=%d", &timeout_val);
timeout = Seconds(timeout_val);
}
}
sscanf(lines[i].c_str(), "Transport: %255s", transport);
}
if ( session.empty() )
Fatal("Unable to get session identifier from response '%s'", response.c_str());
Debug(2, "Got RTSP session %s, timeout %" PRIi64 " secs",
session.c_str(),
static_cast<int64>(Seconds(timeout).count()));
if ( !transport[0] )
Fatal("Unable to get transport details from response '%s'", response.c_str());
Debug(2, "Got RTSP transport %s", transport);
std::string method = "";
int remotePorts[2] = { 0, 0 };
int remoteChannels[2] = { 0, 0 };
std::string distribution = "";
unsigned long ssrc = 0;
StringVector parts = Split(transport, ";");
for ( size_t i = 0; i < parts.size(); i++ ) {
if ( parts[i] == "unicast" || parts[i] == "multicast" )
distribution = parts[i];
else if (StartsWith(parts[i], "server_port=") ) {
method = "RTP/UNICAST";
StringVector subparts = Split(parts[i], "=");
StringVector ports = Split(subparts[1], "-");
remotePorts[0] = strtol( ports[0].c_str(), nullptr, 10 );
remotePorts[1] = strtol( ports[1].c_str(), nullptr, 10 );
} else if (StartsWith(parts[i], "interleaved=") ) {
method = "RTP/RTSP";
StringVector subparts = Split(parts[i], "=");
StringVector channels = Split(subparts[1], "-");
remoteChannels[0] = strtol( channels[0].c_str(), nullptr, 10 );
remoteChannels[1] = strtol( channels[1].c_str(), nullptr, 10 );
} else if (StartsWith(parts[i], "port=") ) {
method = "RTP/MULTICAST";
StringVector subparts = Split(parts[i], "=");
StringVector ports = Split(subparts[1], "-");
localPorts[0] = strtol( ports[0].c_str(), nullptr, 10 );
localPorts[1] = strtol( ports[1].c_str(), nullptr, 10 );
} else if (StartsWith(parts[i], "destination=") ) {
StringVector subparts = Split(parts[i], "=");
localHost = subparts[1];
} else if (StartsWith(parts[i], "ssrc=") ) {
StringVector subparts = Split(parts[i], "=");
ssrc = strtoll( subparts[1].c_str(), nullptr, 16 );
}
}
Debug( 2, "RTSP Method is %s", method.c_str() );
Debug( 2, "RTSP Distribution is %s", distribution.c_str() );
Debug( 2, "RTSP SSRC is %lx", ssrc );
Debug( 2, "RTSP Local Host is %s", localHost.c_str() );
Debug( 2, "RTSP Local Ports are %d/%d", localPorts[0], localPorts[1] );
Debug( 2, "RTSP Remote Ports are %d/%d", remotePorts[0], remotePorts[1] );
Debug( 2, "RTSP Remote Channels are %d/%d", remoteChannels[0], remoteChannels[1] );
message = "PLAY "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\nRange: npt=0.000-\r\n";
if ( !sendCommand(message) )
return;
if ( !recvResponse(response) )
return;
lines = Split(response, "\r\n");
std::string rtpInfo;
for ( size_t i = 0; i < lines.size(); i++ ) {
if ( ( lines[i].size() > 9 ) && ( lines[i].substr(0, 9) == "RTP-Info:" ) )
rtpInfo = TrimSpaces(lines[i].substr(9));
// Check for a timeout again. Some rtsp devices don't send a timeout until after the PLAY command is sent
if ((lines[i].size() > 8) && (lines[i].substr(0, 8) == "Session:") && (timeout == Seconds(0))) {
StringVector sessionLine = Split(lines[i].substr(9), ";");
if ( sessionLine.size() == 2 ) {
int32 timeout_val = 0;
sscanf(TrimSpaces(sessionLine[1]).c_str(), "timeout=%d", &timeout_val);
timeout = Seconds(timeout_val);
}
if ( timeout > Seconds(0) ) {
Debug(2, "Got timeout %" PRIi64 " secs from PLAY command response",
static_cast<int64>(Seconds(timeout).count()));
}
}
}
int seq = 0;
unsigned long rtpTime = 0;
StringVector streams;
if ( rtpInfo.empty() ) {
Debug( 1, "RTP Info Empty. Starting values for Sequence and Rtptime shall be zero.");
} else {
Debug( 2, "Got RTP Info %s", rtpInfo.c_str() );
// More than one stream can be included in the RTP Info
streams = Split(rtpInfo, ",");
for ( size_t i = 0; i < streams.size(); i++ ) {
// We want the stream that matches the trackUrl we are using
if ( streams[i].find(controlUrl.c_str()) != std::string::npos ) {
// Parse the sequence and rtptime values
parts = Split(streams[i].c_str(), ";");
for ( size_t j = 0; j < parts.size(); j++ ) {
if (StartsWith(parts[j], "seq=") ) {
StringVector subparts = Split(parts[j], "=");
seq = strtol( subparts[1].c_str(), nullptr, 10 );
} else if (StartsWith(parts[j], "rtptime=") ) {
StringVector subparts = Split(parts[j], "=");
rtpTime = strtol( subparts[1].c_str(), nullptr, 10 );
}
}
break;
}
}
}
Debug( 2, "RTSP Seq is %d", seq );
Debug( 2, "RTSP Rtptime is %ld", rtpTime );
TimePoint lastKeepalive = std::chrono::steady_clock::now();
TimePoint now;
message = "GET_PARAMETER "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\n";
switch( mMethod ) {
case RTP_UNICAST : {
RtpSource *source = new RtpSource( mId, "", localPorts[0], mHost, remotePorts[0], ssrc, seq, rtpClock, rtpTime, codecId );
mSources[ssrc] = source;
RtpDataThread rtpDataThread( *this, *source );
RtpCtrlThread rtpCtrlThread( *this, *source );
while (!mTerminate) {
now = std::chrono::steady_clock::now();
// Send a keepalive message if the server supports this feature and we are close to the timeout expiration
Debug(5, "sendkeepalive %d, timeout %" PRIi64 " s, now: %" PRIi64 " s last: %" PRIi64 " s since: %" PRIi64 "s ",
sendKeepalive,
static_cast<int64>(Seconds(timeout).count()),
static_cast<int64>(std::chrono::duration_cast<Seconds>(now.time_since_epoch()).count()),
static_cast<int64>(std::chrono::duration_cast<Seconds>(lastKeepalive.time_since_epoch()).count()),
static_cast<int64>(std::chrono::duration_cast<Seconds>((now - lastKeepalive)).count()));
if (sendKeepalive && (timeout > Seconds(0)) && ((now - lastKeepalive) > (timeout - Seconds(5)))) {
if (!sendCommand(message))
return;
lastKeepalive = now;
}
std::this_thread::sleep_for(Microseconds(100));
}
#if 0
message = "PAUSE "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\n";
if ( !sendCommand( message ) )
return( -1 );
if ( !recvResponse( response ) )
return( -1 );
#endif
message = "TEARDOWN "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\n";
if ( !sendCommand( message ) )
return;
if ( !recvResponse( response ) )
return;
rtpDataThread.Stop();
rtpCtrlThread.Stop();
//rtpDataThread.kill( SIGTERM );
//rtpCtrlThread.kill( SIGTERM );
delete mSources[ssrc];
mSources.clear();
releasePorts( localPorts[0] );
break;
}
case RTP_RTSP :
case RTP_RTSP_HTTP : {
RtpSource *source = new RtpSource( mId, "", remoteChannels[0], mHost, remoteChannels[0], ssrc, seq, rtpClock, rtpTime, codecId );
mSources[ssrc] = source;
// These never actually run
RtpDataThread rtpDataThread( *this, *source );
RtpCtrlThread rtpCtrlThread( *this, *source );
zm::Select select(Milliseconds(config.http_timeout));
select.addReader( &mRtspSocket );
Buffer buffer( ZM_NETWORK_BUFSIZ );
std::string keepaliveMessage = "OPTIONS "+mUrl+" RTSP/1.0\r\n";
std::string keepaliveResponse = "RTSP/1.0 200 OK\r\n";
while (!mTerminate && select.wait() >= 0) {
zm::Select::CommsList readable = select.getReadable();
if ( readable.size() == 0 ) {
Error( "RTSP timed out" );
break;
}
static char tempBuffer[ZM_NETWORK_BUFSIZ];
ssize_t nBytes = mRtspSocket.recv( tempBuffer, sizeof(tempBuffer) );
buffer.append( tempBuffer, nBytes );
Debug( 4, "Read %zd bytes on sd %d, %d total", nBytes, mRtspSocket.getReadDesc(), buffer.size() );
while( buffer.size() > 0 ) {
if ( buffer[0] == '$' ) {
if ( buffer.size() < 4 )
break;
unsigned char channel = buffer[1];
unsigned short len = ntohs( *((unsigned short *)(buffer+2)) );
Debug( 4, "Got %d bytes left, expecting %d byte packet on channel %d", buffer.size(), len, channel );
if ( (unsigned short)buffer.size() < (len+4) ) {
Debug( 4, "Missing %d bytes, rereading", (len+4)-buffer.size() );
break;
}
if ( channel == remoteChannels[0] ) {
Debug(4, "Got %d bytes on data channel %d, packet length is %d", buffer.size(), channel, len);
Hexdump(4, (char *)buffer, 16);
rtpDataThread.recvPacket(buffer+4, len);
} else if ( channel == remoteChannels[1] ) {
// len = ntohs( *((unsigned short *)(buffer+2)) );
// Debug( 4, "Got %d bytes on control channel %d", nBytes, channel );
Debug(4, "Got %d bytes on control channel %d, packet length is %d", buffer.size(), channel, len);
Hexdump(4, (char *)buffer, 16);
rtpCtrlThread.recvPackets(buffer+4, len);
} else {
Error("Unexpected channel selector %d in RTSP interleaved data", buffer[1]);
buffer.clear();
break;
}
buffer.consume(len+4);
nBytes -= len+4;
} else {
if ( keepaliveResponse.compare( 0, keepaliveResponse.size(), (char *)buffer, keepaliveResponse.size() ) == 0 ) {
Debug( 4, "Got keepalive response '%s'", (char *)buffer );
//buffer.consume( keepaliveResponse.size() );
if ( const char *charPtr = (char *)memchr( (char *)buffer, '$', buffer.size() ) ) {
int discardBytes = charPtr-(char *)buffer;
buffer -= discardBytes;
} else {
buffer.clear();
}
} else {
if ( const char *charPtr = (char *)memchr( (char *)buffer, '$', buffer.size() ) ) {
int discardBytes = charPtr-(char *)buffer;
Warning( "Unexpected format RTSP interleaved data, resyncing by %d bytes", discardBytes );
Hexdump( -1, (char *)buffer, discardBytes );
buffer -= discardBytes;
} else {
Warning( "Unexpected format RTSP interleaved data, dumping %d bytes", buffer.size() );
Hexdump( -1, (char *)buffer, 32 );
buffer.clear();
}
}
}
}
// Send a keepalive message if the server supports this feature and we are close to the timeout expiration
// FIXME: Is this really necessary when using tcp ?
now = std::chrono::steady_clock::now();
// Send a keepalive message if the server supports this feature and we are close to the timeout expiration
Debug(5, "sendkeepalive %d, timeout %" PRIi64 " s, now: %" PRIi64 " s last: %" PRIi64 " s since: %" PRIi64 " s",
sendKeepalive,
static_cast<int64>(Seconds(timeout).count()),
static_cast<int64>(std::chrono::duration_cast<Seconds>(now.time_since_epoch()).count()),
static_cast<int64>(std::chrono::duration_cast<Seconds>(lastKeepalive.time_since_epoch()).count()),
static_cast<int64>(std::chrono::duration_cast<Seconds>((now - lastKeepalive)).count()));
if (sendKeepalive && (timeout > Seconds(0)) && ((now - lastKeepalive) > (timeout - Seconds(5)))) {
if (!sendCommand(message)) {
return;
}
lastKeepalive = now;
}
buffer.tidy(true);
}
#if 0
message = "PAUSE "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\n";
if ( !sendCommand( message ) )
return( -1 );
if ( !recvResponse( response ) )
return( -1 );
#endif
// Send a teardown message but don't expect a response as this may not be implemented on the server when using TCP
message = "TEARDOWN "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\n";
if ( !sendCommand( message ) )
return;
delete mSources[ssrc];
mSources.clear();
break;
}
case RTP_MULTICAST : {
RtpSource *source = new RtpSource( mId, localHost, localPorts[0], mHost, remotePorts[0], ssrc, seq, rtpClock, rtpTime, codecId );
mSources[ssrc] = source;
RtpDataThread rtpDataThread( *this, *source );
RtpCtrlThread rtpCtrlThread( *this, *source );
while (!mTerminate) {
// Send a keepalive message if the server supports this feature and we are close to the timeout expiration
if (sendKeepalive && (timeout > Seconds(0))
&& ((std::chrono::steady_clock::now() - lastKeepalive) > (timeout - Seconds(5)))) {
if (!sendCommand(message)) {
return;
}
lastKeepalive = std::chrono::steady_clock::now();
}
std::this_thread::sleep_for(Microseconds(100));
}
#if 0
message = "PAUSE "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\n";
if ( !sendCommand( message ) )
return( -1 );
if ( !recvResponse( response ) )
return( -1 );
#endif
message = "TEARDOWN "+mUrl+" RTSP/1.0\r\nSession: "+session+"\r\n";
if ( !sendCommand(message) )
return;
if ( !recvResponse(response) )
return;
rtpDataThread.Stop();
rtpCtrlThread.Stop();
delete mSources[ssrc];
mSources.clear();
releasePorts( localPorts[0] );
break;
}
default:
Panic("Got unexpected method %d", mMethod);
break;
}
return;
}
-138
View File
@@ -1,138 +0,0 @@
//
// ZoneMinder RTSP Class Interface, $Date$, $Revision$
// Copyright (C) 2001-2008 Philip Coombes
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
#ifndef ZM_RTSP_H
#define ZM_RTSP_H
#include "zm_comms.h"
#include "zm_rtp_source.h"
#include "zm_rtsp_auth.h"
#include "zm_sdp.h"
#include <atomic>
#include <map>
#include <set>
#include <thread>
class RtspThread {
public:
typedef enum { RTP_UNICAST, RTP_MULTICAST, RTP_RTSP, RTP_RTSP_HTTP } RtspMethod;
typedef enum { UNDEFINED, UNICAST, MULTICAST } RtspDist;
private:
typedef std::set<int> PortSet;
typedef std::set<uint32_t> SsrcSet;
typedef std::map<uint32_t,RtpSource *> SourceMap;
private:
static int smMinDataPort;
static int smMaxDataPort;
static PortSet smLocalSsrcs;
static PortSet smAssignedPorts;
private:
int mId;
RtspMethod mMethod;
std::string mProtocol;
std::string mHost;
std::string mPort;
std::string mPath;
bool mRtspDescribe;
std::string mUrl;
// Reworked authentication system
// First try without authentication, even if we have a username and password
// on receiving a 401 response, select authentication method (basic or digest)
// fill required fields and set needAuth
// subsequent requests can set the required authentication header.
bool mNeedAuth;
int respCode;
zm::Authenticator* mAuthenticator;
std::string mHttpSession; ///< Only for RTSP over HTTP sessions
zm::TcpInetClient mRtspSocket;
zm::TcpInetClient mRtspSocket2;
SourceMap mSources;
SessionDescriptor *mSessDesc;
AVFormatContext *mFormatContext;
uint16_t mSeq;
uint32_t mSession;
uint32_t mSsrc;
int mRemotePorts[2];
int mRemoteChannels[2];
RtspDist mDist;
unsigned long mRtpTime;
std::thread mThread;
std::atomic<bool> mTerminate;
private:
bool sendCommand( std::string message );
bool recvResponse( std::string &response );
void checkAuthResponse(std::string &response);
void Run();
public:
RtspThread( int id, RtspMethod method, const std::string &protocol, const std::string &host,
const std::string &port, const std::string &path, const std::string &user, const std::string &pass,
bool rtsp_describe );
~RtspThread();
public:
int requestPorts();
void releasePorts( int port );
bool isValidSsrc( uint32_t ssrc );
bool updateSsrc( uint32_t ssrc, const RtpDataHeader *header );
uint32_t getSsrc() const {
return( mSsrc );
}
bool hasSources() const {
return( !mSources.empty() );
}
AVFormatContext *getFormatContext() {
return( mFormatContext );
}
bool getFrame( Buffer &frame ) {
SourceMap::iterator iter = mSources.begin();
if ( iter == mSources.end() )
return( false );
return( iter->second->getFrame( frame ) );
}
void Stop() { mTerminate = true; }
bool IsStopped() const { return mTerminate; }
int getAddressFamily () {
return mRtspSocket.getDomain();
}
};
#endif // ZM_RTSP_H
-431
View File
@@ -1,431 +0,0 @@
//
// ZoneMinder SDP Class Implementation, $Date: 2009-04-14 21:20:02 +0100 (Tue, 14 Apr 2009) $, $Revision: 2850 $
// Copyright (C) 2001-2008 Philip Coombes
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
#include "zm_sdp.h"
#include "zm_config.h"
#include "zm_exception.h"
#include "zm_logger.h"
SessionDescriptor::StaticPayloadDesc SessionDescriptor::smStaticPayloads[] = {
{ 0, "PCMU", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_PCM_MULAW, 8000, 1 },
{ 3, "GSM", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_NONE, 8000, 1 },
{ 4, "G723", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_NONE, 8000, 1 },
{ 5, "DVI4", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_NONE, 8000, 1 },
{ 6, "DVI4", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_NONE, 16000, 1 },
{ 7, "LPC", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_NONE, 8000, 1 },
{ 8, "PCMA", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_PCM_ALAW, 8000, 1 },
{ 9, "G722", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_NONE, 8000, 1 },
{ 10, "L16", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_PCM_S16BE, 44100, 2 },
{ 11, "L16", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_PCM_S16BE, 44100, 1 },
{ 12, "QCELP", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_QCELP, 8000, 1 },
{ 13, "CN", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_NONE, 8000, 1 },
{ 14, "MPA", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_MP2, -1, -1 },
{ 14, "MPA", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_MP3, -1, -1 },
{ 15, "G728", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_NONE, 8000, 1 },
{ 16, "DVI4", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_NONE, 11025, 1 },
{ 17, "DVI4", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_NONE, 22050, 1 },
{ 18, "G729", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_NONE, 8000, 1 },
{ 25, "CelB", AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_NONE, 90000, -1 },
{ 26, "JPEG", AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_MJPEG, 90000, -1 },
{ 28, "nv", AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_NONE, 90000, -1 },
{ 31, "H261", AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_H261, 90000, -1 },
{ 32, "MPV", AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_MPEG1VIDEO, 90000, -1 },
{ 32, "MPV", AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_MPEG2VIDEO, 90000, -1 },
{ 33, "MP2T", AVMEDIA_TYPE_DATA, AV_CODEC_ID_MPEG2TS, 90000, -1 },
{ 34, "H263", AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_H263, 90000, -1 },
{ -1, "", AVMEDIA_TYPE_UNKNOWN, AV_CODEC_ID_NONE, -1, -1 }
};
SessionDescriptor::DynamicPayloadDesc SessionDescriptor::smDynamicPayloads[] = {
{ "MP4V-ES", AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_MPEG4 },
{ "mpeg4-generic", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_AAC },
{ "H264", AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_H264 },
{ "AMR", AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_AMR_NB },
{ "vnd.onvif.metadata", AVMEDIA_TYPE_DATA, AV_CODEC_ID_NONE }
};
SessionDescriptor::ConnInfo::ConnInfo( const std::string &connInfo ) :
mTtl( 16 ),
mNoAddresses( 0 ) {
StringVector tokens = Split(connInfo, " ");
if ( tokens.size() < 3 )
throw Exception( "Unable to parse SDP connection info from '"+connInfo+"'" );
mNetworkType = tokens[0];
if ( mNetworkType != "IN" )
throw Exception( "Invalid SDP network type '"+mNetworkType+"' in connection info '"+connInfo+"'" );
mAddressType = tokens[1];
if ( mAddressType != "IP4" && mAddressType != "IP6" )
throw Exception( "Invalid SDP address type '"+mAddressType+"' in connection info '"+connInfo+"'" );
StringVector addressTokens = Split(tokens[2], "/");
if ( addressTokens.size() < 1 )
throw Exception( "Invalid SDP address '"+tokens[2]+"' in connection info '"+connInfo+"'" );
mAddress = addressTokens[0];
if ( addressTokens.size() >= 2 )
mTtl = atoi(addressTokens[1].c_str());
if ( addressTokens.size() >= 3 )
mNoAddresses = atoi(addressTokens[2].c_str());
}
SessionDescriptor::BandInfo::BandInfo( const std::string &bandInfo ) :
mValue( 0 ) {
StringVector tokens = Split(bandInfo, ":");
if ( tokens.size() < 2 )
throw Exception( "Unable to parse SDP bandwidth info from '"+bandInfo+"'" );
mType = tokens[0];
//if ( mNetworkType != "IN" )
//throw Exception( "Invalid SDP network type '"+mNetworkType+"' in connection info '"+connInfo+"'" );
mValue = atoi(tokens[1].c_str());
}
SessionDescriptor::MediaDescriptor::MediaDescriptor(
const std::string &type,
int port,
int numPorts,
const std::string &transport,
int payloadType ) :
mType( type ),
mPort( port ),
mNumPorts( numPorts ),
mTransport( transport ),
mPayloadType( payloadType ),
mFrameRate( 0.0 ),
mClock( 0 ),
mWidth( 0 ),
mHeight( 0 ),
mSprops( "" ),
mConnInfo( nullptr ) {
}
SessionDescriptor::SessionDescriptor( const std::string &url, const std::string &sdp ) :
mUrl( url ),
mConnInfo( nullptr ),
mBandInfo( nullptr ) {
MediaDescriptor *currMedia = nullptr;
StringVector lines = Split(sdp, "\r\n");
for ( StringVector::const_iterator iter = lines.begin(); iter != lines.end(); ++iter ) {
std::string line = *iter;
if ( line.empty() )
break;
Debug(3, "Processing SDP line '%s'", line.c_str());
const char sdpType = line[0];
if ( line[1] != '=' )
throw Exception("Invalid SDP format at '"+line+"'");
line.erase(0, 2);
switch( sdpType ) {
case 'v' :
mVersion = line;
break;
case 'o' :
mOwner = line;
break;
case 's' :
mName = line;
break;
case 'i' :
mInfo = line;
break;
case 'c' :
// This prevent a memory leak if the field appears more than one time
if ( mConnInfo )
delete mConnInfo;
mConnInfo = new ConnInfo( line );
break;
case 'b' :
// This prevent a memory leak if the field appears more than one time
if ( mBandInfo )
delete mBandInfo;
mBandInfo = new BandInfo( line );
break;
case 't' :
mTimeInfo = line;
break;
case 'a' : {
mAttributes.push_back( line );
StringVector tokens = Split(line, ":", 2);
std::string attrName = tokens[0];
if ( currMedia ) {
if ( attrName == "control" ) {
if ( tokens.size() < 2 )
throw Exception( "Unable to parse SDP control attribute '"+line+"' for media '"+currMedia->getType()+"'" );
currMedia->setControlUrl( tokens[1] );
} else if ( attrName == "range" ) {
} else if ( attrName == "rtpmap" ) {
// a=rtpmap:96 MP4V-ES/90000
if ( tokens.size() < 2 )
throw Exception( "Unable to parse SDP rtpmap attribute '"+line+"' for media '"+currMedia->getType()+"'" );
StringVector attrTokens = Split(tokens[1], " ");
int payloadType = atoi(attrTokens[0].c_str());
if ( payloadType != currMedia->getPayloadType() )
throw Exception( stringtf( "Payload type mismatch, expected %d, got %d in '%s'", currMedia->getPayloadType(), payloadType, line.c_str() ) );
if ( attrTokens.size() > 1 ) {
StringVector payloadTokens = Split(attrTokens[1], "/");
std::string payloadDesc = payloadTokens[0];
int payloadClock = atoi(payloadTokens[1].c_str());
currMedia->setPayloadDesc( payloadDesc );
currMedia->setClock( payloadClock );
}
} else if ( attrName == "framesize" ) {
// a=framesize:96 320-240
if ( tokens.size() < 2 )
throw Exception("Unable to parse SDP framesize attribute '"+line+"' for media '"+currMedia->getType()+"'");
StringVector attrTokens = Split(tokens[1], " ");
int payloadType = atoi(attrTokens[0].c_str());
if ( payloadType != currMedia->getPayloadType() )
throw Exception( stringtf("Payload type mismatch, expected %d, got %d in '%s'",
currMedia->getPayloadType(), payloadType, line.c_str()));
//currMedia->setPayloadType( payloadType );
StringVector sizeTokens = Split(attrTokens[1], "-");
int width = atoi(sizeTokens[0].c_str());
int height = atoi(sizeTokens[1].c_str());
currMedia->setFrameSize(width, height);
} else if ( attrName == "framerate" ) {
// a=framerate:5.0
if ( tokens.size() < 2 )
throw Exception("Unable to parse SDP framerate attribute '"+line+"' for media '"+currMedia->getType()+"'");
double frameRate = atof(tokens[1].c_str());
currMedia->setFrameRate(frameRate);
} else if ( attrName == "fmtp" ) {
// a=fmtp:96 profile-level-id=247; config=000001B0F7000001B509000001000000012008D48D8803250F042D14440F
if ( tokens.size() < 2 )
throw Exception("Unable to parse SDP fmtp attribute '"+line+"' for media '"+currMedia->getType()+"'");
StringVector attrTokens = Split(tokens[1], " ", 2);
int payloadType = atoi(attrTokens[0].c_str());
if ( payloadType != currMedia->getPayloadType() )
throw Exception(stringtf("Payload type mismatch, expected %d, got %d in '%s'",
currMedia->getPayloadType(), payloadType, line.c_str()));
//currMedia->setPayloadType( payloadType );
if ( attrTokens.size() > 1 ) {
StringVector attr2Tokens = Split(attrTokens[1], "; ");
for ( unsigned int i = 0; i < attr2Tokens.size(); i++ ) {
StringVector attr3Tokens = Split(attr2Tokens[i], "=");
//Info( "Name = %s, Value = %s", attr3Tokens[0].c_str(), attr3Tokens[1].c_str() );
if ( attr3Tokens[0] == "profile-level-id" ) {
} else if ( attr3Tokens[0] == "config" ) {
} else if ( attr3Tokens[0] == "sprop-parameter-sets" ) {
size_t t = attr2Tokens[i].find("=");
char *c = (char *)attr2Tokens[i].c_str() + t + 1;
Debug(4, "sprop-parameter-sets value %s", c);
currMedia->setSprops(std::string(c));
} else {
Debug(3, "Ignoring SDP fmtp attribute '%s' for media '%s'",
attr3Tokens[0].c_str(),
currMedia->getType().c_str());
}
}
}
} else if ( attrName == "mpeg4-iod" ) {
// a=mpeg4-iod: "data:application/mpeg4-iod;base64,AoEAAE8BAf73AQOAkwABQHRkYXRhOmFwcGxpY2F0aW9uL21wZWc0LW9kLWF1O2Jhc2U2NCxBVGdCR3dVZkF4Y0F5U1FBWlFRTklCRUVrK0FBQWEyd0FBR3RzQVlCQkFFWkFwOERGUUJsQlFRTlFCVUFDN2dBQVBvQUFBRDZBQVlCQXc9PQQNAQUABAAAAAAAAAAAAAYJAQAAAAAAAAAAA0IAAkA+ZGF0YTphcHBsaWNhdGlvbi9tcGVnNC1iaWZzLWF1O2Jhc2U2NCx3QkFTZ1RBcUJYSmhCSWhRUlFVL0FBPT0EEgINAAACAAAAAAAAAAAFAwAAQAYJAQAAAAAAAAAA"
} else if ( attrName == "mpeg4-esid" ) {
// a=mpeg4-esid:201
} else {
Debug(3, "Ignoring SDP attribute '%s' for media '%s'", line.c_str(), currMedia->getType().c_str());
}
} else {
Debug(3, "Ignoring general SDP attribute '%s'", line.c_str());
}
break;
}
case 'm' : {
StringVector tokens = Split(line, " ");
if ( tokens.size() < 4 )
throw Exception("Can't parse SDP media description '"+line+"'");
std::string mediaType = tokens[0];
if ( mediaType != "audio" && mediaType != "video" && mediaType != "application" )
throw Exception("Unsupported media type '"+mediaType+"' in SDP media attribute '"+line+"'");
StringVector portTokens = Split(tokens[1], "/");
int mediaPort = atoi(portTokens[0].c_str());
int mediaNumPorts = 1;
if ( portTokens.size() > 1 )
mediaNumPorts = atoi(portTokens[1].c_str());
std::string mediaTransport = tokens[2];
if ( mediaTransport != "RTP/AVP" )
throw Exception("Unsupported media transport '"+mediaTransport+"' in SDP media attribute '"+line+"'");
int payloadType = atoi(tokens[3].c_str());
currMedia = new MediaDescriptor(mediaType, mediaPort, mediaNumPorts, mediaTransport, payloadType);
mMediaList.push_back(currMedia);
break;
}
} // end switch
} // end foreach line
}
SessionDescriptor::~SessionDescriptor() {
if ( mConnInfo )
delete mConnInfo;
if ( mBandInfo )
delete mBandInfo;
for ( unsigned int i = 0; i < mMediaList.size(); i++ )
delete mMediaList[i];
}
AVFormatContext *SessionDescriptor::generateFormatContext() const {
AVFormatContext *formatContext = avformat_alloc_context();
if (!formatContext) {
Error("Failed to allocate AVFormatContext");
return nullptr;
}
#if (LIBAVFORMAT_VERSION_CHECK(58, 12, 0, 0, 100))
formatContext->url = av_strdup(mUrl.c_str());
#else
strncpy(formatContext->filename, mUrl.c_str(), sizeof(formatContext->filename) - 1);
formatContext->filename[sizeof(formatContext->filename) - 1] = '\0';
#endif
/*
if ( mName.length() )
strncpy( formatContext->title, mName.c_str(), sizeof(formatContext->title) );
if ( mInfo.length() )
strncpy( formatContext->comment, mInfo.c_str(), sizeof(formatContext->comment) );
*/
//formatContext->nb_streams = mMediaList.size();
for ( unsigned int i = 0; i < mMediaList.size(); i++ ) {
const MediaDescriptor *mediaDesc = mMediaList[i];
AVStream *stream = avformat_new_stream(formatContext, nullptr);
if (!stream) {
Error("Failed to allocate AVStream for media %d", i);
continue;
}
stream->id = i;
AVCodecContext *codec_context = avcodec_alloc_context3(nullptr);
if (!codec_context) {
Error("Failed to allocate AVCodecContext for media %d", i);
continue;
}
std::string type = mediaDesc->getType();
Debug(1, "Looking for codec for %s payload type %d / %s",
type.c_str(), mediaDesc->getPayloadType(), mediaDesc->getPayloadDesc().c_str());
if ( type == "video" )
codec_context->codec_type = AVMEDIA_TYPE_VIDEO;
else if ( type == "audio" )
codec_context->codec_type = AVMEDIA_TYPE_AUDIO;
else if ( type == "application" )
codec_context->codec_type = AVMEDIA_TYPE_DATA;
else
Warning("Unknown media_type %s", type.c_str());
std::string codec_name;
if ( mediaDesc->getPayloadType() < PAYLOAD_TYPE_DYNAMIC ) {
// Look in static table
for ( unsigned int j = 0; j < (sizeof(smStaticPayloads)/sizeof(*smStaticPayloads)); j++ ) {
if ( smStaticPayloads[j].payloadType == mediaDesc->getPayloadType() ) {
Debug( 1, "Got static payload type %d, %s", smStaticPayloads[j].payloadType, smStaticPayloads[j].payloadName );
codec_name = std::string(smStaticPayloads[j].payloadName);
codec_context->codec_type = smStaticPayloads[j].codecType;
codec_context->codec_id = smStaticPayloads[j].codecId;
codec_context->sample_rate = smStaticPayloads[j].clockRate;
break;
}
}
} else {
// Look in dynamic table
for ( unsigned int j = 0; j < (sizeof(smDynamicPayloads)/sizeof(*smDynamicPayloads)); j++ ) {
if ( smDynamicPayloads[j].payloadName == mediaDesc->getPayloadDesc() ) {
Debug(1, "Got dynamic payload type %d, %s", mediaDesc->getPayloadType(), smDynamicPayloads[j].payloadName);
codec_name = std::string(smDynamicPayloads[j].payloadName);
codec_context->codec_type = smDynamicPayloads[j].codecType;
codec_context->codec_id = smDynamicPayloads[j].codecId;
codec_context->sample_rate = mediaDesc->getClock();
break;
}
}
} /// end if static or dynamic
if (codec_name.empty()) {
Warning( "Can't find payload details for %s payload type %d, name %s",
mediaDesc->getType().c_str(), mediaDesc->getPayloadType(), mediaDesc->getPayloadDesc().c_str() );
}
if ( mediaDesc->getWidth() )
codec_context->width = mediaDesc->getWidth();
if ( mediaDesc->getHeight() )
codec_context->height = mediaDesc->getHeight();
if ( codec_context->codec_id == AV_CODEC_ID_H264 && mediaDesc->getSprops().size()) {
uint8_t start_sequence[]= { 0, 0, 1 };
codec_context->extradata_size= 0;
codec_context->extradata= nullptr;
char pvalue[1024], *value = pvalue;
// sprop-parameter-sets comes from the (untrusted) camera SDP. A real
// H.264 SPS/PPS base64 blob is small; anything approaching the buffer
// size is malformed or hostile, so refuse it rather than overflow the
// stack with an unbounded strcpy.
if (mediaDesc->getSprops().size() >= sizeof(pvalue)) {
Warning("Ignoring oversized sprop-parameter-sets (%zu bytes) from SDP",
mediaDesc->getSprops().size());
pvalue[0] = '\0';
} else {
strcpy(pvalue, mediaDesc->getSprops().c_str());
}
while ( *value ) {
char base64packet[1024];
uint8_t decoded_packet[1024];
uint32_t packet_size;
char *dst = base64packet;
while (*value && *value != ','
&& (dst - base64packet) < (long)(sizeof(base64packet)) - 1) {
*dst++ = *value++;
}
*dst++ = '\0';
if ( *value == ',' )
value++;
packet_size = av_base64_decode(decoded_packet, (const char *)base64packet, (int)sizeof(decoded_packet));
Hexdump(4, (char *)decoded_packet, packet_size);
if ( packet_size ) {
uint8_t *dest =
(uint8_t *) av_malloc(
packet_size + sizeof(start_sequence) + codec_context->extradata_size + AV_INPUT_BUFFER_PADDING_SIZE);
if ( dest ) {
if ( codec_context->extradata_size ) {
// av_realloc?
memcpy(dest, codec_context->extradata, codec_context->extradata_size);
av_free(codec_context->extradata);
}
memcpy(dest+codec_context->extradata_size, start_sequence, sizeof(start_sequence));
memcpy(dest+codec_context->extradata_size+sizeof(start_sequence), decoded_packet, packet_size);
memset(dest+codec_context->extradata_size+sizeof(start_sequence)+
packet_size, 0,
AV_INPUT_BUFFER_PADDING_SIZE
);
codec_context->extradata= dest;
codec_context->extradata_size+= sizeof(start_sequence)+packet_size;
// } else {
// av_log(codec, AV_LOG_ERROR, "Unable to allocate memory for extradata!");
// return AVERROR(ENOMEM);
}
}
}
}
avcodec_parameters_from_context(stream->codecpar, codec_context);
avcodec_free_context(&codec_context);
} // end foreach mediaList
return formatContext;
}
-224
View File
@@ -1,224 +0,0 @@
//
// ZoneMinder SDP Class Interface, $Date: 2009-02-16 18:21:50 +0000 (Mon, 16 Feb 2009) $, $Revision: 2765 $
// Copyright (C) 2001-2008 Philip Coombes
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
#ifndef ZM_SDP_H
#define ZM_SDP_H
#include "zm_ffmpeg.h"
#include "zm_utils.h"
#include <string>
#include <vector>
class SessionDescriptor {
protected:
enum { PAYLOAD_TYPE_DYNAMIC=96 };
struct StaticPayloadDesc {
int payloadType;
const char payloadName[6];
AVMediaType codecType;
_AVCODECID codecId;
int clockRate;
int autoChannels;
};
struct DynamicPayloadDesc {
const char payloadName[32];
AVMediaType codecType;
_AVCODECID codecId;
//int clockRate;
//int autoChannels;
};
public:
class ConnInfo {
protected:
std::string mNetworkType;
std::string mAddressType;
std::string mAddress;
int mTtl;
int mNoAddresses;
public:
explicit ConnInfo( const std::string &connInfo );
};
class BandInfo {
protected:
std::string mType;
int mValue;
public:
explicit BandInfo( const std::string &bandInfo );
};
class MediaDescriptor {
protected:
std::string mType;
int mPort;
int mNumPorts;
std::string mTransport;
int mPayloadType;
std::string mPayloadDesc;
std::string mControlUrl;
double mFrameRate;
int mClock;
int mWidth;
int mHeight;
std::string mSprops;
ConnInfo *mConnInfo;
public:
MediaDescriptor( const std::string &type, int port, int numPorts, const std::string &transport, int payloadType );
const std::string &getType() const {
return( mType );
}
int getPort() const {
return( mPort );
}
int getNumPorts() const {
return( mNumPorts );
}
const std::string &getTransport() const {
return( mTransport );
}
int getPayloadType() const {
return( mPayloadType );
}
const std::string &getPayloadDesc() const {
return( mPayloadDesc );
}
void setPayloadDesc( const std::string &payloadDesc ) {
mPayloadDesc = payloadDesc;
}
const std::string &getControlUrl() const {
return( mControlUrl );
}
void setControlUrl( const std::string &controlUrl ) {
mControlUrl = controlUrl;
}
int getClock() const {
return( mClock );
}
void setClock( int clock ) {
mClock = clock;
}
void setFrameSize( int width, int height ) {
mWidth = width;
mHeight = height;
}
int getWidth() const {
return( mWidth );
}
int getHeight() const {
return( mHeight );
}
void setSprops(const std::string &props) {
mSprops = props;
}
std::string getSprops() const {
return ( mSprops );
}
double getFrameRate() const {
return( mFrameRate );
}
void setFrameRate( double frameRate ) {
mFrameRate = frameRate;
}
};
typedef std::vector<MediaDescriptor *> MediaList;
protected:
static StaticPayloadDesc smStaticPayloads[];
static DynamicPayloadDesc smDynamicPayloads[];
protected:
std::string mUrl;
std::string mVersion;
std::string mOwner;
std::string mName;
std::string mInfo;
ConnInfo *mConnInfo;
BandInfo *mBandInfo;
std::string mTimeInfo;
StringVector mAttributes;
MediaList mMediaList;
public:
SessionDescriptor( const std::string &url, const std::string &sdp );
~SessionDescriptor();
const std::string &getUrl() const {
return( mUrl );
}
int getNumStreams() const {
return( mMediaList.size() );
}
MediaDescriptor *getStream( int index ) {
if ( index < 0 || (unsigned int)index >= mMediaList.size() )
return nullptr;
return( mMediaList[index] );
}
AVFormatContext *generateFormatContext() const;
};
#if 0
v=0
o=- 1239719297054659 1239719297054674 IN IP4 192.168.1.11
s=Media Presentation
e=NONE
c=IN IP4 0.0.0.0
b=AS:174
t=0 0
a=control:
*
a=range:
npt=now-
a=mpeg4-iod: "data:application/mpeg4-iod;base64,AoEAAE8BAf73AQOAkwABQHRkYXRhOmFwcGxpY2F0aW9uL21wZWc0LW9kLWF1O2Jhc2U2NCxBVGdCR3dVZkF4Y0F5U1FBWlFRTklCRUVrK0FBQWEyd0FBR3RzQVlCQkFFWkFwOERGUUJsQlFRTlFCVUFDN2dBQVBvQUFBRDZBQVlCQXc9PQQNAQUABAAAAAAAAAAAAAYJAQAAAAAAAAAAA0IAAkA+ZGF0YTphcHBsaWNhdGlvbi9tcGVnNC1iaWZzLWF1O2Jhc2U2NCx3QkFTZ1RBcUJYSmhCSWhRUlFVL0FBPT0EEgINAAACAAAAAAAAAAAFAwAAQAYJAQAAAAAAAAAA"
m=video 0 RTP/AVP 96
b=AS:110
a=framerate:5.0
a=control:
trackID=1
a=rtpmap:96 MP4V-ES/90000
a=fmtp:96 profile-level-id=247;
config=000001B0F7000001B509000001000000012008D48D8803250F042D14440F
a=mpeg4-esid:201
m=audio 0 RTP/AVP 0
b=AS:64
a=control:
trackID=2
#endif
#endif // ZM_SDP_H