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