1 /*
2   Copyright (C) 2019-2020 Andreas Weber <octave@josoansi.de>
3 
4   This file is part of octave-video; you can redistribute it and/or
5   modify it under the terms of the GNU General Public License
6   as published by the Free Software Foundation; either version 2
7   of the License, or (at your option) any later version.
8 
9   This program is distributed in the hope that it will be useful,
10   but WITHOUT ANY WARRANTY; without even the implied warranty of
11   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   GNU General Public License for more details.
13 
14   You should have received a copy of the GNU General Public License
15   along with this program; if not, see <https://www.gnu.org/licenses/>.
16 */
17 
18 #include "cap_ffmpeg_impl_ov.hpp"
19 
20 // PKG_ADD: autoload ("__ffmpeg_defines__", "cap_ffmpeg_wrapper.oct");
21 // PKG_DEL: autoload ("__ffmpeg_defines__", "cap_ffmpeg_wrapper.oct", "remove");
22 DEFUN_DLD(__ffmpeg_defines__, args, nargout,
23           "-*- texinfo -*-\n\
24 @deftypefn {Loadable Function} {@var{def} =} __ffmpeg_defines__ ()\n\
25 undocumented internal function\n\
26 @end deftypefn")
27 {
28   octave_value_list retval;
29   octave_scalar_map opt;
30 
31   opt.contents ("LIBAVUTIL_BUILD") = LIBAVUTIL_BUILD;
32   opt.contents ("LIBAVUTIL_IDENT") = LIBAVUTIL_IDENT;
33 
34   opt.contents ("LIBSWSCALE_BUILD") = LIBSWSCALE_BUILD;
35   opt.contents ("LIBSWSCALE_IDENT") = LIBSWSCALE_IDENT;
36 
37   opt.contents ("LIBAVCODEC_BUILD") = LIBAVCODEC_BUILD;
38   opt.contents ("LIBAVCODEC_IDENT") = LIBAVCODEC_IDENT;
39 
40   opt.contents ("LIBAVFORMAT_BUILD") = LIBAVFORMAT_BUILD;
41   opt.contents ("LIBAVFORMAT_IDENT") = LIBAVFORMAT_IDENT;
42 
43 
44   //join ident
45   opt.contents ("LIBAV_IDENT") = LIBAVUTIL_IDENT ", " LIBSWSCALE_IDENT ", " LIBAVCODEC_IDENT ", " LIBAVFORMAT_IDENT;
46 
47 
48   retval.append (opt);
49 
50   return retval;
51 }
52 
53 // PKG_ADD: autoload ("__ffmpeg_output_formats__", "cap_ffmpeg_wrapper.oct");
54 // PKG_DEL: autoload ("__ffmpeg_output_formats__", "cap_ffmpeg_wrapper.oct", "remove");
55 DEFUN_DLD(__ffmpeg_output_formats__, args, nargout,
56           "-*- texinfo -*-\n\
57 @deftypefn {Loadable Function} {@var{f} =} __ffmpeg_output_formats__ ()\n\
58 undocumented internal function\n\
59 @end deftypefn")
60 {
61   av_register_all();
62 
63   octave_idx_type n = 0;
64 
65   // first loop to get numer of output formats
66   AVOutputFormat * oformat = av_oformat_next(NULL);
67   while (oformat != NULL)
68     {
69       n++;
70       oformat = av_oformat_next (oformat);
71     }
72 
73   Cell names (n, 1);
74   Cell long_names (n, 1);
75   Cell mime_types (n, 1);
76   Cell extensions (n, 1);
77   Cell codecs (n, 1);
78 
79   // second loop, now fill the cells
80   oformat = av_oformat_next(NULL);
81   int i = 0;
82   while(oformat != NULL)
83     {
84       names (i) = oformat->name;
85       long_names (i) = oformat->long_name;
86       mime_types (i) = oformat->mime_type;
87       extensions (i) = oformat->extensions;
88 
89       octave_map map_codecs;
90 
91       if (oformat->codec_tag)
92         {
93           // printf ("%s %s %s\n", oformat->name, oformat->long_name, oformat->mime_type);
94 
95           std::vector<std::string> video_codecs;
96           const AVCodecTag * ptags = oformat->codec_tag[0];
97           while (ptags->id != AV_CODEC_ID_NONE)
98             {
99               AVCodecID id = (AVCodecID) ptags->id;
100               // get descriptor
101               const AVCodecDescriptor* d = avcodec_descriptor_get (id);
102               if (d)
103                 {
104                   // only add encoder video codecs
105                   if (d->type == AVMEDIA_TYPE_VIDEO)
106                     {
107                       // prüfen, ob es einen encoder gibt
108                       if (avcodec_find_encoder (d->id))
109                         {
110                           unsigned int tag = ptags->tag;
111 
112                           if (! strcmp (oformat->name, "mp4")) // use riff
113                             {
114                               const struct AVCodecTag *table[] = { avformat_get_riff_video_tags(), 0 };
115                               tag = av_codec_get_tag(table, id);
116                             }
117 
118                           char buf[5];
119                           snprintf (buf, 5, "%c%c%c%c", CV_TAG_TO_PRINTABLE_CHAR4(tag));
120 
121                           //printf("fourcc tag 0x%08x '%s' codec_id %04X\n", tag, buf, id);
122 
123                           video_codecs.push_back (buf);
124                         }
125                     }
126 
127                 }
128 
129               ptags++;
130 
131             }
132 
133           // unique but keep order
134           {
135             auto last = std::unique(video_codecs.begin(), video_codecs.end());
136             video_codecs.erase (last, video_codecs.end());
137             Cell codec_fourcc (video_codecs.size (), 1);
138             for (unsigned int k = 0; k < video_codecs.size (); ++k)
139               codec_fourcc(k) = video_codecs[k];
140             codecs (i) = codec_fourcc;
141           }
142         }
143 
144       oformat = av_oformat_next(oformat);
145       i++;
146     }
147 
148   octave_map m;
149 
150   m.assign ("name", names);
151   m.assign ("long_name", long_names);
152   m.assign ("mime_type", mime_types);
153   m.assign ("extensions", extensions);
154   m.assign ("codecs", codecs);
155 
156   return octave_value (m);
157 }
158 
159 /******************    CvCapture_FFMPEG     **************************/
160 
get_cap_from_ov(octave_value ov)161 CvCapture_FFMPEG* get_cap_from_ov (octave_value ov)
162 {
163   if (!capture_type_loaded)
164     {
165       CvCapture_FFMPEG::register_type();
166       capture_type_loaded = true;
167     }
168 
169   if (ov.type_id() != CvCapture_FFMPEG::static_type_id())
170     {
171       error("get_handler_from_ov: Not a valid CvCapture_FFMPEG");
172       return 0;
173     }
174 
175   CvCapture_FFMPEG* p = 0;
176   const octave_base_value& rep = ov.get_rep();
177   p = &((CvCapture_FFMPEG &)rep);
178   return p;
179 }
180 
181 // PKG_ADD: autoload ("__cap_open__", "cap_ffmpeg_wrapper.oct");
182 // PKG_DEL: autoload ("__cap_open__", "cap_ffmpeg_wrapper.oct", "remove");
183 DEFUN_DLD(__cap_open__, args, nargout,
184           "-*- texinfo -*-\n\
185 @deftypefn {Loadable Function} {@var{h} =} __cap_open__ (@var{filename})\n\
186 Creates an instance of CvCapture_FFMPEG.\n\
187 @end deftypefn")
188 {
189   octave_value_list retval;
190   int nargin = args.length ();
191 
192   if (nargin != 1 || !args(0).is_string ())
193     {
194       print_usage();
195       return retval;
196     }
197 
198   std::string filename = args(0).string_value ();
199   CvCapture_FFMPEG *h = new CvCapture_FFMPEG ();
200 
201   // returns "valid" (true if open was successful)
202   bool valid = h->open (filename.c_str ());
203   if (valid)
204     retval.append (octave_value (h));
205   else
206     error ("Opening '%s' failed : '%s'", filename.c_str (), get_last_err_msg().c_str ());
207   return retval;
208 }
209 
210 // PKG_ADD: autoload ("__cap_get_properties__", "cap_ffmpeg_wrapper.oct");
211 // PKG_DEL: autoload ("__cap_get_properties__", "cap_ffmpeg_wrapper.oct", "remove");
212 DEFUN_DLD(__cap_get_properties__, args, nargout,
213           "-*- texinfo -*-\n\
214 @deftypefn {Loadable Function} {[@var{h}, @var{opt}] =} __cap_get_properties__ (@var{h})\n\
215 Gets CvCapture_FFMPEG properties like bitrate, fps, total_frames, duration_sec...\n\
216 @end deftypefn")
217 {
218   octave_value_list retval;
219   int nargin = args.length ();
220 
221   if (nargin != 1)
222     error("__cap_get_properties__ needs one parameter");
223 
224   CvCapture_FFMPEG* h = get_cap_from_ov (args(0));
225   if (h)
226     {
227       octave_scalar_map opt;
228       opt.contents ("total_frames") = h->get_total_frames ();
229       opt.contents ("duration_sec") = h->get_duration_sec ();
230       opt.contents ("fps")          = h->get_fps ();
231       opt.contents ("bitrate")      = h->get_bitrate ();
232       opt.contents ("width")        = h->frame.width;
233       opt.contents ("height")       = h->frame.height;
234 
235       // Current position of the video file in milliseconds
236       //opt.contents ("pos")          = (h->picture_pts == AV_NOPTS_VALUE_) ? 0 : h->dts_to_sec(h->picture_pts) * 1000;
237 
238       // Relative position of the video file: 0=start of the film, 1=end of the film.
239       //opt.contents ("rel_pos") = h->r2d(h->ic->streams[h->video_stream]->time_base);
240 
241       //  0-based index of the frame to be decoded/captured next.
242       opt.contents ("frame_number") = h->frame_number;
243 
244       opt.contents ("video_codec_name") = h->get_video_codec_name ();
245 
246       // aspect ratio
247       // 0, 1 is "undefined"
248       {
249         AVRational s = h->get_sample_aspect_ratio ();
250         opt.contents ("aspect_ration_num") = s.num;
251         opt.contents ("aspect_ration_den") = s.den;
252       }
253 
254       retval.append (opt);
255     }
256   return retval;
257 }
258 
259 // PKG_ADD: autoload ("__cap_grab_frame__", "cap_ffmpeg_wrapper.oct");
260 // PKG_DEL: autoload ("__cap_grab_frame__", "cap_ffmpeg_wrapper.oct", "remove");
261 DEFUN_DLD(__cap_grab_frame__, args, nargout,
262           "-*- texinfo -*-\n\
263 @deftypefn {Loadable Function} {@var{f} =} __cap_grab_frame__ (@var{h})\n\
264 \n\
265 @end deftypefn")
266 {
267   octave_value_list retval;
268   int nargin = args.length ();
269 
270   if (nargin != 1)
271     error("__cap_grab_frame__ needs one parameter");
272 
273   CvCapture_FFMPEG* p = get_cap_from_ov (args(0));
274   if (p)
275     return (octave_value (p->grabFrame ()));
276 
277   return retval;
278 }
279 
280 // PKG_ADD: autoload ("__cap_retrieve_frame__", "cap_ffmpeg_wrapper.oct");
281 // PKG_DEL: autoload ("__cap_retrieve_frame__", "cap_ffmpeg_wrapper.oct", "remove");
282 DEFUN_DLD(__cap_retrieve_frame__, args, nargout,
283           "-*- texinfo -*-\n\
284 @deftypefn {Loadable Function} {@var{f} =} __cap_retrieve_frame__ (@var{h})\n\
285 \n\
286 @end deftypefn")
287 {
288   octave_value_list retval;
289   int nargin = args.length ();
290 
291   if (nargin != 1)
292     error("__cap_retrieve_frame__ needs one parameter");
293 
294   CvCapture_FFMPEG* p = get_cap_from_ov (args(0));
295   if (p)
296     {
297       unsigned char* data;
298       int width = 0;
299       int height = 0;
300       int step;         // AVFrame::linesize, size in bytes of each picture line
301       int cn;           // number of colors and should always be 3 here
302 
303       bool ret = p->retrieveFrame (0, &data, &step, &width, &height, &cn);
304 
305       //printf ("ret = %i, width = %i, height = %i, step = %i, cn = %i\n", ret, width, height, step, cn);
306 
307       assert (cn == 3);
308 
309       // step may be bigger because of padding
310       assert (step >= width * cn);
311 
312       if (ret)
313         {
314 #if 0
315           // Attention: step and cn not handled yet
316           dim_vector dv (3, step/cn, height);
317           uint8NDArray img (dv);
318 
319           unsigned char *p = reinterpret_cast<unsigned char*>(img.fortran_vec());
320           memcpy(p, data, img.numel ());
321 
322           Array<octave_idx_type> perm (dim_vector (3, 1));
323           perm(0) = 2;
324           perm(1) = 1;
325           perm(2) = 0;
326 
327           // FIXME: howto handle padding? Extract submatrix with "extract"?
328           retval(0) = octave_value(img.permute (perm));
329 #else
330 
331           dim_vector dv (height, width, cn);
332           uint8NDArray img (dv);
333 
334           for (int x = 0; x < width; ++x)
335             for (int y = 0; y < height; ++y)
336               for (int c = 0; c < cn; ++c)
337                 img (y, x, c) = data[x * cn + y * step + c];
338 
339 
340           retval(0) = octave_value(img);
341 
342 #endif
343         }
344 
345     }
346   return retval;
347 }
348 
349 // PKG_ADD: autoload ("__cap_close__", "cap_ffmpeg_wrapper.oct");
350 // PKG_DEL: autoload ("__cap_close__", "cap_ffmpeg_wrapper.oct", "remove");
351 DEFUN_DLD(__cap_close__, args, nargout,
352           "-*- texinfo -*-\n\
353 @deftypefn {Loadable Function} {@var{h} =} __cap_close__ (@var{h})\n\
354 undocumented internal function\n\
355 @end deftypefn")
356 {
357   octave_value_list retval;
358   int nargin = args.length ();
359 
360   if (nargin != 1)
361     {
362       print_usage();
363       return retval;
364     }
365 
366   CvCapture_FFMPEG* p = get_cap_from_ov (args(0));
367   if (p)
368     p->close ();
369 
370   return retval;
371 }
372 
373 /*************    CvVideoWriter_FFMPEG     ****************/
374 
get_writer_from_ov(octave_value ov)375 CvVideoWriter_FFMPEG* get_writer_from_ov (octave_value ov)
376 {
377   if (!writer_type_loaded)
378     {
379       CvVideoWriter_FFMPEG::register_type();
380       writer_type_loaded = true;
381     }
382 
383   if (ov.type_id() != CvVideoWriter_FFMPEG::static_type_id())
384     {
385       error("get_handler_from_ov: Not a valid CvVideoWriter_FFMPEG");
386       return 0;
387     }
388 
389   CvVideoWriter_FFMPEG* p = 0;
390   const octave_base_value& rep = ov.get_rep();
391   p = &((CvVideoWriter_FFMPEG &)rep);
392   return (CvVideoWriter_FFMPEG *) p;
393 }
394 
395 // PKG_ADD: autoload ("__writer_open__", "cap_ffmpeg_wrapper.oct");
396 // PKG_DEL: autoload ("__writer_open__", "cap_ffmpeg_wrapper.oct", "remove");
397 DEFUN_DLD(__writer_open__, args, nargout,
398           "-*- texinfo -*-\n\
399 @deftypefn {Loadable Function} {@var{h} =} __writer_open__ (@var{filename}, @var{fourcc}, @var{fps}, @var{width}, @var{height}, @var{isColor})\n\
400 undocumented internal function\n\
401 @end deftypefn")
402 {
403   octave_value_list retval;
404   int nargin = args.length ();
405 
406   if (nargin != 6)
407     {
408       print_usage();
409       return retval;
410     }
411 
412   if (! writer_type_loaded)
413     {
414       CvVideoWriter_FFMPEG::register_type();
415       writer_type_loaded = true;
416       av_register_all();
417     }
418 
419   std::string filename = args(0).string_value ();
420 
421   // codec tag, in OpenCV "fourcc" is used interchangeably
422   // empty fourcc selects default codec_id for guessed container
423   unsigned int tag;
424   std::string fourcc = args(1).string_value ();
425 
426   // FIXME no error handling yet
427   double fps   = args(2).double_value ();
428   int width    = args(3).int_value ();
429   int height   = args(4).int_value ();
430   bool isColor = args(5).bool_value ();
431 
432   if (fourcc.size () == 0)
433     {
434       // get tag for default codec for guessed container from filename
435       AVOutputFormat* foo = av_guess_format	(NULL, filename.c_str (), NULL);
436 
437       // list supported codecs for guessed format
438 #if 0
439       if (foo->codec_tag)
440         {
441           const AVCodecTag * ptags = foo->codec_tag[0];
442           while (ptags->id != AV_CODEC_ID_NONE)
443             {
444               unsigned int tag = ptags->tag;
445               printf("fourcc tag 0x%08x/'%c%c%c%c' codec_id %04X\n", tag, CV_TAG_TO_PRINTABLE_CHAR4(tag), ptags->id);
446               ptags++;
447             }
448         }
449 #endif
450 
451       tag = av_codec_get_tag (foo->codec_tag, foo->video_codec);
452     }
453   else if (fourcc.size () == 4)
454     {
455       tag = MKTAG(fourcc[0], fourcc[1], fourcc[2], fourcc[3]);
456     }
457   else
458     error ("fourcc has to be empty or 4 chars long");
459 
460   // list codecs
461   //~ AVCodec * codec = av_codec_next(NULL);
462   //~ while(codec != NULL)
463   //~ {
464   //~ fprintf(stderr, "%s\n", codec->long_name);
465   //~ codec = av_codec_next(codec);
466   //~ }
467 
468   // list formats
469   //~ AVOutputFormat * oformat = av_oformat_next(NULL);
470   //~ while(oformat != NULL)
471   //~ {
472   //~ printf ("%s; %s; %s; %s\n", oformat->name, oformat->long_name, oformat->mime_type, oformat->extensions);
473 
474   //~ //cv_ff_codec_tag_dump (oformat->codec_tag);
475 
476   //~ oformat = av_oformat_next(oformat);
477   //~ }
478 
479   //~ AVOutputFormat * oformat = av_oformat_next(NULL);
480   //~ while(oformat != NULL)
481   //~ {
482   //~ fprintf(stderr, "%s\n", oformat->long_name);
483   //~ if (oformat->codec_tag != NULL)
484   //~ {
485   //~ int i = 0;
486 
487   //~ CV_CODEC_ID cid = CV_CODEC(CODEC_ID_MPEG1VIDEO);
488   //~ while (cid != CV_CODEC(CODEC_ID_NONE))
489   //~ {
490   //~ cid = av_codec_get_id(oformat->codec_tag, i++);
491   //~ fprintf(stderr, "    %d\n", cid);
492   //~ }
493   //~ }
494   //~ oformat = av_oformat_next(oformat);
495   //~ }
496 
497   //printf ("tag = %i = %#x = %c%c%c%c\n", tag, tag, CV_TAG_TO_PRINTABLE_CHAR4(tag));
498 
499 #if 0
500   // that would be a workaround:
501   AVOutputFormat* foo = av_guess_format	(NULL, "foo.mp4", NULL);
502   printf ("default video_codec = %i = %#x\n", foo->video_codec, foo->video_codec);
503 
504   unsigned int tag = av_codec_get_tag (foo->codec_tag, AV_CODEC_ID_H264);
505   printf ("tag = %i = %#x\n", tag, tag);
506 
507   // vom tag über riff zum codec_id:
508   tag = MKTAG('H', '2', '6', '4');
509   const struct AVCodecTag *table[] = { avformat_get_riff_video_tags(), 0 };
510   enum AVCodecID id = av_codec_get_id (table, tag);
511   printf ("id = %i = %#x, AV_CODEC_ID_H264 = %#x\n", id, id, AV_CODEC_ID_H264);
512 
513   // und zum tag zurück, Achtung, das ergibt nicht mehr 0x21
514   tag = av_codec_get_tag (table, AV_CODEC_ID_H264);
515   printf ("tag = %i = %#x = %c%c%c%c\n", tag, tag, CV_TAG_TO_PRINTABLE_CHAR4(tag));
516 
517 #endif
518 
519   // welche API wäre denn von Octave aus gewünscht?
520   // Ich denke direkt AVCodecID angeben wäre sinnvoller, als die fourcc
521 
522   /*
523    * codecs anzeigen:
524    * andy@Ryzen5Babe:~/Downloads/libav-12.3$ grep -r show_codecs
525    * cmdutils_common_opts.h:    { "codecs"     , OPT_EXIT, {.func_arg = show_codecs   },    "show available codecs" },
526    * cmdutils.h:int show_codecs(void *optctx, const char *opt, const char *arg);
527    * cmdutils.c:int show_codecs(void *optctx, const char *opt, const char *arg)
528    */
529 
530   CvVideoWriter_FFMPEG *h = new CvVideoWriter_FFMPEG ();
531 
532   // https://docs.opencv.org/3.4.1/dd/d9e/classcv_1_1VideoWriter.html#ac3478f6257454209fa99249cc03a5c59
533   // fourcc	4-character code of codec used to compress the frames. For example,
534   // VideoWriter::fourcc('P','I','M','1') is a MPEG-1 codec,
535   // VideoWriter::fourcc('M','J','P','G') is a motion-jpeg codec etc.
536   // List of codes can be obtained at Video Codecs by FOURCC page.
537   // FFMPEG backend with MP4 container natively uses other values as fourcc code: see http://mp4ra.org/#/codecs,
538   // so you may receive a warning message from OpenCV about fourcc code conversion.
539 
540   // fps	Framerate of the created video stream.
541   // isColor	If it is not zero, the encoder will expect and encode color frames,
542   // otherwise it will work with grayscale frames (the flag is currently supported on Windows only).
543 
544   //printf ("h->open (%s, %i, %f, %u, %u, %u);\n", filename.c_str (), tag, fps, width, height, isColor);
545 
546   bool valid = h->open (filename.c_str (), tag, fps, width, height, isColor);
547   if (valid)
548     {
549       retval.append (octave_value (h));
550     }
551   else
552     {
553       // FIXME: CvVideoWriter_FFMPEG::open just returns false without explanation why
554       error ("Opening '%s' for writing failed", filename.c_str ());
555     }
556   return retval;
557 }
558 
559 // PKG_ADD: autoload ("__writer_get_properties__", "cap_ffmpeg_wrapper.oct");
560 // PKG_DEL: autoload ("__writer_get_properties__", "cap_ffmpeg_wrapper.oct", "remove");
561 DEFUN_DLD(__writer_get_properties__, args, nargout,
562           "-*- texinfo -*-\n\
563 @deftypefn {Loadable Function} {[@var{h}, @var{opt}] =} __cap_get_properties__ (@var{h})\n\
564 Gets CvVideoWriter_FFMPEG properties...\n\
565 @end deftypefn")
566 {
567   octave_value_list retval;
568   int nargin = args.length ();
569 
570   if (nargin != 1)
571     error("__writer_get_properties__ needs one parameter");
572 
573   CvVideoWriter_FFMPEG* h = get_writer_from_ov (args(0));
574   if (h)
575     {
576       octave_scalar_map opt;
577       opt.contents ("ok")              = h->ok;
578       opt.contents ("frame_width")     = h->frame_width;
579       opt.contents ("output_format_long_name") = h->fmt->long_name;
580       opt.contents ("output_video_stream_codec") = h->get_video_codec_name ();
581       opt.contents ("frame_height")    = h->frame_height;
582       opt.contents ("frame_idx")       = h->frame_idx;
583       retval.append (opt);
584     }
585   return retval;
586 }
587 
588 // PKG_ADD: autoload ("__writer_write_frame__", "cap_ffmpeg_wrapper.oct");
589 // PKG_DEL: autoload ("__writer_write_frame__", "cap_ffmpeg_wrapper.oct", "remove");
590 DEFUN_DLD(__writer_write_frame__, args, nargout,
591           "-*- texinfo -*-\n\
592 @deftypefn {Loadable Function} {@var{h} =} __writer_write_frame__ (@var{h}, @var{frame})\n\
593 undocumented internal function\n\
594 @end deftypefn")
595 {
596   octave_value_list retval;
597   int nargin = args.length ();
598 
599   if (nargin != 2)
600     {
601       print_usage();
602       return retval;
603     }
604 
605   //NDArray f = args(1).array_value();
606   uint8NDArray f = args(1).uint8_array_value();
607 
608   CvVideoWriter_FFMPEG* p = get_writer_from_ov (args(0));
609   if (p)
610     {
611       int width = f.columns ();
612       int height = f.rows ();
613       int cn = f.dim3 ();
614       int step = width * cn;
615       int origin = 0;
616 
617       //printf ("width=%i, height=%i, step=%i\n", width, height, step);
618 
619       // permute, see also __cap_retrieve_frame__
620       // for opposite
621 
622       Array<octave_idx_type> perm (dim_vector (3, 1));
623       perm(0) = 2;
624       perm(1) = 1;
625       perm(2) = 0;
626 
627       f = f.permute (perm);
628 
629       unsigned char *t = reinterpret_cast<unsigned char*>(f.fortran_vec());
630 
631       bool ret = p->writeFrame (t, step, width, height, cn, origin);
632       if (! ret)
633         error ("CvVideoWriter_FFMPEG::writeFrame failed");
634 
635     }
636   return retval;
637 }
638 
639 // PKG_ADD: autoload ("__writer_close__", "cap_ffmpeg_wrapper.oct");
640 // PKG_DEL: autoload ("__writer_close__", "cap_ffmpeg_wrapper.oct", "remove");
641 DEFUN_DLD(__writer_close__, args, nargout,
642           "-*- texinfo -*-\n\
643 @deftypefn {Loadable Function} {@var{h} =} __writer_close__ (@var{h})\n\
644 undocumented internal function\n\
645 @end deftypefn")
646 {
647   octave_value_list retval;
648   int nargin = args.length ();
649 
650   if (nargin != 1)
651     {
652       print_usage();
653       return retval;
654     }
655 
656   CvVideoWriter_FFMPEG* p = get_writer_from_ov (args(0));
657   if (p)
658     p->close ();
659 
660   return retval;
661 }
662