12#include <QMutexLocker>
13#include <QRegularExpression>
15#include <QWaitCondition>
16#include <QtAlgorithms>
41#include "libavcodec/avcodec.h"
42#include "libswscale/swscale.h"
45#define LOC QString("Transcode: ")
62 int height,
int frameRate)
64 if (profileName.toLower() ==
"autodetect")
69 QString autoProfileName = QObject::tr(
"Autodetect from %1").arg(height);
70 if (frameRate == 25 || frameRate == 30)
71 autoProfileName +=
"i";
72 if (frameRate == 50 || frameRate == 60)
73 autoProfileName +=
"p";
76 LOG(VB_GENERAL, LOG_NOTICE,
77 QString(
"Transcode: Looking for autodetect profile: %1")
78 .arg(autoProfileName));
81 if (!result && encodingType ==
"MPEG-2")
84 autoProfileName =
"MPEG2";
86 if (!result && (encodingType ==
"MPEG-4" || encodingType ==
"RTjpeg"))
90 autoProfileName =
"RTjpeg/MPEG4";
94 LOG(VB_GENERAL, LOG_ERR,
95 QString(
"Transcode: Couldn't find profile for : %1")
101 LOG(VB_GENERAL, LOG_NOTICE,
102 QString(
"Transcode: Using autodetect profile: %1")
103 .arg(autoProfileName));
108 int profileID = profileName.toInt(&isNum);
110 if (isNum && profileID > 0)
116 LOG(VB_GENERAL, LOG_ERR, QString(
"Couldn't find profile #: %1")
126 if (player_ctx ==
m_ctx)
134 const QString &outputname,
135 [[maybe_unused]]
const QString &profileName,
136 bool honorCutList,
bool framecontrol,
137 int jobID,
const QString& fifodir,
138 bool fifo_info,
bool cleanCut,
144 QDateTime statustime = curtime;
146 std::unique_ptr<Cutter> cutter =
nullptr;
147 std::unique_ptr<MythAVFormatWriter> avfw =
nullptr;
154 LOG(VB_GENERAL, LOG_ERR,
"No output mode is set.");
164 LOG(VB_GENERAL, LOG_ERR,
165 QString(
"Transcoding aborted, error: '%1'")
170 player_ctx->SetRingBuffer(rb);
173 auto * player = qobject_cast<MythTranscodePlayer*>(
GetPlayer());
174 if (player ==
nullptr)
176 LOG(VB_GENERAL, LOG_ERR,
177 QString(
"Transcoding aborted, failed to retrieve MythPlayer object"));
183 player->SetWatchingRecording(
true);
188 statustime = statustime.addSecs(5);
194 player->GetAudio()->SetAudioOutput(audioOutput);
195 player->SetTranscoding(
true);
197 if (player->OpenFile() < 0)
199 LOG(VB_GENERAL, LOG_ERR,
"Transcoding aborted, error opening file.");
204 if (AudioTrackNo > -1)
206 LOG(VB_GENERAL, LOG_INFO,
207 QString(
"Set audiotrack number to %1").arg(AudioTrackNo));
211 long long total_frame_count = player->GetTotalFrameCount();
212 long long new_frame_count = total_frame_count;
215 LOG(VB_GENERAL, LOG_INFO,
"Honoring the cutlist while transcoding");
217 frm_dir_map_t::const_iterator it;
219 long long lastStart = 0;
221 if (deleteMap.empty())
224 for (it = deleteMap.cbegin(); it != deleteMap.cend(); ++it)
228 if (!cutStr.isEmpty())
230 cutStr += QString(
"%1-").arg((
long)it.key());
231 lastStart = it.key();
235 if (cutStr.isEmpty())
237 cutStr += QString(
"%1").arg((
long)it.key());
238 new_frame_count -= (it.key() - lastStart);
241 if (cutStr.isEmpty())
245 else if (cutStr.endsWith(
'-') && (total_frame_count > lastStart))
247 new_frame_count -= (total_frame_count - lastStart);
248 cutStr += QString(
"%1").arg(total_frame_count);
250 LOG(VB_GENERAL, LOG_INFO, QString(
"Cutlist : %1").arg(cutStr));
251 LOG(VB_GENERAL, LOG_INFO, QString(
"Original Length: %1 frames")
252 .arg((
long)total_frame_count));
253 LOG(VB_GENERAL, LOG_INFO, QString(
"New Length : %1 frames")
254 .arg((
long)new_frame_count));
259 LOG(VB_GENERAL, LOG_INFO,
"Transcoding aborted, cutlist changed");
264 curtime = curtime.addSecs(60);
267 player->GetAudio()->ReinitAudio();
269 QString vidsetting =
nullptr;
271 QSize buf_size = player->GetVideoBufferSize();
272 int video_width = buf_size.width();
273 int video_height = buf_size.height();
275 if (video_height == 1088) {
276 LOG(VB_GENERAL, LOG_NOTICE,
277 "Found video height of 1088. This is unusual and "
278 "more than likely the video is actually 1080 so mythtranscode "
279 "will treat it as such.");
284 float video_frame_rate = player->GetFrameRate();
285 int newWidth = video_width;
286 int newHeight = video_height;
287 bool halfFramerate =
false;
288 bool skippedLastFrame =
false;
300 if (newHeight == 0 && newWidth > 0)
302 newHeight = (int)(1.0F * newWidth / video_aspect);
304 else if (newWidth == 0 && newHeight > 0)
306 newWidth = (int)(1.0F * newHeight * video_aspect);
308 else if (newWidth == 0 && newHeight == 0)
311 newWidth = (int)(1.0F * 480 * video_aspect);
315 newHeight = (int)(1.0F * 640 / video_aspect);
320 newHeight = (newHeight + 15) & ~0xF;
321 newWidth = (newWidth + 15) & ~0xF;
323 avfw = std::make_unique<MythAVFormatWriter>();
326 LOG(VB_GENERAL, LOG_ERR,
327 "Transcoding aborted, error creating AVFormatWriter.");
333 avfw->SetHeight(newHeight);
334 avfw->SetWidth(newWidth);
335 avfw->SetAspect(video_aspect);
345 avfw->SetFilename(outputname);
346 avfw->SetFramerate(video_frame_rate);
347 avfw->SetKeyFrameDist(30);
354 LOG(VB_GENERAL, LOG_NOTICE,
355 QString(
"x264 using: %1 threads, '%2' profile and '%3' tune")
356 .arg(QString::number(threads), preset, tune));
358 avfw->SetThreadCount(threads);
359 avfw->SetEncodingPreset(preset);
360 avfw->SetEncodingTune(tune);
364 LOG(VB_GENERAL, LOG_ERR,
"avfw->Init() failed");
369 if (!avfw->OpenFile())
371 LOG(VB_GENERAL, LOG_ERR,
"avfw->OpenFile() failed");
379 if (honorCutList && !deleteMap.empty())
386 cutter = std::make_unique<Cutter>();
387 cutter->SetCutList(deleteMap,
m_ctx);
388 player->SetCutList(cutter->AdjustedCutList());
393 player->SetCutList(deleteMap);
397 player->InitForTranscode();
398 if (player->IsErrored())
400 LOG(VB_GENERAL, LOG_ERR,
401 "Unable to initialize MythPlayer for Transcode");
410 bool nonAligned = vidsetting ==
"RTjpeg" || !fifodir.isEmpty();
411 bool rescale = (video_width != newWidth) || (video_height != newHeight) || nonAligned;
422 video_width, video_height == 1080 ? 1088 : video_height, 0 );
426 frame.
Init(
FMT_YV12, newbuffer, newSize, video_width, video_height,
nullptr, 0);
434 if (!fifodir.isEmpty())
437 const char *audio_codec_name {
nullptr};
441 case AV_CODEC_ID_AC3:
442 audio_codec_name =
"ac3";
444 case AV_CODEC_ID_EAC3:
445 audio_codec_name =
"eac3";
447 case AV_CODEC_ID_DTS:
448 audio_codec_name =
"dts";
450 case AV_CODEC_ID_TRUEHD:
451 audio_codec_name =
"truehd";
453 case AV_CODEC_ID_MP3:
454 audio_codec_name =
"mp3";
456 case AV_CODEC_ID_MP2:
457 audio_codec_name =
"mp2";
459 case AV_CODEC_ID_AAC:
460 audio_codec_name =
"aac";
462 case AV_CODEC_ID_AAC_LATM:
463 audio_codec_name =
"aac_latm";
466 audio_codec_name =
"unknown";
470 audio_codec_name =
"raw";
473 if (honorCutList && fifo_info)
477 player->TranscodeGetNextFrame(did_ff, is_key,
true);
479 QSize buf_size2 = player->GetVideoBufferSize();
480 video_width = buf_size2.width();
481 video_height = buf_size2.height();
482 video_aspect = player->GetVideoAspect();
483 video_frame_rate = player->GetFrameRate();
487 LOG(VB_GENERAL, LOG_INFO,
488 QString(
"FifoVideoWidth %1").arg(video_width));
489 LOG(VB_GENERAL, LOG_INFO,
490 QString(
"FifoVideoHeight %1").arg(video_height));
491 LOG(VB_GENERAL, LOG_INFO,
492 QString(
"FifoVideoAspectRatio %1").arg(video_aspect));
493 LOG(VB_GENERAL, LOG_INFO,
494 QString(
"FifoVideoFrameRate %1").arg(video_frame_rate));
495 LOG(VB_GENERAL, LOG_INFO,
496 QString(
"FifoAudioFormat %1").arg(audio_codec_name));
497 LOG(VB_GENERAL, LOG_INFO,
498 QString(
"FifoAudioChannels %1").arg(arb->
m_channels));
499 LOG(VB_GENERAL, LOG_INFO,
506 unlink(outputname.toLocal8Bit().constData());
511 QString audfifo = fifodir + QString(
"/audout");
512 QString vidfifo = fifodir + QString(
"/vidout");
516 LOG(VB_GENERAL, LOG_INFO,
"Enforcing sync on fifos");
522 LOG(VB_GENERAL, LOG_ERR,
523 "Error initializing fifo writer. Aborting");
524 unlink(outputname.toLocal8Bit().constData());
528 LOG(VB_GENERAL, LOG_INFO,
529 QString(
"Video %1x%2@%3fps Audio rate: %4")
530 .arg(video_width).arg(video_height)
531 .arg(video_frame_rate)
533 LOG(VB_GENERAL, LOG_INFO,
"Created fifos. Waiting for connection.");
536 frm_dir_map_t::iterator dm_iter;
540 long curFrameNum = 0;
545 std::chrono::milliseconds lasttimecode = 0ms;
547 std::chrono::milliseconds lastWrittenTime = 0ms;
549 std::chrono::milliseconds timecodeOffset = 0ms;
552 float vidFrameTime = 1000.0F / video_frame_rate;
554 int wait_recover = 0;
559 struct SwsContext *scontext =
nullptr;
562 LOG(VB_GENERAL, LOG_INFO,
"Dumping Video and Audio data to fifos");
564 LOG(VB_GENERAL, LOG_INFO,
"Transcoding to libavformat container");
566 LOG(VB_GENERAL, LOG_INFO,
"Transcoding Video and Audio");
572 QElapsedTimer flagTime;
576 cutter->Activate(vidFrameTime * rateTimeConv, total_frame_count);
578 bool stopSignalled =
false;
581 while ((!stopSignalled) &&
582 (lastDecode = videoBuffer->GetFrame(did_ff, is_key)))
584 float new_aspect = lastDecode->
m_aspect;
594 frame.
m_timecode = lasttimecode + vidFrameTimeMs;
601 scontext = sws_getCachedContext(scontext,
604 SWS_FAST_BILINEAR,
nullptr,
nullptr,
nullptr);
607 sws_scale(scontext, imageIn.data, imageIn.linesize, 0,
608 lastDecode->
m_height, imageOut.data, imageOut.linesize);
612 std::chrono::milliseconds auddelta = frame.
m_timecode - audbufTime;
614 std::chrono::milliseconds viddelta = frame.
m_timecode - vidTime;
615 std::chrono::milliseconds delta = viddelta - auddelta;
616 std::chrono::milliseconds absdelta = std::chrono::abs(delta);
617 if (absdelta < 500ms && absdelta >= vidFrameTimeMs)
619 QString msg = QString(
"Audio is %1ms %2 video at # %3: "
620 "auddelta=%4, viddelta=%5")
621 .arg(absdelta.count())
622 .arg(((delta > 0ms) ?
"ahead of" :
"behind"))
623 .arg((
int)curFrameNum)
624 .arg(auddelta.count())
625 .arg(viddelta.count());
626 LOG(VB_GENERAL, LOG_INFO, msg);
627 dropvideo = (delta > 0ms) ? 1 : -1;
630 else if (delta >= 500ms && delta < 10s)
632 if (wait_recover == 0)
637 else if (wait_recover == 1)
641 while (delta > vidFrameTimeMs)
643 if (!cutter || !cutter->InhibitDummyFrame())
647 delta -= vidFrameTimeMs;
649 QString msg = QString(
"Added %1 blank video frames")
651 LOG(VB_GENERAL, LOG_INFO, msg);
652 curFrameNum += count;
668 int buflen = (int)(arb->audiobuffer_len / rateTimeConv);
669 LOG(VB_GENERAL, LOG_DEBUG,
670 QString(
"%1: video time: %2 audio time: %3 "
671 "buf: %4 exp: %5 delta: %6")
672 .arg(curFrameNum) .arg(frame.
m_timecode.count())
673 .arg(arb->last_audiotime) .arg(buflen) .arg(audbufTime.count())
674 .arg(delta.count()));
680 !cutter->InhibitUseAudioFrames(ab->
m_frames, &totalAudio))
688 if (cutter && cutter->InhibitDropFrame())
691 LOG(VB_GENERAL, LOG_INFO,
"Dropping video frame");
697 if (!cutter || !cutter->InhibitUseVideoFrame())
702 if (!cutter || !cutter->InhibitDummyFrame())
710 player->GetCC608Reader()->FlushTxtBuffers();
718 timecodeOffset += (frame.
m_timecode - lasttimecode -
722 if (video_aspect != new_aspect)
724 video_aspect = new_aspect;
728 QSize buf_size4 = player->GetVideoBufferSize();
730 if (video_width != buf_size4.width() ||
731 video_height != buf_size4.height())
733 video_width = buf_size4.width();
734 video_height = buf_size4.height();
736 LOG(VB_GENERAL, LOG_INFO,
737 QString(
"Resizing from %1x%2 to %3x%4")
738 .arg(video_width).arg(video_height)
739 .arg(newWidth).arg(newHeight));
747 int bottomBand = (lastDecode->
m_height == 1088) ? 8 : 0;
748 scontext = sws_getCachedContext(scontext,
751 SWS_FAST_BILINEAR,
nullptr,
nullptr,
nullptr);
753 sws_scale(scontext, imageIn.data, imageIn.linesize, 0,
755 imageOut.data, imageOut.linesize);
760 while ((ab = arb->
GetData(lastWrittenTime)) !=
nullptr)
762 auto *buf = (
unsigned char *)ab->
data();
767 std::chrono::milliseconds tc = ab->
m_time - timecodeOffset;
768 avfw->WriteAudioFrame(buf, audioFrame, tc);
778 LOG(VB_GENERAL, LOG_ERR,
779 "AVFormat mode not set.");
787 if (halfFramerate && !skippedLastFrame)
789 skippedLastFrame =
true;
793 skippedLastFrame =
false;
795 if (avfw->WriteVideoFrame(rescale ? &frame : lastDecode) > 0)
797 lastWrittenTime = frame.
m_timecode + timecodeOffset;
807 LOG(VB_GENERAL, LOG_INFO,
808 QString(
"Processed: %1 of %2 frames(%3 seconds)").
809 arg(curFrameNum).arg((
long)total_frame_count).
810 arg((
long)(curFrameNum / video_frame_rate)));
820 LOG(VB_GENERAL, LOG_NOTICE,
821 "Transcoding aborted, cutlist updated");
823 unlink(outputname.toLocal8Bit().constData());
834 LOG(VB_GENERAL, LOG_NOTICE,
835 "Transcoding STOPped by JobQueue");
837 unlink(outputname.toLocal8Bit().constData());
845 float elapsed = flagTime.elapsed() / 1000.0F;
847 flagFPS = curFrameNum / elapsed;
849 total_frame_count = player->GetCurrentFrameCount();
850 int percentage = curFrameNum * 100 / total_frame_count;
855 QObject::tr(
"%1% Completed @ %2 fps.")
856 .arg(percentage).arg(flagFPS));
860 LOG(VB_GENERAL, LOG_INFO,
861 QString(
"mythtranscode: %1% Completed @ %2 fps.")
862 .arg(percentage).arg(flagFPS));
872 player->DiscardVideoFrame(lastDecode);
875 sws_freeContext(scontext);
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)
MythPlayer * GetPlayer(void)
RecordingProfile * m_recProfile
bool GetProfile(const QString &profileName, const QString &encodingType, int height, int frameRate)
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)