12#include <QMutexLocker>
13#include <QRegularExpression>
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;
165 hls->UpdateStatusMessage(
"Transcoding Starting");
175 LOG(VB_GENERAL, LOG_ERR,
"No output mode is set.");
187 LOG(VB_GENERAL, LOG_ERR,
188 QString(
"Transcoding aborted, error: '%1'")
193 player_ctx->SetRingBuffer(rb);
197 if (player ==
nullptr)
199 LOG(VB_GENERAL, LOG_ERR,
200 QString(
"Transcoding aborted, failed to retrieve MythPlayer object"));
206 player->SetWatchingRecording(
true);
211 statustime = statustime.addSecs(5);
217 player->GetAudio()->SetAudioOutput(audioOutput);
218 player->SetTranscoding(
true);
220 if (player->OpenFile() < 0)
222 LOG(VB_GENERAL, LOG_ERR,
"Transcoding aborted, error opening file.");
227 if (AudioTrackNo > -1)
229 LOG(VB_GENERAL, LOG_INFO,
230 QString(
"Set audiotrack number to %1").arg(AudioTrackNo));
234 long long total_frame_count = player->GetTotalFrameCount();
235 long long new_frame_count = total_frame_count;
238 LOG(VB_GENERAL, LOG_INFO,
"Honoring the cutlist while transcoding");
240 frm_dir_map_t::const_iterator it;
242 long long lastStart = 0;
244 if (deleteMap.empty())
247 for (it = deleteMap.cbegin(); it != deleteMap.cend(); ++it)
251 if (!cutStr.isEmpty())
253 cutStr += QString(
"%1-").arg((
long)it.key());
254 lastStart = it.key();
258 if (cutStr.isEmpty())
260 cutStr += QString(
"%1").arg((
long)it.key());
261 new_frame_count -= (it.key() - lastStart);
264 if (cutStr.isEmpty())
266 else if (cutStr.endsWith(
'-') && (total_frame_count > lastStart))
268 new_frame_count -= (total_frame_count - lastStart);
269 cutStr += QString(
"%1").arg(total_frame_count);
271 LOG(VB_GENERAL, LOG_INFO, QString(
"Cutlist : %1").arg(cutStr));
272 LOG(VB_GENERAL, LOG_INFO, QString(
"Original Length: %1 frames")
273 .arg((
long)total_frame_count));
274 LOG(VB_GENERAL, LOG_INFO, QString(
"New Length : %1 frames")
275 .arg((
long)new_frame_count));
280 LOG(VB_GENERAL, LOG_INFO,
"Transcoding aborted, cutlist changed");
285 curtime = curtime.addSecs(60);
288 player->GetAudio()->ReinitAudio();
290 QString vidsetting =
nullptr;
292 QSize buf_size = player->GetVideoBufferSize();
293 int video_width = buf_size.width();
294 int video_height = buf_size.height();
296 if (video_height == 1088) {
297 LOG(VB_GENERAL, LOG_NOTICE,
298 "Found video height of 1088. This is unusual and "
299 "more than likely the video is actually 1080 so mythtranscode "
300 "will treat it as such.");
305 float video_frame_rate = player->GetFrameRate();
306 int newWidth = video_width;
307 int newHeight = video_height;
308 bool halfFramerate =
false;
309 bool skippedLastFrame =
false;
327 if (newHeight > video_height)
329 newHeight = video_height;
339 if (newHeight == 0 && newWidth > 0)
340 newHeight = (int)(1.0F * newWidth / video_aspect);
341 else if (newWidth == 0 && newHeight > 0)
342 newWidth = (int)(1.0F * newHeight * video_aspect);
343 else if (newWidth == 0 && newHeight == 0)
346 newWidth = (int)(1.0F * 480 * video_aspect);
350 newHeight = (int)(1.0F * 640 / video_aspect);
355 newHeight = (newHeight + 15) & ~0xF;
356 newWidth = (newWidth + 15) & ~0xF;
358 avfw = std::make_unique<MythAVFormatWriter>();
361 LOG(VB_GENERAL, LOG_ERR,
362 "Transcoding aborted, error creating AVFormatWriter.");
368 avfw->SetHeight(newHeight);
369 avfw->SetWidth(newWidth);
370 avfw->SetAspect(video_aspect);
381 hls = std::make_unique<HTTPLiveStream>(inputname, newWidth, newHeight,
388 LOG(VB_GENERAL, LOG_ERR,
"Unable to create new stream");
394 int segmentSize = hls->GetSegmentSize();
396 LOG(VB_GENERAL, LOG_NOTICE,
397 QString(
"HLS: Using segment size of %1 seconds")
402 int audioOnlyBitrate = hls->GetAudioOnlyBitrate();
404 avfw2 = std::make_unique<MythAVFormatWriter>();
405 avfw2->SetContainer(
"mpegts");
406 avfw2->SetAudioCodec(
"aac");
407 avfw2->SetAudioBitrate(audioOnlyBitrate);
413 avfw->SetContainer(
"mpegts");
414 avfw->SetVideoCodec(
"libx264");
415 avfw->SetAudioCodec(
"aac");
417 hls->UpdateStatusMessage(
"Transcoding Starting");
418 hls->UpdateSizeInfo(newWidth, newHeight, video_width, video_height);
420 if (!hls->InitForWrite())
422 LOG(VB_GENERAL, LOG_ERR,
"hls->InitForWrite() failed");
427 if (video_frame_rate > 30)
429 halfFramerate =
true;
430 avfw->SetFramerate(video_frame_rate/2);
433 avfw2->SetFramerate(video_frame_rate/2);
435 hlsSegmentSize = (int)(segmentSize * video_frame_rate / 2);
439 avfw->SetFramerate(video_frame_rate);
442 avfw2->SetFramerate(video_frame_rate);
444 hlsSegmentSize = (int)(segmentSize * video_frame_rate);
447 avfw->SetKeyFrameDist(30);
449 avfw2->SetKeyFrameDist(30);
452 avfw->SetFilename(hls->GetCurrentFilename());
454 avfw2->SetFilename(hls->GetCurrentFilename(
true));
461 avfw->SetFilename(outputname);
462 avfw->SetFramerate(video_frame_rate);
463 avfw->SetKeyFrameDist(30);
470 LOG(VB_GENERAL, LOG_NOTICE,
471 QString(
"x264 HLS using: %1 threads, '%2' profile and '%3' tune")
472 .arg(QString::number(threads), preset, tune));
474 avfw->SetThreadCount(threads);
475 avfw->SetEncodingPreset(preset);
476 avfw->SetEncodingTune(tune);
479 avfw2->SetThreadCount(1);
483 LOG(VB_GENERAL, LOG_ERR,
"avfw->Init() failed");
488 if (!avfw->OpenFile())
490 LOG(VB_GENERAL, LOG_ERR,
"avfw->OpenFile() failed");
495 if (avfw2 && !avfw2->Init())
497 LOG(VB_GENERAL, LOG_ERR,
"avfw2->Init() failed");
502 if (avfw2 && !avfw2->OpenFile())
504 LOG(VB_GENERAL, LOG_ERR,
"avfw2->OpenFile() failed");
512 if (honorCutList && !deleteMap.empty())
519 cutter = std::make_unique<Cutter>();
520 cutter->SetCutList(deleteMap,
m_ctx);
521 player->SetCutList(cutter->AdjustedCutList());
526 player->SetCutList(deleteMap);
530 player->InitForTranscode();
531 if (player->IsErrored())
533 LOG(VB_GENERAL, LOG_ERR,
534 "Unable to initialize MythPlayer for Transcode");
540 if (
m_hlsMode && player->GetVideoOutput())
547 bool nonAligned = vidsetting ==
"RTjpeg" || !fifodir.isEmpty();
548 bool rescale = (video_width != newWidth) || (video_height != newHeight) || nonAligned;
559 video_width, video_height == 1080 ? 1088 : video_height, 0 );
563 frame.
Init(
FMT_YV12, newbuffer, newSize, video_width, video_height,
nullptr, 0);
571 if (!fifodir.isEmpty())
574 const char *audio_codec_name {
nullptr};
578 case AV_CODEC_ID_AC3:
579 audio_codec_name =
"ac3";
581 case AV_CODEC_ID_EAC3:
582 audio_codec_name =
"eac3";
584 case AV_CODEC_ID_DTS:
585 audio_codec_name =
"dts";
587 case AV_CODEC_ID_TRUEHD:
588 audio_codec_name =
"truehd";
590 case AV_CODEC_ID_MP3:
591 audio_codec_name =
"mp3";
593 case AV_CODEC_ID_MP2:
594 audio_codec_name =
"mp2";
596 case AV_CODEC_ID_AAC:
597 audio_codec_name =
"aac";
599 case AV_CODEC_ID_AAC_LATM:
600 audio_codec_name =
"aac_latm";
603 audio_codec_name =
"unknown";
607 audio_codec_name =
"raw";
610 if (honorCutList && fifo_info)
614 player->TranscodeGetNextFrame(did_ff, is_key,
true);
616 QSize buf_size2 = player->GetVideoBufferSize();
617 video_width = buf_size2.width();
618 video_height = buf_size2.height();
619 video_aspect = player->GetVideoAspect();
620 video_frame_rate = player->GetFrameRate();
624 LOG(VB_GENERAL, LOG_INFO,
625 QString(
"FifoVideoWidth %1").arg(video_width));
626 LOG(VB_GENERAL, LOG_INFO,
627 QString(
"FifoVideoHeight %1").arg(video_height));
628 LOG(VB_GENERAL, LOG_INFO,
629 QString(
"FifoVideoAspectRatio %1").arg(video_aspect));
630 LOG(VB_GENERAL, LOG_INFO,
631 QString(
"FifoVideoFrameRate %1").arg(video_frame_rate));
632 LOG(VB_GENERAL, LOG_INFO,
633 QString(
"FifoAudioFormat %1").arg(audio_codec_name));
634 LOG(VB_GENERAL, LOG_INFO,
635 QString(
"FifoAudioChannels %1").arg(arb->
m_channels));
636 LOG(VB_GENERAL, LOG_INFO,
643 unlink(outputname.toLocal8Bit().constData());
648 QString audfifo = fifodir + QString(
"/audout");
649 QString vidfifo = fifodir + QString(
"/vidout");
653 LOG(VB_GENERAL, LOG_INFO,
"Enforcing sync on fifos");
659 LOG(VB_GENERAL, LOG_ERR,
660 "Error initializing fifo writer. Aborting");
661 unlink(outputname.toLocal8Bit().constData());
665 LOG(VB_GENERAL, LOG_INFO,
666 QString(
"Video %1x%2@%3fps Audio rate: %4")
667 .arg(video_width).arg(video_height)
668 .arg(video_frame_rate)
670 LOG(VB_GENERAL, LOG_INFO,
"Created fifos. Waiting for connection.");
673 frm_dir_map_t::iterator dm_iter;
677 long curFrameNum = 0;
682 std::chrono::milliseconds lasttimecode = 0ms;
684 std::chrono::milliseconds lastWrittenTime = 0ms;
686 std::chrono::milliseconds timecodeOffset = 0ms;
689 float vidFrameTime = 1000.0F / video_frame_rate;
691 int wait_recover = 0;
696 struct SwsContext *scontext =
nullptr;
699 LOG(VB_GENERAL, LOG_INFO,
"Dumping Video and Audio data to fifos");
701 LOG(VB_GENERAL, LOG_INFO,
"Transcoding for HTTP Live Streaming");
703 LOG(VB_GENERAL, LOG_INFO,
"Transcoding to libavformat container");
705 LOG(VB_GENERAL, LOG_INFO,
"Transcoding Video and Audio");
711 QElapsedTimer flagTime;
715 cutter->Activate(vidFrameTime * rateTimeConv, total_frame_count);
717 bool stopSignalled =
false;
723 hls->UpdateStatusMessage(
"Transcoding");
726 while ((!stopSignalled) &&
727 (lastDecode = videoBuffer->GetFrame(did_ff, is_key)))
729 float new_aspect = lastDecode->
m_aspect;
739 frame.
m_timecode = lasttimecode + vidFrameTimeMs;
746 scontext = sws_getCachedContext(scontext,
749 SWS_FAST_BILINEAR,
nullptr,
nullptr,
nullptr);
752 sws_scale(scontext, imageIn.data, imageIn.linesize, 0,
753 lastDecode->
m_height, imageOut.data, imageOut.linesize);
757 std::chrono::milliseconds auddelta = frame.
m_timecode - audbufTime;
759 std::chrono::milliseconds viddelta = frame.
m_timecode - vidTime;
760 std::chrono::milliseconds delta = viddelta - auddelta;
761 std::chrono::milliseconds absdelta = std::chrono::abs(delta);
762 if (absdelta < 500ms && absdelta >= vidFrameTimeMs)
764 QString msg = QString(
"Audio is %1ms %2 video at # %3: "
765 "auddelta=%4, viddelta=%5")
766 .arg(absdelta.count())
767 .arg(((delta > 0ms) ?
"ahead of" :
"behind"))
768 .arg((
int)curFrameNum)
769 .arg(auddelta.count())
770 .arg(viddelta.count());
771 LOG(VB_GENERAL, LOG_INFO, msg);
772 dropvideo = (delta > 0ms) ? 1 : -1;
775 else if (delta >= 500ms && delta < 10s)
777 if (wait_recover == 0)
782 else if (wait_recover == 1)
786 while (delta > vidFrameTimeMs)
788 if (!cutter || !cutter->InhibitDummyFrame())
792 delta -= vidFrameTimeMs;
794 QString msg = QString(
"Added %1 blank video frames")
796 LOG(VB_GENERAL, LOG_INFO, msg);
797 curFrameNum += count;
813 int buflen = (int)(arb->audiobuffer_len / rateTimeConv);
814 LOG(VB_GENERAL, LOG_DEBUG,
815 QString(
"%1: video time: %2 audio time: %3 "
816 "buf: %4 exp: %5 delta: %6")
817 .arg(curFrameNum) .arg(frame.
m_timecode.count())
818 .arg(arb->last_audiotime) .arg(buflen) .arg(audbufTime.count())
819 .arg(delta.count()));
825 !cutter->InhibitUseAudioFrames(ab->
m_frames, &totalAudio))
833 if (cutter && cutter->InhibitDropFrame())
836 LOG(VB_GENERAL, LOG_INFO,
"Dropping video frame");
842 if (!cutter || !cutter->InhibitUseVideoFrame())
847 if (!cutter || !cutter->InhibitDummyFrame())
855 player->GetCC608Reader()->FlushTxtBuffers();
863 timecodeOffset += (frame.
m_timecode - lasttimecode -
867 if (video_aspect != new_aspect)
869 video_aspect = new_aspect;
873 QSize buf_size4 = player->GetVideoBufferSize();
875 if (video_width != buf_size4.width() ||
876 video_height != buf_size4.height())
878 video_width = buf_size4.width();
879 video_height = buf_size4.height();
881 LOG(VB_GENERAL, LOG_INFO,
882 QString(
"Resizing from %1x%2 to %3x%4")
883 .arg(video_width).arg(video_height)
884 .arg(newWidth).arg(newHeight));
892 int bottomBand = (lastDecode->
m_height == 1088) ? 8 : 0;
893 scontext = sws_getCachedContext(scontext,
896 SWS_FAST_BILINEAR,
nullptr,
nullptr,
nullptr);
898 sws_scale(scontext, imageIn.data, imageIn.linesize, 0,
900 imageOut.data, imageOut.linesize);
905 while ((ab = arb->
GetData(lastWrittenTime)) !=
nullptr)
907 auto *buf = (
unsigned char *)ab->
data();
912 std::chrono::milliseconds tc = ab->
m_time - timecodeOffset;
913 avfw->WriteAudioFrame(buf, audioFrame, tc);
917 if ((avfw2->GetTimecodeOffset() == -1ms) &&
918 (avfw->GetTimecodeOffset() != -1ms))
920 avfw2->SetTimecodeOffset(
921 avfw->GetTimecodeOffset());
924 tc = ab->
m_time - timecodeOffset;
925 avfw2->WriteAudioFrame(buf, audioFrame, tc);
936 LOG(VB_GENERAL, LOG_ERR,
937 "AVFormat mode not set.");
945 if (halfFramerate && !skippedLastFrame)
947 skippedLastFrame =
true;
951 skippedLastFrame =
false;
954 (avfw->GetFramesWritten()) &&
955 (hlsSegmentFrames > hlsSegmentSize) &&
956 (avfw->NextFrameIsKeyFrame()))
959 avfw->ReOpen(hls->GetCurrentFilename());
962 avfw2->ReOpen(hls->GetCurrentFilename(
true));
964 hlsSegmentFrames = 0;
967 if (avfw->WriteVideoFrame(rescale ? &frame : lastDecode) > 0)
969 lastWrittenTime = frame.
m_timecode + timecodeOffset;
981 LOG(VB_GENERAL, LOG_INFO,
982 QString(
"Processed: %1 of %2 frames(%3 seconds)").
983 arg(curFrameNum).arg((
long)total_frame_count).
984 arg((
long)(curFrameNum / video_frame_rate)));
987 if (hls && hls->CheckStop())
990 stopSignalled =
true;
1000 LOG(VB_GENERAL, LOG_NOTICE,
1001 "Transcoding aborted, cutlist updated");
1003 unlink(outputname.toLocal8Bit().constData());
1006 videoBuffer->stop();
1014 LOG(VB_GENERAL, LOG_NOTICE,
1015 "Transcoding STOPped by JobQueue");
1017 unlink(outputname.toLocal8Bit().constData());
1020 videoBuffer->stop();
1024 hls->UpdateStatusMessage(
"Transcoding Stopped");
1029 float flagFPS = 0.0;
1030 float elapsed = flagTime.elapsed() / 1000.0F;
1031 if (elapsed != 0.0F)
1032 flagFPS = curFrameNum / elapsed;
1034 total_frame_count = player->GetCurrentFrameCount();
1035 int percentage = curFrameNum * 100 / total_frame_count;
1038 hls->UpdatePercentComplete(percentage);
1043 QObject::tr(
"%1% Completed @ %2 fps.")
1044 .arg(percentage).arg(flagFPS));
1048 LOG(VB_GENERAL, LOG_INFO,
1049 QString(
"mythtranscode: %1% Completed @ %2 fps.")
1050 .arg(percentage).arg(flagFPS));
1060 player->DiscardVideoFrame(lastDecode);
1063 sws_freeContext(scontext);
1089 hls->UpdateStatusMessage(
"Transcoding Completed");
1090 hls->UpdatePercentComplete(100);
1095 hls->UpdateStatusMessage(
"Transcoding Stopped");
1100 videoBuffer->stop();
std::chrono::milliseconds m_time
AVCodecID GetCodec(void) const
This class is to act as a fake audio output device to store the data for reencoding.
AudioBuffer * GetData(std::chrono::milliseconds time)
long long GetSamples(std::chrono::milliseconds time)
float GetVideoAspect(void) const
static enum JobCmds GetJobCmd(int jobID)
static bool ChangeJobComment(int jobID, const QString &comment="")
static bool IsJobRunning(int jobType, uint chanid, const QDateTime &recstartts)
static MThreadPool * globalInstance(void)
void start(QRunnable *runnable, const QString &debugName, int priority=0)
static int FillAVFrame(AVFrame *Frame, const MythVideoFrame *From, AVPixelFormat Fmt=AV_PIX_FMT_NONE)
Initialise AVFrame with content from MythVideoFrame.
static AVPixelFormat FrameTypeToPixelFormat(VideoFrameType Type)
QString GetSetting(const QString &key, const QString &defaultval="")
int GetNumSetting(const QString &key, int defaultval=0)
bool FIFOInit(uint Id, const QString &Desc, const QString &Name, long Size, int NumBufs)
void FIFOWrite(uint Id, void *Buffer, long Size)
static size_t GetBufferSize(VideoFrameType Type, int Width, int Height, int Aligned=MYTH_WIDTH_ALIGNMENT)
void Init(VideoFrameType Type, int Width, int Height, const VideoFrameTypes *RenderFormats=nullptr)
std::chrono::milliseconds m_timecode
static uint8_t * GetAlignedBuffer(size_t Size)
virtual void DoneDisplayingFrame(MythVideoFrame *Frame)
Releases frame returned from GetLastShownFrame() onto the queue of frames ready for decoding onto.
Holds information on recordings and videos.
void ClearMarkupFlag(MarkTypes type) const
void ClearPositionMap(MarkTypes type) const
bool QueryIsEditing(void) const
Queries "recorded" table for its "editing" field and returns true if it is set to true.
bool QueryCutList(frm_dir_map_t &delMap, bool loadAutosave=false) const
bool QueryMarkupFlag(MarkTypes type) const
Returns true iff the speficied mark type is set on frame 0.
QDateTime GetRecordingEndTime(void) const
Approximate time the recording should have ended, did end, or is intended to end.
virtual void loadByID(int id)
virtual bool loadByGroup(const QString &name, const QString &group)
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)
Transcode(ProgramInfo *pginfo)
MythMediaBuffer * m_outBuffer
void SetPlayerContext(PlayerContext *player_ctx)
bool m_hlsDisableAudioOnly
MythPlayer * GetPlayer(void)
RecordingProfile * m_recProfile
bool GetProfile(const QString &profileName, const QString &encodingType, int height, int frameRate)
std::enable_if_t< std::is_floating_point_v< T >, std::chrono::milliseconds > millisecondsFromFloat(T value)
Helper function for convert a floating point number to a duration.
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
static bool VERBOSE_LEVEL_CHECK(uint64_t mask, LogLevel_t level)
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
const QString kTranscoderInUseID
QMap< uint64_t, MarkTypes > frm_dir_map_t
Frame # -> Mark map.
@ REENCODE_CUTLIST_CHANGE
RemoteEncoder * RemoteGetExistingRecorder(const ProgramInfo *pginfo)