12#include <QMutexLocker>
13#include <QRegularExpression>
15#include <QWaitCondition>
16#include <QtAlgorithms>
42#include "libavcodec/avcodec.h"
43#include "libswscale/swscale.h"
46#define LOC QString("Transcode: ")
63 int height,
int frameRate)
65 if (profileName.toLower() ==
"autodetect")
70 QString autoProfileName = QObject::tr(
"Autodetect from %1").arg(height);
71 if (frameRate == 25 || frameRate == 30)
72 autoProfileName +=
"i";
73 if (frameRate == 50 || frameRate == 60)
74 autoProfileName +=
"p";
77 LOG(VB_GENERAL, LOG_NOTICE,
78 QString(
"Transcode: Looking for autodetect profile: %1")
79 .arg(autoProfileName));
82 if (!result && encodingType ==
"MPEG-2")
85 autoProfileName =
"MPEG2";
87 if (!result && (encodingType ==
"MPEG-4" || encodingType ==
"RTjpeg"))
91 autoProfileName =
"RTjpeg/MPEG4";
95 LOG(VB_GENERAL, LOG_ERR,
96 QString(
"Transcode: Couldn't find profile for : %1")
102 LOG(VB_GENERAL, LOG_NOTICE,
103 QString(
"Transcode: Using autodetect profile: %1")
104 .arg(autoProfileName));
109 int profileID = profileName.toInt(&isNum);
111 if (isNum && profileID > 0)
115 LOG(VB_GENERAL, LOG_ERR, QString(
"Couldn't find profile #: %1")
125 if (player_ctx ==
m_ctx)
133 const QString &outputname,
134 [[maybe_unused]]
const QString &profileName,
135 bool honorCutList,
bool framecontrol,
136 int jobID,
const QString& fifodir,
137 bool fifo_info,
bool cleanCut,
143 QDateTime statustime = curtime;
145 std::unique_ptr<Cutter> cutter =
nullptr;
146 std::unique_ptr<MythAVFormatWriter> avfw =
nullptr;
147 std::unique_ptr<MythAVFormatWriter> avfw2 =
nullptr;
148 std::unique_ptr<HTTPLiveStream> hls =
nullptr;
149 int hlsSegmentSize = 0;
150 int hlsSegmentFrames = 0;
163 hls->UpdateStatusMessage(
"Transcoding Starting");
173 LOG(VB_GENERAL, LOG_ERR,
"No output mode is set.");
185 LOG(VB_GENERAL, LOG_ERR,
186 QString(
"Transcoding aborted, error: '%1'")
191 player_ctx->SetRingBuffer(rb);
195 if (player ==
nullptr)
197 LOG(VB_GENERAL, LOG_ERR,
198 QString(
"Transcoding aborted, failed to retrieve MythPlayer object"));
204 player->SetWatchingRecording(
true);
209 statustime = statustime.addSecs(5);
215 player->GetAudio()->SetAudioOutput(audioOutput);
216 player->SetTranscoding(
true);
218 if (player->OpenFile() < 0)
220 LOG(VB_GENERAL, LOG_ERR,
"Transcoding aborted, error opening file.");
225 if (AudioTrackNo > -1)
227 LOG(VB_GENERAL, LOG_INFO,
228 QString(
"Set audiotrack number to %1").arg(AudioTrackNo));
232 long long total_frame_count = player->GetTotalFrameCount();
233 long long new_frame_count = total_frame_count;
236 LOG(VB_GENERAL, LOG_INFO,
"Honoring the cutlist while transcoding");
238 frm_dir_map_t::const_iterator it;
240 long long lastStart = 0;
242 if (deleteMap.empty())
245 for (it = deleteMap.cbegin(); it != deleteMap.cend(); ++it)
249 if (!cutStr.isEmpty())
251 cutStr += QString(
"%1-").arg((
long)it.key());
252 lastStart = it.key();
256 if (cutStr.isEmpty())
258 cutStr += QString(
"%1").arg((
long)it.key());
259 new_frame_count -= (it.key() - lastStart);
262 if (cutStr.isEmpty())
264 else if (cutStr.endsWith(
'-') && (total_frame_count > lastStart))
266 new_frame_count -= (total_frame_count - lastStart);
267 cutStr += QString(
"%1").arg(total_frame_count);
269 LOG(VB_GENERAL, LOG_INFO, QString(
"Cutlist : %1").arg(cutStr));
270 LOG(VB_GENERAL, LOG_INFO, QString(
"Original Length: %1 frames")
271 .arg((
long)total_frame_count));
272 LOG(VB_GENERAL, LOG_INFO, QString(
"New Length : %1 frames")
273 .arg((
long)new_frame_count));
278 LOG(VB_GENERAL, LOG_INFO,
"Transcoding aborted, cutlist changed");
283 curtime = curtime.addSecs(60);
286 player->GetAudio()->ReinitAudio();
288 QString vidsetting =
nullptr;
290 QSize buf_size = player->GetVideoBufferSize();
291 int video_width = buf_size.width();
292 int video_height = buf_size.height();
294 if (video_height == 1088) {
295 LOG(VB_GENERAL, LOG_NOTICE,
296 "Found video height of 1088. This is unusual and "
297 "more than likely the video is actually 1080 so mythtranscode "
298 "will treat it as such.");
303 float video_frame_rate = player->GetFrameRate();
304 int newWidth = video_width;
305 int newHeight = video_height;
306 bool halfFramerate =
false;
307 bool skippedLastFrame =
false;
325 if (newHeight > video_height)
327 newHeight = video_height;
337 if (newHeight == 0 && newWidth > 0)
338 newHeight = (int)(1.0F * newWidth / video_aspect);
339 else if (newWidth == 0 && newHeight > 0)
340 newWidth = (int)(1.0F * newHeight * video_aspect);
341 else if (newWidth == 0 && newHeight == 0)
344 newWidth = (int)(1.0F * 480 * video_aspect);
348 newHeight = (int)(1.0F * 640 / video_aspect);
353 newHeight = (newHeight + 15) & ~0xF;
354 newWidth = (newWidth + 15) & ~0xF;
356 avfw = std::make_unique<MythAVFormatWriter>();
359 LOG(VB_GENERAL, LOG_ERR,
360 "Transcoding aborted, error creating AVFormatWriter.");
366 avfw->SetHeight(newHeight);
367 avfw->SetWidth(newWidth);
368 avfw->SetAspect(video_aspect);
379 hls = std::make_unique<HTTPLiveStream>(inputname, newWidth, newHeight,
386 LOG(VB_GENERAL, LOG_ERR,
"Unable to create new stream");
392 int segmentSize = hls->GetSegmentSize();
394 LOG(VB_GENERAL, LOG_NOTICE,
395 QString(
"HLS: Using segment size of %1 seconds")
400 int audioOnlyBitrate = hls->GetAudioOnlyBitrate();
402 avfw2 = std::make_unique<MythAVFormatWriter>();
403 avfw2->SetContainer(
"mpegts");
404 avfw2->SetAudioCodec(
"aac");
405 avfw2->SetAudioBitrate(audioOnlyBitrate);
411 avfw->SetContainer(
"mpegts");
412 avfw->SetVideoCodec(
"libx264");
413 avfw->SetAudioCodec(
"aac");
415 hls->UpdateStatusMessage(
"Transcoding Starting");
416 hls->UpdateSizeInfo(newWidth, newHeight, video_width, video_height);
418 if (!hls->InitForWrite())
420 LOG(VB_GENERAL, LOG_ERR,
"hls->InitForWrite() failed");
425 if (video_frame_rate > 30)
427 halfFramerate =
true;
428 avfw->SetFramerate(video_frame_rate/2);
431 avfw2->SetFramerate(video_frame_rate/2);
433 hlsSegmentSize = (int)(segmentSize * video_frame_rate / 2);
437 avfw->SetFramerate(video_frame_rate);
440 avfw2->SetFramerate(video_frame_rate);
442 hlsSegmentSize = (int)(segmentSize * video_frame_rate);
445 avfw->SetKeyFrameDist(30);
447 avfw2->SetKeyFrameDist(30);
450 avfw->SetFilename(hls->GetCurrentFilename());
452 avfw2->SetFilename(hls->GetCurrentFilename(
true));
459 avfw->SetFilename(outputname);
460 avfw->SetFramerate(video_frame_rate);
461 avfw->SetKeyFrameDist(30);
468 LOG(VB_GENERAL, LOG_NOTICE,
469 QString(
"x264 HLS using: %1 threads, '%2' profile and '%3' tune")
470 .arg(QString::number(threads), preset, tune));
472 avfw->SetThreadCount(threads);
473 avfw->SetEncodingPreset(preset);
474 avfw->SetEncodingTune(tune);
477 avfw2->SetThreadCount(1);
481 LOG(VB_GENERAL, LOG_ERR,
"avfw->Init() failed");
486 if (!avfw->OpenFile())
488 LOG(VB_GENERAL, LOG_ERR,
"avfw->OpenFile() failed");
493 if (avfw2 && !avfw2->Init())
495 LOG(VB_GENERAL, LOG_ERR,
"avfw2->Init() failed");
500 if (avfw2 && !avfw2->OpenFile())
502 LOG(VB_GENERAL, LOG_ERR,
"avfw2->OpenFile() failed");
510 if (honorCutList && !deleteMap.empty())
517 cutter = std::make_unique<Cutter>();
518 cutter->SetCutList(deleteMap,
m_ctx);
519 player->SetCutList(cutter->AdjustedCutList());
524 player->SetCutList(deleteMap);
528 player->InitForTranscode();
529 if (player->IsErrored())
531 LOG(VB_GENERAL, LOG_ERR,
532 "Unable to initialize MythPlayer for Transcode");
538 if (
m_hlsMode && player->GetVideoOutput())
545 bool nonAligned = vidsetting ==
"RTjpeg" || !fifodir.isEmpty();
546 bool rescale = (video_width != newWidth) || (video_height != newHeight) || nonAligned;
557 video_width, video_height == 1080 ? 1088 : video_height, 0 );
561 frame.
Init(
FMT_YV12, newbuffer, newSize, video_width, video_height,
nullptr, 0);
569 if (!fifodir.isEmpty())
572 const char *audio_codec_name {
nullptr};
576 case AV_CODEC_ID_AC3:
577 audio_codec_name =
"ac3";
579 case AV_CODEC_ID_EAC3:
580 audio_codec_name =
"eac3";
582 case AV_CODEC_ID_DTS:
583 audio_codec_name =
"dts";
585 case AV_CODEC_ID_TRUEHD:
586 audio_codec_name =
"truehd";
588 case AV_CODEC_ID_MP3:
589 audio_codec_name =
"mp3";
591 case AV_CODEC_ID_MP2:
592 audio_codec_name =
"mp2";
594 case AV_CODEC_ID_AAC:
595 audio_codec_name =
"aac";
597 case AV_CODEC_ID_AAC_LATM:
598 audio_codec_name =
"aac_latm";
601 audio_codec_name =
"unknown";
605 audio_codec_name =
"raw";
608 if (honorCutList && fifo_info)
612 player->TranscodeGetNextFrame(did_ff, is_key,
true);
614 QSize buf_size2 = player->GetVideoBufferSize();
615 video_width = buf_size2.width();
616 video_height = buf_size2.height();
617 video_aspect = player->GetVideoAspect();
618 video_frame_rate = player->GetFrameRate();
622 LOG(VB_GENERAL, LOG_INFO,
623 QString(
"FifoVideoWidth %1").arg(video_width));
624 LOG(VB_GENERAL, LOG_INFO,
625 QString(
"FifoVideoHeight %1").arg(video_height));
626 LOG(VB_GENERAL, LOG_INFO,
627 QString(
"FifoVideoAspectRatio %1").arg(video_aspect));
628 LOG(VB_GENERAL, LOG_INFO,
629 QString(
"FifoVideoFrameRate %1").arg(video_frame_rate));
630 LOG(VB_GENERAL, LOG_INFO,
631 QString(
"FifoAudioFormat %1").arg(audio_codec_name));
632 LOG(VB_GENERAL, LOG_INFO,
633 QString(
"FifoAudioChannels %1").arg(arb->
m_channels));
634 LOG(VB_GENERAL, LOG_INFO,
641 unlink(outputname.toLocal8Bit().constData());
646 QString audfifo = fifodir + QString(
"/audout");
647 QString vidfifo = fifodir + QString(
"/vidout");
651 LOG(VB_GENERAL, LOG_INFO,
"Enforcing sync on fifos");
657 LOG(VB_GENERAL, LOG_ERR,
658 "Error initializing fifo writer. Aborting");
659 unlink(outputname.toLocal8Bit().constData());
663 LOG(VB_GENERAL, LOG_INFO,
664 QString(
"Video %1x%2@%3fps Audio rate: %4")
665 .arg(video_width).arg(video_height)
666 .arg(video_frame_rate)
668 LOG(VB_GENERAL, LOG_INFO,
"Created fifos. Waiting for connection.");
671 frm_dir_map_t::iterator dm_iter;
675 long curFrameNum = 0;
680 std::chrono::milliseconds lasttimecode = 0ms;
682 std::chrono::milliseconds lastWrittenTime = 0ms;
684 std::chrono::milliseconds timecodeOffset = 0ms;
687 float vidFrameTime = 1000.0F / video_frame_rate;
689 int wait_recover = 0;
694 struct SwsContext *scontext =
nullptr;
697 LOG(VB_GENERAL, LOG_INFO,
"Dumping Video and Audio data to fifos");
699 LOG(VB_GENERAL, LOG_INFO,
"Transcoding for HTTP Live Streaming");
701 LOG(VB_GENERAL, LOG_INFO,
"Transcoding to libavformat container");
703 LOG(VB_GENERAL, LOG_INFO,
"Transcoding Video and Audio");
709 QElapsedTimer flagTime;
713 cutter->Activate(vidFrameTime * rateTimeConv, total_frame_count);
715 bool stopSignalled =
false;
721 hls->UpdateStatusMessage(
"Transcoding");
724 while ((!stopSignalled) &&
725 (lastDecode = videoBuffer->GetFrame(did_ff, is_key)))
727 float new_aspect = lastDecode->
m_aspect;
737 frame.
m_timecode = lasttimecode + vidFrameTimeMs;
744 scontext = sws_getCachedContext(scontext,
747 SWS_FAST_BILINEAR,
nullptr,
nullptr,
nullptr);
750 sws_scale(scontext, imageIn.data, imageIn.linesize, 0,
751 lastDecode->
m_height, imageOut.data, imageOut.linesize);
755 std::chrono::milliseconds auddelta = frame.
m_timecode - audbufTime;
757 std::chrono::milliseconds viddelta = frame.
m_timecode - vidTime;
758 std::chrono::milliseconds delta = viddelta - auddelta;
759 std::chrono::milliseconds absdelta = std::chrono::abs(delta);
760 if (absdelta < 500ms && absdelta >= vidFrameTimeMs)
762 QString msg = QString(
"Audio is %1ms %2 video at # %3: "
763 "auddelta=%4, viddelta=%5")
764 .arg(absdelta.count())
765 .arg(((delta > 0ms) ?
"ahead of" :
"behind"))
766 .arg((
int)curFrameNum)
767 .arg(auddelta.count())
768 .arg(viddelta.count());
769 LOG(VB_GENERAL, LOG_INFO, msg);
770 dropvideo = (delta > 0ms) ? 1 : -1;
773 else if (delta >= 500ms && delta < 10s)
775 if (wait_recover == 0)
780 else if (wait_recover == 1)
784 while (delta > vidFrameTimeMs)
786 if (!cutter || !cutter->InhibitDummyFrame())
790 delta -= vidFrameTimeMs;
792 QString msg = QString(
"Added %1 blank video frames")
794 LOG(VB_GENERAL, LOG_INFO, msg);
795 curFrameNum += count;
811 int buflen = (int)(arb->audiobuffer_len / rateTimeConv);
812 LOG(VB_GENERAL, LOG_DEBUG,
813 QString(
"%1: video time: %2 audio time: %3 "
814 "buf: %4 exp: %5 delta: %6")
815 .arg(curFrameNum) .arg(frame.
m_timecode.count())
816 .arg(arb->last_audiotime) .arg(buflen) .arg(audbufTime.count())
817 .arg(delta.count()));
823 !cutter->InhibitUseAudioFrames(ab->
m_frames, &totalAudio))
831 if (cutter && cutter->InhibitDropFrame())
834 LOG(VB_GENERAL, LOG_INFO,
"Dropping video frame");
840 if (!cutter || !cutter->InhibitUseVideoFrame())
845 if (!cutter || !cutter->InhibitDummyFrame())
853 player->GetCC608Reader()->FlushTxtBuffers();
861 timecodeOffset += (frame.
m_timecode - lasttimecode -
865 if (video_aspect != new_aspect)
867 video_aspect = new_aspect;
871 QSize buf_size4 = player->GetVideoBufferSize();
873 if (video_width != buf_size4.width() ||
874 video_height != buf_size4.height())
876 video_width = buf_size4.width();
877 video_height = buf_size4.height();
879 LOG(VB_GENERAL, LOG_INFO,
880 QString(
"Resizing from %1x%2 to %3x%4")
881 .arg(video_width).arg(video_height)
882 .arg(newWidth).arg(newHeight));
890 int bottomBand = (lastDecode->
m_height == 1088) ? 8 : 0;
891 scontext = sws_getCachedContext(scontext,
894 SWS_FAST_BILINEAR,
nullptr,
nullptr,
nullptr);
896 sws_scale(scontext, imageIn.data, imageIn.linesize, 0,
898 imageOut.data, imageOut.linesize);
903 while ((ab = arb->
GetData(lastWrittenTime)) !=
nullptr)
905 auto *buf = (
unsigned char *)ab->
data();
910 std::chrono::milliseconds tc = ab->
m_time - timecodeOffset;
911 avfw->WriteAudioFrame(buf, audioFrame, tc);
915 if ((avfw2->GetTimecodeOffset() == -1ms) &&
916 (avfw->GetTimecodeOffset() != -1ms))
918 avfw2->SetTimecodeOffset(
919 avfw->GetTimecodeOffset());
922 tc = ab->
m_time - timecodeOffset;
923 avfw2->WriteAudioFrame(buf, audioFrame, tc);
934 LOG(VB_GENERAL, LOG_ERR,
935 "AVFormat mode not set.");
943 if (halfFramerate && !skippedLastFrame)
945 skippedLastFrame =
true;
949 skippedLastFrame =
false;
952 (avfw->GetFramesWritten()) &&
953 (hlsSegmentFrames > hlsSegmentSize) &&
954 (avfw->NextFrameIsKeyFrame()))
957 avfw->ReOpen(hls->GetCurrentFilename());
960 avfw2->ReOpen(hls->GetCurrentFilename(
true));
962 hlsSegmentFrames = 0;
965 if (avfw->WriteVideoFrame(rescale ? &frame : lastDecode) > 0)
967 lastWrittenTime = frame.
m_timecode + timecodeOffset;
979 LOG(VB_GENERAL, LOG_INFO,
980 QString(
"Processed: %1 of %2 frames(%3 seconds)").
981 arg(curFrameNum).arg((
long)total_frame_count).
982 arg((
long)(curFrameNum / video_frame_rate)));
985 if (hls && hls->CheckStop())
988 stopSignalled =
true;
998 LOG(VB_GENERAL, LOG_NOTICE,
999 "Transcoding aborted, cutlist updated");
1001 unlink(outputname.toLocal8Bit().constData());
1004 videoBuffer->stop();
1012 LOG(VB_GENERAL, LOG_NOTICE,
1013 "Transcoding STOPped by JobQueue");
1015 unlink(outputname.toLocal8Bit().constData());
1018 videoBuffer->stop();
1022 hls->UpdateStatusMessage(
"Transcoding Stopped");
1027 float flagFPS = 0.0;
1028 float elapsed = flagTime.elapsed() / 1000.0F;
1029 if (elapsed != 0.0F)
1030 flagFPS = curFrameNum / elapsed;
1032 total_frame_count = player->GetCurrentFrameCount();
1033 int percentage = curFrameNum * 100 / total_frame_count;
1036 hls->UpdatePercentComplete(percentage);
1041 QObject::tr(
"%1% Completed @ %2 fps.")
1042 .arg(percentage).arg(flagFPS));
1046 LOG(VB_GENERAL, LOG_INFO,
1047 QString(
"mythtranscode: %1% Completed @ %2 fps.")
1048 .arg(percentage).arg(flagFPS));
1058 player->DiscardVideoFrame(lastDecode);
1061 sws_freeContext(scontext);
1087 hls->UpdateStatusMessage(
"Transcoding Completed");
1088 hls->UpdatePercentComplete(100);
1093 hls->UpdateStatusMessage(
"Transcoding Stopped");
1098 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)