Skip to content

Commit 2603883

Browse files
authored
alternative filter interface (#129)
Add `void FilterGraph::parse(const std::string &graphDescription, OptionalErrorCode ec)` to allow use FFmpeg-like string description for filter. Look into example/api2-samples/api2-decode-overlay-encode.cpp for details.
1 parent e80e54b commit 2603883

File tree

4 files changed

+420
-2
lines changed

4 files changed

+420
-2
lines changed
Lines changed: 389 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,389 @@
1+
#include <functional>
2+
#include <iostream>
3+
#include <map>
4+
#include <memory>
5+
#include <set>
6+
7+
#include "audioresampler.h"
8+
#include "av.h"
9+
#include "avutils.h"
10+
#include "codec.h"
11+
#include "ffmpeg.h"
12+
#include "packet.h"
13+
#include "videorescaler.h"
14+
15+
// API2
16+
#include "codec.h"
17+
#include "codeccontext.h"
18+
#include "filters/buffersink.h"
19+
#include "filters/buffersrc.h"
20+
#include "filters/filtergraph.h"
21+
#include "format.h"
22+
#include "formatcontext.h"
23+
24+
using namespace std;
25+
using namespace av;
26+
27+
int main(int argc, char **argv)
28+
{
29+
if (argc < 3)
30+
return 1;
31+
32+
av::init();
33+
av::setFFmpegLoggingLevel(AV_LOG_DEBUG);
34+
35+
string uri{argv[1]};
36+
string image{argv[2]};
37+
string out{argv[3]};
38+
39+
error_code ec;
40+
41+
//
42+
// INPUT
43+
//
44+
FormatContext ictx;
45+
ssize_t videoStream = -1;
46+
VideoDecoderContext vdec;
47+
Stream vst;
48+
49+
int count = 0;
50+
51+
ictx.openInput(uri, ec);
52+
if (ec)
53+
{
54+
cerr << "Can't open input\n";
55+
return 1;
56+
}
57+
58+
ictx.findStreamInfo();
59+
60+
for (size_t i = 0; i < ictx.streamsCount(); ++i)
61+
{
62+
auto st = ictx.stream(i);
63+
if (st.mediaType() == AVMEDIA_TYPE_VIDEO)
64+
{
65+
videoStream = i;
66+
vst = st;
67+
break;
68+
}
69+
}
70+
71+
if (vst.isNull())
72+
{
73+
cerr << "Video stream not found\n";
74+
return 1;
75+
}
76+
77+
if (vst.isValid())
78+
{
79+
vdec = VideoDecoderContext(vst);
80+
vdec.setRefCountedFrames(true);
81+
82+
vdec.open(Codec(), ec);
83+
if (ec)
84+
{
85+
cerr << "Can't open codec\n";
86+
return 1;
87+
}
88+
}
89+
90+
//
91+
// STATIC IMAGE (more or less identical to INPUT)
92+
//
93+
FormatContext static_image_ctx;
94+
ssize_t imageStream = -1;
95+
VideoDecoderContext static_image_dec;
96+
Stream image_st;
97+
98+
static_image_ctx.openInput(image, ec);
99+
if (ec)
100+
{
101+
cerr << "Can't open static image\n";
102+
return 1;
103+
}
104+
105+
static_image_ctx.findStreamInfo();
106+
107+
for (size_t i = 0; i < static_image_ctx.streamsCount(); ++i)
108+
{
109+
auto st = static_image_ctx.stream(i);
110+
// An image is still a video stream in the ffmpeg world
111+
if (st.mediaType() == AVMEDIA_TYPE_VIDEO)
112+
{
113+
imageStream = i;
114+
image_st = st;
115+
break;
116+
}
117+
}
118+
119+
if (image_st.isNull())
120+
{
121+
cerr << "Image stream not found\n";
122+
return 1;
123+
}
124+
125+
if (image_st.isValid())
126+
{
127+
static_image_dec = VideoDecoderContext(image_st);
128+
static_image_dec.setRefCountedFrames(true);
129+
130+
static_image_dec.open(Codec(), ec);
131+
if (ec)
132+
{
133+
cerr << "Can't open codec\n";
134+
return 1;
135+
}
136+
}
137+
138+
//
139+
// OUTPUT
140+
//
141+
OutputFormat ofrmt;
142+
FormatContext octx;
143+
144+
ofrmt.setFormat(string(), out);
145+
octx.setFormat(ofrmt);
146+
147+
Codec ocodec = findEncodingCodec(ofrmt);
148+
VideoEncoderContext encoder{ocodec};
149+
150+
// Settings
151+
encoder.setWidth(vdec.width());
152+
encoder.setHeight(vdec.height());
153+
if (vdec.pixelFormat() > -1)
154+
encoder.setPixelFormat(vdec.pixelFormat());
155+
encoder.setTimeBase(Rational{1, 1000});
156+
encoder.setBitRate(vdec.bitRate());
157+
158+
encoder.open(Codec(), ec);
159+
if (ec)
160+
{
161+
cerr << "Can't opent encodec\n";
162+
return 1;
163+
}
164+
165+
Stream ost = octx.addStream(encoder);
166+
ost.setFrameRate(vst.frameRate());
167+
168+
octx.openOutput(out, ec);
169+
if (ec)
170+
{
171+
cerr << "Can't open output\n";
172+
return 1;
173+
}
174+
175+
octx.dump();
176+
octx.writeHeader();
177+
octx.flush();
178+
179+
// FILTER (OVERLAY)
180+
FilterGraph filter_graph;
181+
182+
// Construct the filter descriptor
183+
stringstream filter_desc;
184+
185+
// * for the video input
186+
filter_desc << "buffer@in=video_size=" << vdec.width() << "x" << vdec.height() << ":pix_fmt=" << vdec.pixelFormat()
187+
<< ":time_base=" << encoder.timeBase() << ":pixel_aspect=" << vdec.sampleAspectRatio() << " [in]; ";
188+
// * for the fixed image input
189+
filter_desc << "buffer@image=video_size=" << static_image_dec.width() << "x" << static_image_dec.height()
190+
<< ":pix_fmt=" << static_image_dec.pixelFormat() << ":time_base=" << encoder.timeBase()
191+
<< ":pixel_aspect=" << static_image_dec.sampleAspectRatio() << " [image_unscaled]; ";
192+
filter_desc << "[image_unscaled] scale=" << vdec.width() / 4 << "x" << vdec.height() / 4 << " [image]; ";
193+
// * for the output
194+
filter_desc << "[in][image] overlay=x=50:y=50:repeatlast=1 [out]; ";
195+
filter_desc << "[out] buffersink@out";
196+
197+
// Setup the filter chain
198+
clog << "Filter descriptor: " << filter_desc.str() << endl;
199+
filter_graph.parse(filter_desc.str(), ec);
200+
if (ec)
201+
{
202+
clog << "Error parsing filter chain: " << ec << ", " << ec.message() << endl;
203+
return 1;
204+
}
205+
filter_graph.config(ec);
206+
if (ec)
207+
{
208+
clog << "Error configuring filter chain: " << ec << ", " << ec.message() << endl;
209+
return 1;
210+
}
211+
212+
// Setup the entry/exit points
213+
BufferSrcFilterContext buffer_video_src{filter_graph.filter("buffer@in")};
214+
BufferSrcFilterContext buffer_image_src{filter_graph.filter("buffer@image")};
215+
BufferSinkFilterContext buffer_sink{filter_graph.filter("buffersink@out")};
216+
217+
// Load the static image into buffer_image_src, it won't change
218+
{
219+
auto image_pkt = static_image_ctx.readPacket(ec);
220+
if (ec)
221+
{
222+
clog << "Image reading error: " << ec << ", " << ec.message() << endl;
223+
return 1;
224+
}
225+
auto image_frame = static_image_dec.decode(image_pkt, ec);
226+
if (ec)
227+
{
228+
clog << "Image decoding error: " << ec << ", " << ec.message() << endl;
229+
return 1;
230+
}
231+
image_frame.setTimeBase(encoder.timeBase());
232+
image_frame.setStreamIndex(0);
233+
image_frame.setPictureType();
234+
clog << "Frame for overlaying: pts=" << image_frame.pts() << " / " << image_frame.pts().seconds() << " / "
235+
<< image_frame.timeBase() << ", " << image_frame.width() << "x" << image_frame.height()
236+
<< ", size=" << image_frame.size() << ", ref=" << image_frame.isReferenced() << ":"
237+
<< image_frame.refCount() << " / type: " << image_frame.pictureType() << endl;
238+
buffer_image_src.addVideoFrame(image_frame, ec);
239+
if (ec)
240+
{
241+
clog << "Image processing error: " << ec << ", " << ec.message() << endl;
242+
return 1;
243+
}
244+
// Very important! Signal that there are no more frames after this one!
245+
// ffmpeg expects a full video, this will allow the repeatlast=1 in
246+
// overlay to kick in
247+
buffer_image_src.writeVideoFrame(VideoFrame::null());
248+
}
249+
250+
//
251+
// PROCESS
252+
//
253+
while (true)
254+
{
255+
256+
// READING
257+
Packet pkt = ictx.readPacket(ec);
258+
if (ec)
259+
{
260+
clog << "Packet reading error: " << ec << ", " << ec.message() << endl;
261+
break;
262+
}
263+
264+
bool flushDecoder = false;
265+
// !EOF
266+
if (pkt)
267+
{
268+
if (pkt.streamIndex() != videoStream)
269+
{
270+
continue;
271+
}
272+
273+
clog << "Read packet: pts=" << pkt.pts() << ", dts=" << pkt.dts() << " / " << pkt.pts().seconds() << " / "
274+
<< pkt.timeBase() << " / st: " << pkt.streamIndex() << endl;
275+
}
276+
else
277+
{
278+
flushDecoder = true;
279+
}
280+
281+
do
282+
{
283+
// DECODING
284+
auto frame = vdec.decode(pkt, ec);
285+
286+
count++;
287+
// if (count > 200)
288+
// break;
289+
290+
bool flushEncoder = false;
291+
292+
if (ec)
293+
{
294+
cerr << "Decoding error: " << ec << endl;
295+
return 1;
296+
}
297+
else if (!frame)
298+
{
299+
if (flushDecoder)
300+
{
301+
flushEncoder = true;
302+
}
303+
}
304+
305+
if (frame)
306+
{
307+
clog << "Frame from decoder: pts=" << frame.pts() << " / " << frame.pts().seconds() << " / "
308+
<< frame.timeBase() << ", " << frame.width() << "x" << frame.height() << ", size=" << frame.size()
309+
<< ", ref=" << frame.isReferenced() << ":" << frame.refCount()
310+
<< " / type: " << frame.pictureType() << endl;
311+
312+
frame.setTimeBase(encoder.timeBase());
313+
frame.setStreamIndex(0);
314+
frame.setPictureType();
315+
316+
// Push into the filter
317+
buffer_video_src.addVideoFrame(frame, ec);
318+
if (ec)
319+
{
320+
clog << "Filter push error: " << ec << ", " << ec.message() << endl;
321+
return 1;
322+
}
323+
}
324+
325+
// Pull from the filter until there are no more incoming frames
326+
VideoFrame filtered;
327+
while (buffer_sink.getVideoFrame(filtered, ec))
328+
{
329+
// Change timebase
330+
filtered.setTimeBase(encoder.timeBase());
331+
filtered.setStreamIndex(0);
332+
filtered.setPictureType();
333+
clog << "Frame from filter: pts=" << filtered.pts() << " / " << filtered.pts().seconds() << " / "
334+
<< filtered.timeBase() << ", " << filtered.width() << "x" << filtered.height()
335+
<< ", size=" << filtered.size() << ", ref=" << filtered.isReferenced() << ":"
336+
<< filtered.refCount() << " / type: " << filtered.pictureType() << endl;
337+
338+
if (filtered || flushEncoder)
339+
{
340+
do
341+
{
342+
// Encode
343+
Packet opkt = filtered ? encoder.encode(filtered, ec) : encoder.encode(ec);
344+
if (ec)
345+
{
346+
cerr << "Encoding error: " << ec << endl;
347+
return 1;
348+
}
349+
else if (!opkt)
350+
{
351+
// cerr << "Empty packet\n";
352+
// continue;
353+
break;
354+
}
355+
356+
// Only one output stream
357+
opkt.setStreamIndex(0);
358+
359+
clog << "Write packet: pts=" << opkt.pts() << ", dts=" << opkt.dts() << " / "
360+
<< opkt.pts().seconds() << " / " << opkt.timeBase() << " / st: " << opkt.streamIndex()
361+
<< endl;
362+
363+
octx.writePacket(opkt, ec);
364+
if (ec)
365+
{
366+
cerr << "Error write packet: " << ec << ", " << ec.message() << endl;
367+
return 1;
368+
}
369+
} while (flushEncoder);
370+
}
371+
}
372+
if (ec && ec.value() != -EAGAIN)
373+
{
374+
cerr << "Error reading frame from the filter chain: " << ec << ", " << ec.message() << endl;
375+
return 1;
376+
}
377+
378+
if (flushEncoder)
379+
break;
380+
381+
} while (flushDecoder);
382+
383+
if (flushDecoder)
384+
break;
385+
}
386+
387+
clog << "done" << endl;
388+
octx.writeTrailer();
389+
}

0 commit comments

Comments
 (0)