mirror of
https://github.com/ZoneMinder/zoneminder.git
synced 2026-06-22 04:29:49 -04:00
- 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>
294 lines
8.7 KiB
C++
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;
|
|
}
|