Skip to content

Commit b87f2a7

Browse files
committed
Add basic tests for the FormatContext and CustomIO
1 parent bea31e9 commit b87f2a7

2 files changed

Lines changed: 340 additions & 1 deletion

File tree

tests/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ add_executable(test_executor
1414
Codec.cpp
1515
PixelSampleFormat.cpp
1616
Common.cpp
17-
Buffer.cpp)
17+
Buffer.cpp
18+
FormatCustomIO_test.cpp)
1819
target_link_libraries(test_executor PUBLIC Catch2::Catch2WithMain avcpp::avcpp)
1920

2021
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../catch2/contrib")

tests/FormatCustomIO_test.cpp

Lines changed: 338 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,338 @@
1+
#include <catch2/catch_test_macros.hpp>
2+
3+
#include "avcpp/format.h"
4+
#include "avcpp/codec.h"
5+
#include "avcpp/ffmpeg.h"
6+
#include "avcpp/formatcontext.h"
7+
#include "catch2/catch_message.hpp"
8+
9+
#include <print>
10+
11+
#if AVCPP_HAS_AVFORMAT && (AVCPP_CXX_STANDARD >= 20)
12+
13+
static constexpr std::size_t ImageW = 640;
14+
static constexpr std::size_t ImageH = 480;
15+
static constexpr std::size_t ImageCount = 11;
16+
static constexpr av::PixelFormat ImagePixFmt = AV_PIX_FMT_GRAY8;
17+
18+
static const std::size_t FrameSizeBytes = ImageW * ImageH * ImagePixFmt.bitsPerPixel() / 8;
19+
20+
struct TestBufferIo : public av::CustomIO
21+
{
22+
public:
23+
std::vector<uint8_t> _buffer{};
24+
std::vector<uint8_t>::iterator _pos{};
25+
26+
TestBufferIo()
27+
{
28+
_buffer.resize(FrameSizeBytes * ImageCount);
29+
_pos = _buffer.begin();
30+
}
31+
32+
std::size_t remain() const noexcept
33+
{
34+
return std::ranges::distance(_pos, _buffer.end());
35+
}
36+
37+
bool eof() const noexcept
38+
{
39+
return _pos == _buffer.end();
40+
}
41+
42+
void fillPattern(uint8_t patternOffset)
43+
{
44+
std::span out = _buffer;
45+
for (auto i = 0u; i < ImageCount; ++i) {
46+
auto buf = out.subspan(i * FrameSizeBytes, FrameSizeBytes);
47+
std::ranges::fill(buf, uint8_t(i + patternOffset));
48+
}
49+
}
50+
51+
void fill(uint8_t val = 0xFF)
52+
{
53+
std::ranges::fill(_buffer, val);
54+
}
55+
56+
//
57+
// Iface
58+
//
59+
60+
int write(const uint8_t *data, size_t size) final
61+
{
62+
UNSCOPED_INFO("IO::write: size=" << size);
63+
// ENOSPC
64+
// EWOULDBLOCK
65+
// EMSGSIZE
66+
// Do not write
67+
if (size > remain()) {
68+
UNSCOPED_INFO("IO::write too big: " << size);
69+
return AVERROR(EMSGSIZE);
70+
}
71+
72+
std::ranges::copy_n(data, size, _pos);
73+
std::advance(_pos, size);
74+
75+
return 0;
76+
}
77+
78+
int read(uint8_t *data, size_t size) final
79+
{
80+
UNSCOPED_INFO("IO::read: size=" << size);
81+
if (eof()) {
82+
INFO("IO::read: EOF reached");
83+
return AVERROR_EOF;
84+
}
85+
86+
auto readed = std::min(size, remain());
87+
std::ranges::copy_n(_pos, readed, data);
88+
std::advance(_pos, readed);
89+
return readed;
90+
}
91+
92+
int64_t seek(int64_t offset, int whence) final
93+
{
94+
UNSCOPED_INFO("IO::seek: offset=" << offset << ", whence=0x" << std::hex << whence << std::dec);
95+
96+
if (whence & AVSEEK_SIZE) {
97+
return _buffer.size();
98+
}
99+
100+
ssize_t cur = -1;
101+
102+
if (whence == SEEK_CUR) {
103+
cur = std::distance(_buffer.begin(), _pos);
104+
} else if (whence == SEEK_SET) {
105+
cur = 0;
106+
} else if (whence == SEEK_END) {
107+
cur = _buffer.size() - 1;
108+
} else {
109+
AVERROR(EINVAL);
110+
}
111+
112+
assert(cur >= 0 && cur <= _buffer.size() - 1);
113+
114+
auto next = cur + offset;
115+
if (next >= _buffer.size() || next < 0)
116+
return AVERROR(EINVAL);
117+
std::advance(_pos, offset);
118+
return next;
119+
}
120+
121+
int seekable() const final
122+
{
123+
return AVIO_SEEKABLE_NORMAL;
124+
}
125+
126+
const char *name() const final
127+
{
128+
return "TestBufferIo";
129+
}
130+
};
131+
132+
133+
TEST_CASE("Format Custom IO checks", "[FormatCustomIo]")
134+
{
135+
SECTION("CustomIo :: Basic")
136+
{
137+
TestBufferIo customIoIn;
138+
TestBufferIo customIoOut;
139+
static auto const VideoSize = std::format("{}x{}", ImageW, ImageH);
140+
static auto const FrameSizeBytes = ImageW * ImageH * ImagePixFmt.bitsPerPixel() / 8;
141+
static auto const PatternOffset = 100u;
142+
143+
// Fill In buffer with pattern
144+
customIoIn.fillPattern(PatternOffset);
145+
146+
// Fill Out buffer with FF
147+
customIoOut.fill();
148+
149+
{
150+
av::FormatContext ictx;
151+
ictx.openInput(&customIoIn,
152+
av::Dictionary {
153+
{"pixel_format", ImagePixFmt.name()},
154+
{"video_size", VideoSize.c_str()},
155+
},
156+
av::InputFormat("rawvideo"));
157+
ictx.findStreamInfo();
158+
std::size_t count = 0;
159+
while (auto pkt = ictx.readPacket()) {
160+
INFO("Pkt counter: " << count << ", pattern value " << (count + PatternOffset));
161+
REQUIRE(std::ranges::find_if_not(pkt.span(), [&](auto val) { return val == count + PatternOffset; }) == pkt.span().end());
162+
++count;
163+
}
164+
REQUIRE(ictx.streamsCount() == 1);
165+
REQUIRE(ImageCount == count);
166+
}
167+
168+
{
169+
av::FormatContext octx;
170+
octx.setFormat(av::OutputFormat("rawvideo"));
171+
octx.addStream();
172+
octx.openOutput(&customIoOut);
173+
octx.writeHeader(av::Dictionary {
174+
{"pixel_format", ImagePixFmt.name()},
175+
{"video_size", VideoSize.c_str()},
176+
});
177+
178+
std::vector<uint8_t> packetData(FrameSizeBytes);
179+
180+
for (auto i = 0u; i < ImageCount; ++i) {
181+
std::ranges::fill(packetData, uint8_t(i + PatternOffset));
182+
av::Packet pkt{packetData, av::Packet::wrap_data_static{}};
183+
pkt.setPts(av::Timestamp{std::chrono::seconds(i)});
184+
pkt.setStreamIndex(0);
185+
octx.writePacket(pkt);
186+
}
187+
octx.flush();
188+
189+
// Check Output data
190+
std::span in = customIoOut._buffer;
191+
for (auto i = 0u; i < ImageCount; ++i) {
192+
auto buf = in.subspan(i * FrameSizeBytes, FrameSizeBytes);
193+
INFO("Pkt counter: " << i << ", pattern value " << (i + PatternOffset));
194+
REQUIRE(std::ranges::find_if_not(buf, [&](auto val) { return val == i + PatternOffset; }) == buf.end());
195+
}
196+
}
197+
}
198+
199+
SECTION("Output Open")
200+
{
201+
TestBufferIo customIo;
202+
static auto const VideoSize = std::format("{}x{}", ImageW, ImageH);
203+
static auto const FrameSizeBytes = ImageW * ImageH * ImagePixFmt.bitsPerPixel() / 8;
204+
static auto const PatternOffset = 100u;
205+
206+
{
207+
av::FormatContext ctx;
208+
CHECK_THROWS(ctx.openOutput(&customIo,
209+
av::Dictionary {
210+
{"pixel_format", ImagePixFmt.name()},
211+
{"video_size", VideoSize.c_str()},
212+
}));
213+
REQUIRE(ctx.isOpened() == false);
214+
}
215+
216+
{
217+
av::FormatContext ctx;
218+
ctx.setFormat(av::OutputFormat{"rawvideo"});
219+
220+
// Yep, format pointed, but there is no any streams for muxing and initOuput called.
221+
REQUIRE_THROWS(ctx.openOutput(&customIo,
222+
av::Dictionary {
223+
{"pixel_format", ImagePixFmt.name()},
224+
{"video_size", VideoSize.c_str()},
225+
}));
226+
227+
REQUIRE(ctx.isOpened() == false);
228+
229+
// set format again
230+
ctx.setFormat(av::OutputFormat{"rawvideo"});
231+
// add stream for muxing, it must be valid now
232+
REQUIRE_NOTHROW(ctx.addStream());
233+
REQUIRE_NOTHROW(ctx.openOutput(&customIo,
234+
av::Dictionary {
235+
{"pixel_format", ImagePixFmt.name()},
236+
{"video_size", VideoSize.c_str()},
237+
}));
238+
}
239+
240+
{
241+
av::FormatContext ctx;
242+
// no streams, should throws
243+
CHECK_THROWS(ctx.openOutput(&customIo,
244+
av::Dictionary {
245+
{"pixel_format", ImagePixFmt.name()},
246+
{"video_size", VideoSize.c_str()},
247+
},
248+
av::OutputFormat{"rawvideo"}));
249+
REQUIRE(ctx.isOpened() == false);
250+
251+
ctx.addStream();
252+
253+
CHECK_NOTHROW(ctx.openOutput(&customIo,
254+
av::Dictionary {
255+
{"pixel_format", ImagePixFmt.name()},
256+
{"video_size", VideoSize.c_str()},
257+
},
258+
av::OutputFormat{"rawvideo"}));
259+
REQUIRE(ctx.isOpened() == true);
260+
}
261+
262+
// non-move stor for options
263+
{
264+
av::FormatContext ctx;
265+
266+
av::Dictionary options {
267+
{"pixel_format", ImagePixFmt.name()},
268+
{"video_size", VideoSize.c_str()}
269+
};
270+
271+
// no streams, should throws
272+
CHECK_THROWS(ctx.openOutput(&customIo,
273+
options,
274+
av::OutputFormat{"rawvideo"}));
275+
REQUIRE(ctx.isOpened() == false);
276+
277+
// Options should kept:
278+
REQUIRE(options.count() == 2);
279+
280+
ctx.addStream();
281+
CHECK_NOTHROW(ctx.openOutput(&customIo,
282+
options,
283+
av::OutputFormat{"rawvideo"}));
284+
REQUIRE(ctx.isOpened() == true);
285+
286+
// Options should be empty
287+
// REQUIRE(options.count() == 0);
288+
}
289+
290+
// Check write
291+
{
292+
av::FormatContext ctx;
293+
294+
av::Dictionary options {
295+
{"pixel_format", ImagePixFmt.name()},
296+
{"video_size", VideoSize.c_str()}
297+
};
298+
299+
// Options should kept:
300+
REQUIRE(options.count() == 2);
301+
302+
ctx.addStream();
303+
bool openOutputFlag{};
304+
CHECK_NOTHROW(openOutputFlag = ctx.openOutput(&customIo,
305+
options,
306+
av::OutputFormat{"rawvideo"}));
307+
REQUIRE(ctx.isOpened() == true);
308+
// for the rawvideo openOutputFlag should be false
309+
REQUIRE(openOutputFlag == false);
310+
311+
ctx.writeHeader();
312+
313+
// fill with FF
314+
customIo.fill();
315+
316+
std::vector<uint8_t> packetData(FrameSizeBytes);
317+
318+
for (auto i = 0u; i < ImageCount; ++i) {
319+
std::ranges::fill(packetData, uint8_t(i + PatternOffset));
320+
av::Packet pkt{packetData, av::Packet::wrap_data_static{}};
321+
pkt.setPts(av::Timestamp{std::chrono::seconds(i)});
322+
pkt.setStreamIndex(0);
323+
ctx.writePacket(pkt);
324+
}
325+
ctx.flush();
326+
327+
// Check Output data
328+
std::span in = customIo._buffer;
329+
for (auto i = 0u; i < ImageCount; ++i) {
330+
auto buf = in.subspan(i * FrameSizeBytes, FrameSizeBytes);
331+
INFO("Pkt counter: " << i << ", pattern value " << (i + PatternOffset));
332+
REQUIRE(std::ranges::find_if_not(buf, [&](auto val) { return val == i + PatternOffset; }) == buf.end());
333+
}
334+
}
335+
}
336+
}
337+
338+
#endif

0 commit comments

Comments
 (0)