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