1
0
mirror of https://github.com/opencv/opencv.git synced 2026-07-31 00:03:03 +04:00

Merge pull request #25584 from dkurt:videocapture_from_buffer

Open VideoCapture from data stream #25584

### Pull Request Readiness Checklist

Add VideoCapture option to read a raw binary video data from `std::streambuf`.

There are multiple motivations:
1. Avoid disk file creation in case of video already in memory (received by network or from database).
2. Streaming mode. Frames decoding starts during sequential file transfer by chunks.

Suppoted backends:
* FFmpeg
* MSMF (no streaming mode)

Supporter interfaces:
* C++ (std::streambuf)
* Python (io.BufferedIOBase)

resolves https://github.com/opencv/opencv/issues/24400

- [x] test h264
- [x]  test IP camera like approach with no metadata but key frame only?
- [x] C API plugin

See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request

- [x] I agree to contribute to the project under Apache 2 License.
- [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV
- [x] The PR is proposed to the proper branch
- [x] There is a reference to the original bug report and related work
- [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable
      Patch to opencv_extra has the same branch name.
- [x] The feature is well documented and sample code can be built with the project CMake
This commit is contained in:
Dmitry Kurtaev
2024-12-26 12:48:49 +03:00
committed by GitHub
parent 96d6395a6d
commit e9982e856f
18 changed files with 966 additions and 48 deletions
+90 -10
View File
@@ -526,7 +526,7 @@ inline static std::string _opencv_ffmpeg_get_error_string(int error_code)
struct CvCapture_FFMPEG
{
bool open(const char* filename, const VideoCaptureParameters& params);
bool open(const char* filename, const Ptr<IStreamReader>& stream, const VideoCaptureParameters& params);
void close();
double getProperty(int) const;
@@ -563,6 +563,8 @@ struct CvCapture_FFMPEG
int64_t pts_in_fps_time_base;
int64_t dts_delay_in_fps_time_base;
AVIOContext * avio_context;
AVPacket packet;
Image_FFMPEG frame;
struct SwsContext *img_convert_ctx;
@@ -580,6 +582,8 @@ struct CvCapture_FFMPEG
*/
char * filename;
Ptr<IStreamReader> readStream;
AVDictionary *dict;
#if USE_AV_INTERRUPT_CALLBACK
int open_timeout;
@@ -628,11 +632,14 @@ void CvCapture_FFMPEG::init()
avcodec = 0;
context = 0;
avio_context = 0;
frame_number = 0;
eps_zero = 0.000025;
rotation_angle = 0;
readStream.reset();
dict = NULL;
#if USE_AV_INTERRUPT_CALLBACK
@@ -730,6 +737,13 @@ void CvCapture_FFMPEG::close()
#endif
}
if (avio_context)
{
av_free(avio_context->buffer);
av_freep(&avio_context);
}
readStream.reset();
init();
}
@@ -1019,7 +1033,7 @@ static bool isThreadSafe() {
return threadSafe;
}
bool CvCapture_FFMPEG::open(const char* _filename, const VideoCaptureParameters& params)
bool CvCapture_FFMPEG::open(const char* _filename, const Ptr<IStreamReader>& stream, const VideoCaptureParameters& params)
{
const bool threadSafe = isThreadSafe();
InternalFFMpegRegister::init(threadSafe);
@@ -1034,6 +1048,8 @@ bool CvCapture_FFMPEG::open(const char* _filename, const VideoCaptureParameters&
close();
readStream = stream;
if (!params.empty())
{
convertRGB = params.get<bool>(CAP_PROP_CONVERT_RGB, true);
@@ -1145,6 +1161,56 @@ bool CvCapture_FFMPEG::open(const char* _filename, const VideoCaptureParameters&
input_format = av_find_input_format(entry->value);
}
if (!_filename)
{
size_t avio_ctx_buffer_size = 4096;
uint8_t* avio_ctx_buffer = (uint8_t*)av_malloc(avio_ctx_buffer_size);
CV_Assert(avio_ctx_buffer);
avio_context = avio_alloc_context(avio_ctx_buffer, avio_ctx_buffer_size, 0, this,
[](void *opaque, uint8_t *buf, int buf_size) -> int {
try {
auto capture = reinterpret_cast<CvCapture_FFMPEG*>(opaque);
auto is = capture->readStream;
int result = (int)is->read(reinterpret_cast<char*>(buf), buf_size);
// https://github.com/FFmpeg/FFmpeg/commit/858db4b01fa2b55ee55056c033054ca54ac9b0fd#diff-863c87afc9bb02fe42d071015fc8218972c80b146d603239f20b483ad0988ae9R394
// https://github.com/FFmpeg/FFmpeg/commit/a606f27f4c610708fa96e35eed7b7537d3d8f712
// https://github.com/FFmpeg/FFmpeg/blob/n4.0/libavformat/version.h#L83C41-L83C73
#if (LIBAVFORMAT_VERSION_MAJOR >= 58) && (LIBAVFORMAT_VERSION_MICRO >= 100) // FFmpeg n4.0+
if (result == 0 && buf_size > 0)
{
result = AVERROR_EOF;
}
#endif
CV_LOG_VERBOSE(NULL, 0, "FFMPEG: IStreamReader::read(" << buf_size << ") = " << result);
return result;
} catch (...) {
CV_LOG_WARNING(NULL, "FFMPEG: IStreamReader::read(" << buf_size << ") failed");
return 0;
}
},
NULL,
[](void *opaque, int64_t offset, int whence) -> int64_t {
try {
int64_t result = -1;
auto capture = reinterpret_cast<CvCapture_FFMPEG*>(opaque);
auto is = capture->readStream;
int origin = whence & (~AVSEEK_FORCE);
if (origin == SEEK_SET || origin == SEEK_CUR || origin == SEEK_END)
{
result = is->seek(offset, origin);
}
CV_LOG_VERBOSE(NULL, 0, "FFMPEG: IStreamReader::seek(" << offset << ", whence=" << whence << ") = " << result);
return result;
} catch (...) {
CV_LOG_WARNING(NULL, "FFMPEG: IStreamReader::seek(" << offset << ", whence=" << whence << ") failed");
return -1;
}
});
CV_Assert(avio_context);
ic->pb = avio_context;
}
int err = avformat_open_input(&ic, _filename, input_format, &dict);
if (err < 0)
@@ -3292,16 +3358,30 @@ bool CvVideoWriter_FFMPEG::open( const char * filename, int fourcc,
static
CvCapture_FFMPEG* cvCreateFileCaptureWithParams_FFMPEG(const char* filename, const VideoCaptureParameters& params)
{
// FIXIT: remove unsafe malloc() approach
CvCapture_FFMPEG* capture = (CvCapture_FFMPEG*)malloc(sizeof(*capture));
CvCapture_FFMPEG* capture = new CvCapture_FFMPEG();
if (!capture)
return 0;
capture->init();
if (capture->open(filename, params))
if (capture->open(filename, nullptr, params))
return capture;
capture->close();
free(capture);
delete capture;
return 0;
}
static
CvCapture_FFMPEG* cvCreateStreamCaptureWithParams_FFMPEG(const Ptr<IStreamReader>& stream, const VideoCaptureParameters& params)
{
CvCapture_FFMPEG* capture = new CvCapture_FFMPEG();
if (!capture)
return 0;
capture->init();
if (capture->open(nullptr, stream, params))
return capture;
capture->close();
delete capture;
return 0;
}
@@ -3310,7 +3390,7 @@ void cvReleaseCapture_FFMPEG(CvCapture_FFMPEG** capture)
if( capture && *capture )
{
(*capture)->close();
free(*capture);
delete *capture;
*capture = 0;
}
}
@@ -3344,14 +3424,14 @@ int cvRetrieveFrame2_FFMPEG(CvCapture_FFMPEG* capture, unsigned char** data, int
static CvVideoWriter_FFMPEG* cvCreateVideoWriterWithParams_FFMPEG( const char* filename, int fourcc, double fps,
int width, int height, const VideoWriterParameters& params )
{
CvVideoWriter_FFMPEG* writer = (CvVideoWriter_FFMPEG*)malloc(sizeof(*writer));
CvVideoWriter_FFMPEG* writer = new CvVideoWriter_FFMPEG();
if (!writer)
return 0;
writer->init();
if( writer->open( filename, fourcc, fps, width, height, params ))
return writer;
writer->close();
free(writer);
delete writer;
return 0;
}
@@ -3368,7 +3448,7 @@ void cvReleaseVideoWriter_FFMPEG( CvVideoWriter_FFMPEG** writer )
if( writer && *writer )
{
(*writer)->close();
free(*writer);
delete *writer;
*writer = 0;
}
}