MythTV  master
transcode.cpp
Go to the documentation of this file.
1 #include <cmath>
2 #include <fcntl.h>
3 #include <iostream>
4 #include <memory>
5 
6 #include <QStringList>
7 #include <QMap>
8 #include <QRegExp>
9 #include <QList>
10 #include <QWaitCondition>
11 #include <QMutex>
12 #include <QMutexLocker>
13 #include <QtAlgorithms>
14 
15 #include "mythconfig.h"
16 
17 #include "transcode.h"
18 #include "audiooutput.h"
19 #include "recordingprofile.h"
20 #include "mythcorecontext.h"
21 #include "jobqueue.h"
22 #include "exitcodes.h"
23 #include "mthreadpool.h"
24 #include "deletemap.h"
25 #include "tvremoteutil.h"
26 
27 #if CONFIG_LIBMP3LAME
28 #include "NuppelVideoRecorder.h"
29 #endif
30 #include "mythtranscodeplayer.h"
31 #include "programinfo.h"
32 #include "mythdbcon.h"
33 #include "io/mythavformatwriter.h"
34 #include "HLS/httplivestream.h"
35 
36 #include "videodecodebuffer.h"
37 #include "cutter.h"
38 #include "audioreencodebuffer.h"
39 
40 extern "C" {
41 #include "libavcodec/avcodec.h"
42 #include "libswscale/swscale.h"
43 }
44 #include "mythavutil.h"
45 
46 #include <unistd.h> // for unlink()
47 
48 #define LOC QString("Transcode: ")
49 
51  m_proginfo(pginfo),
52  m_recProfile(new RecordingProfile("Transcoders"))
53 {
54 }
55 
57 {
58 #if CONFIG_LIBMP3LAME
59  delete m_nvr;
60 #endif
61  SetPlayerContext(nullptr);
62  delete m_outBuffer;
63  delete m_fifow;
64  delete m_kfaTable;
65  delete m_recProfile;
66 }
67 void Transcode::ReencoderAddKFA(long curframe, long lastkey, long num_keyframes)
68 {
69  long delta = curframe - lastkey;
70  if (delta != 0 && delta != m_keyframeDist)
71  {
72  struct kfatable_entry kfate {};
73  kfate.adjust = m_keyframeDist - delta;
74  kfate.keyframe_number = num_keyframes;
75  m_kfaTable->push_back(kfate);
76  }
77 }
78 
79 bool Transcode::GetProfile(const QString& profileName, const QString& encodingType,
80  int height, int frameRate)
81 {
82  if (profileName.toLower() == "autodetect")
83  {
84  if (height == 1088)
85  height = 1080;
86 
87  QString autoProfileName = QObject::tr("Autodetect from %1").arg(height);
88  if (frameRate == 25 || frameRate == 30)
89  autoProfileName += "i";
90  if (frameRate == 50 || frameRate == 60)
91  autoProfileName += "p";
92 
93  bool result = false;
94  LOG(VB_GENERAL, LOG_NOTICE,
95  QString("Transcode: Looking for autodetect profile: %1")
96  .arg(autoProfileName));
97  result = m_recProfile->loadByGroup(autoProfileName, "Transcoders");
98 
99  if (!result && encodingType == "MPEG-2")
100  {
101  result = m_recProfile->loadByGroup("MPEG2", "Transcoders");
102  autoProfileName = "MPEG2";
103  }
104  if (!result && (encodingType == "MPEG-4" || encodingType == "RTjpeg"))
105  {
106  result = m_recProfile->loadByGroup("RTjpeg/MPEG4",
107  "Transcoders");
108  autoProfileName = "RTjpeg/MPEG4";
109  }
110  if (!result)
111  {
112  LOG(VB_GENERAL, LOG_ERR,
113  QString("Transcode: Couldn't find profile for : %1")
114  .arg(encodingType));
115 
116  return false;
117  }
118 
119  LOG(VB_GENERAL, LOG_NOTICE,
120  QString("Transcode: Using autodetect profile: %1")
121  .arg(autoProfileName));
122  }
123  else
124  {
125  bool isNum = false;
126  int profileID = profileName.toInt(&isNum);
127  // If a bad profile is specified, there will be trouble
128  if (isNum && profileID > 0)
129  m_recProfile->loadByID(profileID);
130  else if (!m_recProfile->loadByGroup(profileName, "Transcoders"))
131  {
132  LOG(VB_GENERAL, LOG_ERR, QString("Couldn't find profile #: %1")
133  .arg(profileName));
134  return false;
135  }
136  }
137  return true;
138 }
139 
141 {
142  if (player_ctx == m_ctx)
143  return;
144 
145  delete m_ctx;
146  m_ctx = player_ctx;
147 }
148 
149 #if CONFIG_LIBMP3LAME
150 static QString get_str_option(RecordingProfile *profile, const QString &name)
151 {
152  const StandardSetting *setting = profile->byName(name);
153  if (setting)
154  return setting->getValue();
155 
156  LOG(VB_GENERAL, LOG_ERR, LOC +
157  QString("get_str_option(...%1): Option not in profile.").arg(name));
158 
159  return QString();
160 }
161 
162 static int get_int_option(RecordingProfile *profile, const QString &name)
163 {
164  QString ret_str = get_str_option(profile, name);
165  if (ret_str.isEmpty())
166  return 0;
167 
168  bool ok = false;
169  int ret_int = ret_str.toInt(&ok);
170 
171  if (!ok)
172  {
173  LOG(VB_GENERAL, LOG_ERR, LOC +
174  QString("get_int_option(...%1): Option is not an int.").arg(name));
175  }
176 
177  return ret_int;
178 }
179 
180 static bool get_bool_option(RecordingProfile *profile, const QString &name)
181 {
182  return get_int_option(profile, name) != 0;
183 }
184 
185 static void TranscodeWriteText(void *ptr, unsigned char *buf, int len,
186  std::chrono::milliseconds timecode, int pagenr)
187 {
188  auto *nvr = (NuppelVideoRecorder *)ptr;
189  nvr->WriteText(buf, len, timecode, pagenr);
190 }
191 #endif // CONFIG_LIBMP3LAME
192 
193 int Transcode::TranscodeFile(const QString &inputname,
194  const QString &outputname,
195  const QString &profileName,
196  bool honorCutList, bool framecontrol,
197  int jobID, const QString& fifodir,
198  bool fifo_info, bool cleanCut,
199  frm_dir_map_t &deleteMap,
200  int AudioTrackNo,
201  bool passthru)
202 {
203  QDateTime curtime = MythDate::current();
204  QDateTime statustime = curtime;
205  int audioFrame = 0;
206  std::unique_ptr<Cutter> cutter = nullptr;
207  std::unique_ptr<MythAVFormatWriter> avfw = nullptr;
208  std::unique_ptr<MythAVFormatWriter> avfw2 = nullptr;
209  std::unique_ptr<HTTPLiveStream> hls = nullptr;
210  int hlsSegmentSize = 0;
211  int hlsSegmentFrames = 0;
212 
213 #if !CONFIG_LIBMP3LAME
214  (void)profileName;
215 #endif
216 
217  if (jobID >= 0)
218  JobQueue::ChangeJobComment(jobID, "0% " + QObject::tr("Completed"));
219 
220  if (m_hlsMode)
221  {
222  m_avfMode = true;
223 
224  if (m_hlsStreamID != -1)
225  {
226  hls = std::make_unique<HTTPLiveStream>(m_hlsStreamID);
227  hls->UpdateStatus(kHLSStatusStarting);
228  hls->UpdateStatusMessage("Transcoding Starting");
229  m_cmdWidth = hls->GetWidth();
230  m_cmdHeight = hls->GetHeight();
231  m_cmdBitrate = hls->GetBitrate();
232  m_cmdAudioBitrate = hls->GetAudioBitrate();
233  }
234  }
235 
236  if (!m_avfMode)
237  {
238 #if CONFIG_LIBMP3LAME
239  m_nvr = new NuppelVideoRecorder(nullptr, nullptr);
240 #else
241  LOG(VB_GENERAL, LOG_ERR,
242  "Not compiled with libmp3lame support");
243  return REENCODE_ERROR;
244 #endif
245  }
246 
247  // Input setup
248  auto *player_ctx = new PlayerContext(kTranscoderInUseID);
249  player_ctx->SetPlayingInfo(m_proginfo);
250  MythMediaBuffer *rb = (hls && (m_hlsStreamID != -1)) ?
251  MythMediaBuffer::Create(hls->GetSourceFile(), false, false) :
252  MythMediaBuffer::Create(inputname, false, false);
253  if (!rb || !rb->GetLastError().isEmpty())
254  {
255  LOG(VB_GENERAL, LOG_ERR,
256  QString("Transcoding aborted, error: '%1'")
257  .arg(rb? rb->GetLastError() : ""));
258  delete player_ctx;
259  return REENCODE_ERROR;
260  }
261  player_ctx->SetRingBuffer(rb);
262  player_ctx->SetPlayer(new MythTranscodePlayer(player_ctx, static_cast<PlayerFlags>(kVideoIsNull | kNoITV)));
263  SetPlayerContext(player_ctx);
264  auto * player = dynamic_cast<MythTranscodePlayer*>(GetPlayer());
265  if (player == nullptr)
266  {
267  LOG(VB_GENERAL, LOG_ERR,
268  QString("Transcoding aborted, failed to retrieve MythPlayer object"));
269  return REENCODE_ERROR;
270  }
271  if (m_proginfo->GetRecordingEndTime() > curtime)
272  {
273  player_ctx->SetRecorder(RemoteGetExistingRecorder(m_proginfo));
274  player->SetWatchingRecording(true);
275  }
276 
277  if (m_showProgress)
278  {
279  statustime = statustime.addSecs(5);
280  }
281 
282  AudioOutput *audioOutput = new AudioReencodeBuffer(FORMAT_NONE, 0,
283  passthru);
284  AudioReencodeBuffer *arb = ((AudioReencodeBuffer*)audioOutput);
285  player->GetAudio()->SetAudioOutput(audioOutput);
286  player->SetTranscoding(true);
287 
288  if (player->OpenFile() < 0)
289  {
290  LOG(VB_GENERAL, LOG_ERR, "Transcoding aborted, error opening file.");
291  SetPlayerContext(nullptr);
292  return REENCODE_ERROR;
293  }
294 
295  if (AudioTrackNo > -1)
296  {
297  LOG(VB_GENERAL, LOG_INFO,
298  QString("Set audiotrack number to %1").arg(AudioTrackNo));
299  player->GetDecoder()->SetTrack(kTrackTypeAudio, AudioTrackNo);
300  }
301 
302  long long total_frame_count = player->GetTotalFrameCount();
303  long long new_frame_count = total_frame_count;
304  if (honorCutList && m_proginfo)
305  {
306  LOG(VB_GENERAL, LOG_INFO, "Honoring the cutlist while transcoding");
307 
308  frm_dir_map_t::const_iterator it;
309  QString cutStr;
310  long long lastStart = 0;
311 
312  if (deleteMap.empty())
313  m_proginfo->QueryCutList(deleteMap);
314 
315  for (it = deleteMap.cbegin(); it != deleteMap.cend(); ++it)
316  {
317  if (*it)
318  {
319  if (!cutStr.isEmpty())
320  cutStr += ",";
321  cutStr += QString("%1-").arg((long)it.key());
322  lastStart = it.key();
323  }
324  else
325  {
326  if (cutStr.isEmpty())
327  cutStr += "0-";
328  cutStr += QString("%1").arg((long)it.key());
329  new_frame_count -= (it.key() - lastStart);
330  }
331  }
332  if (cutStr.isEmpty())
333  cutStr = "Is Empty";
334  else if (cutStr.endsWith('-') && (total_frame_count > lastStart))
335  {
336  new_frame_count -= (total_frame_count - lastStart);
337  cutStr += QString("%1").arg(total_frame_count);
338  }
339  LOG(VB_GENERAL, LOG_INFO, QString("Cutlist : %1").arg(cutStr));
340  LOG(VB_GENERAL, LOG_INFO, QString("Original Length: %1 frames")
341  .arg((long)total_frame_count));
342  LOG(VB_GENERAL, LOG_INFO, QString("New Length : %1 frames")
343  .arg((long)new_frame_count));
344 
345  if ((m_proginfo->QueryIsEditing()) ||
347  {
348  LOG(VB_GENERAL, LOG_INFO, "Transcoding aborted, cutlist changed");
349  SetPlayerContext(nullptr);
351  }
353  curtime = curtime.addSecs(60);
354  }
355 
356  player->GetAudio()->ReinitAudio();
357  QString encodingType = player->GetEncodingType();
358  bool copyvideo = false;
359  bool copyaudio = false;
360 
361  QString vidsetting = nullptr;
362  QString audsetting = nullptr;
363  QString vidfilters = nullptr;
364 
365  QSize buf_size = player->GetVideoBufferSize();
366  int video_width = buf_size.width();
367  int video_height = buf_size.height();
368 
369  if (video_height == 1088) {
370  LOG(VB_GENERAL, LOG_NOTICE,
371  "Found video height of 1088. This is unusual and "
372  "more than likely the video is actually 1080 so mythtranscode "
373  "will treat it as such.");
374  }
375 
376  DecoderBase* dec = player->GetDecoder();
377  float video_aspect = dec ? dec->GetVideoAspect() : 4.0F / 3.0F;
378  float video_frame_rate = player->GetFrameRate();
379  int newWidth = video_width;
380  int newHeight = video_height;
381  bool halfFramerate = false;
382  bool skippedLastFrame = false;
383 
384  m_kfaTable = new std::vector<struct kfatable_entry>;
385 
386  if (m_avfMode)
387  {
388  newWidth = m_cmdWidth;
389  newHeight = m_cmdHeight;
390 
391  // Absolutely no purpose is served by scaling video up beyond it's
392  // original resolution, quality is degraded, transcoding is
393  // slower and in future we may wish to scale bitrate according to
394  // resolution, so it would also waste bandwidth (when streaming)
395  //
396  // This change could be said to apply for all transcoding, but for now
397  // we're limiting it to HLS where it's uncontroversial
398  if (m_hlsMode)
399  {
400 // if (newWidth > video_width)
401 // newWidth = video_width;
402  if (newHeight > video_height)
403  {
404  newHeight = video_height;
405  newWidth = 0;
406  }
407  }
408 
409  // TODO: is this necessary? It got commented out, but may still be
410  // needed.
411  // int actualHeight = (video_height == 1088 ? 1080 : video_height);
412 
413  // If height or width are 0, then we need to calculate them
414  if (newHeight == 0 && newWidth > 0)
415  newHeight = (int)(1.0F * newWidth / video_aspect);
416  else if (newWidth == 0 && newHeight > 0)
417  newWidth = (int)(1.0F * newHeight * video_aspect);
418  else if (newWidth == 0 && newHeight == 0)
419  {
420  newHeight = 480;
421  newWidth = (int)(1.0F * 480 * video_aspect);
422  if (newWidth > 640)
423  {
424  newWidth = 640;
425  newHeight = (int)(1.0F * 640 / video_aspect);
426  }
427  }
428 
429  // make sure dimensions are valid for MPEG codecs
430  newHeight = (newHeight + 15) & ~0xF;
431  newWidth = (newWidth + 15) & ~0xF;
432 
433  avfw = std::make_unique<MythAVFormatWriter>();
434  if (!avfw)
435  {
436  LOG(VB_GENERAL, LOG_ERR,
437  "Transcoding aborted, error creating AVFormatWriter.");
438  SetPlayerContext(nullptr);
439  return REENCODE_ERROR;
440  }
441 
442  avfw->SetVideoBitrate(m_cmdBitrate);
443  avfw->SetHeight(newHeight);
444  avfw->SetWidth(newWidth);
445  avfw->SetAspect(video_aspect);
446  avfw->SetAudioBitrate(m_cmdAudioBitrate);
447  avfw->SetAudioChannels(arb->m_channels);
448  avfw->SetAudioFrameRate(arb->m_eff_audiorate);
449  avfw->SetAudioFormat(FORMAT_S16);
450 
451  if (m_hlsMode)
452  {
453 
454  if (m_hlsStreamID == -1)
455  {
456  hls = std::make_unique<HTTPLiveStream>(inputname, newWidth, newHeight,
458  m_hlsMaxSegments, 0, 0);
459 
460  m_hlsStreamID = hls->GetStreamID();
461  if (!hls || m_hlsStreamID == -1)
462  {
463  LOG(VB_GENERAL, LOG_ERR, "Unable to create new stream");
464  SetPlayerContext(nullptr);
465  return REENCODE_ERROR;
466  }
467  }
468 
469  int segmentSize = hls->GetSegmentSize();
470 
471  LOG(VB_GENERAL, LOG_NOTICE,
472  QString("HLS: Using segment size of %1 seconds")
473  .arg(segmentSize));
474 
476  {
477  int audioOnlyBitrate = hls->GetAudioOnlyBitrate();
478 
479  avfw2 = std::make_unique<MythAVFormatWriter>();
480  avfw2->SetContainer("mpegts");
481  avfw2->SetAudioCodec("aac");
482  avfw2->SetAudioBitrate(audioOnlyBitrate);
483  avfw2->SetAudioChannels(arb->m_channels);
484  avfw2->SetAudioFrameRate(arb->m_eff_audiorate);
485  avfw2->SetAudioFormat(FORMAT_S16);
486  }
487 
488  avfw->SetContainer("mpegts");
489  avfw->SetVideoCodec("libx264");
490  avfw->SetAudioCodec("aac");
491  hls->UpdateStatus(kHLSStatusStarting);
492  hls->UpdateStatusMessage("Transcoding Starting");
493  hls->UpdateSizeInfo(newWidth, newHeight, video_width, video_height);
494 
495  if (!hls->InitForWrite())
496  {
497  LOG(VB_GENERAL, LOG_ERR, "hls->InitForWrite() failed");
498  SetPlayerContext(nullptr);
499  return REENCODE_ERROR;
500  }
501 
502  if (video_frame_rate > 30)
503  {
504  halfFramerate = true;
505  avfw->SetFramerate(video_frame_rate/2);
506 
507  if (avfw2)
508  avfw2->SetFramerate(video_frame_rate/2);
509 
510  hlsSegmentSize = (int)(segmentSize * video_frame_rate / 2);
511  }
512  else
513  {
514  avfw->SetFramerate(video_frame_rate);
515 
516  if (avfw2)
517  avfw2->SetFramerate(video_frame_rate);
518 
519  hlsSegmentSize = (int)(segmentSize * video_frame_rate);
520  }
521 
522  avfw->SetKeyFrameDist(30);
523  if (avfw2)
524  avfw2->SetKeyFrameDist(30);
525 
526  hls->AddSegment();
527  avfw->SetFilename(hls->GetCurrentFilename());
528  if (avfw2)
529  avfw2->SetFilename(hls->GetCurrentFilename(true));
530  }
531  else
532  {
533  avfw->SetContainer(m_cmdContainer);
534  avfw->SetVideoCodec(m_cmdVideoCodec);
535  avfw->SetAudioCodec(m_cmdAudioCodec);
536  avfw->SetFilename(outputname);
537  avfw->SetFramerate(video_frame_rate);
538  avfw->SetKeyFrameDist(30);
539  }
540 
541  int threads = gCoreContext->GetNumSetting("HTTPLiveStreamThreads", 2);
542  QString preset = gCoreContext->GetSetting("HTTPLiveStreamPreset", "veryfast");
543  QString tune = gCoreContext->GetSetting("HTTPLiveStreamTune", "film");
544 
545  LOG(VB_GENERAL, LOG_NOTICE,
546  QString("x264 HLS using: %1 threads, '%2' profile and '%3' tune")
547  .arg(threads).arg(preset).arg(tune));
548 
549  avfw->SetThreadCount(threads);
550  avfw->SetEncodingPreset(preset);
551  avfw->SetEncodingTune(tune);
552 
553  if (avfw2)
554  avfw2->SetThreadCount(1);
555 
556  if (!avfw->Init())
557  {
558  LOG(VB_GENERAL, LOG_ERR, "avfw->Init() failed");
559  SetPlayerContext(nullptr);
560  return REENCODE_ERROR;
561  }
562 
563  if (!avfw->OpenFile())
564  {
565  LOG(VB_GENERAL, LOG_ERR, "avfw->OpenFile() failed");
566  SetPlayerContext(nullptr);
567  return REENCODE_ERROR;
568  }
569 
570  if (avfw2 && !avfw2->Init())
571  {
572  LOG(VB_GENERAL, LOG_ERR, "avfw2->Init() failed");
573  SetPlayerContext(nullptr);
574  return REENCODE_ERROR;
575  }
576 
577  if (avfw2 && !avfw2->OpenFile())
578  {
579  LOG(VB_GENERAL, LOG_ERR, "avfw2->OpenFile() failed");
580  SetPlayerContext(nullptr);
581  return REENCODE_ERROR;
582  }
583 
584  arb->m_audioFrameSize = avfw->GetAudioFrameSize() * arb->m_channels * 2;
585  }
586 #if CONFIG_LIBMP3LAME
587  else if (fifodir.isEmpty())
588  {
589  if (!GetProfile(profileName, encodingType, video_height,
590  (int)round(video_frame_rate))) {
591  LOG(VB_GENERAL, LOG_ERR, "Transcoding aborted, no profile found.");
592  SetPlayerContext(nullptr);
593  return REENCODE_ERROR;
594  }
595 
596  // For overriding settings on the command line
597  QMap<QString, QString> recorderOptionsMap;
598  if (!m_recorderOptions.isEmpty())
599  {
600 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
601  QStringList options = m_recorderOptions
602  .split(",", QString::SkipEmptyParts);
603 #else
604  QStringList options = m_recorderOptions
605  .split(",", Qt::SkipEmptyParts);
606 #endif
607  int loop = 0;
608  while (loop < options.size())
609  {
610  QStringList tokens = options[loop].split("=");
611  if (tokens.length() < 2)
612  {
613  LOG(VB_GENERAL, LOG_ERR, "Transcoding aborted, invalid option settings.");
614  return REENCODE_ERROR;
615  }
616  recorderOptionsMap[tokens[0]] = tokens[1];
617 
618  loop++;
619  }
620  }
621 
622  vidsetting = get_str_option(m_recProfile, "videocodec");
623  audsetting = get_str_option(m_recProfile, "audiocodec");
624  vidfilters = get_str_option(m_recProfile, "transcodefilters");
625 
626  if (encodingType == "MPEG-2" &&
627  get_bool_option(m_recProfile, "transcodelossless"))
628  {
629  LOG(VB_GENERAL, LOG_NOTICE, "Switching to MPEG-2 transcoder.");
630  SetPlayerContext(nullptr);
631  return REENCODE_MPEG2TRANS;
632  }
633 
634  // Recorder setup
635  if (get_bool_option(m_recProfile, "transcodelossless"))
636  {
637  vidsetting = encodingType;
638  audsetting = "MP3";
639  }
640  else if (get_bool_option(m_recProfile, "transcoderesize"))
641  {
642  int actualHeight = (video_height == 1088 ? 1080 : video_height);
643 
644  //player->SetVideoFilters(vidfilters);
645  newWidth = get_int_option(m_recProfile, "width");
646  newHeight = get_int_option(m_recProfile, "height");
647 
648  // If height or width are 0, then we need to calculate them
649  if (newHeight == 0 && newWidth > 0)
650  newHeight = (int)(1.0 * newWidth * actualHeight / video_width);
651  else if (newWidth == 0 && newHeight > 0)
652  newWidth = (int)(1.0 * newHeight * video_width / actualHeight);
653  else if (newWidth == 0 && newHeight == 0)
654  {
655  newHeight = 480;
656  newWidth = (int)(1.0 * 480 * video_width / actualHeight);
657  if (newWidth > 640)
658  {
659  newWidth = 640;
660  newHeight = (int)(1.0 * 640 * actualHeight / video_width);
661  }
662  }
663 
664  if (encodingType.startsWith("mpeg", Qt::CaseInsensitive))
665  {
666  // make sure dimensions are valid for MPEG codecs
667  newHeight = (newHeight + 15) & ~0xF;
668  newWidth = (newWidth + 15) & ~0xF;
669  }
670 
671  LOG(VB_GENERAL, LOG_INFO, QString("Resizing from %1x%2 to %3x%4")
672  .arg(video_width).arg(video_height)
673  .arg(newWidth).arg(newHeight));
674  }
675  else // lossy and no resize
676  {
677  //player->SetVideoFilters(vidfilters);
678  }
679 
680  // this is ripped from tv_rec SetupRecording. It'd be nice to merge
681  m_nvr->SetOption("inpixfmt", FMT_YV12);
682 
683  m_nvr->SetOption("width", newWidth);
684  m_nvr->SetOption("height", newHeight);
685 
686  m_nvr->SetOption("tvformat", gCoreContext->GetSetting("TVFormat"));
687  m_nvr->SetOption("vbiformat", gCoreContext->GetSetting("VbiFormat"));
688 
689  m_nvr->SetFrameRate(video_frame_rate);
690  m_nvr->SetVideoAspect(video_aspect);
691  m_nvr->SetTranscoding(true);
692 
693  if ((vidsetting == "MPEG-4") ||
694  (recorderOptionsMap["videocodec"] == "mpeg4"))
695  {
696  m_nvr->SetOption("videocodec", "mpeg4");
697 
698  m_nvr->SetIntOption(m_recProfile, "mpeg4bitrate");
699  m_nvr->SetIntOption(m_recProfile, "scalebitrate");
700  m_nvr->SetIntOption(m_recProfile, "mpeg4maxquality");
701  m_nvr->SetIntOption(m_recProfile, "mpeg4minquality");
702  m_nvr->SetIntOption(m_recProfile, "mpeg4qualdiff");
703  m_nvr->SetIntOption(m_recProfile, "mpeg4optionvhq");
704  m_nvr->SetIntOption(m_recProfile, "mpeg4option4mv");
705 #ifdef USING_FFMPEG_THREADS
706  m_nvr->SetIntOption(m_recProfile, "encodingthreadcount");
707 #endif
708  }
709  else if ((vidsetting == "MPEG-2") ||
710  (recorderOptionsMap["videocodec"] == "mpeg2video"))
711  {
712  m_nvr->SetOption("videocodec", "mpeg2video");
713 
714  m_nvr->SetIntOption(m_recProfile, "mpeg2bitrate");
715  m_nvr->SetIntOption(m_recProfile, "scalebitrate");
716 #ifdef USING_FFMPEG_THREADS
717  m_nvr->SetIntOption(m_recProfile, "encodingthreadcount");
718 #endif
719  }
720  else if ((vidsetting == "RTjpeg") ||
721  (recorderOptionsMap["videocodec"] == "rtjpeg"))
722  {
723  m_nvr->SetOption("videocodec", "rtjpeg");
724  m_nvr->SetIntOption(m_recProfile, "rtjpegquality");
725  m_nvr->SetIntOption(m_recProfile, "rtjpegchromafilter");
726  m_nvr->SetIntOption(m_recProfile, "rtjpeglumafilter");
727  }
728  else if (vidsetting.isEmpty())
729  {
730  LOG(VB_GENERAL, LOG_ERR, "No video information found!");
731  LOG(VB_GENERAL, LOG_ERR, "Please ensure that recording profiles "
732  "for the transcoder are set");
733  SetPlayerContext(nullptr);
734  return REENCODE_ERROR;
735  }
736  else
737  {
738  LOG(VB_GENERAL, LOG_ERR,
739  QString("Unknown video codec: %1").arg(vidsetting));
740  SetPlayerContext(nullptr);
741  return REENCODE_ERROR;
742  }
743 
744  m_nvr->SetOption("samplerate", arb->m_eff_audiorate);
745  if (audsetting == "MP3")
746  {
747  m_nvr->SetOption("audiocompression", 1);
748  m_nvr->SetIntOption(m_recProfile, "mp3quality");
749  copyaudio = true;
750  }
751  else if (audsetting == "Uncompressed")
752  {
753  m_nvr->SetOption("audiocompression", 0);
754  }
755  else
756  {
757  LOG(VB_GENERAL, LOG_ERR,
758  QString("Unknown audio codec: %1").arg(audsetting));
759  }
760 
761  m_nvr->AudioInit(true);
762 
763  // For overriding settings on the command line
764  if (!recorderOptionsMap.empty())
765  {
766  QMap<QString, QString>::Iterator it;
767  QString key;
768  QString value;
769  for (it = recorderOptionsMap.begin();
770  it != recorderOptionsMap.end(); ++it)
771  {
772  key = it.key();
773  value = *it;
774 
775  LOG(VB_GENERAL, LOG_NOTICE,
776  QString("Forcing Recorder option '%1' to '%2'")
777  .arg(key).arg(value));
778 
779  if (value.contains(QRegExp("[^0-9]")))
780  m_nvr->SetOption(key, value);
781  else
782  m_nvr->SetOption(key, value.toInt());
783 
784  if (key == "width")
785  newWidth = (value.toInt() + 15) & ~0xF;
786  else if (key == "height")
787  newHeight = (value.toInt() + 15) & ~0xF;
788  else if (key == "videocodec")
789  {
790  if (value == "mpeg4")
791  vidsetting = "MPEG-4";
792  else if (value == "mpeg2video")
793  vidsetting = "MPEG-2";
794  else if (value == "rtjpeg")
795  vidsetting = "RTjpeg";
796  }
797  }
798  }
799 
800  if ((vidsetting == "MPEG-4") ||
801  (vidsetting == "MPEG-2"))
802  m_nvr->SetupAVCodecVideo();
803  else if (vidsetting == "RTjpeg")
804  m_nvr->SetupRTjpeg();
805 
806  m_outBuffer = MythMediaBuffer::Create(outputname, true, false);
807  m_nvr->SetRingBuffer(m_outBuffer);
808  m_nvr->WriteHeader();
809  m_nvr->StreamAllocate();
810  }
811 
812  if (vidsetting == encodingType && !framecontrol && !m_avfMode &&
813  fifodir.isEmpty() && honorCutList &&
814  video_width == newWidth && video_height == newHeight)
815  {
816  copyvideo = true;
817  LOG(VB_GENERAL, LOG_INFO, "Reencoding video in 'raw' mode");
818  }
819 #endif // CONFIG_LIBMP3LAME
820 
821  if (honorCutList && !deleteMap.empty())
822  {
823  if (cleanCut)
824  {
825  // Have the player seek only part of the way
826  // through a cut, and then use the cutter to
827  // discard the rest
828  cutter = std::make_unique<Cutter>();
829  cutter->SetCutList(deleteMap, m_ctx);
830  player->SetCutList(cutter->AdjustedCutList());
831  }
832  else
833  {
834  // Have the player apply the cut list
835  player->SetCutList(deleteMap);
836  }
837  }
838 
839  player->InitForTranscode(copyaudio, copyvideo);
840  if (player->IsErrored())
841  {
842  LOG(VB_GENERAL, LOG_ERR,
843  "Unable to initialize MythPlayer for Transcode");
844  SetPlayerContext(nullptr);
845  return REENCODE_ERROR;
846  }
847 
848  // must come after InitForTranscode - which creates the VideoOutput instance
849  if (m_hlsMode && player->GetVideoOutput())
850  player->GetVideoOutput()->SetDeinterlacing(true, false, DEINT_CPU | DEINT_MEDIUM);
851 
852  MythVideoFrame frame;
853  // Do not use padding when compressing to RTjpeg or when in fifomode.
854  // The RTjpeg compressor doesn't know how to handle strides different to
855  // video width.
856  bool nonAligned = vidsetting == "RTjpeg" || !fifodir.isEmpty();
857  bool rescale = (video_width != newWidth) || (video_height != newHeight) || nonAligned;
858 
859  if (rescale)
860  {
861  if (nonAligned)
862  {
863  // Set a stride identical to actual width, to ease fifo post-conversion process.
864  // 1080i/p video is actually 1088 because of the 16x16 blocks so
865  // we have to fudge the output size here. nuvexport knows how to handle
866  // this and as of right now it is the only app that uses the fifo ability.
867  size_t newSize = MythVideoFrame::GetBufferSize(FMT_YV12,
868  video_width, video_height == 1080 ? 1088 : video_height, 0 /* aligned */);
869  uint8_t* newbuffer = MythVideoFrame::GetAlignedBuffer(newSize);
870  if (!newbuffer)
871  return REENCODE_ERROR;
872  frame.Init(FMT_YV12, newbuffer, newSize, video_width, video_height, nullptr, 0);
873  }
874  else
875  {
876  frame.Init(FMT_YV12, newWidth, newHeight);
877  }
878  }
879 
880  if (!fifodir.isEmpty())
881  {
882  AudioPlayer *aplayer = player->GetAudio();
883  const char *audio_codec_name = "unknown";
884 
885  switch(aplayer->GetCodec())
886  {
887  case AV_CODEC_ID_AC3:
888  audio_codec_name = "ac3";
889  break;
890  case AV_CODEC_ID_EAC3:
891  audio_codec_name = "eac3";
892  break;
893  case AV_CODEC_ID_DTS:
894  audio_codec_name = "dts";
895  break;
896  case AV_CODEC_ID_TRUEHD:
897  audio_codec_name = "truehd";
898  break;
899  case AV_CODEC_ID_MP3:
900  audio_codec_name = "mp3";
901  break;
902  case AV_CODEC_ID_MP2:
903  audio_codec_name = "mp2";
904  break;
905  case AV_CODEC_ID_AAC:
906  audio_codec_name = "aac";
907  break;
908  case AV_CODEC_ID_AAC_LATM:
909  audio_codec_name = "aac_latm";
910  break;
911  default:
912  audio_codec_name = "unknown";
913  }
914 
915  if (!arb->m_passthru)
916  audio_codec_name = "raw";
917 
918  // If cutlist is used then get info on first uncut frame
919  if (honorCutList && fifo_info)
920  {
921  bool is_key = false;
922  int did_ff = 0;
923  player->TranscodeGetNextFrame(did_ff, is_key, true);
924 
925  QSize buf_size2 = player->GetVideoBufferSize();
926  video_width = buf_size2.width();
927  video_height = buf_size2.height();
928  video_aspect = player->GetVideoAspect();
929  video_frame_rate = player->GetFrameRate();
930  }
931 
932  // Display details of the format of the fifo data.
933  LOG(VB_GENERAL, LOG_INFO,
934  QString("FifoVideoWidth %1").arg(video_width));
935  LOG(VB_GENERAL, LOG_INFO,
936  QString("FifoVideoHeight %1").arg(video_height));
937  LOG(VB_GENERAL, LOG_INFO,
938  QString("FifoVideoAspectRatio %1").arg(video_aspect));
939  LOG(VB_GENERAL, LOG_INFO,
940  QString("FifoVideoFrameRate %1").arg(video_frame_rate));
941  LOG(VB_GENERAL, LOG_INFO,
942  QString("FifoAudioFormat %1").arg(audio_codec_name));
943  LOG(VB_GENERAL, LOG_INFO,
944  QString("FifoAudioChannels %1").arg(arb->m_channels));
945  LOG(VB_GENERAL, LOG_INFO,
946  QString("FifoAudioSampleRate %1").arg(arb->m_eff_audiorate));
947 
948  if(fifo_info)
949  {
950  // Request was for just the format of fifo data, not for
951  // the actual transcode, so stop here.
952  unlink(outputname.toLocal8Bit().constData());
953  SetPlayerContext(nullptr);
954  return REENCODE_OK;
955  }
956 
957  QString audfifo = fifodir + QString("/audout");
958  QString vidfifo = fifodir + QString("/vidout");
959  int audio_size = arb->m_eff_audiorate * arb->m_bytes_per_frame;
960  // framecontrol is true if we want to enforce fifo sync.
961  if (framecontrol)
962  LOG(VB_GENERAL, LOG_INFO, "Enforcing sync on fifos");
963  m_fifow = new MythFIFOWriter(2, framecontrol);
964 
965  if (!m_fifow->FIFOInit(0, QString("video"), vidfifo, frame.m_bufferSize, 50) ||
966  !m_fifow->FIFOInit(1, QString("audio"), audfifo, audio_size, 25))
967  {
968  LOG(VB_GENERAL, LOG_ERR,
969  "Error initializing fifo writer. Aborting");
970  unlink(outputname.toLocal8Bit().constData());
971  SetPlayerContext(nullptr);
972  return REENCODE_ERROR;
973  }
974  LOG(VB_GENERAL, LOG_INFO,
975  QString("Video %1x%2@%3fps Audio rate: %4")
976  .arg(video_width).arg(video_height)
977  .arg(video_frame_rate)
978  .arg(arb->m_eff_audiorate));
979  LOG(VB_GENERAL, LOG_INFO, "Created fifos. Waiting for connection.");
980  }
981 
982 #if CONFIG_LIBMP3LAME
983  bool forceKeyFrames = (m_fifow == nullptr) ? framecontrol : false;
984  bool writekeyframe = true;
985  long lastKeyFrame = 0;
986  int num_keyframes = 0;
987 #endif
988 
989  frm_dir_map_t::iterator dm_iter;
990 
991  int did_ff = 0;
992 
993  long curFrameNum = 0;
994  frame.m_frameNumber = 1;
995  long totalAudio = 0;
996  int dropvideo = 0;
997  // timecode of the last read video frame in input time
998  std::chrono::milliseconds lasttimecode = 0ms;
999  // timecode of the last write video frame in input or output time
1000  std::chrono::milliseconds lastWrittenTime = 0ms;
1001  // delta between the same video frame in input and output due to applying the cut list
1002  std::chrono::milliseconds timecodeOffset = 0ms;
1003 
1004  float rateTimeConv = arb->m_eff_audiorate / 1000.0F;
1005  float vidFrameTime = 1000.0F / video_frame_rate;
1006  auto vidFrameTimeMs = millisecondsFromFloat(vidFrameTime);
1007  int wait_recover = 0;
1008  MythVideoOutput *videoOutput = player->GetVideoOutput();
1009  bool is_key = false;
1010  bool first_loop = true;
1011  AVFrame imageIn;
1012  AVFrame imageOut;
1013  struct SwsContext *scontext = nullptr;
1014 
1015  if (m_fifow)
1016  LOG(VB_GENERAL, LOG_INFO, "Dumping Video and Audio data to fifos");
1017  else if (copyaudio)
1018  LOG(VB_GENERAL, LOG_INFO, "Copying Audio while transcoding Video");
1019  else if (m_hlsMode)
1020  LOG(VB_GENERAL, LOG_INFO, "Transcoding for HTTP Live Streaming");
1021  else if (m_avfMode)
1022  LOG(VB_GENERAL, LOG_INFO, "Transcoding to libavformat container");
1023  else
1024  LOG(VB_GENERAL, LOG_INFO, "Transcoding Video and Audio");
1025 
1026  auto *videoBuffer =
1027  new VideoDecodeBuffer(player, videoOutput, honorCutList);
1028  MThreadPool::globalInstance()->start(videoBuffer, "VideoDecodeBuffer");
1029 
1030  QElapsedTimer flagTime;
1031  flagTime.start();
1032 
1033  if (cutter)
1034  cutter->Activate(vidFrameTime * rateTimeConv, total_frame_count);
1035 
1036  bool stopSignalled = false;
1037  MythVideoFrame *lastDecode = nullptr;
1038 
1039  if (hls)
1040  {
1041  hls->UpdateStatus(kHLSStatusRunning);
1042  hls->UpdateStatusMessage("Transcoding");
1043  }
1044 
1045  while ((!stopSignalled) &&
1046  (lastDecode = videoBuffer->GetFrame(did_ff, is_key)))
1047  {
1048  if (first_loop)
1049  {
1050  copyaudio = player->GetRawAudioState();
1051  first_loop = false;
1052  }
1053 
1054  float new_aspect = lastDecode->m_aspect;
1055 
1056  if (cutter)
1057  cutter->NewFrame(lastDecode->m_frameNumber);
1058 
1059 // frame timecode is on input time base
1060  frame.m_timecode = lastDecode->m_timecode;
1061 
1062  // if the timecode jumps backwards just use the last frame's timecode plus the duration of a frame
1063  if (frame.m_timecode < lasttimecode)
1064  frame.m_timecode = lasttimecode + vidFrameTimeMs;
1065 
1066  if (m_fifow)
1067  {
1068  MythAVUtil::FillAVFrame(&imageIn, lastDecode);
1069  MythAVUtil::FillAVFrame(&imageOut, &frame);
1070 
1071  scontext = sws_getCachedContext(scontext,
1072  lastDecode->m_width, lastDecode->m_height, MythAVUtil::FrameTypeToPixelFormat(lastDecode->m_type),
1074  SWS_FAST_BILINEAR, nullptr, nullptr, nullptr);
1075  // Typically, wee aren't rescaling per say, we're just correcting the stride set by the decoder.
1076  // However, it allows to properly handle recordings that see their resolution change half-way.
1077  sws_scale(scontext, imageIn.data, imageIn.linesize, 0,
1078  lastDecode->m_height, imageOut.data, imageOut.linesize);
1079 
1080  totalAudio += arb->GetSamples(frame.m_timecode);
1081  std::chrono::milliseconds audbufTime = millisecondsFromFloat(totalAudio / rateTimeConv);
1082  std::chrono::milliseconds auddelta = frame.m_timecode - audbufTime;
1083  std::chrono::milliseconds vidTime = millisecondsFromFloat(curFrameNum * vidFrameTime);
1084  std::chrono::milliseconds viddelta = frame.m_timecode - vidTime;
1085  std::chrono::milliseconds delta = viddelta - auddelta;
1086  std::chrono::milliseconds absdelta = std::chrono::abs(delta);
1087  if (absdelta < 500ms && absdelta >= vidFrameTimeMs)
1088  {
1089  QString msg = QString("Audio is %1ms %2 video at # %3: "
1090  "auddelta=%4, viddelta=%5")
1091  .arg(absdelta.count())
1092  .arg(((delta > 0ms) ? "ahead of" : "behind"))
1093  .arg((int)curFrameNum)
1094  .arg(auddelta.count())
1095  .arg(viddelta.count());
1096  LOG(VB_GENERAL, LOG_INFO, msg);
1097  dropvideo = (delta > 0ms) ? 1 : -1;
1098  wait_recover = 0;
1099  }
1100  else if (delta >= 500ms && delta < 10s)
1101  {
1102  if (wait_recover == 0)
1103  {
1104  dropvideo = 5;
1105  wait_recover = 6;
1106  }
1107  else if (wait_recover == 1)
1108  {
1109  // Video is badly lagging. Try to catch up.
1110  int count = 0;
1111  while (delta > vidFrameTimeMs)
1112  {
1113  if (!cutter || !cutter->InhibitDummyFrame())
1114  m_fifow->FIFOWrite(0, frame.m_buffer, frame.m_bufferSize);
1115 
1116  count++;
1117  delta -= vidFrameTimeMs;
1118  }
1119  QString msg = QString("Added %1 blank video frames")
1120  .arg(count);
1121  LOG(VB_GENERAL, LOG_INFO, msg);
1122  curFrameNum += count;
1123  dropvideo = 0;
1124  wait_recover = 0;
1125  }
1126  else
1127  wait_recover--;
1128  }
1129  else
1130  {
1131  dropvideo = 0;
1132  wait_recover = 0;
1133  }
1134 
1135 #if 0
1136  int buflen = (int)(arb->audiobuffer_len / rateTimeConv);
1137  LOG(VB_GENERAL, LOG_DEBUG,
1138  QString("%1: video time: %2 audio time: %3 "
1139  "buf: %4 exp: %5 delta: %6")
1140  .arg(curFrameNum) .arg(frame.m_timecode.count())
1141  .arg(arb->last_audiotime) .arg(buflen) .arg(audbufTime.count())
1142  .arg(delta.count()));
1143 #endif
1144  AudioBuffer *ab = nullptr;
1145  while ((ab = arb->GetData(frame.m_timecode)) != nullptr)
1146  {
1147  if (!cutter ||
1148  !cutter->InhibitUseAudioFrames(ab->m_frames, &totalAudio))
1149  m_fifow->FIFOWrite(1, ab->data(), ab->size());
1150 
1151  delete ab;
1152  }
1153 
1154  if (dropvideo < 0)
1155  {
1156  if (cutter && cutter->InhibitDropFrame())
1157  m_fifow->FIFOWrite(0, frame.m_buffer, frame.m_bufferSize);
1158 
1159  LOG(VB_GENERAL, LOG_INFO, "Dropping video frame");
1160  dropvideo++;
1161  curFrameNum--;
1162  }
1163  else
1164  {
1165  if (!cutter || !cutter->InhibitUseVideoFrame())
1166  m_fifow->FIFOWrite(0, frame.m_buffer, frame.m_bufferSize);
1167 
1168  if (dropvideo)
1169  {
1170  if (!cutter || !cutter->InhibitDummyFrame())
1171  m_fifow->FIFOWrite(0, frame.m_buffer, frame.m_bufferSize);
1172 
1173  curFrameNum++;
1174  dropvideo--;
1175  }
1176  }
1177  videoOutput->DoneDisplayingFrame(lastDecode);
1178  player->GetCC608Reader()->FlushTxtBuffers();
1179  lasttimecode = frame.m_timecode;
1180  }
1181  else if (copyaudio)
1182  {
1183 #if CONFIG_LIBMP3LAME
1184  // Encoding from NuppelVideo to NuppelVideo with MP3 audio
1185  // So let's not decode/reencode audio
1186  if (!player->GetRawAudioState())
1187  {
1188  // The Raw state changed during decode. This is not good
1189  LOG(VB_GENERAL, LOG_ERR, "Transcoding aborted, MythPlayer "
1190  "is not in raw audio mode.");
1191 
1192  unlink(outputname.toLocal8Bit().constData());
1193  SetPlayerContext(nullptr);
1194  if (videoBuffer)
1195  videoBuffer->stop();
1196  if (hls)
1197  {
1198  hls->UpdateStatus(kHLSStatusErrored);
1199  hls->UpdateStatusMessage("Transcoding Errored");
1200  }
1201  return REENCODE_ERROR;
1202  }
1203 
1204  if (forceKeyFrames)
1205  writekeyframe = true;
1206  else
1207  {
1208  writekeyframe = is_key;
1209  if (writekeyframe)
1210  {
1211  // Currently, we don't create new sync frames,
1212  // (though we do create new 'I' frames), so we mark
1213  // the key-frames before deciding whether we need a
1214  // new 'I' frame.
1215 
1216  //need to correct the frame# and timecode here
1217  // Question: Is it necessary to change the timecodes?
1218  long sync_offset =
1219  player->UpdateStoredFrameNum(curFrameNum);
1220  m_nvr->UpdateSeekTable(num_keyframes, sync_offset);
1221  ReencoderAddKFA(curFrameNum, lastKeyFrame, num_keyframes);
1222  num_keyframes++;
1223  lastKeyFrame = curFrameNum;
1224 
1225  if (did_ff)
1226  did_ff = 0;
1227  }
1228  }
1229 
1230  if (did_ff == 1)
1231  {
1232  timecodeOffset += (frame.m_timecode - lasttimecode - vidFrameTimeMs);
1233  }
1234  lasttimecode = frame.m_timecode;
1235 // from here on the timecode is on the output time base
1236  frame.m_timecode -= timecodeOffset;
1237 
1238  if (!player->WriteStoredData(m_outBuffer, (did_ff == 0), timecodeOffset))
1239  {
1240  if (video_aspect != new_aspect)
1241  {
1242  video_aspect = new_aspect;
1243  m_nvr->SetNewVideoParams(video_aspect);
1244  }
1245 
1246  QSize buf_size3 = player->GetVideoBufferSize();
1247 
1248  if (video_width != buf_size3.width() ||
1249  video_height != buf_size3.height())
1250  {
1251  video_width = buf_size3.width();
1252  video_height = buf_size3.height();
1253 
1254  LOG(VB_GENERAL, LOG_INFO,
1255  QString("Resizing from %1x%2 to %3x%4")
1256  .arg(video_width).arg(video_height)
1257  .arg(newWidth).arg(newHeight));
1258 
1259  }
1260 
1261  if (did_ff == 1)
1262  {
1263  // Create a new 'I' frame if we just processed a cut.
1264  did_ff = 2;
1265  writekeyframe = true;
1266  }
1267 
1268  if (rescale)
1269  {
1270  MythAVUtil::FillAVFrame(&imageIn, lastDecode);
1271  MythAVUtil::FillAVFrame(&imageOut, &frame);
1272 
1273  int bottomBand = (lastDecode->m_height == 1088) ? 8 : 0;
1274  scontext = sws_getCachedContext(scontext,
1275  lastDecode->m_width, lastDecode->m_height, MythAVUtil::FrameTypeToPixelFormat(lastDecode->m_type),
1277  SWS_FAST_BILINEAR, nullptr, nullptr, nullptr);
1278 
1279  sws_scale(scontext, imageIn.data, imageIn.linesize, 0,
1280  lastDecode->m_height - bottomBand,
1281  imageOut.data, imageOut.linesize);
1282  }
1283 
1284  m_nvr->WriteVideo(rescale ? &frame : lastDecode, true, writekeyframe);
1285  }
1286  player->GetCC608Reader()->FlushTxtBuffers();
1287 #else
1288  LOG(VB_GENERAL, LOG_ERR,
1289  "Not compiled with libmp3lame support. Should never get here");
1290  return REENCODE_ERROR;
1291 #endif // CONFIG_LIBMP3LAME
1292  }
1293  else
1294  {
1295  if (did_ff == 1)
1296  {
1297  did_ff = 2;
1298  timecodeOffset += (frame.m_timecode - lasttimecode -
1299  millisecondsFromFloat(vidFrameTime));
1300  }
1301 
1302  if (video_aspect != new_aspect)
1303  {
1304  video_aspect = new_aspect;
1305 #if CONFIG_LIBMP3LAME
1306  if (m_nvr)
1307  m_nvr->SetNewVideoParams(video_aspect);
1308 #endif
1309  }
1310 
1311 
1312  QSize buf_size4 = player->GetVideoBufferSize();
1313 
1314  if (video_width != buf_size4.width() ||
1315  video_height != buf_size4.height())
1316  {
1317  video_width = buf_size4.width();
1318  video_height = buf_size4.height();
1319 
1320  LOG(VB_GENERAL, LOG_INFO,
1321  QString("Resizing from %1x%2 to %3x%4")
1322  .arg(video_width).arg(video_height)
1323  .arg(newWidth).arg(newHeight));
1324  }
1325 
1326  if (rescale)
1327  {
1328  MythAVUtil::FillAVFrame(&imageIn, lastDecode);
1329  MythAVUtil::FillAVFrame(&imageOut, &frame);
1330 
1331  int bottomBand = (lastDecode->m_height == 1088) ? 8 : 0;
1332  scontext = sws_getCachedContext(scontext,
1333  lastDecode->m_width, lastDecode->m_height, MythAVUtil::FrameTypeToPixelFormat(lastDecode->m_type),
1335  SWS_FAST_BILINEAR, nullptr, nullptr, nullptr);
1336 
1337  sws_scale(scontext, imageIn.data, imageIn.linesize, 0,
1338  lastDecode->m_height - bottomBand,
1339  imageOut.data, imageOut.linesize);
1340  }
1341 
1342  // audio is fully decoded, so we need to reencode it
1343  AudioBuffer *ab = nullptr;
1344  while ((ab = arb->GetData(lastWrittenTime)) != nullptr)
1345  {
1346  auto *buf = (unsigned char *)ab->data();
1347  if (m_avfMode)
1348  {
1349  if (did_ff != 1)
1350  {
1351  std::chrono::milliseconds tc = ab->m_time - timecodeOffset;
1352  avfw->WriteAudioFrame(buf, audioFrame, tc);
1353 
1354  if (avfw2)
1355  {
1356  if ((avfw2->GetTimecodeOffset() == -1ms) &&
1357  (avfw->GetTimecodeOffset() != -1ms))
1358  {
1359  avfw2->SetTimecodeOffset(
1360  avfw->GetTimecodeOffset());
1361  }
1362 
1363  tc = ab->m_time - timecodeOffset;
1364  avfw2->WriteAudioFrame(buf, audioFrame, tc);
1365  }
1366 
1367  ++audioFrame;
1368  }
1369  }
1370 #if CONFIG_LIBMP3LAME
1371  else
1372  {
1373  m_nvr->SetOption("audioframesize", ab->size());
1374  m_nvr->WriteAudio(buf, audioFrame++,
1375  (ab->m_time - timecodeOffset));
1376  if (m_nvr->IsErrored())
1377  {
1378  LOG(VB_GENERAL, LOG_ERR,
1379  "Transcode: Encountered irrecoverable error in "
1380  "NVR::WriteAudio");
1381  SetPlayerContext(nullptr);
1382  if (videoBuffer)
1383  videoBuffer->stop();
1384  delete ab;
1385  return REENCODE_ERROR;
1386  }
1387  }
1388 #endif
1389  delete ab;
1390  }
1391 
1392  if (!m_avfMode)
1393  {
1394 #if CONFIG_LIBMP3LAME
1395  player->GetCC608Reader()->
1396  TranscodeWriteText(&TranscodeWriteText, (void *)(m_nvr));
1397 #else
1398  LOG(VB_GENERAL, LOG_ERR,
1399  "Not compiled with libmp3lame support");
1400  return REENCODE_ERROR;
1401 #endif
1402  }
1403  lasttimecode = frame.m_timecode;
1404  frame.m_timecode -= timecodeOffset;
1405 
1406  if (m_avfMode)
1407  {
1408  if (halfFramerate && !skippedLastFrame)
1409  {
1410  skippedLastFrame = true;
1411  }
1412  else
1413  {
1414  skippedLastFrame = false;
1415 
1416  if ((hls) &&
1417  (avfw->GetFramesWritten()) &&
1418  (hlsSegmentFrames > hlsSegmentSize) &&
1419  (avfw->NextFrameIsKeyFrame()))
1420  {
1421  hls->AddSegment();
1422  avfw->ReOpen(hls->GetCurrentFilename());
1423 
1424  if (avfw2)
1425  avfw2->ReOpen(hls->GetCurrentFilename(true));
1426 
1427  hlsSegmentFrames = 0;
1428  }
1429 
1430  if (avfw->WriteVideoFrame(rescale ? &frame : lastDecode) > 0)
1431  {
1432  lastWrittenTime = frame.m_timecode + timecodeOffset;
1433  if (hls)
1434  ++hlsSegmentFrames;
1435  }
1436 
1437  }
1438  }
1439 #if CONFIG_LIBMP3LAME
1440  else
1441  {
1442  if (forceKeyFrames)
1443  m_nvr->WriteVideo(rescale ? &frame : lastDecode, true, true);
1444  else
1445  m_nvr->WriteVideo(rescale ? &frame : lastDecode);
1446  lastWrittenTime = frame.m_timecode + timecodeOffset;
1447  }
1448 #endif
1449  }
1450  if (MythDate::current() > statustime)
1451  {
1452  if (m_showProgress)
1453  {
1454  LOG(VB_GENERAL, LOG_INFO,
1455  QString("Processed: %1 of %2 frames(%3 seconds)").
1456  arg(curFrameNum).arg((long)total_frame_count).
1457  arg((long)(curFrameNum / video_frame_rate)));
1458  }
1459 
1460  if (hls && hls->CheckStop())
1461  {
1462  hls->UpdateStatus(kHLSStatusStopping);
1463  stopSignalled = true;
1464  }
1465 
1466  statustime = MythDate::current().addSecs(5);
1467  }
1468  if (MythDate::current() > curtime)
1469  {
1470  if (honorCutList && m_proginfo && !m_avfMode &&
1472  {
1473  LOG(VB_GENERAL, LOG_NOTICE,
1474  "Transcoding aborted, cutlist updated");
1475 
1476  unlink(outputname.toLocal8Bit().constData());
1477  SetPlayerContext(nullptr);
1478  if (videoBuffer)
1479  videoBuffer->stop();
1480  return REENCODE_CUTLIST_CHANGE;
1481  }
1482 
1483  if ((jobID >= 0) || (VERBOSE_LEVEL_CHECK(VB_GENERAL, LOG_INFO)))
1484  {
1486  {
1487  LOG(VB_GENERAL, LOG_NOTICE,
1488  "Transcoding STOPped by JobQueue");
1489 
1490  unlink(outputname.toLocal8Bit().constData());
1491  SetPlayerContext(nullptr);
1492  if (videoBuffer)
1493  videoBuffer->stop();
1494  if (hls)
1495  {
1496  hls->UpdateStatus(kHLSStatusStopped);
1497  hls->UpdateStatusMessage("Transcoding Stopped");
1498  }
1499  return REENCODE_STOPPED;
1500  }
1501 
1502  float flagFPS = 0.0;
1503  float elapsed = flagTime.elapsed() / 1000.0;
1504  if (elapsed != 0.0F)
1505  flagFPS = curFrameNum / elapsed;
1506 
1507  total_frame_count = player->GetCurrentFrameCount();
1508  int percentage = curFrameNum * 100 / total_frame_count;
1509 
1510  if (hls)
1511  hls->UpdatePercentComplete(percentage);
1512 
1513  if (jobID >= 0)
1514  {
1516  QObject::tr("%1% Completed @ %2 fps.")
1517  .arg(percentage).arg(flagFPS));
1518  }
1519  else
1520  {
1521  LOG(VB_GENERAL, LOG_INFO,
1522  QString("mythtranscode: %1% Completed @ %2 fps.")
1523  .arg(percentage).arg(flagFPS));
1524  }
1525 
1526  }
1527  curtime = MythDate::current().addSecs(20);
1528  }
1529 
1530  curFrameNum++;
1531  frame.m_frameNumber = 1 + (curFrameNum << 1);
1532 
1533  player->DiscardVideoFrame(lastDecode);
1534  }
1535 
1536  sws_freeContext(scontext);
1537 
1538  if (!m_fifow)
1539  {
1540  if (avfw)
1541  avfw->CloseFile();
1542 
1543  if (avfw2)
1544  avfw2->CloseFile();
1545 
1546  if (!m_avfMode && m_proginfo)
1547  {
1552  }
1553 
1554 #if CONFIG_LIBMP3LAME
1555  if (m_nvr)
1556  {
1557  m_nvr->WriteSeekTable();
1558  if (!m_kfaTable->empty())
1559  m_nvr->WriteKeyFrameAdjustTable(*m_kfaTable);
1560  }
1561 #endif // CONFIG_LIBMP3LAME
1562  } else {
1563  m_fifow->FIFODrain();
1564  }
1565 
1566  if (hls)
1567  {
1568  if (!stopSignalled)
1569  {
1570  hls->UpdateStatus(kHLSStatusCompleted);
1571  hls->UpdateStatusMessage("Transcoding Completed");
1572  hls->UpdatePercentComplete(100);
1573  }
1574  else
1575  {
1576  hls->UpdateStatus(kHLSStatusStopped);
1577  hls->UpdateStatusMessage("Transcoding Stopped");
1578  }
1579  }
1580 
1581  if (videoBuffer)
1582  videoBuffer->stop();
1583 
1584  SetPlayerContext(nullptr);
1585 
1586  return REENCODE_OK;
1587 }
1588 
1589 /* vim: set expandtab tabstop=4 shiftwidth=4: */
1590 
JobQueue::GetJobCmd
static enum JobCmds GetJobCmd(int jobID)
Definition: jobqueue.cpp:1465
FORMAT_NONE
@ FORMAT_NONE
Definition: audiooutputsettings.h:26
MARK_KEYFRAME
@ MARK_KEYFRAME
Definition: programtypes.h:62
Transcode::m_cmdContainer
QString m_cmdContainer
Definition: transcode.h:67
DEINT_MEDIUM
@ DEINT_MEDIUM
Definition: mythframe.h:71
RemoteGetExistingRecorder
RemoteEncoder * RemoteGetExistingRecorder(const ProgramInfo *pginfo)
Definition: tvremoteutil.cpp:311
MythVideoOutput
Definition: mythvideoout.h:35
deletemap.h
FORMAT_S16
@ FORMAT_S16
Definition: audiooutputsettings.h:28
Transcode::m_fifow
MythFIFOWriter * m_fifow
Definition: transcode.h:58
AudioReencodeBuffer
This class is to act as a fake audio output device to store the data for reencoding.
Definition: audioreencodebuffer.h:31
AudioPlayer
Definition: audioplayer.h:24
Transcode::m_outBuffer
MythMediaBuffer * m_outBuffer
Definition: transcode.h:57
AudioReencodeBuffer::m_eff_audiorate
int m_eff_audiorate
Definition: audioreencodebuffer.h:73
NuppelVideoRecorder.h
Transcode::m_keyframeDist
int m_keyframeDist
Definition: transcode.h:52
RecordingProfile::loadByGroup
virtual bool loadByGroup(const QString &name, const QString &group)
Definition: recordingprofile.cpp:1545
Transcode::m_recProfile
RecordingProfile * m_recProfile
Definition: transcode.h:51
JOB_COMMFLAG
@ JOB_COMMFLAG
Definition: jobqueue.h:74
cutter.h
Transcode::Transcode
Transcode(ProgramInfo *pginfo)
Definition: transcode.cpp:50
arg
arg(title).arg(filename).arg(doDelete))
mythdbcon.h
frm_dir_map_t
QMap< uint64_t, MarkTypes > frm_dir_map_t
Frame # -> Mark map.
Definition: programtypes.h:82
kfatable_entry::keyframe_number
int keyframe_number
Definition: format.h:136
MythVideoFrame::m_width
int m_width
Definition: mythframe.h:121
Transcode::m_cmdAudioBitrate
int m_cmdAudioBitrate
Definition: transcode.h:73
MythMediaBuffer
Definition: mythmediabuffer.h:50
RecordingProfile::loadByID
virtual void loadByID(int id)
Definition: recordingprofile.cpp:1449
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23
MythFIFOWriter
Definition: mythfifowriter.h:31
MythVideoFrame::Init
void Init(VideoFrameType Type, int Width, int Height, const VideoFrameTypes *RenderFormats=nullptr)
Definition: mythframe.cpp:42
httplivestream.h
JOB_STOP
@ JOB_STOP
Definition: jobqueue.h:49
PlayerFlags
PlayerFlags
Definition: mythplayer.h:62
kfatable_entry
Definition: format.h:133
ProgramInfo::GetRecordingEndTime
QDateTime GetRecordingEndTime(void) const
Approximate time the recording should have ended, did end, or is intended to end.
Definition: programinfo.h:410
Transcode::m_cmdHeight
int m_cmdHeight
Definition: transcode.h:71
Transcode::m_hlsStreamID
int m_hlsStreamID
Definition: transcode.h:64
MythFIFOWriter::FIFODrain
void FIFODrain(void)
Definition: mythfifowriter.cpp:254
MythDate::current
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:10
JobQueue::IsJobRunning
static bool IsJobRunning(int jobType, uint chanid, const QDateTime &recstartts)
Definition: jobqueue.cpp:1095
kHLSStatusStarting
@ kHLSStatusStarting
Definition: httplivestream.h:13
Transcode::m_hlsDisableAudioOnly
bool m_hlsDisableAudioOnly
Definition: transcode.h:65
DEINT_CPU
@ DEINT_CPU
Definition: mythframe.h:73
Transcode::m_hlsMaxSegments
int m_hlsMaxSegments
Definition: transcode.h:66
AVFrame
struct AVFrame AVFrame
Definition: BorderDetector.h:15
AudioOutput
Definition: audiooutput.h:26
AudioBuffer::m_time
std::chrono::milliseconds m_time
Definition: audioreencodebuffer.h:24
kHLSStatusStopped
@ kHLSStatusStopped
Definition: httplivestream.h:18
programinfo.h
MythVideoFrame::GetBufferSize
static size_t GetBufferSize(VideoFrameType Type, int Width, int Height, int Aligned=MYTH_WIDTH_ALIGNMENT)
Definition: mythframe.cpp:412
ProgramInfo::QueryMarkupFlag
bool QueryMarkupFlag(MarkTypes type) const
Returns true iff the speficied mark type is set on frame 0.
Definition: programinfo.cpp:3573
Transcode::m_cmdVideoCodec
QString m_cmdVideoCodec
Definition: transcode.h:69
AudioReencodeBuffer::GetData
AudioBuffer * GetData(std::chrono::milliseconds time)
Definition: audioreencodebuffer.cpp:202
Transcode::GetPlayer
MythPlayer * GetPlayer(void)
Definition: transcode.h:47
hardwareprofile.scan.profile
profile
Definition: scan.py:99
kHLSStatusCompleted
@ kHLSStatusCompleted
Definition: httplivestream.h:15
AudioReencodeBuffer::m_bytes_per_frame
int m_bytes_per_frame
Definition: audioreencodebuffer.h:72
MARK_DURATION_MS
@ MARK_DURATION_MS
Definition: programtypes.h:74
JobQueue::ChangeJobComment
static bool ChangeJobComment(int jobID, const QString &comment="")
Definition: jobqueue.cpp:1019
FMT_YV12
@ FMT_YV12
Definition: mythframe.h:24
MARK_GOP_START
@ MARK_GOP_START
Definition: programtypes.h:61
MythVideoFrame::m_timecode
std::chrono::milliseconds m_timecode
Definition: mythframe.h:131
Transcode::m_cmdAudioCodec
QString m_cmdAudioCodec
Definition: transcode.h:68
kTranscoderInUseID
const QString kTranscoderInUseID
Definition: programtypes.cpp:21
mythtranscodeplayer.h
ProgramInfo::QueryCutList
bool QueryCutList(frm_dir_map_t &delMap, bool loadAutosave=false) const
Definition: programinfo.cpp:3293
MythVideoFrame::m_frameNumber
long long m_frameNumber
Definition: mythframe.h:129
REENCODE_OK
#define REENCODE_OK
Definition: transcodedefs.h:6
MythFIFOWriter::FIFOInit
bool FIFOInit(uint Id, const QString &Desc, const QString &Name, long Size, int NumBufs)
Definition: mythfifowriter.cpp:108
AudioReencodeBuffer::m_audioFrameSize
int m_audioFrameSize
Definition: audioreencodebuffer.h:76
AudioBuffer::size
int size(void) const
Definition: audioreencodebuffer.h:18
MythVideoFrame::m_bufferSize
size_t m_bufferSize
Definition: mythframe.h:124
ProgramInfo::ClearMarkupFlag
void ClearMarkupFlag(MarkTypes type) const
Definition: programinfo.h:629
jobqueue.h
StandardSetting::getValue
virtual QString getValue(void) const
Definition: standardsettings.h:52
AudioBuffer::data
char * data(void) const
Definition: audioreencodebuffer.h:17
kTrackTypeAudio
@ kTrackTypeAudio
Definition: decoderbase.h:29
mythavformatwriter.h
kNoITV
@ kNoITV
Definition: mythplayer.h:73
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:60
kHLSStatusStopping
@ kHLSStatusStopping
Definition: httplivestream.h:17
MythCoreContext::GetNumSetting
int GetNumSetting(const QString &key, int defaultval=0)
Definition: mythcorecontext.cpp:932
Transcode::m_cmdBitrate
int m_cmdBitrate
Definition: transcode.h:72
MythFIFOWriter::FIFOWrite
void FIFOWrite(uint Id, void *Buffer, long Size)
Definition: mythfifowriter.cpp:209
Transcode::GetProfile
bool GetProfile(const QString &profileName, const QString &encodingType, int height, int frameRate)
Definition: transcode.cpp:79
tvremoteutil.h
DecoderBase::GetVideoAspect
float GetVideoAspect(void) const
Definition: decoderbase.h:182
Transcode::m_recorderOptions
QString m_recorderOptions
Definition: transcode.h:61
Transcode::m_proginfo
ProgramInfo * m_proginfo
Definition: transcode.h:50
mthreadpool.h
AudioBuffer::m_frames
int m_frames
Definition: audioreencodebuffer.h:23
kVideoIsNull
@ kVideoIsNull
Definition: mythplayer.h:71
Transcode::m_avfMode
bool m_avfMode
Definition: transcode.h:62
VERBOSE_LEVEL_CHECK
#define VERBOSE_LEVEL_CHECK(_MASK_, _LEVEL_)
Definition: mythlogging.h:14
ProgramInfo
Holds information on recordings and videos.
Definition: programinfo.h:67
AudioPlayer::GetCodec
AVCodecID GetCodec(void) const
Definition: audioplayer.h:60
kHLSStatusErrored
@ kHLSStatusErrored
Definition: httplivestream.h:16
VideoDecodeBuffer
Definition: videodecodebuffer.h:16
mythcorecontext.h
Transcode::m_kfaTable
KFATable * m_kfaTable
Definition: transcode.h:59
transcode.h
MARK_GOP_BYFRAME
@ MARK_GOP_BYFRAME
Definition: programtypes.h:64
MythVideoFrame::m_type
VideoFrameType m_type
Definition: mythframe.h:119
MARK_UPDATED_CUT
@ MARK_UPDATED_CUT
Definition: programtypes.h:53
ProgramInfo::QueryIsEditing
bool QueryIsEditing(void) const
Queries "recorded" table for its "editing" field and returns true if it is set to true.
Definition: programinfo.cpp:2932
ProgramInfo::ClearPositionMap
void ClearPositionMap(MarkTypes type) const
Definition: programinfo.cpp:3637
AudioReencodeBuffer::m_passthru
bool m_passthru
Definition: audioreencodebuffer.h:75
audiooutput.h
MythAVUtil::FrameTypeToPixelFormat
static AVPixelFormat FrameTypeToPixelFormat(VideoFrameType Type)
Definition: mythavutil.cpp:26
Transcode::~Transcode
~Transcode() override
Definition: transcode.cpp:56
mythavutil.h
Transcode::m_hlsMode
bool m_hlsMode
Definition: transcode.h:63
PlayerContext
Definition: playercontext.h:48
MythMediaBuffer::Create
static MythMediaBuffer * Create(const QString &Filename, bool Write, bool UseReadAhead=true, std::chrono::milliseconds Timeout=kDefaultOpenTimeout, bool StreamOnly=false)
Creates a RingBuffer instance.
Definition: mythmediabuffer.cpp:97
MythMediaBuffer::GetLastError
QString GetLastError(void) const
Definition: mythmediabuffer.cpp:1749
AudioBuffer
Definition: audioreencodebuffer.h:9
videodecodebuffer.h
MythVideoFrame::m_height
int m_height
Definition: mythframe.h:122
audioreencodebuffer.h
REENCODE_CUTLIST_CHANGE
#define REENCODE_CUTLIST_CHANGE
Definition: transcodedefs.h:5
Transcode::m_ctx
PlayerContext * m_ctx
Definition: transcode.h:56
StandardSetting
Definition: standardsettings.h:29
REENCODE_ERROR
#define REENCODE_ERROR
Definition: transcodedefs.h:7
MythVideoFrame::GetAlignedBuffer
static uint8_t * GetAlignedBuffer(size_t Size)
Definition: mythframe.cpp:430
recordingprofile.h
Transcode::m_showProgress
bool m_showProgress
Definition: transcode.h:60
RecordingProfile
Definition: recordingprofile.h:39
LOC
#define LOC
Definition: transcode.cpp:48
MythVideoFrame
Definition: mythframe.h:88
round
#define round(x)
Definition: mythplayer.cpp:59
REENCODE_MPEG2TRANS
#define REENCODE_MPEG2TRANS
Definition: transcodedefs.h:4
exitcodes.h
jobID
int jobID
Definition: mythtv/programs/mythcommflag/main.cpp:83
Transcode::SetPlayerContext
void SetPlayerContext(PlayerContext *player_ctx)
Definition: transcode.cpp:140
Transcode::m_cmdWidth
int m_cmdWidth
Definition: transcode.h:70
MThreadPool::globalInstance
static MThreadPool * globalInstance(void)
Definition: mthreadpool.cpp:306
kHLSStatusRunning
@ kHLSStatusRunning
Definition: httplivestream.h:14
build_compdb.options
options
Definition: build_compdb.py:11
Transcode::ReencoderAddKFA
void ReencoderAddKFA(long curframe, long lastkey, long num_keyframes)
Definition: transcode.cpp:67
AudioReencodeBuffer::m_channels
int m_channels
Definition: audioreencodebuffer.h:71
kfatable_entry::adjust
int adjust
Definition: format.h:135
MythAVUtil::FillAVFrame
static int FillAVFrame(AVFrame *Frame, const MythVideoFrame *From, AVPixelFormat Fmt=AV_PIX_FMT_NONE)
Initialise AVFrame with content from MythVideoFrame.
Definition: mythavutil.cpp:153
MythVideoFrame::m_aspect
float m_aspect
Definition: mythframe.h:127
AudioReencodeBuffer::GetSamples
long long GetSamples(std::chrono::milliseconds time)
Definition: audioreencodebuffer.cpp:220
MThreadPool::start
void start(QRunnable *runnable, const QString &debugName, int priority=0)
Definition: mthreadpool.cpp:341
DecoderBase
Definition: decoderbase.h:120
MythCoreContext::GetSetting
QString GetSetting(const QString &key, const QString &defaultval="")
Definition: mythcorecontext.cpp:918
REENCODE_STOPPED
#define REENCODE_STOPPED
Definition: transcodedefs.h:8
millisecondsFromFloat
std::enable_if_t< std::is_floating_point_v< T >, std::chrono::milliseconds > millisecondsFromFloat(T value)
Helper function for convert a floating point number to a duration.
Definition: mythchrono.h:91
MythVideoFrame::m_buffer
uint8_t * m_buffer
Definition: mythframe.h:120
MythTranscodePlayer
Definition: mythtranscodeplayer.h:7
MythVideoOutput::DoneDisplayingFrame
virtual void DoneDisplayingFrame(MythVideoFrame *Frame)
Releases frame returned from GetLastShownFrame() onto the queue of frames ready for decoding onto.
Definition: mythvideoout.cpp:414
NuppelVideoRecorder
Definition: NuppelVideoRecorder.h:71
Transcode::TranscodeFile
int TranscodeFile(const QString &inputname, const QString &outputname, const QString &profileName, bool honorCutList, bool framecontrol, int jobID, const QString &fifodir, bool fifo_info, bool cleanCut, frm_dir_map_t &deleteMap, int AudioTrackNo, bool passthru=false)
Definition: transcode.cpp:193