1
0
mirror of https://github.com/opencv/opencv.git synced 2026-07-30 15:53: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
+130
View File
@@ -3,6 +3,7 @@
// of this distribution and at http://opencv.org/license.html.
#include "test_precomp.hpp"
#include "opencv2/core/utils/filesystem.hpp"
namespace opencv_test
{
@@ -1024,4 +1025,133 @@ INSTANTIATE_TEST_CASE_P(videoio, videowriter_acceleration, testing::Combine(
testing::ValuesIn(hw_use_umat)
));
class BufferStream : public cv::IStreamReader
{
public:
BufferStream(const std::string& filename)
{
Ptr<std::filebuf> file = makePtr<std::filebuf>();
file->open(filename.c_str(), std::ios::in | std::ios::binary);
stream = file;
}
BufferStream(const Ptr<std::stringbuf>& _stream) : stream(_stream) {}
long long read(char* buffer, long long size) CV_OVERRIDE
{
auto result = stream->sgetn(buffer, size);
return result;
}
long long seek(long long offset, int way) CV_OVERRIDE
{
auto result = stream->pubseekoff(offset, way == SEEK_SET ? std::ios_base::beg : (way == SEEK_END ? std::ios_base::end : std::ios_base::cur));
return result;
}
private:
Ptr<std::streambuf> stream;
};
typedef testing::TestWithParam<tuple<std::string, VideoCaptureAPIs>> stream_capture;
TEST_P(stream_capture, read)
{
std::string ext = get<0>(GetParam());
VideoCaptureAPIs apiPref = get<1>(GetParam());
std::vector<VideoCaptureAPIs> supportedAPIs = videoio_registry::getStreamBufferedBackends();
if (!videoio_registry::hasBackend(apiPref))
throw SkipTestException(cv::String("Backend is not available/disabled: ") + cv::videoio_registry::getBackendName(apiPref));
if (std::find(supportedAPIs.begin(), supportedAPIs.end(), apiPref) == supportedAPIs.end())
throw SkipTestException(cv::String("Backend is not supported: ") + cv::videoio_registry::getBackendName(apiPref));
if (cvtest::skipUnstableTests && apiPref == CAP_MSMF && (ext == "h264" || ext == "h265" || ext == "mpg"))
throw SkipTestException("Unstable MSMF test");
if (!videoio_registry::isBackendBuiltIn(apiPref))
{
int pluginABI, pluginAPI;
videoio_registry::getStreamBufferedBackendPluginVersion(apiPref, pluginABI, pluginAPI);
if (pluginABI < 1 || (pluginABI == 1 && pluginAPI < 2))
throw SkipTestException(format("Buffer capture supported since ABI/API = 1/2. %s plugin is %d/%d",
cv::videoio_registry::getBackendName(apiPref).c_str(), pluginABI, pluginAPI));
}
VideoCapture cap;
String video_file = BunnyParameters::getFilename(String(".") + ext);
ASSERT_TRUE(utils::fs::exists(video_file));
EXPECT_NO_THROW(cap.open(makePtr<BufferStream>(video_file), apiPref, {}));
ASSERT_TRUE(cap.isOpened());
const int numFrames = 10;
Mat frames[numFrames];
Mat hardCopies[numFrames];
for(int i = 0; i < numFrames; i++)
{
ASSERT_NO_THROW(cap >> frames[i]);
EXPECT_FALSE(frames[i].empty());
hardCopies[i] = frames[i].clone();
}
for(int i = 0; i < numFrames; i++)
EXPECT_EQ(0, cv::norm(frames[i], hardCopies[i], NORM_INF)) << i;
}
INSTANTIATE_TEST_CASE_P(videoio, stream_capture,
testing::Combine(
testing::ValuesIn(bunny_params),
testing::ValuesIn(backend_params)));
// This test for stream input for container format (See test_ffmpeg/videoio_container.read test)
typedef testing::TestWithParam<std::string> stream_capture_ffmpeg;
TEST_P(stream_capture_ffmpeg, raw)
{
std::string ext = GetParam();
VideoCaptureAPIs apiPref = CAP_FFMPEG;
std::vector<VideoCaptureAPIs> supportedAPIs = videoio_registry::getStreamBufferedBackends();
if (!videoio_registry::hasBackend(apiPref))
throw SkipTestException(cv::String("Backend is not available/disabled: ") + cv::videoio_registry::getBackendName(apiPref));
if (std::find(supportedAPIs.begin(), supportedAPIs.end(), apiPref) == supportedAPIs.end())
throw SkipTestException(cv::String("Backend is not supported: ") + cv::videoio_registry::getBackendName(apiPref));
if (!videoio_registry::isBackendBuiltIn(apiPref))
{
int pluginABI, pluginAPI;
videoio_registry::getStreamBufferedBackendPluginVersion(apiPref, pluginABI, pluginAPI);
if (pluginABI < 1 || (pluginABI == 1 && pluginAPI < 2))
throw SkipTestException(format("Buffer capture supported since ABI/API = 1/2. %s plugin is %d/%d",
cv::videoio_registry::getBackendName(apiPref).c_str(), pluginABI, pluginAPI));
}
VideoCapture container;
String video_file = BunnyParameters::getFilename(String(".") + ext);
ASSERT_TRUE(utils::fs::exists(video_file));
EXPECT_NO_THROW(container.open(video_file, apiPref, {CAP_PROP_FORMAT, -1}));
ASSERT_TRUE(container.isOpened());
ASSERT_EQ(-1.f, container.get(CAP_PROP_FORMAT));
auto stream = std::make_shared<std::stringbuf>();
Mat keyFrame;
while (true)
{
container >> keyFrame;
if (keyFrame.empty())
break;
stream->sputn(keyFrame.ptr<char>(), keyFrame.total());
}
VideoCapture capRef(video_file);
VideoCapture capStream;
EXPECT_NO_THROW(capStream.open(makePtr<BufferStream>(stream), apiPref, {}));
ASSERT_TRUE(capStream.isOpened());
const int numFrames = 10;
Mat frameRef, frame;
for (int i = 0; i < numFrames; ++i)
{
capRef >> frameRef;
ASSERT_NO_THROW(capStream >> frame);
EXPECT_FALSE(frame.empty());
EXPECT_EQ(0, cv::norm(frame, frameRef, NORM_INF)) << i;
}
}
INSTANTIATE_TEST_CASE_P(videoio, stream_capture_ffmpeg, testing::Values("h264", "h265", "mjpg.avi"));
} // namespace