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)
114 LOG(VB_GENERAL, LOG_ERR, QString(
"Couldn't find profile #: %1")
124 if (player_ctx ==
m_ctx)
132 const QString &outputname,
133 [[maybe_unused]]
const QString &profileName,
134 bool honorCutList,
bool framecontrol,
135 int jobID,
const QString& fifodir,
136 bool fifo_info,
bool cleanCut,
142 QDateTime statustime = curtime;
144 std::unique_ptr<Cutter> cutter =
nullptr;
145 std::unique_ptr<MythAVFormatWriter> avfw =
nullptr;
152 LOG(VB_GENERAL, LOG_ERR,
"No output mode is set.");
162 LOG(VB_GENERAL, LOG_ERR,
163 QString(
"Transcoding aborted, error: '%1'")
168 player_ctx->SetRingBuffer(rb);
172 if (player ==
nullptr)
174 LOG(VB_GENERAL, LOG_ERR,
175 QString(
"Transcoding aborted, failed to retrieve MythPlayer object"));
181 player->SetWatchingRecording(
true);
186 statustime = statustime.addSecs(5);
192 player->GetAudio()->SetAudioOutput(audioOutput);
193 player->SetTranscoding(
true);
195 if (player->OpenFile() < 0)
197 LOG(VB_GENERAL, LOG_ERR,
"Transcoding aborted, error opening file.");
202 if (AudioTrackNo > -1)
204 LOG(VB_GENERAL, LOG_INFO,
205 QString(
"Set audiotrack number to %1").arg(AudioTrackNo));
209 long long total_frame_count = player->GetTotalFrameCount();
210 long long new_frame_count = total_frame_count;
213 LOG(VB_GENERAL, LOG_INFO,
"Honoring the cutlist while transcoding");
215 frm_dir_map_t::const_iterator it;
217 long long lastStart = 0;
219 if (deleteMap.empty())
222 for (it = deleteMap.cbegin(); it != deleteMap.cend(); ++it)
226 if (!cutStr.isEmpty())
228 cutStr += QString(
"%1-").arg((
long)it.key());
229 lastStart = it.key();
233 if (cutStr.isEmpty())
235 cutStr += QString(
"%1").arg((
long)it.key());
236 new_frame_count -= (it.key() - lastStart);
239 if (cutStr.isEmpty())
241 else if (cutStr.endsWith(
'-') && (total_frame_count > lastStart))
243 new_frame_count -= (total_frame_count - lastStart);
244 cutStr += QString(
"%1").arg(total_frame_count);
246 LOG(VB_GENERAL, LOG_INFO, QString(
"Cutlist : %1").arg(cutStr));
247 LOG(VB_GENERAL, LOG_INFO, QString(
"Original Length: %1 frames")
248 .arg((
long)total_frame_count));
249 LOG(VB_GENERAL, LOG_INFO, QString(
"New Length : %1 frames")
250 .arg((
long)new_frame_count));
255 LOG(VB_GENERAL, LOG_INFO,
"Transcoding aborted, cutlist changed");
260 curtime = curtime.addSecs(60);
263 player->GetAudio()->ReinitAudio();
265 QString vidsetting =
nullptr;
267 QSize buf_size = player->GetVideoBufferSize();
268 int video_width = buf_size.width();
269 int video_height = buf_size.height();
271 if (video_height == 1088) {
272 LOG(VB_GENERAL, LOG_NOTICE,
273 "Found video height of 1088. This is unusual and "
274 "more than likely the video is actually 1080 so mythtranscode "
275 "will treat it as such.");
280 float video_frame_rate = player->GetFrameRate();
281 int newWidth = video_width;
282 int newHeight = video_height;
283 bool halfFramerate =
false;
284 bool skippedLastFrame =
false;
296 if (newHeight == 0 && newWidth > 0)
297 newHeight = (int)(1.0F * newWidth / video_aspect);
298 else if (newWidth == 0 && newHeight > 0)
299 newWidth = (int)(1.0F * newHeight * video_aspect);
300 else if (newWidth == 0 && newHeight == 0)
303 newWidth = (int)(1.0F * 480 * video_aspect);
307 newHeight = (int)(1.0F * 640 / video_aspect);
312 newHeight = (newHeight + 15) & ~0xF;
313 newWidth = (newWidth + 15) & ~0xF;
315 avfw = std::make_unique<MythAVFormatWriter>();
318 LOG(VB_GENERAL, LOG_ERR,
319 "Transcoding aborted, error creating AVFormatWriter.");
325 avfw->SetHeight(newHeight);
326 avfw->SetWidth(newWidth);
327 avfw->SetAspect(video_aspect);
337 avfw->SetFilename(outputname);
338 avfw->SetFramerate(video_frame_rate);
339 avfw->SetKeyFrameDist(30);
346 LOG(VB_GENERAL, LOG_NOTICE,
347 QString(
"x264 using: %1 threads, '%2' profile and '%3' tune")
348 .arg(QString::number(threads), preset, tune));
350 avfw->SetThreadCount(threads);
351 avfw->SetEncodingPreset(preset);
352 avfw->SetEncodingTune(tune);
356 LOG(VB_GENERAL, LOG_ERR,
"avfw->Init() failed");
361 if (!avfw->OpenFile())
363 LOG(VB_GENERAL, LOG_ERR,
"avfw->OpenFile() failed");
371 if (honorCutList && !deleteMap.empty())
378 cutter = std::make_unique<Cutter>();
379 cutter->SetCutList(deleteMap,
m_ctx);
380 player->SetCutList(cutter->AdjustedCutList());
385 player->SetCutList(deleteMap);
389 player->InitForTranscode();
390 if (player->IsErrored())
392 LOG(VB_GENERAL, LOG_ERR,
393 "Unable to initialize MythPlayer for Transcode");
402 bool nonAligned = vidsetting ==
"RTjpeg" || !fifodir.isEmpty();
403 bool rescale = (video_width != newWidth) || (video_height != newHeight) || nonAligned;
414 video_width, video_height == 1080 ? 1088 : video_height, 0 );
418 frame.
Init(
FMT_YV12, newbuffer, newSize, video_width, video_height,
nullptr, 0);
426 if (!fifodir.isEmpty())
429 const char *audio_codec_name {
nullptr};
433 case AV_CODEC_ID_AC3:
434 audio_codec_name =
"ac3";
436 case AV_CODEC_ID_EAC3:
437 audio_codec_name =
"eac3";
439 case AV_CODEC_ID_DTS:
440 audio_codec_name =
"dts";
442 case AV_CODEC_ID_TRUEHD:
443 audio_codec_name =
"truehd";
445 case AV_CODEC_ID_MP3:
446 audio_codec_name =
"mp3";
448 case AV_CODEC_ID_MP2:
449 audio_codec_name =
"mp2";
451 case AV_CODEC_ID_AAC:
452 audio_codec_name =
"aac";
454 case AV_CODEC_ID_AAC_LATM:
455 audio_codec_name =
"aac_latm";
458 audio_codec_name =
"unknown";
462 audio_codec_name =
"raw";
465 if (honorCutList && fifo_info)
469 player->TranscodeGetNextFrame(did_ff, is_key,
true);
471 QSize buf_size2 = player->GetVideoBufferSize();
472 video_width = buf_size2.width();
473 video_height = buf_size2.height();
474 video_aspect = player->GetVideoAspect();
475 video_frame_rate = player->GetFrameRate();
479 LOG(VB_GENERAL, LOG_INFO,
480 QString(
"FifoVideoWidth %1").arg(video_width));
481 LOG(VB_GENERAL, LOG_INFO,
482 QString(
"FifoVideoHeight %1").arg(video_height));
483 LOG(VB_GENERAL, LOG_INFO,
484 QString(
"FifoVideoAspectRatio %1").arg(video_aspect));
485 LOG(VB_GENERAL, LOG_INFO,
486 QString(
"FifoVideoFrameRate %1").arg(video_frame_rate));
487 LOG(VB_GENERAL, LOG_INFO,
488 QString(
"FifoAudioFormat %1").arg(audio_codec_name));
489 LOG(VB_GENERAL, LOG_INFO,
490 QString(
"FifoAudioChannels %1").arg(arb->
m_channels));
491 LOG(VB_GENERAL, LOG_INFO,
498 unlink(outputname.toLocal8Bit().constData());
503 QString audfifo = fifodir + QString(
"/audout");
504 QString vidfifo = fifodir + QString(
"/vidout");
508 LOG(VB_GENERAL, LOG_INFO,
"Enforcing sync on fifos");
514 LOG(VB_GENERAL, LOG_ERR,
515 "Error initializing fifo writer. Aborting");
516 unlink(outputname.toLocal8Bit().constData());
520 LOG(VB_GENERAL, LOG_INFO,
521 QString(
"Video %1x%2@%3fps Audio rate: %4")
522 .arg(video_width).arg(video_height)
523 .arg(video_frame_rate)
525 LOG(VB_GENERAL, LOG_INFO,
"Created fifos. Waiting for connection.");
528 frm_dir_map_t::iterator dm_iter;
532 long curFrameNum = 0;
537 std::chrono::milliseconds lasttimecode = 0ms;
539 std::chrono::milliseconds lastWrittenTime = 0ms;
541 std::chrono::milliseconds timecodeOffset = 0ms;
544 float vidFrameTime = 1000.0F / video_frame_rate;
546 int wait_recover = 0;
551 struct SwsContext *scontext =
nullptr;
554 LOG(VB_GENERAL, LOG_INFO,
"Dumping Video and Audio data to fifos");
556 LOG(VB_GENERAL, LOG_INFO,
"Transcoding to libavformat container");
558 LOG(VB_GENERAL, LOG_INFO,
"Transcoding Video and Audio");
564 QElapsedTimer flagTime;
568 cutter->Activate(vidFrameTime * rateTimeConv, total_frame_count);
570 bool stopSignalled =
false;
573 while ((!stopSignalled) &&
574 (lastDecode = videoBuffer->GetFrame(did_ff, is_key)))
576 float new_aspect = lastDecode->
m_aspect;
586 frame.
m_timecode = lasttimecode + vidFrameTimeMs;
593 scontext = sws_getCachedContext(scontext,
596 SWS_FAST_BILINEAR,
nullptr,
nullptr,
nullptr);
599 sws_scale(scontext, imageIn.data, imageIn.linesize, 0,
600 lastDecode->
m_height, imageOut.data, imageOut.linesize);
604 std::chrono::milliseconds auddelta = frame.
m_timecode - audbufTime;
606 std::chrono::milliseconds viddelta = frame.
m_timecode - vidTime;
607 std::chrono::milliseconds delta = viddelta - auddelta;
608 std::chrono::milliseconds absdelta = std::chrono::abs(delta);
609 if (absdelta < 500ms && absdelta >= vidFrameTimeMs)
611 QString msg = QString(
"Audio is %1ms %2 video at # %3: "
612 "auddelta=%4, viddelta=%5")
613 .arg(absdelta.count())
614 .arg(((delta > 0ms) ?
"ahead of" :
"behind"))
615 .arg((
int)curFrameNum)
616 .arg(auddelta.count())
617 .arg(viddelta.count());
618 LOG(VB_GENERAL, LOG_INFO, msg);
619 dropvideo = (delta > 0ms) ? 1 : -1;
622 else if (delta >= 500ms && delta < 10s)
624 if (wait_recover == 0)
629 else if (wait_recover == 1)
633 while (delta > vidFrameTimeMs)
635 if (!cutter || !cutter->InhibitDummyFrame())
639 delta -= vidFrameTimeMs;
641 QString msg = QString(
"Added %1 blank video frames")
643 LOG(VB_GENERAL, LOG_INFO, msg);
644 curFrameNum += count;
660 int buflen = (int)(arb->audiobuffer_len / rateTimeConv);
661 LOG(VB_GENERAL, LOG_DEBUG,
662 QString(
"%1: video time: %2 audio time: %3 "
663 "buf: %4 exp: %5 delta: %6")
664 .arg(curFrameNum) .arg(frame.
m_timecode.count())
665 .arg(arb->last_audiotime) .arg(buflen) .arg(audbufTime.count())
666 .arg(delta.count()));
672 !cutter->InhibitUseAudioFrames(ab->
m_frames, &totalAudio))
680 if (cutter && cutter->InhibitDropFrame())
683 LOG(VB_GENERAL, LOG_INFO,
"Dropping video frame");
689 if (!cutter || !cutter->InhibitUseVideoFrame())
694 if (!cutter || !cutter->InhibitDummyFrame())
702 player->GetCC608Reader()->FlushTxtBuffers();
710 timecodeOffset += (frame.
m_timecode - lasttimecode -
714 if (video_aspect != new_aspect)
716 video_aspect = new_aspect;
720 QSize buf_size4 = player->GetVideoBufferSize();
722 if (video_width != buf_size4.width() ||
723 video_height != buf_size4.height())
725 video_width = buf_size4.width();
726 video_height = buf_size4.height();
728 LOG(VB_GENERAL, LOG_INFO,
729 QString(
"Resizing from %1x%2 to %3x%4")
730 .arg(video_width).arg(video_height)
731 .arg(newWidth).arg(newHeight));
739 int bottomBand = (lastDecode->
m_height == 1088) ? 8 : 0;
740 scontext = sws_getCachedContext(scontext,
743 SWS_FAST_BILINEAR,
nullptr,
nullptr,
nullptr);
745 sws_scale(scontext, imageIn.data, imageIn.linesize, 0,
747 imageOut.data, imageOut.linesize);
752 while ((ab = arb->
GetData(lastWrittenTime)) !=
nullptr)
754 auto *buf = (
unsigned char *)ab->
data();
759 std::chrono::milliseconds tc = ab->
m_time - timecodeOffset;
760 avfw->WriteAudioFrame(buf, audioFrame, tc);
770 LOG(VB_GENERAL, LOG_ERR,
771 "AVFormat mode not set.");
779 if (halfFramerate && !skippedLastFrame)
781 skippedLastFrame =
true;
785 skippedLastFrame =
false;
787 if (avfw->WriteVideoFrame(rescale ? &frame : lastDecode) > 0)
789 lastWrittenTime = frame.
m_timecode + timecodeOffset;
799 LOG(VB_GENERAL, LOG_INFO,
800 QString(
"Processed: %1 of %2 frames(%3 seconds)").
801 arg(curFrameNum).arg((
long)total_frame_count).
802 arg((
long)(curFrameNum / video_frame_rate)));
812 LOG(VB_GENERAL, LOG_NOTICE,
813 "Transcoding aborted, cutlist updated");
815 unlink(outputname.toLocal8Bit().constData());
826 LOG(VB_GENERAL, LOG_NOTICE,
827 "Transcoding STOPped by JobQueue");
829 unlink(outputname.toLocal8Bit().constData());
837 float elapsed = flagTime.elapsed() / 1000.0F;
839 flagFPS = curFrameNum / elapsed;
841 total_frame_count = player->GetCurrentFrameCount();
842 int percentage = curFrameNum * 100 / total_frame_count;
847 QObject::tr(
"%1% Completed @ %2 fps.")
848 .arg(percentage).arg(flagFPS));
852 LOG(VB_GENERAL, LOG_INFO,
853 QString(
"mythtranscode: %1% Completed @ %2 fps.")
854 .arg(percentage).arg(flagFPS));
864 player->DiscardVideoFrame(lastDecode);
867 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)