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