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