In the following code, i trying to create a class to record frames from an IP camera (RTSP), save frames on a .avi file and finish the record, but, when i kill the operation, the video file may be corrupted. Have any other more safely way to stop the ffmpeg recording?
.cpp file:
#include "videorecorder.h"
VideoRecorder::VideoRecorder(const std::string& rtspUrl) :
url(rtspUrl),
recording(false)
{
}
VideoRecorder::~VideoRecorder()
{
end_record();
}
bool VideoRecorder::start_record(const std::string &fileName)
{
if (recording) {
std::cerr << "Already recording." << std::endl;
return false;
}
std::string command = "ffmpeg -rtsp_transport udp -i " + url
+ " -c:v mjpeg -preset fast -qp 0 " + fileName;
videoWriter = popen(command.c_str(), "w");
if (!videoWriter) {
std::cerr << "Error opening ffmpeg process." << std::endl;
return false;
}
recording = true;
ffmpegProcessId = getpid();
std::cout << "Recording started." << std::endl;
return true;
}
bool VideoRecorder::end_record()
{
if (recording) {
if (videoWriter) {
pid_t ffmpegPID = fileno(videoWriter);
if (kill(ffmpegPID, SIGTERM) == 0) {
std::cout << "Recording terminated successfully." << std::endl;
} else {
std::cerr << "Error terminating recording." << std::endl;
return false;
}
int status = pclose(videoWriter);
if (status == 0) {
std::cout << "Recording ended successfully." << std::endl;
} else {
std::cerr << "Error ending recording. pclose status: " << status << std::endl;
return false;
}
} else {
std::cerr << "Error ending recording. videoWriter is nullptr." << std::endl;
return false;
}
recording = false;
return true;
}
return false;
}
.h file:
#ifndef VIDEORECORDER_H
#define VIDEORECORDER_H
#include <string>
#include <iostream>
#include <fstream>
#include <cstdlib>
#include <csignal>
#include <sys/wait.h>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
#include <libavutil/opt.h>
#include <libswscale/swscale.h>
#include <unistd.h>
}
#include <linux/videodev2.h>
#include <opencv2/opencv.hpp>
#include <opencv2/videoio.hpp>
#include <opencv2/highgui/highgui.hpp>
class VideoRecorder
{
public:
VideoRecorder(const std::string& rtspUrl);
~VideoRecorder();
bool start_record(const std::string& fileName);
bool end_record();
private:
std::string url;
AVFormatContext *formatContext;
AVStream *videoStream;
AVCodecContext *codecContext;
AVCodec *codec;
SwsContext *swsContext;
AVFrame *frame;
AVPacket packet;
bool recording;
pid_t ffmpegProcessId;
FILE* videoWriter;
};
#endif // VIDEORECORDER_H
I'm using the ffmpeg lib becouse i need max speed on frames recording, and OpenCV and AV Lib is much slowness than ffmpeg.
This my terminal output after recording during 10 seconds (generated a file with 23 seconds duration):
Recording started.
ffmpeg version 4.3.6-0+deb11u1+rpt5 Copyright (c) 2000-2023 the FFmpeg developers
built with gcc 10 (Debian 10.2.1-6)
configuration: --prefix=/usr --extra-version=0+deb11u1+rpt5 --toolchain=hardened --incdir=/usr/include/aarch64-linux-gnu --enable-gpl --disable-stripping --enable-avresample --disable-filter=resample --enable-gnutls --enable-ladspa --enable-libaom --enable-libass --enable-libbluray --enable-libbs2b --enable-libcaca --enable-libcdio --enable-libcodec2 --enable-libdav1d --enable-libflite --enable-libfontconfig --enable-libfreetype --enable-libfribidi --enable-libgme --enable-libgsm --enable-libjack --enable-libmp3lame --enable-libmysofa --enable-libopenjpeg --enable-libopenmpt --enable-libopus --enable-libpulse --enable-librabbitmq --enable-librsvg --enable-librubberband --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libsrt --enable-libssh --enable-libtheora --enable-libtwolame --enable-libvidstab --enable-libvorbis --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx265 --enable-libxml2 --enable-libxvid --enable-libzmq --enable-libzvbi --enable-lv2 --enable-omx --enable-openal --enable-opencl --enable-opengl --enable-sdl2 --disable-mmal --enable-neon --enable-v4l2-request --enable-libudev --enable-epoxy --enable-sand --libdir=/usr/lib/aarch64-linux-gnu --arch=arm64 --enable-pocketsphinx --enable-libdc1394 --enable-libdrm --enable-vout-drm --enable-libiec61883 --enable-chromaprint --enable-frei0r --enable-libx264 --enable-shared
libavutil 56. 51.100 / 56. 51.100
libavcodec 58. 91.100 / 58. 91.100
libavformat 58. 45.100 / 58. 45.100
libavdevice 58. 10.100 / 58. 10.100
libavfilter 7. 85.100 / 7. 85.100
libavresample 4. 0. 0 / 4. 0. 0
libswscale 5. 7.100 / 5. 7.100
libswresample 3. 7.100 / 3. 7.100
libpostproc 55. 7.100 / 55. 7.100
Input #0, rtsp, from 'rtsp://admin:[password]@[ip]:[port]/live/0/MAIN':
Metadata:
title : RTSP Server
Duration: N/A, start: 0.280000, bitrate: N/A
Stream #0:0: Video: h264 (Main), yuvj420p(pc, bt709, progressive), 1920x1080, 25 fps, 25 tbr, 90k tbn, 50 tbc
Codec AVOption preset (Configuration preset) specified for output file #0 (/home/guardian-tech/Pictures/output_frame.avi) has not been used for any stream. The most likely reason is either wrong type (e.g. a video option with no video streams) or that it is a private option of some encoder which was not actually used for any stream.
Codec AVOption qp (Constant quantization parameter rate control method) specified for output file #0 (/home/guardian-tech/Pictures/output_frame.avi) has not been used for any stream. The most likely reason is either wrong type (e.g. a video option with no video streams) or that it is a private option of some encoder which was not actually used for any stream.
Stream mapping:
Stream #0:0 -> #0:0 (h264 (native) -> mjpeg (native))
Press [q] to stop, [?] for help
Output #0, avi, to '/home/guardian-tech/Pictures/output_frame.avi':
Metadata:
INAM : RTSP Server
ISFT : Lavf58.45.100
Stream #0:0: Video: mjpeg (MJPG / 0x47504A4D), yuvj420p(pc), 1920x1080, q=2-31, 200 kb/s, 25 fps, 25 tbn, 25 tbc
Metadata:
encoder : Lavc58.91.100 mjpeg
Side data:
cpb: bitrate max/min/avg: 0/0/200000 buffer size: 0 vbv_delay: N/A
[rtsp @ 0x5592e7bb00] max delay reached. need to consume packet
[rtsp @ 0x5592e7bb00] RTP: missed 212 packets
[h264 @ 0x5592ebb790] concealing 2192 DC, 2192 AC, 2192 MV errors in I frame
rtsp://admin:[password]@[ip]:[port]/live/0/MAIN: corrupt decoded frame in stream 0
[rtsp @ 0x5592e7bb00] max delay reached. need to consume packet
[rtsp @ 0x5592e7bb00] RTP: missed 6 packets
[rtsp @ 0x5592e7bb00] max delay reached. need to consume packet
[rtsp @ 0x5592e7bb00] RTP: missed 14 packets
[h264 @ 0x5592f1bd30] cabac decode of qscale diff failed at 42 29
[h264 @ 0x5592f1bd30] error while decoding MB 42 29, bytestream 0
[h264 @ 0x5592f1bd30] concealing 4687 DC, 4687 AC, 4687 MV errors in I frame
rtsp://admin:[password]@[ip]:[port]/live/0/MAIN: corrupt decoded frame in stream 0
Error terminating recording.
Please refer to examples to learn how to work with ffmpeg.
Basically, what you need for your task is demux the incoming RTSP and mux it to .avi file.
Remuxing example can be helpful for you with this task. Input context for rtsp can be initialized the same way as for files, you can also set "tcp" flag.
AVDictionary* options = nullptr;
// Open with tcp
av_dict_set(
&options,
"rtsp_transport",
"tcp",
0
);
std::string camera_address = "rtsp://put_your_address";
int ret = avformat_open_input(
&input_format_context,
camera_address.c_str(),
nullptr,
&options
);
Dont't forget to write header (avformat_write_header function) before writing packets to file. And at the end write trailer (av_write_trailer function).