MythTV  master
transcode.cpp
Go to the documentation of this file.
1 #include <fcntl.h>
2 #include <cmath>
3 #include <iostream>
4 
5 #include <QStringList>
6 #include <QMap>
7 #include <QRegExp>
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 "mythplayer.h"
30 #include "programinfo.h"
31 #include "mythdbcon.h"
32 #include "avformatwriter.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 using namespace std;
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_outRingBuffer;
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 QString();
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  int 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  Cutter *cutter = nullptr;
208  AVFormatWriter *avfw = nullptr;
209  AVFormatWriter *avfw2 = nullptr;
210  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 = new HTTPLiveStream(m_hlsStreamID);
229  hls->UpdateStatusMessage("Transcoding Starting");
230  m_cmdWidth = hls->GetWidth();
231  m_cmdHeight = hls->GetHeight();
232  m_cmdBitrate = hls->GetBitrate();
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  RingBuffer *rb = (hls && (m_hlsStreamID != -1)) ?
252  RingBuffer::Create(hls->GetSourceFile(), false, false) :
253  RingBuffer::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  delete hls;
261  return REENCODE_ERROR;
262  }
263  player_ctx->SetRingBuffer(rb);
264  player_ctx->SetPlayer(new MythPlayer((PlayerFlags)(kVideoIsNull | kNoITV)));
265  SetPlayerContext(player_ctx);
266  GetPlayer()->SetPlayerInfo(nullptr, nullptr, GetPlayerContext());
267  if (m_proginfo->GetRecordingEndTime() > curtime)
268  {
269  player_ctx->SetRecorder(RemoteGetExistingRecorder(m_proginfo));
271  }
272 
273  if (m_showProgress)
274  {
275  statustime = statustime.addSecs(5);
276  }
277 
278  AudioOutput *audioOutput = new AudioReencodeBuffer(FORMAT_NONE, 0,
279  passthru);
280  AudioReencodeBuffer *arb = ((AudioReencodeBuffer*)audioOutput);
281  GetPlayer()->GetAudio()->SetAudioOutput(audioOutput);
282  GetPlayer()->SetTranscoding(true);
283 
284  if (GetPlayer()->OpenFile() < 0)
285  {
286  LOG(VB_GENERAL, LOG_ERR, "Transcoding aborted, error opening file.");
287  SetPlayerContext(nullptr);
288  delete hls;
289  return REENCODE_ERROR;
290  }
291 
292  if (AudioTrackNo > -1)
293  {
294  LOG(VB_GENERAL, LOG_INFO,
295  QString("Set audiotrack number to %1").arg(AudioTrackNo));
296  GetPlayer()->GetDecoder()->SetTrack(kTrackTypeAudio, AudioTrackNo);
297  }
298 
299  long long total_frame_count = GetPlayer()->GetTotalFrameCount();
300  long long new_frame_count = total_frame_count;
301  if (honorCutList && m_proginfo)
302  {
303  LOG(VB_GENERAL, LOG_INFO, "Honoring the cutlist while transcoding");
304 
305  frm_dir_map_t::const_iterator it;
306  QString cutStr;
307  long long lastStart = 0;
308 
309  if (deleteMap.empty())
310  m_proginfo->QueryCutList(deleteMap);
311 
312  for (it = deleteMap.begin(); it != deleteMap.end(); ++it)
313  {
314  if (*it)
315  {
316  if (!cutStr.isEmpty())
317  cutStr += ",";
318  cutStr += QString("%1-").arg((long)it.key());
319  lastStart = it.key();
320  }
321  else
322  {
323  if (cutStr.isEmpty())
324  cutStr += "0-";
325  cutStr += QString("%1").arg((long)it.key());
326  new_frame_count -= (it.key() - lastStart);
327  }
328  }
329  if (cutStr.isEmpty())
330  cutStr = "Is Empty";
331  else if (cutStr.endsWith('-') && (total_frame_count > lastStart))
332  {
333  new_frame_count -= (total_frame_count - lastStart);
334  cutStr += QString("%1").arg(total_frame_count);
335  }
336  LOG(VB_GENERAL, LOG_INFO, QString("Cutlist : %1").arg(cutStr));
337  LOG(VB_GENERAL, LOG_INFO, QString("Original Length: %1 frames")
338  .arg((long)total_frame_count));
339  LOG(VB_GENERAL, LOG_INFO, QString("New Length : %1 frames")
340  .arg((long)new_frame_count));
341 
342  if ((m_proginfo->QueryIsEditing()) ||
344  {
345  LOG(VB_GENERAL, LOG_INFO, "Transcoding aborted, cutlist changed");
346  SetPlayerContext(nullptr);
347  delete hls;
349  }
351  curtime = curtime.addSecs(60);
352  }
353 
355  QString encodingType = GetPlayer()->GetEncodingType();
356  bool copyvideo = false;
357  bool copyaudio = false;
358 
359  QString vidsetting = nullptr;
360  QString audsetting = nullptr;
361  QString vidfilters = nullptr;
362 
363  QSize buf_size = GetPlayer()->GetVideoBufferSize();
364  int video_width = buf_size.width();
365  int video_height = buf_size.height();
366 
367  if (video_height == 1088) {
368  LOG(VB_GENERAL, LOG_NOTICE,
369  "Found video height of 1088. This is unusual and "
370  "more than likely the video is actually 1080 so mythtranscode "
371  "will treat it as such.");
372  }
373 
374  DecoderBase* dec = GetPlayer()->GetDecoder();
375  float video_aspect = dec ? dec->GetVideoAspect() : 4.0F / 3.0F;
376  float video_frame_rate = GetPlayer()->GetFrameRate();
377  int newWidth = video_width;
378  int newHeight = video_height;
379  bool halfFramerate = false;
380  bool skippedLastFrame = false;
381 
382  m_kfaTable = new vector<struct kfatable_entry>;
383 
384  if (m_avfMode)
385  {
386  newWidth = m_cmdWidth;
387  newHeight = m_cmdHeight;
388 
389  // Absolutely no purpose is served by scaling video up beyond it's
390  // original resolution, quality is degraded, transcoding is
391  // slower and in future we may wish to scale bitrate according to
392  // resolution, so it would also waste bandwidth (when streaming)
393  //
394  // This change could be said to apply for all transcoding, but for now
395  // we're limiting it to HLS where it's uncontroversial
396  if (m_hlsMode)
397  {
398 // if (newWidth > video_width)
399 // newWidth = video_width;
400  if (newHeight > video_height)
401  {
402  newHeight = video_height;
403  newWidth = 0;
404  }
405  }
406 
407  // TODO: is this necessary? It got commented out, but may still be
408  // needed.
409  // int actualHeight = (video_height == 1088 ? 1080 : video_height);
410 
411  // If height or width are 0, then we need to calculate them
412  if (newHeight == 0 && newWidth > 0)
413  newHeight = (int)(1.0F * newWidth / video_aspect);
414  else if (newWidth == 0 && newHeight > 0)
415  newWidth = (int)(1.0F * newHeight * video_aspect);
416  else if (newWidth == 0 && newHeight == 0)
417  {
418  newHeight = 480;
419  newWidth = (int)(1.0F * 480 * video_aspect);
420  if (newWidth > 640)
421  {
422  newWidth = 640;
423  newHeight = (int)(1.0F * 640 / video_aspect);
424  }
425  }
426 
427  // make sure dimensions are valid for MPEG codecs
428  newHeight = (newHeight + 15) & ~0xF;
429  newWidth = (newWidth + 15) & ~0xF;
430 
431  avfw = new AVFormatWriter();
432  if (!avfw)
433  {
434  LOG(VB_GENERAL, LOG_ERR,
435  "Transcoding aborted, error creating AVFormatWriter.");
436  SetPlayerContext(nullptr);
437  delete hls;
438  return REENCODE_ERROR;
439  }
440 
442  avfw->SetHeight(newHeight);
443  avfw->SetWidth(newWidth);
444  avfw->SetAspect(video_aspect);
446  avfw->SetAudioChannels(arb->m_channels);
448  avfw->SetAudioFormat(FORMAT_S16);
449 
450  if (m_hlsMode)
451  {
452 
453  if (m_hlsStreamID == -1)
454  {
455  hls = new 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  delete avfw;
465  delete avfw2;
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 = new AVFormatWriter();
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");
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  delete hls;
501  delete avfw;
502  delete avfw2;
503  return REENCODE_ERROR;
504  }
505 
506  if (video_frame_rate > 30)
507  {
508  halfFramerate = true;
509  avfw->SetFramerate(video_frame_rate/2);
510 
511  if (avfw2)
512  avfw2->SetFramerate(video_frame_rate/2);
513 
514  hlsSegmentSize = (int)(segmentSize * video_frame_rate / 2);
515  }
516  else
517  {
518  avfw->SetFramerate(video_frame_rate);
519 
520  if (avfw2)
521  avfw2->SetFramerate(video_frame_rate);
522 
523  hlsSegmentSize = (int)(segmentSize * video_frame_rate);
524  }
525 
526  avfw->SetKeyFrameDist(30);
527  if (avfw2)
528  avfw2->SetKeyFrameDist(30);
529 
530  hls->AddSegment();
531  avfw->SetFilename(hls->GetCurrentFilename());
532  if (avfw2)
533  avfw2->SetFilename(hls->GetCurrentFilename(true));
534  }
535  else
536  {
540  avfw->SetFilename(outputname);
541  avfw->SetFramerate(video_frame_rate);
542  avfw->SetKeyFrameDist(30);
543  }
544 
545  int threads = gCoreContext->GetNumSetting("HTTPLiveStreamThreads", 2);
546  QString preset = gCoreContext->GetSetting("HTTPLiveStreamPreset", "veryfast");
547  QString tune = gCoreContext->GetSetting("HTTPLiveStreamTune", "film");
548 
549  LOG(VB_GENERAL, LOG_NOTICE,
550  QString("x264 HLS using: %1 threads, '%2' profile and '%3' tune")
551  .arg(threads).arg(preset).arg(tune));
552 
553  avfw->SetThreadCount(threads);
554  avfw->SetEncodingPreset(preset);
555  avfw->SetEncodingTune(tune);
556 
557  if (avfw2)
558  avfw2->SetThreadCount(1);
559 
560  if (!avfw->Init())
561  {
562  LOG(VB_GENERAL, LOG_ERR, "avfw->Init() failed");
563  SetPlayerContext(nullptr);
564  delete hls;
565  delete avfw;
566  delete avfw2;
567  return REENCODE_ERROR;
568  }
569 
570  if (!avfw->OpenFile())
571  {
572  LOG(VB_GENERAL, LOG_ERR, "avfw->OpenFile() failed");
573  SetPlayerContext(nullptr);
574  delete hls;
575  delete avfw;
576  delete avfw2;
577  return REENCODE_ERROR;
578  }
579 
580  if (avfw2 && !avfw2->Init())
581  {
582  LOG(VB_GENERAL, LOG_ERR, "avfw2->Init() failed");
583  SetPlayerContext(nullptr);
584  delete hls;
585  delete avfw;
586  delete avfw2;
587  return REENCODE_ERROR;
588  }
589 
590  if (avfw2 && !avfw2->OpenFile())
591  {
592  LOG(VB_GENERAL, LOG_ERR, "avfw2->OpenFile() failed");
593  SetPlayerContext(nullptr);
594  delete hls;
595  delete avfw;
596  delete avfw2;
597  return REENCODE_ERROR;
598  }
599 
600  arb->m_audioFrameSize = avfw->GetAudioFrameSize() * arb->m_channels * 2;
601  }
602 #if CONFIG_LIBMP3LAME
603  else if (fifodir.isEmpty())
604  {
605  if (!GetProfile(profileName, encodingType, video_height,
606  (int)round(video_frame_rate))) {
607  LOG(VB_GENERAL, LOG_ERR, "Transcoding aborted, no profile found.");
608  SetPlayerContext(nullptr);
609  return REENCODE_ERROR;
610  }
611 
612  // For overriding settings on the command line
613  QMap<QString, QString> recorderOptionsMap;
614  if (!m_recorderOptions.isEmpty())
615  {
616  QStringList options = m_recorderOptions
617  .split(",", QString::SkipEmptyParts);
618  int loop = 0;
619  while (loop < options.size())
620  {
621  QStringList tokens = options[loop].split("=");
622  if (tokens.length() < 2)
623  {
624  LOG(VB_GENERAL, LOG_ERR, "Transcoding aborted, invalid option settings.");
625  return REENCODE_ERROR;
626  }
627  recorderOptionsMap[tokens[0]] = tokens[1];
628 
629  loop++;
630  }
631  }
632 
633  vidsetting = get_str_option(m_recProfile, "videocodec");
634  audsetting = get_str_option(m_recProfile, "audiocodec");
635  vidfilters = get_str_option(m_recProfile, "transcodefilters");
636 
637  if (encodingType == "MPEG-2" &&
638  get_bool_option(m_recProfile, "transcodelossless"))
639  {
640  LOG(VB_GENERAL, LOG_NOTICE, "Switching to MPEG-2 transcoder.");
641  SetPlayerContext(nullptr);
642  return REENCODE_MPEG2TRANS;
643  }
644 
645  // Recorder setup
646  if (get_bool_option(m_recProfile, "transcodelossless"))
647  {
648  vidsetting = encodingType;
649  audsetting = "MP3";
650  }
651  else if (get_bool_option(m_recProfile, "transcoderesize"))
652  {
653  int actualHeight = (video_height == 1088 ? 1080 : video_height);
654 
655  //GetPlayer()->SetVideoFilters(vidfilters);
656  newWidth = get_int_option(m_recProfile, "width");
657  newHeight = get_int_option(m_recProfile, "height");
658 
659  // If height or width are 0, then we need to calculate them
660  if (newHeight == 0 && newWidth > 0)
661  newHeight = (int)(1.0 * newWidth * actualHeight / video_width);
662  else if (newWidth == 0 && newHeight > 0)
663  newWidth = (int)(1.0 * newHeight * video_width / actualHeight);
664  else if (newWidth == 0 && newHeight == 0)
665  {
666  newHeight = 480;
667  newWidth = (int)(1.0 * 480 * video_width / actualHeight);
668  if (newWidth > 640)
669  {
670  newWidth = 640;
671  newHeight = (int)(1.0 * 640 * actualHeight / video_width);
672  }
673  }
674 
675  if (encodingType.startsWith("mpeg", Qt::CaseInsensitive))
676  {
677  // make sure dimensions are valid for MPEG codecs
678  newHeight = (newHeight + 15) & ~0xF;
679  newWidth = (newWidth + 15) & ~0xF;
680  }
681 
682  LOG(VB_GENERAL, LOG_INFO, QString("Resizing from %1x%2 to %3x%4")
683  .arg(video_width).arg(video_height)
684  .arg(newWidth).arg(newHeight));
685  }
686  else // lossy and no resize
687  {
688  //GetPlayer()->SetVideoFilters(vidfilters);
689  }
690 
691  // this is ripped from tv_rec SetupRecording. It'd be nice to merge
692  m_nvr->SetOption("inpixfmt", FMT_YV12);
693 
694  m_nvr->SetOption("width", newWidth);
695  m_nvr->SetOption("height", newHeight);
696 
697  m_nvr->SetOption("tvformat", gCoreContext->GetSetting("TVFormat"));
698  m_nvr->SetOption("vbiformat", gCoreContext->GetSetting("VbiFormat"));
699 
700  m_nvr->SetFrameRate(video_frame_rate);
701  m_nvr->SetVideoAspect(video_aspect);
702  m_nvr->SetTranscoding(true);
703 
704  if ((vidsetting == "MPEG-4") ||
705  (recorderOptionsMap["videocodec"] == "mpeg4"))
706  {
707  m_nvr->SetOption("videocodec", "mpeg4");
708 
709  m_nvr->SetIntOption(m_recProfile, "mpeg4bitrate");
710  m_nvr->SetIntOption(m_recProfile, "scalebitrate");
711  m_nvr->SetIntOption(m_recProfile, "mpeg4maxquality");
712  m_nvr->SetIntOption(m_recProfile, "mpeg4minquality");
713  m_nvr->SetIntOption(m_recProfile, "mpeg4qualdiff");
714  m_nvr->SetIntOption(m_recProfile, "mpeg4optionvhq");
715  m_nvr->SetIntOption(m_recProfile, "mpeg4option4mv");
716 #ifdef USING_FFMPEG_THREADS
717  m_nvr->SetIntOption(m_recProfile, "encodingthreadcount");
718 #endif
719  }
720  else if ((vidsetting == "MPEG-2") ||
721  (recorderOptionsMap["videocodec"] == "mpeg2video"))
722  {
723  m_nvr->SetOption("videocodec", "mpeg2video");
724 
725  m_nvr->SetIntOption(m_recProfile, "mpeg2bitrate");
726  m_nvr->SetIntOption(m_recProfile, "scalebitrate");
727 #ifdef USING_FFMPEG_THREADS
728  m_nvr->SetIntOption(m_recProfile, "encodingthreadcount");
729 #endif
730  }
731  else if ((vidsetting == "RTjpeg") ||
732  (recorderOptionsMap["videocodec"] == "rtjpeg"))
733  {
734  m_nvr->SetOption("videocodec", "rtjpeg");
735  m_nvr->SetIntOption(m_recProfile, "rtjpegquality");
736  m_nvr->SetIntOption(m_recProfile, "rtjpegchromafilter");
737  m_nvr->SetIntOption(m_recProfile, "rtjpeglumafilter");
738  }
739  else if (vidsetting.isEmpty())
740  {
741  LOG(VB_GENERAL, LOG_ERR, "No video information found!");
742  LOG(VB_GENERAL, LOG_ERR, "Please ensure that recording profiles "
743  "for the transcoder are set");
744  SetPlayerContext(nullptr);
745  return REENCODE_ERROR;
746  }
747  else
748  {
749  LOG(VB_GENERAL, LOG_ERR,
750  QString("Unknown video codec: %1").arg(vidsetting));
751  SetPlayerContext(nullptr);
752  return REENCODE_ERROR;
753  }
754 
755  m_nvr->SetOption("samplerate", arb->m_eff_audiorate);
756  if (audsetting == "MP3")
757  {
758  m_nvr->SetOption("audiocompression", 1);
759  m_nvr->SetIntOption(m_recProfile, "mp3quality");
760  copyaudio = true;
761  }
762  else if (audsetting == "Uncompressed")
763  {
764  m_nvr->SetOption("audiocompression", 0);
765  }
766  else
767  {
768  LOG(VB_GENERAL, LOG_ERR,
769  QString("Unknown audio codec: %1").arg(audsetting));
770  }
771 
772  m_nvr->AudioInit(true);
773 
774  // For overriding settings on the command line
775  if (!recorderOptionsMap.empty())
776  {
777  QMap<QString, QString>::Iterator it;
778  QString key;
779  QString value;
780  for (it = recorderOptionsMap.begin();
781  it != recorderOptionsMap.end(); ++it)
782  {
783  key = it.key();
784  value = *it;
785 
786  LOG(VB_GENERAL, LOG_NOTICE,
787  QString("Forcing Recorder option '%1' to '%2'")
788  .arg(key).arg(value));
789 
790  if (value.contains(QRegExp("[^0-9]")))
791  m_nvr->SetOption(key, value);
792  else
793  m_nvr->SetOption(key, value.toInt());
794 
795  if (key == "width")
796  newWidth = (value.toInt() + 15) & ~0xF;
797  else if (key == "height")
798  newHeight = (value.toInt() + 15) & ~0xF;
799  else if (key == "videocodec")
800  {
801  if (value == "mpeg4")
802  vidsetting = "MPEG-4";
803  else if (value == "mpeg2video")
804  vidsetting = "MPEG-2";
805  else if (value == "rtjpeg")
806  vidsetting = "RTjpeg";
807  }
808  }
809  }
810 
811  if ((vidsetting == "MPEG-4") ||
812  (vidsetting == "MPEG-2"))
813  m_nvr->SetupAVCodecVideo();
814  else if (vidsetting == "RTjpeg")
815  m_nvr->SetupRTjpeg();
816 
817  m_outRingBuffer = RingBuffer::Create(outputname, true, false);
818  m_nvr->SetRingBuffer(m_outRingBuffer);
819  m_nvr->WriteHeader();
820  m_nvr->StreamAllocate();
821  }
822 
823  if (vidsetting == encodingType && !framecontrol && !m_avfMode &&
824  fifodir.isEmpty() && honorCutList &&
825  video_width == newWidth && video_height == newHeight)
826  {
827  copyvideo = true;
828  LOG(VB_GENERAL, LOG_INFO, "Reencoding video in 'raw' mode");
829  }
830 #endif // CONFIG_LIBMP3LAME
831 
832  if (honorCutList && !deleteMap.empty())
833  {
834  if (cleanCut)
835  {
836  // Have the player seek only part of the way
837  // through a cut, and then use the cutter to
838  // discard the rest
839  cutter = new Cutter();
840  cutter->SetCutList(deleteMap, m_ctx);
841  GetPlayer()->SetCutList(cutter->AdjustedCutList());
842  }
843  else
844  {
845  // Have the player apply the cut list
846  GetPlayer()->SetCutList(deleteMap);
847  }
848  }
849 
850  GetPlayer()->InitForTranscode(copyaudio, copyvideo);
851  if (GetPlayer()->IsErrored())
852  {
853  LOG(VB_GENERAL, LOG_ERR,
854  "Unable to initialize MythPlayer for Transcode");
855  SetPlayerContext(nullptr);
856  delete hls;
857  delete avfw;
858  delete avfw2;
859  return REENCODE_ERROR;
860  }
861 
862  // must come after InitForTranscode - which creates the VideoOutput instance
863  if (m_hlsMode)
865 
866  VideoFrame frame {};
867  memset(&frame, 0, sizeof(frame));
868  // Do not use padding when compressing to RTjpeg or when in fifomode.
869  // The RTjpeg compressor doesn't know how to handle strides different to
870  // video width.
871  // cppcheck-suppress knownConditionTrueFalse
872  bool nonAligned = vidsetting == "RTjpeg" || !fifodir.isEmpty();
873  bool rescale =
874  (video_width != newWidth) || (video_height != newHeight)
875  || nonAligned;
876 
877  if (rescale)
878  {
879  size_t newSize = 0;
880  if (nonAligned)
881  {
882  // Set a stride identical to actual width, to ease fifo post-conversion process.
883  // 1080i/p video is actually 1088 because of the 16x16 blocks so
884  // we have to fudge the output size here. nuvexport knows how to handle
885  // this and as of right now it is the only app that uses the fifo ability.
886  newSize = GetBufferSize(FMT_YV12, video_width, video_height == 1080 ? 1088 : video_height, 0 /* aligned */);
887  }
888  else
889  {
890  newSize = GetBufferSize(FMT_YV12, newWidth, newHeight);
891  }
892  unsigned char *newFrame = GetAlignedBuffer(newSize);
893  if (!newFrame)
894  {
895  // OOM
896  delete hls;
897  return REENCODE_ERROR;
898  }
899  if (nonAligned)
900  {
901  // Set a stride identical to actual width, to ease fifo post-conversion process.
902  init(&frame, FMT_YV12, newFrame, video_width, video_height,
903  static_cast<int>(newSize), nullptr, nullptr, -1, -1, 0 /* aligned */);
904  }
905  else
906  {
907  // use default stride size.
908  init(&frame, FMT_YV12, newFrame, newWidth, newHeight, static_cast<int>(newSize));
909  }
910  }
911 
912  if (!fifodir.isEmpty())
913  {
914  AudioPlayer *aplayer = GetPlayer()->GetAudio();
915  const char *audio_codec_name = "unknown";
916 
917  switch(aplayer->GetCodec())
918  {
919  case AV_CODEC_ID_AC3:
920  audio_codec_name = "ac3";
921  break;
922  case AV_CODEC_ID_EAC3:
923  audio_codec_name = "eac3";
924  break;
925  case AV_CODEC_ID_DTS:
926  audio_codec_name = "dts";
927  break;
928  case AV_CODEC_ID_TRUEHD:
929  audio_codec_name = "truehd";
930  break;
931  case AV_CODEC_ID_MP3:
932  audio_codec_name = "mp3";
933  break;
934  case AV_CODEC_ID_MP2:
935  audio_codec_name = "mp2";
936  break;
937  case AV_CODEC_ID_AAC:
938  audio_codec_name = "aac";
939  break;
940  case AV_CODEC_ID_AAC_LATM:
941  audio_codec_name = "aac_latm";
942  break;
943  default:
944  audio_codec_name = "unknown";
945  }
946 
947  if (!arb->m_passthru)
948  audio_codec_name = "raw";
949 
950  // If cutlist is used then get info on first uncut frame
951  if (honorCutList && fifo_info)
952  {
953  bool is_key = false;
954  int did_ff = 0;
955  GetPlayer()->TranscodeGetNextFrame(did_ff, is_key, true);
956 
957  QSize buf_size2 = GetPlayer()->GetVideoBufferSize();
958  video_width = buf_size2.width();
959  video_height = buf_size2.height();
960  video_aspect = GetPlayer()->GetVideoAspect();
961  video_frame_rate = GetPlayer()->GetFrameRate();
962  }
963 
964  // Display details of the format of the fifo data.
965  LOG(VB_GENERAL, LOG_INFO,
966  QString("FifoVideoWidth %1").arg(video_width));
967  LOG(VB_GENERAL, LOG_INFO,
968  QString("FifoVideoHeight %1").arg(video_height));
969  LOG(VB_GENERAL, LOG_INFO,
970  QString("FifoVideoAspectRatio %1").arg(video_aspect));
971  LOG(VB_GENERAL, LOG_INFO,
972  QString("FifoVideoFrameRate %1").arg(video_frame_rate));
973  LOG(VB_GENERAL, LOG_INFO,
974  QString("FifoAudioFormat %1").arg(audio_codec_name));
975  LOG(VB_GENERAL, LOG_INFO,
976  QString("FifoAudioChannels %1").arg(arb->m_channels));
977  LOG(VB_GENERAL, LOG_INFO,
978  QString("FifoAudioSampleRate %1").arg(arb->m_eff_audiorate));
979 
980  if(fifo_info)
981  {
982  // Request was for just the format of fifo data, not for
983  // the actual transcode, so stop here.
984  unlink(outputname.toLocal8Bit().constData());
985  SetPlayerContext(nullptr);
986  if (rescale)
987  {
988  av_freep(&frame.buf);
989  }
990  delete hls;
991  return REENCODE_OK;
992  }
993 
994  QString audfifo = fifodir + QString("/audout");
995  QString vidfifo = fifodir + QString("/vidout");
996  int audio_size = arb->m_eff_audiorate * arb->m_bytes_per_frame;
997  // framecontrol is true if we want to enforce fifo sync.
998  if (framecontrol)
999  LOG(VB_GENERAL, LOG_INFO, "Enforcing sync on fifos");
1000  m_fifow = new FIFOWriter(2, framecontrol);
1001 
1002  if (!m_fifow->FIFOInit(0, QString("video"), vidfifo, frame.size, 50) ||
1003  !m_fifow->FIFOInit(1, QString("audio"), audfifo, audio_size, 25))
1004  {
1005  LOG(VB_GENERAL, LOG_ERR,
1006  "Error initializing fifo writer. Aborting");
1007  unlink(outputname.toLocal8Bit().constData());
1008  SetPlayerContext(nullptr);
1009  if (rescale)
1010  {
1011  av_freep(&frame.buf);
1012  }
1013  delete hls;
1014  return REENCODE_ERROR;
1015  }
1016  LOG(VB_GENERAL, LOG_INFO,
1017  QString("Video %1x%2@%3fps Audio rate: %4")
1018  .arg(video_width).arg(video_height)
1019  .arg(video_frame_rate)
1020  .arg(arb->m_eff_audiorate));
1021  LOG(VB_GENERAL, LOG_INFO, "Created fifos. Waiting for connection.");
1022  }
1023 
1024 #if CONFIG_LIBMP3LAME
1025  bool forceKeyFrames = (m_fifow == nullptr) ? framecontrol : false;
1026  bool writekeyframe = true;
1027  long lastKeyFrame = 0;
1028  int num_keyframes = 0;
1029 #endif
1030 
1031  frm_dir_map_t::iterator dm_iter;
1032 
1033  int did_ff = 0;
1034 
1035  long curFrameNum = 0;
1036  frame.frameNumber = 1;
1037  long totalAudio = 0;
1038  int dropvideo = 0;
1039  // timecode of the last read video frame in input time
1040  long long lasttimecode = 0;
1041  // timecode of the last write video frame in input or output time
1042  long long lastWrittenTime = 0;
1043  // delta between the same video frame in input and output due to applying the cut list
1044  long long timecodeOffset = 0;
1045 
1046  float rateTimeConv = arb->m_eff_audiorate / 1000.0F;
1047  float vidFrameTime = 1000.0F / video_frame_rate;
1048  int wait_recover = 0;
1049  MythVideoOutput *videoOutput = GetPlayer()->GetVideoOutput();
1050  bool is_key = false;
1051  bool first_loop = true;
1052  AVFrame imageIn;
1053  AVFrame imageOut;
1054  struct SwsContext *scontext = nullptr;
1055 
1056  if (m_fifow)
1057  LOG(VB_GENERAL, LOG_INFO, "Dumping Video and Audio data to fifos");
1058  else if (copyaudio)
1059  LOG(VB_GENERAL, LOG_INFO, "Copying Audio while transcoding Video");
1060  else if (m_hlsMode)
1061  LOG(VB_GENERAL, LOG_INFO, "Transcoding for HTTP Live Streaming");
1062  else if (m_avfMode)
1063  LOG(VB_GENERAL, LOG_INFO, "Transcoding to libavformat container");
1064  else
1065  LOG(VB_GENERAL, LOG_INFO, "Transcoding Video and Audio");
1066 
1067  auto *videoBuffer =
1068  new VideoDecodeBuffer(GetPlayer(), videoOutput, honorCutList);
1069  MThreadPool::globalInstance()->start(videoBuffer, "VideoDecodeBuffer");
1070 
1071  QElapsedTimer flagTime;
1072  flagTime.start();
1073 
1074  if (cutter)
1075  cutter->Activate(vidFrameTime * rateTimeConv, total_frame_count);
1076 
1077  bool stopSignalled = false;
1078  VideoFrame *lastDecode = nullptr;
1079 
1080  if (hls)
1081  {
1083  hls->UpdateStatusMessage("Transcoding");
1084  }
1085 
1086  while ((!stopSignalled) &&
1087  (lastDecode = videoBuffer->GetFrame(did_ff, is_key)))
1088  {
1089  if (first_loop)
1090  {
1091  copyaudio = GetPlayer()->GetRawAudioState();
1092  first_loop = false;
1093  }
1094 
1095  float new_aspect = lastDecode->aspect;
1096 
1097  if (cutter)
1098  cutter->NewFrame(lastDecode->frameNumber);
1099 
1100 // frame timecode is on input time base
1101  frame.timecode = lastDecode->timecode;
1102 
1103  // if the timecode jumps backwards just use the last frame's timecode plus the duration of a frame
1104  if (frame.timecode < lasttimecode)
1105  frame.timecode = (long long)(lasttimecode + vidFrameTime);
1106 
1107  if (m_fifow)
1108  {
1109  AVPictureFill(&imageIn, lastDecode);
1110  AVPictureFill(&imageOut, &frame);
1111 
1112  scontext = sws_getCachedContext(scontext,
1113  lastDecode->width, lastDecode->height, FrameTypeToPixelFormat(lastDecode->codec),
1114  frame.width, frame.height, FrameTypeToPixelFormat(frame.codec),
1115  SWS_FAST_BILINEAR, nullptr, nullptr, nullptr);
1116  // Typically, wee aren't rescaling per say, we're just correcting the stride set by the decoder.
1117  // However, it allows to properly handle recordings that see their resolution change half-way.
1118  sws_scale(scontext, imageIn.data, imageIn.linesize, 0,
1119  lastDecode->height, imageOut.data, imageOut.linesize);
1120 
1121  totalAudio += arb->GetSamples(frame.timecode);
1122  int audbufTime = (int)(totalAudio / rateTimeConv);
1123  int auddelta = frame.timecode - audbufTime;
1124  int vidTime = lroundf(curFrameNum * vidFrameTime);
1125  int viddelta = frame.timecode - vidTime;
1126  int delta = viddelta - auddelta;
1127  int absdelta = delta < 0 ? -delta : delta;
1128  if (absdelta < 500 && absdelta >= vidFrameTime)
1129  {
1130  QString msg = QString("Audio is %1ms %2 video at # %3: "
1131  "auddelta=%4, viddelta=%5")
1132  .arg(absdelta)
1133  .arg(((delta > 0) ? "ahead of" : "behind"))
1134  .arg((int)curFrameNum)
1135  .arg(auddelta)
1136  .arg(viddelta);
1137  LOG(VB_GENERAL, LOG_INFO, msg);
1138  dropvideo = (delta > 0) ? 1 : -1;
1139  wait_recover = 0;
1140  }
1141  else if (delta >= 500 && delta < 10000)
1142  {
1143  if (wait_recover == 0)
1144  {
1145  dropvideo = 5;
1146  wait_recover = 6;
1147  }
1148  else if (wait_recover == 1)
1149  {
1150  // Video is badly lagging. Try to catch up.
1151  int count = 0;
1152  while (delta > vidFrameTime)
1153  {
1154  if (!cutter || !cutter->InhibitDummyFrame())
1155  m_fifow->FIFOWrite(0, frame.buf, frame.size);
1156 
1157  count++;
1158  delta -= (int)vidFrameTime;
1159  }
1160  QString msg = QString("Added %1 blank video frames")
1161  .arg(count);
1162  LOG(VB_GENERAL, LOG_INFO, msg);
1163  curFrameNum += count;
1164  dropvideo = 0;
1165  wait_recover = 0;
1166  }
1167  else
1168  wait_recover--;
1169  }
1170  else
1171  {
1172  dropvideo = 0;
1173  wait_recover = 0;
1174  }
1175 
1176 #if 0
1177  int buflen = (int)(arb->audiobuffer_len / rateTimeConv);
1178  LOG(VB_GENERAL, LOG_DEBUG,
1179  QString("%1: video time: %2 audio time: %3 "
1180  "buf: %4 exp: %5 delta: %6")
1181  .arg(curFrameNum) .arg(frame.timecode)
1182  .arg(arb->last_audiotime) .arg(buflen) .arg(audbufTime)
1183  .arg(delta));
1184 #endif
1185  AudioBuffer *ab = nullptr;
1186  while ((ab = arb->GetData(frame.timecode)) != nullptr)
1187  {
1188  if (!cutter ||
1189  !cutter->InhibitUseAudioFrames(ab->m_frames, &totalAudio))
1190  m_fifow->FIFOWrite(1, ab->data(), ab->size());
1191 
1192  delete ab;
1193  }
1194 
1195  if (dropvideo < 0)
1196  {
1197  if (cutter && cutter->InhibitDropFrame())
1198  m_fifow->FIFOWrite(0, frame.buf, frame.size);
1199 
1200  LOG(VB_GENERAL, LOG_INFO, "Dropping video frame");
1201  dropvideo++;
1202  curFrameNum--;
1203  }
1204  else
1205  {
1206  if (!cutter || !cutter->InhibitUseVideoFrame())
1207  m_fifow->FIFOWrite(0, frame.buf, frame.size);
1208 
1209  if (dropvideo)
1210  {
1211  if (!cutter || !cutter->InhibitDummyFrame())
1212  m_fifow->FIFOWrite(0, frame.buf, frame.size);
1213 
1214  curFrameNum++;
1215  dropvideo--;
1216  }
1217  }
1218  videoOutput->DoneDisplayingFrame(lastDecode);
1220  lasttimecode = frame.timecode;
1221  }
1222  else if (copyaudio)
1223  {
1224 #if CONFIG_LIBMP3LAME
1225  // Encoding from NuppelVideo to NuppelVideo with MP3 audio
1226  // So let's not decode/reencode audio
1227  if (!GetPlayer()->GetRawAudioState())
1228  {
1229  // The Raw state changed during decode. This is not good
1230  LOG(VB_GENERAL, LOG_ERR, "Transcoding aborted, MythPlayer "
1231  "is not in raw audio mode.");
1232 
1233  unlink(outputname.toLocal8Bit().constData());
1234  if (rescale)
1235  {
1236  av_freep(&frame.buf);
1237  }
1238  SetPlayerContext(nullptr);
1239  if (videoBuffer)
1240  videoBuffer->stop();
1241  if (hls)
1242  {
1244  hls->UpdateStatusMessage("Transcoding Errored");
1245  delete hls;
1246  }
1247  return REENCODE_ERROR;
1248  }
1249 
1250  if (forceKeyFrames)
1251  writekeyframe = true;
1252  else
1253  {
1254  writekeyframe = is_key;
1255  if (writekeyframe)
1256  {
1257  // Currently, we don't create new sync frames,
1258  // (though we do create new 'I' frames), so we mark
1259  // the key-frames before deciding whether we need a
1260  // new 'I' frame.
1261 
1262  //need to correct the frame# and timecode here
1263  // Question: Is it necessary to change the timecodes?
1264  long sync_offset =
1265  GetPlayer()->UpdateStoredFrameNum(curFrameNum);
1266  m_nvr->UpdateSeekTable(num_keyframes, sync_offset);
1267  ReencoderAddKFA(curFrameNum, lastKeyFrame, num_keyframes);
1268  num_keyframes++;
1269  lastKeyFrame = curFrameNum;
1270 
1271  if (did_ff)
1272  did_ff = 0;
1273  }
1274  }
1275 
1276  if (did_ff == 1)
1277  {
1278  timecodeOffset +=
1279  (frame.timecode - lasttimecode - (int)vidFrameTime);
1280  }
1281  lasttimecode = frame.timecode;
1282 // from here on the timecode is on the output time base
1283  frame.timecode -= timecodeOffset;
1284 
1285  if (!GetPlayer()->WriteStoredData(
1286  m_outRingBuffer, (did_ff == 0), timecodeOffset))
1287  {
1288  if (video_aspect != new_aspect)
1289  {
1290  video_aspect = new_aspect;
1291  m_nvr->SetNewVideoParams(video_aspect);
1292  }
1293 
1294  QSize buf_size3 = GetPlayer()->GetVideoBufferSize();
1295 
1296  if (video_width != buf_size3.width() ||
1297  video_height != buf_size3.height())
1298  {
1299  video_width = buf_size3.width();
1300  video_height = buf_size3.height();
1301 
1302  LOG(VB_GENERAL, LOG_INFO,
1303  QString("Resizing from %1x%2 to %3x%4")
1304  .arg(video_width).arg(video_height)
1305  .arg(newWidth).arg(newHeight));
1306 
1307  }
1308 
1309  if (did_ff == 1)
1310  {
1311  // Create a new 'I' frame if we just processed a cut.
1312  did_ff = 2;
1313  writekeyframe = true;
1314  }
1315 
1316  if (rescale)
1317  {
1318  AVPictureFill(&imageIn, lastDecode);
1319  AVPictureFill(&imageOut, &frame);
1320 
1321  int bottomBand = (lastDecode->height == 1088) ? 8 : 0;
1322  scontext = sws_getCachedContext(scontext,
1323  lastDecode->width, lastDecode->height, FrameTypeToPixelFormat(lastDecode->codec),
1324  frame.width, frame.height, FrameTypeToPixelFormat(frame.codec),
1325  SWS_FAST_BILINEAR, nullptr, nullptr, nullptr);
1326 
1327  sws_scale(scontext, imageIn.data, imageIn.linesize, 0,
1328  lastDecode->height - bottomBand,
1329  imageOut.data, imageOut.linesize);
1330  }
1331 
1332  m_nvr->WriteVideo(rescale ? &frame : lastDecode, true, writekeyframe);
1333  }
1335 #else
1336  LOG(VB_GENERAL, LOG_ERR,
1337  "Not compiled with libmp3lame support. Should never get here");
1338  return REENCODE_ERROR;
1339 #endif // CONFIG_LIBMP3LAME
1340  }
1341  else
1342  {
1343  if (did_ff == 1)
1344  {
1345  did_ff = 2;
1346  timecodeOffset +=
1347  (frame.timecode - lasttimecode - (int)vidFrameTime);
1348  }
1349 
1350  if (video_aspect != new_aspect)
1351  {
1352  video_aspect = new_aspect;
1353 #if CONFIG_LIBMP3LAME
1354  if (m_nvr)
1355  m_nvr->SetNewVideoParams(video_aspect);
1356 #endif
1357  }
1358 
1359 
1360  QSize buf_size4 = GetPlayer()->GetVideoBufferSize();
1361 
1362  if (video_width != buf_size4.width() ||
1363  video_height != buf_size4.height())
1364  {
1365  video_width = buf_size4.width();
1366  video_height = buf_size4.height();
1367 
1368  LOG(VB_GENERAL, LOG_INFO,
1369  QString("Resizing from %1x%2 to %3x%4")
1370  .arg(video_width).arg(video_height)
1371  .arg(newWidth).arg(newHeight));
1372  }
1373 
1374  if (rescale)
1375  {
1376  AVPictureFill(&imageIn, lastDecode);
1377  AVPictureFill(&imageOut, &frame);
1378 
1379  int bottomBand = (lastDecode->height == 1088) ? 8 : 0;
1380  scontext = sws_getCachedContext(scontext,
1381  lastDecode->width, lastDecode->height, FrameTypeToPixelFormat(lastDecode->codec),
1382  frame.width, frame.height, FrameTypeToPixelFormat(frame.codec),
1383  SWS_FAST_BILINEAR, nullptr, nullptr, nullptr);
1384 
1385  sws_scale(scontext, imageIn.data, imageIn.linesize, 0,
1386  lastDecode->height - bottomBand,
1387  imageOut.data, imageOut.linesize);
1388  }
1389 
1390  // audio is fully decoded, so we need to reencode it
1391  AudioBuffer *ab = nullptr;
1392  while ((ab = arb->GetData(lastWrittenTime)) != nullptr)
1393  {
1394  auto *buf = (unsigned char *)ab->data();
1395  if (m_avfMode)
1396  {
1397  if (did_ff != 1)
1398  {
1399  long long tc = ab->m_time - timecodeOffset;
1400  avfw->WriteAudioFrame(buf, audioFrame, tc);
1401 
1402  if (avfw2)
1403  {
1404  if ((avfw2->GetTimecodeOffset() == -1) &&
1405  (avfw->GetTimecodeOffset() != -1))
1406  {
1407  avfw2->SetTimecodeOffset(
1408  avfw->GetTimecodeOffset());
1409  }
1410 
1411  tc = ab->m_time - timecodeOffset;
1412  avfw2->WriteAudioFrame(buf, audioFrame, tc);
1413  }
1414 
1415  ++audioFrame;
1416  }
1417  }
1418 #if CONFIG_LIBMP3LAME
1419  else
1420  {
1421  m_nvr->SetOption("audioframesize", ab->size());
1422  m_nvr->WriteAudio(buf, audioFrame++,
1423  ab->m_time - timecodeOffset);
1424  if (m_nvr->IsErrored())
1425  {
1426  LOG(VB_GENERAL, LOG_ERR,
1427  "Transcode: Encountered irrecoverable error in "
1428  "NVR::WriteAudio");
1429 
1430  if (rescale)
1431  {
1432  av_freep(&frame.buf);
1433  }
1434  SetPlayerContext(nullptr);
1435  if (videoBuffer)
1436  videoBuffer->stop();
1437  delete ab;
1438  delete hls; // HLS isn't actually going to be running here
1439  return REENCODE_ERROR;
1440  }
1441  }
1442 #endif
1443  delete ab;
1444  }
1445 
1446  if (!m_avfMode)
1447  {
1448 #if CONFIG_LIBMP3LAME
1449  GetPlayer()->GetCC608Reader()->
1450  TranscodeWriteText(&TranscodeWriteText, (void *)(m_nvr));
1451 #else
1452  LOG(VB_GENERAL, LOG_ERR,
1453  "Not compiled with libmp3lame support");
1454  return REENCODE_ERROR;
1455 #endif
1456  }
1457  lasttimecode = frame.timecode;
1458  frame.timecode -= timecodeOffset;
1459 
1460  if (m_avfMode)
1461  {
1462  if (halfFramerate && !skippedLastFrame)
1463  {
1464  skippedLastFrame = true;
1465  }
1466  else
1467  {
1468  skippedLastFrame = false;
1469 
1470  if ((hls) &&
1471  (avfw->GetFramesWritten()) &&
1472  (hlsSegmentFrames > hlsSegmentSize) &&
1473  (avfw->NextFrameIsKeyFrame()))
1474  {
1475  hls->AddSegment();
1476  avfw->ReOpen(hls->GetCurrentFilename());
1477 
1478  if (avfw2)
1479  avfw2->ReOpen(hls->GetCurrentFilename(true));
1480 
1481  hlsSegmentFrames = 0;
1482  }
1483 
1484  if (avfw->WriteVideoFrame(rescale ? &frame : lastDecode) > 0)
1485  {
1486  lastWrittenTime = frame.timecode + timecodeOffset;
1487  if (hls)
1488  ++hlsSegmentFrames;
1489  }
1490 
1491  }
1492  }
1493 #if CONFIG_LIBMP3LAME
1494  else
1495  {
1496  if (forceKeyFrames)
1497  m_nvr->WriteVideo(rescale ? &frame : lastDecode, true, true);
1498  else
1499  m_nvr->WriteVideo(rescale ? &frame : lastDecode);
1500  lastWrittenTime = frame.timecode + timecodeOffset;
1501  }
1502 #endif
1503  }
1504  if (MythDate::current() > statustime)
1505  {
1506  if (m_showProgress)
1507  {
1508  LOG(VB_GENERAL, LOG_INFO,
1509  QString("Processed: %1 of %2 frames(%3 seconds)").
1510  arg(curFrameNum).arg((long)total_frame_count).
1511  arg((long)(curFrameNum / video_frame_rate)));
1512  }
1513 
1514  if (hls && hls->CheckStop())
1515  {
1517  stopSignalled = true;
1518  }
1519 
1520  statustime = MythDate::current().addSecs(5);
1521  }
1522  if (MythDate::current() > curtime)
1523  {
1524  if (honorCutList && m_proginfo && !m_avfMode &&
1526  {
1527  LOG(VB_GENERAL, LOG_NOTICE,
1528  "Transcoding aborted, cutlist updated");
1529 
1530  unlink(outputname.toLocal8Bit().constData());
1531  if (rescale)
1532  {
1533  av_freep(&frame.buf);
1534  }
1535  SetPlayerContext(nullptr);
1536  if (videoBuffer)
1537  videoBuffer->stop();
1538  return REENCODE_CUTLIST_CHANGE;
1539  }
1540 
1541  if ((jobID >= 0) || (VERBOSE_LEVEL_CHECK(VB_GENERAL, LOG_INFO)))
1542  {
1544  {
1545  LOG(VB_GENERAL, LOG_NOTICE,
1546  "Transcoding STOPped by JobQueue");
1547 
1548  unlink(outputname.toLocal8Bit().constData());
1549  if (rescale)
1550  {
1551  av_freep(&frame.buf);
1552  }
1553  SetPlayerContext(nullptr);
1554  if (videoBuffer)
1555  videoBuffer->stop();
1556  if (hls)
1557  {
1559  hls->UpdateStatusMessage("Transcoding Stopped");
1560  delete hls;
1561  }
1562  return REENCODE_STOPPED;
1563  }
1564 
1565  float flagFPS = 0.0;
1566  float elapsed = flagTime.elapsed() / 1000.0;
1567  if (elapsed)
1568  flagFPS = curFrameNum / elapsed;
1569 
1570  total_frame_count = GetPlayer()->GetCurrentFrameCount();
1571  int percentage = curFrameNum * 100 / total_frame_count;
1572 
1573  if (hls)
1574  hls->UpdatePercentComplete(percentage);
1575 
1576  if (jobID >= 0)
1577  {
1579  QObject::tr("%1% Completed @ %2 fps.")
1580  .arg(percentage).arg(flagFPS));
1581  }
1582  else
1583  {
1584  LOG(VB_GENERAL, LOG_INFO,
1585  QString("mythtranscode: %1% Completed @ %2 fps.")
1586  .arg(percentage).arg(flagFPS));
1587  }
1588 
1589  }
1590  curtime = MythDate::current().addSecs(20);
1591  }
1592 
1593  curFrameNum++;
1594  frame.frameNumber = 1 + (curFrameNum << 1);
1595 
1596  GetPlayer()->DiscardVideoFrame(lastDecode);
1597  }
1598 
1599  sws_freeContext(scontext);
1600 
1601  if (!m_fifow)
1602  {
1603  if (avfw)
1604  avfw->CloseFile();
1605 
1606  if (avfw2)
1607  avfw2->CloseFile();
1608 
1609  if (!m_avfMode && m_proginfo)
1610  {
1615  }
1616 
1617 #if CONFIG_LIBMP3LAME
1618  if (m_nvr)
1619  {
1620  m_nvr->WriteSeekTable();
1621  if (!m_kfaTable->empty())
1622  m_nvr->WriteKeyFrameAdjustTable(*m_kfaTable);
1623  }
1624 #endif // CONFIG_LIBMP3LAME
1625  } else {
1626  m_fifow->FIFODrain();
1627  }
1628 
1629  delete cutter;
1630  delete avfw;
1631  delete avfw2;
1632 
1633  if (hls)
1634  {
1635  if (!stopSignalled)
1636  {
1638  hls->UpdateStatusMessage("Transcoding Completed");
1639  hls->UpdatePercentComplete(100);
1640  }
1641  else
1642  {
1644  hls->UpdateStatusMessage("Transcoding Stopped");
1645  }
1646  delete hls;
1647  }
1648 
1649  if (videoBuffer)
1650  {
1651  videoBuffer->stop();
1652  }
1653 
1654  if (rescale)
1655  {
1656  av_freep(&frame.buf);
1657  }
1658  SetPlayerContext(nullptr);
1659 
1660  return REENCODE_OK;
1661 }
1662 
1663 /* vim: set expandtab tabstop=4 shiftwidth=4: */
1664 
AudioBuffer * GetData(long long time)
bool m_showProgress
Definition: transcode.h:60
uint16_t GetWidth(void) const
#define REENCODE_MPEG2TRANS
Definition: transcodedefs.h:4
bool InhibitDropFrame(void)
Definition: cutter.cpp:167
void SetVideoBitrate(int bitrate)
float GetVideoAspect(void) const
Definition: mythplayer.h:210
int WriteAudioFrame(unsigned char *buf, int fnum, long long &timecode) override
PlayerContext * GetPlayerContext(void)
Definition: transcode.h:46
void SetAudioFormat(AudioFormat f)
void ReencoderAddKFA(long curframe, long lastkey, long num_keyframes)
Definition: transcode.cpp:68
bool GetRawAudioState(void) const
bool UpdateStatusMessage(const QString &message)
PlayerFlags
Definition: mythplayer.h:86
void SetCutList(const frm_dir_map_t &newCutList)
void SetEncodingTune(const QString &tune)
PlayerContext * m_ctx
Definition: transcode.h:56
bool WriteStoredData(RingBuffer *outRingBuffer, bool writevideo, long timecodeOffset)
int m_cmdWidth
Definition: transcode.h:70
long long timecode
Definition: mythframe.h:149
void SetCutList(frm_dir_map_t &deleteMap, PlayerContext *ctx)
Definition: cutter.cpp:11
void SetAudioBitrate(int bitrate)
QString GetLastError(void) const
#define round(x)
Definition: mythplayer.cpp:65
int m_hlsMaxSegments
Definition: transcode.h:66
bool InhibitUseAudioFrames(int64_t frames, long *totalAudio)
Definition: cutter.cpp:120
VideoFrameType codec
Definition: mythframe.h:138
bool m_avfMode
Definition: transcode.h:62
void SetAudioFrameRate(int rate)
void SetFramerate(double rate)
struct AVFrame AVFrame
bool ReOpen(const QString &filename)
static enum JobCmds GetJobCmd(int jobID)
Definition: jobqueue.cpp:1459
AVPixelFormat FrameTypeToPixelFormat(VideoFrameType type)
Convert VideoFrameType into FFmpeg's PixelFormat equivalent and vice-versa.
Definition: mythavutil.cpp:24
int m_keyframeDist
Definition: transcode.h:52
bool Init(void) override
ProgramInfo * m_proginfo
Definition: transcode.h:50
int keyframe_number
Definition: format.h:132
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
static RingBuffer * Create(const QString &xfilename, bool write, bool usereadahead=true, int timeout_ms=kDefaultOpenTimeout, bool stream_only=false)
Creates a RingBuffer instance.
Definition: ringbuffer.cpp:104
QString ReinitAudio(void)
static unsigned char * GetAlignedBuffer(size_t Size)
Definition: mythframe.h:676
QString GetSourceFile(void) const
QString m_recorderOptions
Definition: transcode.h:61
void FlushTxtBuffers(void)
Definition: cc608reader.cpp:32
void SetFilename(const QString &fname)
void NewFrame(int64_t currentFrame)
Definition: cutter.cpp:73
virtual bool loadByGroup(const QString &name, const QString &group)
uint16_t GetSegmentSize(void) const
float aspect
Definition: mythframe.h:143
bool InhibitUseVideoFrame(void)
Definition: cutter.cpp:100
virtual QString getValue(void) const
static bool ChangeJobComment(int jobID, const QString &comment="")
Definition: jobqueue.cpp:1013
void SetAudioOutput(AudioOutput *ao)
bool TranscodeGetNextFrame(int &did_ff, bool &is_key, bool honorCutList)
Holds information on recordings and videos.
Definition: programinfo.h:67
void InitForTranscode(bool copyaudio, bool copyvideo)
~Transcode() override
Definition: transcode.cpp:57
void ClearMarkupFlag(MarkTypes type) const
Definition: programinfo.h:623
void SetKeyFrameDist(int dist)
void SetHeight(int height)
MythVideoOutput * GetVideoOutput(void)
Definition: mythplayer.h:273
AudioPlayer * GetAudio(void)
Definition: mythplayer.h:223
int AVPictureFill(AVFrame *pic, const VideoFrame *frame, AVPixelFormat fmt)
AVPictureFill Initialise AVFrame pic with content from VideoFrame frame.
Definition: mythavutil.cpp:196
bool CloseFile(void) override
QString m_cmdAudioCodec
Definition: transcode.h:68
#define VERBOSE_LEVEL_CHECK(_MASK_, _LEVEL_)
Definition: mythlogging.h:24
bool InhibitDummyFrame(void)
Definition: cutter.cpp:155
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:10
QString GetSetting(const QString &key, const QString &defaultval="")
QString m_cmdContainer
Definition: transcode.h:67
RecordingProfile * m_recProfile
Definition: transcode.h:51
int m_hlsStreamID
Definition: transcode.h:64
void SetAudioChannels(int channels)
bool GetProfile(const QString &profileName, const QString &encodingType, int height, int frameRate)
Definition: transcode.cpp:80
RingBuffer * m_outRingBuffer
Definition: transcode.h:57
int m_cmdHeight
Definition: transcode.h:71
float GetFrameRate(void) const
Definition: mythplayer.h:211
int WriteVideoFrame(VideoFrame *frame) override
bool UpdatePercentComplete(int percent)
int GetStreamID(void) const
bool CheckStop(void)
#define REENCODE_OK
Definition: transcodedefs.h:6
AVCodecID GetCodec(void) const
Definition: audioplayer.h:59
QString GetCurrentFilename(bool audioOnly=false, bool encoded=false) const
bool FIFOInit(int id, const QString &desc, const QString &name, long size, int num_bufs)
Definition: fifowriter.cpp:81
uint32_t GetBitrate(void) const
void Activate(float v2a, int64_t total)
Definition: cutter.cpp:63
void SetVideoCodec(const QString &codec)
uint32_t GetAudioOnlyBitrate(void) const
frm_dir_map_t AdjustedCutList() const
Definition: cutter.cpp:58
MythPlayer * GetPlayer(void)
Definition: transcode.h:47
QString GetEncodingType(void) const
int m_cmdAudioBitrate
Definition: transcode.h:73
#define LOC
Definition: transcode.cpp:49
This class is to act as a fake audio output device to store the data for reencoding.
void SetPlayerContext(PlayerContext *player_ctx)
Definition: transcode.cpp:141
void SetAudioCodec(const QString &codec)
bool UpdateSizeInfo(uint16_t width, uint16_t height, uint16_t srcwidth, uint16_t srcheight)
static void init(VideoFrame *vf, VideoFrameType _codec, unsigned char *_buf, int _width, int _height, int _size, const int *p=nullptr, const int *o=nullptr, float _aspect=-1.0F, double _rate=-1.0F, int _aligned=MYTH_WIDTH_ALIGNMENT)
Definition: mythframe.h:230
static bool IsJobRunning(int jobType, uint chanid, const QDateTime &recstartts)
Definition: jobqueue.cpp:1089
bool InitForWrite(void)
long long frameNumber
Definition: mythframe.h:147
void SetThreadCount(int count)
const char * kTranscoderInUseID
uint16_t GetHeight(void) const
void SetPlayerInfo(TV *tv, QWidget *widget, PlayerContext *ctx)
static MThreadPool * globalInstance(void)
int GetNumSetting(const QString &key, int defaultval=0)
bool UpdateStatus(HTTPLiveStreamStatus status)
#define REENCODE_CUTLIST_CHANGE
Definition: transcodedefs.h:5
long long GetSamples(long long time)
FIFOWriter * m_fifow
Definition: transcode.h:58
float GetVideoAspect(void) const
Definition: decoderbase.h:184
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
QSize GetVideoBufferSize(void) const
Definition: mythplayer.h:208
long long GetFramesWritten(void) const
void start(QRunnable *runnable, const QString &debugName, int priority=0)
bool AddSegment(void)
bool m_hlsDisableAudioOnly
Definition: transcode.h:65
long UpdateStoredFrameNum(long curFrameNum)
void DiscardVideoFrame(VideoFrame *buffer)
Places frame in the available frames queue.
Definition: mythplayer.cpp:944
KFATable * m_kfaTable
Definition: transcode.h:59
void SetTranscoding(bool value)
bool NextFrameIsKeyFrame(void)
bool QueryIsEditing(void) const
Queries "recorded" table for its "editing" field and returns true if it is set to true.
int height
Definition: mythframe.h:142
void SetEncodingPreset(const QString &preset)
void SetAspect(float aspect)
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
uint64_t GetTotalFrameCount(void) const
Definition: mythplayer.h:228
Transcode(ProgramInfo *pginfo)
Definition: transcode.cpp:51
void FIFODrain(void)
Definition: fifowriter.cpp:234
Definition: cutter.h:13
char * data(void)
int m_cmdBitrate
Definition: transcode.h:72
virtual int SetTrack(uint type, int trackNo)
void SetContainer(const QString &cont)
QDateTime GetRecordingEndTime(void) const
Approximate time the recording should have ended, did end, or is intended to end.
Definition: programinfo.h:406
bool OpenFile(void) override
static size_t GetBufferSize(VideoFrameType Type, int Width, int Height, int Aligned=MYTH_WIDTH_ALIGNMENT)
Definition: mythframe.h:655
int adjust
Definition: format.h:131
virtual void loadByID(int id)
QMap< uint64_t, MarkTypes > frm_dir_map_t
Frame # -> Mark map.
Definition: programtypes.h:81
Implements a file/stream reader/writer.
virtual CC608Reader * GetCC608Reader(uint=0)
Definition: mythplayer.h:326
bool QueryMarkupFlag(MarkTypes type) const
Returns true iff the speficied mark type is set on frame 0.
uint32_t GetAudioBitrate(void) const
void FIFOWrite(int id, void *buf, long size)
Definition: fifowriter.cpp:191
#define REENCODE_ERROR
Definition: transcodedefs.h:7
void SetTimecodeOffset(long long o)
bool m_hlsMode
Definition: transcode.h:63
#define REENCODE_STOPPED
Definition: transcodedefs.h:8
bool QueryCutList(frm_dir_map_t &delMap, bool loadAutosave=false) const
void SetWidth(int width)
virtual void DoneDisplayingFrame(VideoFrame *Frame)
Releases frame returned from GetLastShownFrame() onto the queue of frames ready for decoding onto.
void SetWatchingRecording(bool mode)
Definition: mythplayer.cpp:217
long long GetTimecodeOffset(void) const
void ForceDeinterlacer(bool DoubleRate, MythDeintType Deinterlacer)
void ClearPositionMap(MarkTypes type) const
DecoderBase * GetDecoder(void)
Returns the stream decoder currently in use.
Definition: mythplayer.h:306
int GetAudioFrameSize(void) const
number of audio samples (per channel) in an AVFrame
Definition: format.h:129
QString m_cmdVideoCodec
Definition: transcode.h:69
RemoteEncoder * RemoteGetExistingRecorder(const ProgramInfo *pginfo)
uint64_t GetCurrentFrameCount(void) const