12 #include <QMutexLocker>
13 #include <QRegularExpression>
14 #include <QStringList>
15 #include <QWaitCondition>
16 #include <QtAlgorithms>
19 #include "libmythbase/mythconfig.h"
44 #include "libavcodec/avcodec.h"
45 #include "libswscale/swscale.h"
48 #define LOC QString("Transcode: ")
65 int height,
int frameRate)
67 if (profileName.toLower() ==
"autodetect")
72 QString autoProfileName = QObject::tr(
"Autodetect from %1").arg(height);
73 if (frameRate == 25 || frameRate == 30)
74 autoProfileName +=
"i";
75 if (frameRate == 50 || frameRate == 60)
76 autoProfileName +=
"p";
79 LOG(VB_GENERAL, LOG_NOTICE,
80 QString(
"Transcode: Looking for autodetect profile: %1")
81 .arg(autoProfileName));
84 if (!result && encodingType ==
"MPEG-2")
87 autoProfileName =
"MPEG2";
89 if (!result && (encodingType ==
"MPEG-4" || encodingType ==
"RTjpeg"))
93 autoProfileName =
"RTjpeg/MPEG4";
97 LOG(VB_GENERAL, LOG_ERR,
98 QString(
"Transcode: Couldn't find profile for : %1")
104 LOG(VB_GENERAL, LOG_NOTICE,
105 QString(
"Transcode: Using autodetect profile: %1")
106 .arg(autoProfileName));
111 int profileID = profileName.toInt(&isNum);
113 if (isNum && profileID > 0)
117 LOG(VB_GENERAL, LOG_ERR, QString(
"Couldn't find profile #: %1")
127 if (player_ctx ==
m_ctx)
135 const QString &outputname,
136 [[maybe_unused]]
const QString &profileName,
137 bool honorCutList,
bool framecontrol,
138 int jobID,
const QString& fifodir,
139 bool fifo_info,
bool cleanCut,
145 QDateTime statustime = curtime;
147 std::unique_ptr<Cutter> cutter =
nullptr;
148 std::unique_ptr<MythAVFormatWriter> avfw =
nullptr;
149 std::unique_ptr<MythAVFormatWriter> avfw2 =
nullptr;
150 std::unique_ptr<HTTPLiveStream> hls =
nullptr;
151 int hlsSegmentSize = 0;
152 int hlsSegmentFrames = 0;
175 LOG(VB_GENERAL, LOG_ERR,
176 "AVFormat mode not set.");
188 LOG(VB_GENERAL, LOG_ERR,
189 QString(
"Transcoding aborted, error: '%1'")
194 player_ctx->SetRingBuffer(rb);
198 if (player ==
nullptr)
200 LOG(VB_GENERAL, LOG_ERR,
201 QString(
"Transcoding aborted, failed to retrieve MythPlayer object"));
207 player->SetWatchingRecording(
true);
212 statustime = statustime.addSecs(5);
218 player->GetAudio()->SetAudioOutput(audioOutput);
219 player->SetTranscoding(
true);
221 if (player->OpenFile() < 0)
223 LOG(VB_GENERAL, LOG_ERR,
"Transcoding aborted, error opening file.");
228 if (AudioTrackNo > -1)
230 LOG(VB_GENERAL, LOG_INFO,
231 QString(
"Set audiotrack number to %1").arg(AudioTrackNo));
235 long long total_frame_count = player->GetTotalFrameCount();
236 long long new_frame_count = total_frame_count;
239 LOG(VB_GENERAL, LOG_INFO,
"Honoring the cutlist while transcoding");
241 frm_dir_map_t::const_iterator it;
243 long long lastStart = 0;
245 if (deleteMap.empty())
248 for (it = deleteMap.cbegin(); it != deleteMap.cend(); ++it)
252 if (!cutStr.isEmpty())
254 cutStr += QString(
"%1-").arg((
long)it.key());
255 lastStart = it.key();
259 if (cutStr.isEmpty())
261 cutStr += QString(
"%1").arg((
long)it.key());
262 new_frame_count -= (it.key() - lastStart);
265 if (cutStr.isEmpty())
267 else if (cutStr.endsWith(
'-') && (total_frame_count > lastStart))
269 new_frame_count -= (total_frame_count - lastStart);
270 cutStr += QString(
"%1").arg(total_frame_count);
272 LOG(VB_GENERAL, LOG_INFO, QString(
"Cutlist : %1").arg(cutStr));
273 LOG(VB_GENERAL, LOG_INFO, QString(
"Original Length: %1 frames")
274 .arg((
long)total_frame_count));
275 LOG(VB_GENERAL, LOG_INFO, QString(
"New Length : %1 frames")
276 .arg((
long)new_frame_count));
281 LOG(VB_GENERAL, LOG_INFO,
"Transcoding aborted, cutlist changed");
286 curtime = curtime.addSecs(60);
289 player->GetAudio()->ReinitAudio();
291 QString vidsetting =
nullptr;
293 QSize buf_size = player->GetVideoBufferSize();
294 int video_width = buf_size.width();
295 int video_height = buf_size.height();
297 if (video_height == 1088) {
298 LOG(VB_GENERAL, LOG_NOTICE,
299 "Found video height of 1088. This is unusual and "
300 "more than likely the video is actually 1080 so mythtranscode "
301 "will treat it as such.");
306 float video_frame_rate = player->GetFrameRate();
307 int newWidth = video_width;
308 int newHeight = video_height;
309 bool halfFramerate =
false;
310 bool skippedLastFrame =
false;
328 if (newHeight > video_height)
330 newHeight = video_height;
340 if (newHeight == 0 && newWidth > 0)
341 newHeight = (int)(1.0F * newWidth / video_aspect);
342 else if (newWidth == 0 && newHeight > 0)
343 newWidth = (int)(1.0F * newHeight * video_aspect);
344 else if (newWidth == 0 && newHeight == 0)
347 newWidth = (int)(1.0F * 480 * video_aspect);
351 newHeight = (int)(1.0F * 640 / video_aspect);
356 newHeight = (newHeight + 15) & ~0xF;
357 newWidth = (newWidth + 15) & ~0xF;
359 avfw = std::make_unique<MythAVFormatWriter>();
362 LOG(VB_GENERAL, LOG_ERR,
363 "Transcoding aborted, error creating AVFormatWriter.");
382 hls = std::make_unique<HTTPLiveStream>(inputname, newWidth, newHeight,
389 LOG(VB_GENERAL, LOG_ERR,
"Unable to create new stream");
397 LOG(VB_GENERAL, LOG_NOTICE,
398 QString(
"HLS: Using segment size of %1 seconds")
405 avfw2 = std::make_unique<MythAVFormatWriter>();
419 hls->
UpdateSizeInfo(newWidth, newHeight, video_width, video_height);
423 LOG(VB_GENERAL, LOG_ERR,
"hls->InitForWrite() failed");
428 if (video_frame_rate > 30)
430 halfFramerate =
true;
436 hlsSegmentSize = (int)(segmentSize * video_frame_rate / 2);
445 hlsSegmentSize = (int)(segmentSize * video_frame_rate);
471 LOG(VB_GENERAL, LOG_NOTICE,
472 QString(
"x264 HLS using: %1 threads, '%2' profile and '%3' tune")
473 .arg(QString::number(threads), preset, tune));
484 LOG(VB_GENERAL, LOG_ERR,
"avfw->Init() failed");
491 LOG(VB_GENERAL, LOG_ERR,
"avfw->OpenFile() failed");
496 if (avfw2 && !avfw2->
Init())
498 LOG(VB_GENERAL, LOG_ERR,
"avfw2->Init() failed");
505 LOG(VB_GENERAL, LOG_ERR,
"avfw2->OpenFile() failed");
513 if (honorCutList && !deleteMap.empty())
520 cutter = std::make_unique<Cutter>();
527 player->SetCutList(deleteMap);
531 player->InitForTranscode();
532 if (player->IsErrored())
534 LOG(VB_GENERAL, LOG_ERR,
535 "Unable to initialize MythPlayer for Transcode");
541 if (
m_hlsMode && player->GetVideoOutput())
548 bool nonAligned = vidsetting ==
"RTjpeg" || !fifodir.isEmpty();
549 bool rescale = (video_width != newWidth) || (video_height != newHeight) || nonAligned;
560 video_width, video_height == 1080 ? 1088 : video_height, 0 );
564 frame.
Init(
FMT_YV12, newbuffer, newSize, video_width, video_height,
nullptr, 0);
572 if (!fifodir.isEmpty())
575 const char *audio_codec_name {
nullptr};
579 case AV_CODEC_ID_AC3:
580 audio_codec_name =
"ac3";
582 case AV_CODEC_ID_EAC3:
583 audio_codec_name =
"eac3";
585 case AV_CODEC_ID_DTS:
586 audio_codec_name =
"dts";
588 case AV_CODEC_ID_TRUEHD:
589 audio_codec_name =
"truehd";
591 case AV_CODEC_ID_MP3:
592 audio_codec_name =
"mp3";
594 case AV_CODEC_ID_MP2:
595 audio_codec_name =
"mp2";
597 case AV_CODEC_ID_AAC:
598 audio_codec_name =
"aac";
600 case AV_CODEC_ID_AAC_LATM:
601 audio_codec_name =
"aac_latm";
604 audio_codec_name =
"unknown";
608 audio_codec_name =
"raw";
611 if (honorCutList && fifo_info)
615 player->TranscodeGetNextFrame(did_ff, is_key,
true);
617 QSize buf_size2 = player->GetVideoBufferSize();
618 video_width = buf_size2.width();
619 video_height = buf_size2.height();
620 video_aspect = player->GetVideoAspect();
621 video_frame_rate = player->GetFrameRate();
625 LOG(VB_GENERAL, LOG_INFO,
626 QString(
"FifoVideoWidth %1").arg(video_width));
627 LOG(VB_GENERAL, LOG_INFO,
628 QString(
"FifoVideoHeight %1").arg(video_height));
629 LOG(VB_GENERAL, LOG_INFO,
630 QString(
"FifoVideoAspectRatio %1").arg(video_aspect));
631 LOG(VB_GENERAL, LOG_INFO,
632 QString(
"FifoVideoFrameRate %1").arg(video_frame_rate));
633 LOG(VB_GENERAL, LOG_INFO,
634 QString(
"FifoAudioFormat %1").arg(audio_codec_name));
635 LOG(VB_GENERAL, LOG_INFO,
636 QString(
"FifoAudioChannels %1").arg(arb->
m_channels));
637 LOG(VB_GENERAL, LOG_INFO,
644 unlink(outputname.toLocal8Bit().constData());
649 QString audfifo = fifodir + QString(
"/audout");
650 QString vidfifo = fifodir + QString(
"/vidout");
654 LOG(VB_GENERAL, LOG_INFO,
"Enforcing sync on fifos");
660 LOG(VB_GENERAL, LOG_ERR,
661 "Error initializing fifo writer. Aborting");
662 unlink(outputname.toLocal8Bit().constData());
666 LOG(VB_GENERAL, LOG_INFO,
667 QString(
"Video %1x%2@%3fps Audio rate: %4")
668 .arg(video_width).arg(video_height)
669 .arg(video_frame_rate)
671 LOG(VB_GENERAL, LOG_INFO,
"Created fifos. Waiting for connection.");
674 frm_dir_map_t::iterator dm_iter;
678 long curFrameNum = 0;
683 std::chrono::milliseconds lasttimecode = 0ms;
685 std::chrono::milliseconds lastWrittenTime = 0ms;
687 std::chrono::milliseconds timecodeOffset = 0ms;
690 float vidFrameTime = 1000.0F / video_frame_rate;
692 int wait_recover = 0;
697 struct SwsContext *scontext =
nullptr;
700 LOG(VB_GENERAL, LOG_INFO,
"Dumping Video and Audio data to fifos");
702 LOG(VB_GENERAL, LOG_INFO,
"Transcoding for HTTP Live Streaming");
704 LOG(VB_GENERAL, LOG_INFO,
"Transcoding to libavformat container");
706 LOG(VB_GENERAL, LOG_INFO,
"Transcoding Video and Audio");
712 QElapsedTimer flagTime;
716 cutter->
Activate(vidFrameTime * rateTimeConv, total_frame_count);
718 bool stopSignalled =
false;
727 while ((!stopSignalled) &&
728 (lastDecode = videoBuffer->GetFrame(did_ff, is_key)))
730 float new_aspect = lastDecode->
m_aspect;
740 frame.
m_timecode = lasttimecode + vidFrameTimeMs;
747 scontext = sws_getCachedContext(scontext,
750 SWS_FAST_BILINEAR,
nullptr,
nullptr,
nullptr);
753 sws_scale(scontext, imageIn.data, imageIn.linesize, 0,
754 lastDecode->
m_height, imageOut.data, imageOut.linesize);
758 std::chrono::milliseconds auddelta = frame.
m_timecode - audbufTime;
760 std::chrono::milliseconds viddelta = frame.
m_timecode - vidTime;
761 std::chrono::milliseconds delta = viddelta - auddelta;
762 std::chrono::milliseconds absdelta = std::chrono::abs(delta);
763 if (absdelta < 500ms && absdelta >= vidFrameTimeMs)
765 QString msg = QString(
"Audio is %1ms %2 video at # %3: "
766 "auddelta=%4, viddelta=%5")
767 .arg(absdelta.count())
768 .arg(((delta > 0ms) ?
"ahead of" :
"behind"))
769 .arg((
int)curFrameNum)
770 .arg(auddelta.count())
771 .arg(viddelta.count());
772 LOG(VB_GENERAL, LOG_INFO, msg);
773 dropvideo = (delta > 0ms) ? 1 : -1;
776 else if (delta >= 500ms && delta < 10s)
778 if (wait_recover == 0)
783 else if (wait_recover == 1)
787 while (delta > vidFrameTimeMs)
793 delta -= vidFrameTimeMs;
795 QString msg = QString(
"Added %1 blank video frames")
797 LOG(VB_GENERAL, LOG_INFO, msg);
798 curFrameNum += count;
814 int buflen = (int)(arb->audiobuffer_len / rateTimeConv);
815 LOG(VB_GENERAL, LOG_DEBUG,
816 QString(
"%1: video time: %2 audio time: %3 "
817 "buf: %4 exp: %5 delta: %6")
818 .arg(curFrameNum) .arg(frame.
m_timecode.count())
819 .arg(arb->last_audiotime) .arg(buflen) .arg(audbufTime.count())
820 .arg(delta.count()));
837 LOG(VB_GENERAL, LOG_INFO,
"Dropping video frame");
856 player->GetCC608Reader()->FlushTxtBuffers();
864 timecodeOffset += (frame.
m_timecode - lasttimecode -
868 if (video_aspect != new_aspect)
870 video_aspect = new_aspect;
874 QSize buf_size4 = player->GetVideoBufferSize();
876 if (video_width != buf_size4.width() ||
877 video_height != buf_size4.height())
879 video_width = buf_size4.width();
880 video_height = buf_size4.height();
882 LOG(VB_GENERAL, LOG_INFO,
883 QString(
"Resizing from %1x%2 to %3x%4")
884 .arg(video_width).arg(video_height)
885 .arg(newWidth).arg(newHeight));
893 int bottomBand = (lastDecode->
m_height == 1088) ? 8 : 0;
894 scontext = sws_getCachedContext(scontext,
897 SWS_FAST_BILINEAR,
nullptr,
nullptr,
nullptr);
899 sws_scale(scontext, imageIn.data, imageIn.linesize, 0,
901 imageOut.data, imageOut.linesize);
906 while ((ab = arb->
GetData(lastWrittenTime)) !=
nullptr)
908 auto *buf = (
unsigned char *)ab->
data();
913 std::chrono::milliseconds tc = ab->
m_time - timecodeOffset;
925 tc = ab->
m_time - timecodeOffset;
937 LOG(VB_GENERAL, LOG_ERR,
938 "AVFormat mode not set.");
946 if (halfFramerate && !skippedLastFrame)
948 skippedLastFrame =
true;
952 skippedLastFrame =
false;
956 (hlsSegmentFrames > hlsSegmentSize) &&
965 hlsSegmentFrames = 0;
970 lastWrittenTime = frame.
m_timecode + timecodeOffset;
982 LOG(VB_GENERAL, LOG_INFO,
983 QString(
"Processed: %1 of %2 frames(%3 seconds)").
984 arg(curFrameNum).arg((
long)total_frame_count).
985 arg((
long)(curFrameNum / video_frame_rate)));
991 stopSignalled =
true;
1001 LOG(VB_GENERAL, LOG_NOTICE,
1002 "Transcoding aborted, cutlist updated");
1004 unlink(outputname.toLocal8Bit().constData());
1007 videoBuffer->stop();
1015 LOG(VB_GENERAL, LOG_NOTICE,
1016 "Transcoding STOPped by JobQueue");
1018 unlink(outputname.toLocal8Bit().constData());
1021 videoBuffer->stop();
1030 float flagFPS = 0.0;
1031 float elapsed = flagTime.elapsed() / 1000.0;
1032 if (elapsed != 0.0F)
1033 flagFPS = curFrameNum / elapsed;
1035 total_frame_count = player->GetCurrentFrameCount();
1036 int percentage = curFrameNum * 100 / total_frame_count;
1044 QObject::tr(
"%1% Completed @ %2 fps.")
1045 .arg(percentage).arg(flagFPS));
1049 LOG(VB_GENERAL, LOG_INFO,
1050 QString(
"mythtranscode: %1% Completed @ %2 fps.")
1051 .arg(percentage).arg(flagFPS));
1061 player->DiscardVideoFrame(lastDecode);
1064 sws_freeContext(scontext);
1101 videoBuffer->stop();