Files
zoneminder/src/zm_remote_camera_rtsp.cpp
Isaac Connor 6765962737 fix: nullptr-safe pixfmt name; route GetImage/getSnapshot through ReadShmFrame (PR #4788)
- Added zm_get_pix_fmt_name() in zm_pixformat.h: av_get_pix_fmt_name()
  returns nullptr for unknown formats (including AV_PIX_FMT_NONE), and
  passing nullptr to %s is undefined and can crash. Wraps with an
  "unknown" fallback. Replaced the five raw uses flagged by review
  (RemoteCameraRtsp/FfmpegCamera/VncCamera/LocalCamera constructors and
  MonitorStream::sendFrame's Debug log) with the wrapper. Each can hit
  AV_PIX_FMT_NONE before the first frame or via an unexpected
  (colours, subpixelorder) pair.
- Monitor::GetImage and Monitor::getSnapshot now route the slot read
  through ReadShmFrame so the per-slot AVPixelFormat zmc recorded is
  adopted on image_buffer[index] before its bytes are interpreted.
  Without this, the JPEG encode in GetImage and the ZMPacket returned
  by getSnapshot would interpret SHM bytes using the placeholder format
  set at attach time and produce garbled output when the slot's actual
  format differs (the contract previously relied on callers calling
  ReadShmFrame themselves). getSnapshot/GetTimestamp drop const since
  ReadShmFrame mutates image_buffer[index]; callers in this codebase
  already use a non-const Monitor pointer.
- Monitor::connect: updated the misleading "+64 bytes reserved" comment
  to match the actual reservation of 63 + (alignof(AVPixelFormat) - 1).

refs #4788

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-31 09:22:09 -04:00

294 lines
8.7 KiB
C++

//
// 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;
}