1 /// experimental feature
2 // transcode.c
3 // LiVES
4 // (c) G. Finch 2008 - 2017 <salsaman_lives@gmail.com>
5 // released under the GNU GPL 3 or later
6 // see file ../COPYING or www.gnu.org for licensing details
7 
8 // fast transcoding via a plugin
9 #define TEST_TRANSREND
10 
11 #ifdef LIBAV_TRANSCODE
12 
13 #include "main.h"
14 #include "resample.h"
15 #include "effects.h"
16 #include "transcode.h"
17 #include "paramwindow.h"
18 #include "effects-weed.h"
19 
20 
send_layer(weed_layer_t * layer,_vid_playback_plugin * vpp,int64_t timecode)21 boolean send_layer(weed_layer_t *layer, _vid_playback_plugin *vpp, int64_t timecode) {
22   // send a weed layer to a (prepared) video playback plugin
23   // warning: will quite likely change the pixel_data of layer
24 
25   // returns: TRUE on rendering error
26 
27   void **pd_array;
28   boolean error = FALSE;
29 
30   // vid plugin expects compacted rowstrides (i.e. no padding/alignment after pixel row)
31   compact_rowstrides(layer);
32 
33   // get a void ** to the planar pixel_data
34   pd_array = weed_layer_get_pixel_data(layer, NULL);
35 
36   if (pd_array) {
37     // send pixel data to the video frame renderer
38     error = !(*vpp->render_frame)(weed_layer_get_width(layer),
39                                   weed_layer_get_height(layer),
40                                   timecode, pd_array, NULL, NULL);
41 
42     lives_free(pd_array);
43   }
44   weed_layer_free(layer);
45   return error;
46 }
47 
48 static _vid_playback_plugin *ovpp;
49 
transcode_prep(void)50 boolean transcode_prep(void) {
51   _vid_playback_plugin *vpp;
52   mainw->suppress_dprint = TRUE;
53 
54   vpp = open_vid_playback_plugin(TRANSCODE_PLUGIN_NAME, FALSE);
55 
56   mainw->suppress_dprint = FALSE;
57   mainw->no_switch_dprint = TRUE;
58 
59   if (!vpp) {
60     d_print(_("Plugin %s not found.\n"), TRANSCODE_PLUGIN_NAME);
61     mainw->no_switch_dprint = FALSE;
62     return FALSE;
63   }
64 
65   // for now we can only have one instance of a vpp: TODO - make vpp plugins re-entrant
66   lives_memset(future_prefs->vpp_name, 0, 1);
67 
68   ovpp = mainw->vpp;
69   mainw->vpp = vpp;
70   return TRUE;
71 }
72 
73 
transcode_get_params(char ** fnameptr)74 boolean transcode_get_params(char **fnameptr) {
75   // create the param window for the plugin, configure it, and retrieve the filename
76   LiVESList *retvals = NULL;
77   _vppaw *vppa = on_vpp_advanced_clicked(NULL, LIVES_INT_TO_POINTER(LIVES_INTENTION_TRANSCODE));
78   lives_rfx_t *rfx = vppa->rfx;
79   LiVESResponseType resp;
80 
81   if (!*fnameptr) *fnameptr = lives_build_filename(mainw->vid_save_dir, DEF_TRANSCODE_FILENAME, NULL);
82 
83   if (!rfx) {
84     lives_widget_destroy(vppa->dialog);
85     return FALSE;
86   }
87 
88   /// we want to keep the rfx around even after the dialog is run, so we can get the filename
89   vppa->keep_rfx = TRUE;
90 
91   // set the default value in the param window
92   set_rfx_param_by_name_string(rfx, TRANSCODE_PARAM_FILENAME, *fnameptr, TRUE);
93   lives_freep((void **)fnameptr);
94 
95   retvals = do_onchange_init(rfx);
96   if (retvals) {
97     // now apply visually anything we got from onchange_init
98     param_demarshall(rfx, retvals, TRUE, TRUE);
99     lives_list_free_all(&retvals);
100   }
101 
102   // run the param window
103   do {
104     resp = lives_dialog_run(LIVES_DIALOG(vppa->dialog));
105   } while (resp == LIVES_RESPONSE_RETRY);
106 
107   /// need to clean this up here, since we asked for the rfx to be kept
108   rfx_clean_exe(rfx);
109 
110   if (resp == LIVES_RESPONSE_CANCEL) {
111     mainw->cancelled = CANCEL_USER;
112     rfx_free(rfx);
113     lives_free(rfx);
114     return FALSE;
115   }
116 
117   mainw->cancelled = CANCEL_NONE;
118 
119   // get the value of the "filename" param (this is only so we can display it in status messages)
120   get_rfx_param_by_name_string(rfx, TRANSCODE_PARAM_FILENAME, fnameptr);
121 
122   rfx_free(rfx);
123   lives_free(rfx);
124   return TRUE;
125 }
126 
127 
transcode_cleanup(_vid_playback_plugin * vpp)128 void transcode_cleanup(_vid_playback_plugin *vpp) {
129   // close vpp, unless mainw->vpp
130   if (!ovpp || (vpp->handle != ovpp->handle)) {
131     close_vid_playback_plugin(vpp);
132   }
133 
134   if (ovpp && (vpp->handle == ovpp->handle)) {
135     // we "borrowed" the playback plugin, so set these back how they were
136     if (ovpp->set_fps)(*ovpp->set_fps)(ovpp->fixed_fpsd);
137     if (ovpp->set_palette)(*ovpp->set_palette)(ovpp->palette);
138     if (ovpp->set_yuv_palette_clamping)(*ovpp->set_yuv_palette_clamping)(ovpp->YUV_clamping);
139   }
140 
141   mainw->vpp = ovpp;
142 }
143 
144 #define COUNT_CHKVAL 100
145 
transcode_clip(int start,int end,boolean internal,char * def_pname)146 boolean transcode_clip(int start, int end, boolean internal, char *def_pname) {
147   _vid_playback_plugin *vpp;
148   weed_plant_t *frame_layer = NULL;
149   lives_proc_thread_t coder = NULL;
150   void *abuff = NULL;
151   short *sbuff = NULL;
152   float **fltbuf = NULL;
153   ticks_t currticks;
154   ssize_t in_bytes;
155   const char *img_ext = NULL;
156   char *afname = NULL;
157   char *msg = NULL, *pname = NULL;
158   double spf = 0., ospf;
159 
160   boolean audio = FALSE;
161   boolean swap_endian = FALSE;
162   boolean error = FALSE;
163   boolean fx1_bool = mainw->fx1_bool;
164   boolean needs_dprint = FALSE;
165   boolean manage_ds = FALSE;
166 
167   int fd = -1;
168   int asigned = 0, aendian = 0;
169   int nsamps;
170   int interp = LIVES_INTERP_FAST; /// TODO - get quality setting
171   int width, height, pwidth, pheight;
172   int tgamma = WEED_GAMMA_SRGB;
173   int count = 0;
174 
175   int i = 0, j;
176 
177   if (def_pname) pname = lives_strdup(def_pname);
178 
179   if (!internal) {
180     if (!transcode_prep()) return FALSE;
181     vpp = mainw->vpp;
182     if (!transcode_get_params(&pname)) goto tr_err2;
183   } else {
184     vpp = mainw->vpp;
185     mainw->transrend_ready = TRUE;
186     lives_nanosleep_until_nonzero(!mainw->transrend_ready
187                                   || lives_proc_thread_cancelled(mainw->transrend_proc));
188     if (lives_proc_thread_cancelled(mainw->transrend_proc)) goto tr_err2;
189   }
190 
191   // (re)set these for the current clip
192   // TODO: need to make sure this is an allowed value for the plugin !!!
193   if (vpp->set_fps)(*vpp->set_fps)(cfile->fps);
194 
195   (*vpp->set_palette)(vpp->palette);
196   if (weed_palette_is_yuv(vpp->palette)) {
197     if (vpp->set_yuv_palette_clamping)(*vpp->set_yuv_palette_clamping)(vpp->YUV_clamping);
198   }
199   //
200   if (vpp->init_audio && mainw->save_with_sound && cfile->achans * cfile->arps > 0) {
201     int in_arate = cfile->arate;
202     if ((*vpp->init_audio)(in_arate, cfile->achans, mainw->vpp->extra_argc, mainw->vpp->extra_argv)) {
203       // we will buffer audio and send it in packets of one frame worth of audio
204       // buffers will be used to convert to float audio
205 
206       audio = TRUE;
207       ospf = spf = (double)(cfile->arate) / cfile->fps;
208 
209       afname = lives_build_filename(prefs->workdir, cfile->handle, CLIP_AUDIO_FILENAME, NULL);
210       fd = lives_open_buffered_rdonly(afname);
211 
212       if (fd < 0) {
213         do_read_failed_error_s(afname, lives_strerror(errno));
214         error = TRUE;
215         goto tr_err;
216       }
217 
218       lives_lseek_buffered_rdonly(fd, (int)((double)(cfile->start - 1.) * spf) * cfile->achans * (cfile->asampsize >> 3));
219 
220       asigned = !(cfile->signed_endian & AFORM_UNSIGNED);
221       aendian = cfile->signed_endian & AFORM_BIG_ENDIAN;
222 
223       if (cfile->asampsize > 8) {
224         if ((aendian && (capable->byte_order == LIVES_BIG_ENDIAN)) || (!aendian && (capable->byte_order == LIVES_LITTLE_ENDIAN)))
225           swap_endian = TRUE;
226         else swap_endian = FALSE;
227       }
228 
229       abuff = lives_malloc((int)(spf + 1.) * cfile->achans * (cfile->asampsize >> 3)); // one extra sample to allow for rounding
230       if (!abuff) {
231         error = TRUE;
232         goto tr_err;
233       }
234       fltbuf = lives_malloc(cfile->achans * sizeof(float *));
235       if (!fltbuf) {
236         error = TRUE;
237         goto tr_err;
238       }
239 
240       for (i = 0; i < cfile->achans; i++) {
241         fltbuf[i] = (float *)lives_malloc((int)(spf + 1.) * cfile->achans * sizeof(float));  // one extra sample to allow for rounding
242         if (!fltbuf[i]) {
243           error = TRUE;
244           goto tr_err;
245         }
246         if (cfile->asampsize == 8) {
247           sbuff = (short *)lives_malloc((int)(spf + 1.) * cfile->achans * 2); // one extra sample to allow for rounding
248 	  // *INDENT-OFF*
249         }}}}
250   // *INDENT-ON*
251 
252   if (!(*vpp->init_screen)(vpp->fwidth, vpp->fheight, FALSE, 0, vpp->extra_argc, vpp->extra_argv)) {
253     error = TRUE;
254     goto tr_err;
255   }
256 
257   ///////////////////////////////////////////////////////////////////////////////
258   /// plugin ready
259 
260   //av_log_set_level(AV_LOG_FATAL);
261   THREADVAR(rowstride_alignment_hint) = 16;
262 
263   if (!internal) {
264     // create a frame layer,
265     frame_layer = weed_layer_new(WEED_LAYER_TYPE_VIDEO);
266     weed_set_int_value(frame_layer, WEED_LEAF_CLIP, mainw->current_file);
267     weed_layer_set_palette_yuv(frame_layer, vpp->palette, vpp->YUV_clamping, vpp->YUV_sampling, vpp->YUV_subspace);
268 
269     // need img_ext for pulling the frame
270     img_ext = get_image_ext_for_type(cfile->img_type);
271 
272     mainw->cancel_type = CANCEL_SOFT; // force "Enough" button to be shown
273 
274     msg = lives_strdup_printf(_("Quick transcoding to %s..."), pname);
275     do_threaded_dialog(msg, TRUE);
276     d_print(msg);
277 
278     needs_dprint = TRUE;
279   }
280 
281   width = pwidth = vpp->fwidth;
282   height = pheight = vpp->fheight;
283   if (prefs->letterbox) {
284     width = cfile->hsize;
285     height = cfile->vsize;
286     get_letterbox_sizes(&pwidth, &pheight, &width, &height, (mainw->vpp->capabilities & VPP_CAN_RESIZE));
287   }
288 
289   if (!internal) {
290     // for internal, the renderer will check diskspace levels
291     if (capable->mountpoint && *capable->mountpoint) {
292       /// if output is on same volume as workdir
293       // then we monitor continuously
294       char *mpf = get_mountpoint_for(pname);
295       if (mpf && *mpf && !lives_strcmp(mpf, capable->mountpoint)) {
296         manage_ds = TRUE;
297       }
298     }
299   }
300 
301   // encoding loop
302   for (i = start; !end || i <= end; i++) {
303     if (manage_ds) {
304       if (count++ == COUNT_CHKVAL)
305         if (!check_storage_space(-1, TRUE)) break;
306     }
307 
308     // set the frame number to pull
309     currticks = q_gint64((i - start) / cfile->fps * TICKS_PER_SECOND_DBL,
310                          cfile->fps);
311     if (!internal) {
312       weed_set_int_value(frame_layer, WEED_LEAF_FRAME, i);
313 
314       // - pull next frame (thread)
315       pull_frame_threaded(frame_layer, img_ext, (weed_timecode_t)currticks, width, height);
316 
317       if (mainw->fx1_bool) {
318         frame_layer = on_rte_apply(frame_layer, cfile->hsize, cfile->vsize, (weed_timecode_t)currticks);
319       }
320     } else {
321       lives_nanosleep_until_nonzero(mainw->transrend_ready
322                                     || lives_proc_thread_cancelled(mainw->transrend_proc));
323       if (lives_proc_thread_cancelled(mainw->transrend_proc)) goto tr_err;
324       frame_layer = mainw->transrend_layer;
325     }
326 
327 #ifdef MATCH_PALETTES
328     if (i == start) {
329       // try to match palettes
330       int *pal_list = (*vpp->get_palette_list)();
331       get_best_palette_match(weed_layer_get_palette(frame_layer), pal_list, &vpp->palette, &vpp->YUV_clamping);
332       (*vpp->set_palette)(vpp->palette);
333       if (weed_palette_is_yuv(vpp->palette)) {
334         if (vpp->set_yuv_palette_clamping)(*vpp->set_yuv_palette_clamping)(vpp->YUV_clamping);
335       }
336       if (!(*vpp->init_screen)(vpp->fwidth, vpp->fheight, FALSE, 0, vpp->extra_argc, vpp->extra_argv)) {
337         error = TRUE;
338         goto tr_err;
339       }
340     }
341 #endif
342 
343     if (audio) {
344       // - read 1 frame worth of audio, to float, send
345       THREADVAR(read_failed) = FALSE;
346       in_bytes = lives_read_buffered(fd, abuff, (size_t)spf * cfile->achans * (cfile->asampsize >> 3), TRUE);
347       if (THREADVAR(read_failed) || in_bytes < 0) {
348         error = TRUE;
349         goto tr_err;
350       }
351 
352       if (in_bytes == 0) {
353         // eof, flush audio
354         // exit_screen() will flush anything left over
355         (*mainw->vpp->render_audio_frame_float)(NULL, 0);
356       } else {
357         nsamps = in_bytes / cfile->achans / (cfile->asampsize >> 3);
358 
359         for (j = 0; j < cfile->achans; j++) {
360           // convert to float
361           if (cfile->asampsize == 16) {
362             sample_move_d16_float(fltbuf[j], (short *)abuff, (uint64_t)nsamps,
363                                   cfile->achans, asigned ? AFORM_SIGNED : AFORM_UNSIGNED, swap_endian,
364                                   lives_vol_from_linear(cfile->vol));
365           } else {
366             sample_move_d8_d16(sbuff, (uint8_t *)abuff, (uint64_t)nsamps, in_bytes,
367                                1.0, cfile->achans, cfile->achans, !asigned ? SWAP_U_TO_S : 0);
368             sample_move_d16_float(fltbuf[j], sbuff, (uint64_t)nsamps, cfile->achans, AFORM_SIGNED, FALSE,
369                                   lives_vol_from_linear(cfile->vol));
370           }
371         }
372 
373         if (!internal) {
374           if (mainw->fx1_bool) {
375             // apply any audio effects with in_channels
376             if (has_audio_filters(AF_TYPE_ANY)) {
377               weed_layer_t *layer = weed_layer_new(WEED_LAYER_TYPE_AUDIO);
378               weed_layer_set_audio_data(layer, fltbuf, cfile->arate, cfile->achans, nsamps);
379               weed_apply_audio_effects_rt(layer, currticks, FALSE, FALSE);
380               lives_free(fltbuf);
381               fltbuf = (float **)weed_layer_get_audio_data(layer, NULL);
382               weed_layer_set_audio_data(layer, NULL, 0, 0, 0);
383               weed_layer_free(layer);
384             }
385           }
386         }
387         (*mainw->vpp->render_audio_frame_float)(fltbuf, nsamps);
388       }
389       // account for rounding errors
390       spf = ospf + (spf - (double)((int)spf));
391     }
392 
393     // get frame, send it
394     //if (deinterlace) weed_leaf_set(frame_layer, WEED_LEAF_HOST_DEINTERLACE, WEED_TRUE);
395     // ensure all threads are complete. optionally deinterlace, optionally overlay subtitles.
396     check_layer_ready(frame_layer);
397     width = weed_layer_get_width(frame_layer) * weed_palette_get_pixels_per_macropixel(weed_layer_get_palette(frame_layer));
398     height = weed_layer_get_height(frame_layer);
399 
400     if (prefs->letterbox && (pwidth != width || pheight != height)) {
401       get_letterbox_sizes(&pwidth, &pheight, &width, &height, (mainw->vpp->capabilities & VPP_CAN_RESIZE));
402       if (!letterbox_layer(frame_layer, pwidth, pheight, width, height, interp, vpp->palette, vpp->YUV_clamping)) goto tr_err;
403     }
404 
405     if (((width ^ pwidth) >> 2) || ((height ^ pheight) >> 1)) {
406       if (!resize_layer(frame_layer, pwidth, pheight, interp, vpp->palette, vpp->YUV_clamping)) goto tr_err;
407     }
408 
409     if (weed_palette_is_rgb(mainw->vpp->palette)) {
410       if (mainw->vpp->capabilities & VPP_LINEAR_GAMMA)
411         tgamma = WEED_GAMMA_LINEAR;
412     } else {
413       if (vpp->YUV_subspace == WEED_YUV_SUBSPACE_BT709)
414         tgamma = WEED_GAMMA_BT709;
415     }
416 
417     convert_layer_palette_full(frame_layer, vpp->palette, vpp->YUV_clamping, vpp->YUV_sampling, vpp->YUV_subspace, tgamma);
418 
419     gamma_convert_layer(tgamma, frame_layer);
420 
421     if (coder) {
422       error = lives_proc_thread_join_boolean(coder);
423     }
424     if (!error) {
425       weed_plant_t *copy_frame_layer = weed_layer_new(WEED_LAYER_TYPE_VIDEO);
426       weed_layer_copy(copy_frame_layer, frame_layer);
427       weed_layer_nullify_pixel_data(frame_layer);
428       coder = lives_proc_thread_create(LIVES_THRDATTR_NONE, (lives_funcptr_t)send_layer,
429                                        WEED_SEED_BOOLEAN, "PVI", copy_frame_layer, vpp, currticks);
430     }
431 
432     if (error) goto tr_err;
433     if (!internal) {
434       // update progress dialog with fraction done
435       threaded_dialog_spin(1. - (double)(cfile->end - i) / (double)(cfile->end - cfile->start + 1.));
436     } else {
437       weed_layer_free(frame_layer);
438       frame_layer = NULL;
439       mainw->transrend_ready = FALSE;
440     }
441 
442     if (mainw->cancelled != CANCEL_NONE) break;
443   }
444 
445   //// encoding done
446 
447 tr_err:
448   mainw->cancel_type = CANCEL_KILL;
449 
450   mainw->fx1_bool = fx1_bool;
451 
452   if (coder) {
453     // do b4 exit_screen
454     error = lives_proc_thread_join_boolean(coder);
455   }
456 
457   // flush streams, write headers, plugin cleanup
458   if (vpp && vpp->exit_screen) {
459     (*vpp->exit_screen)(0, 0);
460   }
461 
462   if (!internal) {
463     // terminate the progress dialog
464     end_threaded_dialog();
465   }
466 
467 tr_err2:
468   transcode_cleanup(vpp);
469 
470   if (!internal) {
471     if (needs_dprint) {
472       if (mainw->cancelled != CANCEL_NONE) {
473         d_print_enough(i - start + 1);
474         mainw->cancelled = CANCEL_NONE;
475       } else {
476         if (!error) d_print_done();
477         else d_print_failed();
478       }
479     }
480     mainw->no_switch_dprint = FALSE;
481   }
482 
483   lives_freep((void **)&pname);
484   lives_freep((void **)&msg);
485 
486   if (!internal && frame_layer) weed_layer_free(frame_layer);
487 
488   if (fd >= 0) lives_close_buffered(fd);
489 
490   lives_freep((void **)&afname);
491 
492   lives_freep((void **)&abuff);
493   if (fltbuf) {
494     for (i = 0; i < cfile->achans; lives_freep((void **) & (fltbuf[i++])));
495     lives_free(fltbuf);
496   }
497 
498   lives_freep((void **)&sbuff);
499 
500   return !error;
501 }
502 
503 #endif
504