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