MythTV master
transcode.cpp
Go to the documentation of this file.
1// C++
2#include <cmath>
3#include <fcntl.h>
4#include <iostream>
5#include <memory>
6#include <unistd.h> // for unlink()
7
8// Qt
9#include <QList>
10#include <QMap>
11#include <QMutex>
12#include <QMutexLocker>
13#include <QRegularExpression>
14#include <QStringList>
15#include <QWaitCondition>
16#include <QtAlgorithms>
17
18// MythTV
19#include "libmythbase/mythconfig.h"
20
29#include "libmythtv/deletemap.h"
31#include "libmythtv/jobqueue.h"
35
36// MythTranscode
37#include "audioreencodebuffer.h"
38#include "cutter.h"
39#include "mythtranscodeplayer.h"
40#include "transcode.h"
41#include "videodecodebuffer.h"
42
43extern "C" {
44#include "libavcodec/avcodec.h"
45#include "libswscale/swscale.h"
46}
47
48#define LOC QString("Transcode: ")
49
51 m_proginfo(pginfo),
52 m_recProfile(new RecordingProfile("Transcoders"))
53{
54}
55
57{
58 SetPlayerContext(nullptr);
59 delete m_outBuffer;
60 delete m_fifow;
61 delete m_recProfile;
62}
63
64bool Transcode::GetProfile(const QString& profileName, const QString& encodingType,
65 int height, int frameRate)
66{
67 if (profileName.toLower() == "autodetect")
68 {
69 if (height == 1088)
70 height = 1080;
71
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";
77
78 bool result = false;
79 LOG(VB_GENERAL, LOG_NOTICE,
80 QString("Transcode: Looking for autodetect profile: %1")
81 .arg(autoProfileName));
82 result = m_recProfile->loadByGroup(autoProfileName, "Transcoders");
83
84 if (!result && encodingType == "MPEG-2")
85 {
86 result = m_recProfile->loadByGroup("MPEG2", "Transcoders");
87 autoProfileName = "MPEG2";
88 }
89 if (!result && (encodingType == "MPEG-4" || encodingType == "RTjpeg"))
90 {
91 result = m_recProfile->loadByGroup("RTjpeg/MPEG4",
92 "Transcoders");
93 autoProfileName = "RTjpeg/MPEG4";
94 }
95 if (!result)
96 {
97 LOG(VB_GENERAL, LOG_ERR,
98 QString("Transcode: Couldn't find profile for : %1")
99 .arg(encodingType));
100
101 return false;
102 }
103
104 LOG(VB_GENERAL, LOG_NOTICE,
105 QString("Transcode: Using autodetect profile: %1")
106 .arg(autoProfileName));
107 }
108 else
109 {
110 bool isNum = false;
111 int profileID = profileName.toInt(&isNum);
112 // If a bad profile is specified, there will be trouble
113 if (isNum && profileID > 0)
114 m_recProfile->loadByID(profileID);
115 else if (!m_recProfile->loadByGroup(profileName, "Transcoders"))
116 {
117 LOG(VB_GENERAL, LOG_ERR, QString("Couldn't find profile #: %1")
118 .arg(profileName));
119 return false;
120 }
121 }
122 return true;
123}
124
126{
127 if (player_ctx == m_ctx)
128 return;
129
130 delete m_ctx;
131 m_ctx = player_ctx;
132}
133
134int Transcode::TranscodeFile(const QString &inputname,
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,
140 frm_dir_map_t &deleteMap,
141 int AudioTrackNo,
142 bool passthru)
143{
144 QDateTime curtime = MythDate::current();
145 QDateTime statustime = curtime;
146 int audioFrame = 0;
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;
153
154 if (jobID >= 0)
155 JobQueue::ChangeJobComment(jobID, "0% " + QObject::tr("Completed"));
156
157 if (m_hlsMode)
158 {
159 m_avfMode = true;
160
161 if (m_hlsStreamID != -1)
162 {
163 hls = std::make_unique<HTTPLiveStream>(m_hlsStreamID);
164 hls->UpdateStatus(kHLSStatusStarting);
165 hls->UpdateStatusMessage("Transcoding Starting");
166 m_cmdWidth = hls->GetWidth();
167 m_cmdHeight = hls->GetHeight();
168 m_cmdBitrate = hls->GetBitrate();
169 m_cmdAudioBitrate = hls->GetAudioBitrate();
170 }
171 }
172
173 if (!m_avfMode && fifodir.isEmpty())
174 {
175 LOG(VB_GENERAL, LOG_ERR, "No output mode is set.");
176 return REENCODE_ERROR;
177 }
178
179 // Input setup
180 auto *player_ctx = new PlayerContext(kTranscoderInUseID);
181 player_ctx->SetPlayingInfo(m_proginfo);
182 MythMediaBuffer *rb = (hls && (m_hlsStreamID != -1)) ?
183 MythMediaBuffer::Create(hls->GetSourceFile(), false, false) :
184 MythMediaBuffer::Create(inputname, false, false);
185 if (!rb || !rb->GetLastError().isEmpty())
186 {
187 LOG(VB_GENERAL, LOG_ERR,
188 QString("Transcoding aborted, error: '%1'")
189 .arg(rb? rb->GetLastError() : ""));
190 delete player_ctx;
191 return REENCODE_ERROR;
192 }
193 player_ctx->SetRingBuffer(rb);
194 player_ctx->SetPlayer(new MythTranscodePlayer(player_ctx, static_cast<PlayerFlags>(kVideoIsNull | kNoITV)));
195 SetPlayerContext(player_ctx);
196 auto * player = dynamic_cast<MythTranscodePlayer*>(GetPlayer());
197 if (player == nullptr)
198 {
199 LOG(VB_GENERAL, LOG_ERR,
200 QString("Transcoding aborted, failed to retrieve MythPlayer object"));
201 return REENCODE_ERROR;
202 }
203 if (m_proginfo->GetRecordingEndTime() > curtime)
204 {
205 player_ctx->SetRecorder(RemoteGetExistingRecorder(m_proginfo));
206 player->SetWatchingRecording(true);
207 }
208
209 if (m_showProgress)
210 {
211 statustime = statustime.addSecs(5);
212 }
213
214 AudioOutput *audioOutput = new AudioReencodeBuffer(FORMAT_NONE, 0,
215 passthru);
216 AudioReencodeBuffer *arb = ((AudioReencodeBuffer*)audioOutput);
217 player->GetAudio()->SetAudioOutput(audioOutput);
218 player->SetTranscoding(true);
219
220 if (player->OpenFile() < 0)
221 {
222 LOG(VB_GENERAL, LOG_ERR, "Transcoding aborted, error opening file.");
223 SetPlayerContext(nullptr);
224 return REENCODE_ERROR;
225 }
226
227 if (AudioTrackNo > -1)
228 {
229 LOG(VB_GENERAL, LOG_INFO,
230 QString("Set audiotrack number to %1").arg(AudioTrackNo));
231 player->GetDecoder()->SetTrack(kTrackTypeAudio, AudioTrackNo);
232 }
233
234 long long total_frame_count = player->GetTotalFrameCount();
235 long long new_frame_count = total_frame_count;
236 if (honorCutList && m_proginfo)
237 {
238 LOG(VB_GENERAL, LOG_INFO, "Honoring the cutlist while transcoding");
239
240 frm_dir_map_t::const_iterator it;
241 QString cutStr;
242 long long lastStart = 0;
243
244 if (deleteMap.empty())
245 m_proginfo->QueryCutList(deleteMap);
246
247 for (it = deleteMap.cbegin(); it != deleteMap.cend(); ++it)
248 {
249 if (*it)
250 {
251 if (!cutStr.isEmpty())
252 cutStr += ",";
253 cutStr += QString("%1-").arg((long)it.key());
254 lastStart = it.key();
255 }
256 else
257 {
258 if (cutStr.isEmpty())
259 cutStr += "0-";
260 cutStr += QString("%1").arg((long)it.key());
261 new_frame_count -= (it.key() - lastStart);
262 }
263 }
264 if (cutStr.isEmpty())
265 cutStr = "Is Empty";
266 else if (cutStr.endsWith('-') && (total_frame_count > lastStart))
267 {
268 new_frame_count -= (total_frame_count - lastStart);
269 cutStr += QString("%1").arg(total_frame_count);
270 }
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));
276
277 if ((m_proginfo->QueryIsEditing()) ||
279 {
280 LOG(VB_GENERAL, LOG_INFO, "Transcoding aborted, cutlist changed");
281 SetPlayerContext(nullptr);
283 }
285 curtime = curtime.addSecs(60);
286 }
287
288 player->GetAudio()->ReinitAudio();
289
290 QString vidsetting = nullptr;
291
292 QSize buf_size = player->GetVideoBufferSize();
293 int video_width = buf_size.width();
294 int video_height = buf_size.height();
295
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.");
301 }
302
303 DecoderBase* dec = player->GetDecoder();
304 float video_aspect = dec ? dec->GetVideoAspect() : 4.0F / 3.0F;
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;
310
311 if (m_avfMode)
312 {
313 newWidth = m_cmdWidth;
314 newHeight = m_cmdHeight;
315
316 // Absolutely no purpose is served by scaling video up beyond it's
317 // original resolution, quality is degraded, transcoding is
318 // slower and in future we may wish to scale bitrate according to
319 // resolution, so it would also waste bandwidth (when streaming)
320 //
321 // This change could be said to apply for all transcoding, but for now
322 // we're limiting it to HLS where it's uncontroversial
323 if (m_hlsMode)
324 {
325// if (newWidth > video_width)
326// newWidth = video_width;
327 if (newHeight > video_height)
328 {
329 newHeight = video_height;
330 newWidth = 0;
331 }
332 }
333
334 // TODO: is this necessary? It got commented out, but may still be
335 // needed.
336 // int actualHeight = (video_height == 1088 ? 1080 : video_height);
337
338 // If height or width are 0, then we need to calculate them
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)
344 {
345 newHeight = 480;
346 newWidth = (int)(1.0F * 480 * video_aspect);
347 if (newWidth > 640)
348 {
349 newWidth = 640;
350 newHeight = (int)(1.0F * 640 / video_aspect);
351 }
352 }
353
354 // make sure dimensions are valid for MPEG codecs
355 newHeight = (newHeight + 15) & ~0xF;
356 newWidth = (newWidth + 15) & ~0xF;
357
358 avfw = std::make_unique<MythAVFormatWriter>();
359 if (!avfw)
360 {
361 LOG(VB_GENERAL, LOG_ERR,
362 "Transcoding aborted, error creating AVFormatWriter.");
363 SetPlayerContext(nullptr);
364 return REENCODE_ERROR;
365 }
366
367 avfw->SetVideoBitrate(m_cmdBitrate);
368 avfw->SetHeight(newHeight);
369 avfw->SetWidth(newWidth);
370 avfw->SetAspect(video_aspect);
371 avfw->SetAudioBitrate(m_cmdAudioBitrate);
372 avfw->SetAudioChannels(arb->m_channels);
373 avfw->SetAudioFrameRate(arb->m_eff_audiorate);
374 avfw->SetAudioFormat(FORMAT_S16);
375
376 if (m_hlsMode)
377 {
378
379 if (m_hlsStreamID == -1)
380 {
381 hls = std::make_unique<HTTPLiveStream>(inputname, newWidth, newHeight,
383 m_hlsMaxSegments, 0, 0);
384
385 m_hlsStreamID = hls->GetStreamID();
386 if (!hls || m_hlsStreamID == -1)
387 {
388 LOG(VB_GENERAL, LOG_ERR, "Unable to create new stream");
389 SetPlayerContext(nullptr);
390 return REENCODE_ERROR;
391 }
392 }
393
394 int segmentSize = hls->GetSegmentSize();
395
396 LOG(VB_GENERAL, LOG_NOTICE,
397 QString("HLS: Using segment size of %1 seconds")
398 .arg(segmentSize));
399
401 {
402 int audioOnlyBitrate = hls->GetAudioOnlyBitrate();
403
404 avfw2 = std::make_unique<MythAVFormatWriter>();
405 avfw2->SetContainer("mpegts");
406 avfw2->SetAudioCodec("aac");
407 avfw2->SetAudioBitrate(audioOnlyBitrate);
408 avfw2->SetAudioChannels(arb->m_channels);
409 avfw2->SetAudioFrameRate(arb->m_eff_audiorate);
410 avfw2->SetAudioFormat(FORMAT_S16);
411 }
412
413 avfw->SetContainer("mpegts");
414 avfw->SetVideoCodec("libx264");
415 avfw->SetAudioCodec("aac");
416 hls->UpdateStatus(kHLSStatusStarting);
417 hls->UpdateStatusMessage("Transcoding Starting");
418 hls->UpdateSizeInfo(newWidth, newHeight, video_width, video_height);
419
420 if (!hls->InitForWrite())
421 {
422 LOG(VB_GENERAL, LOG_ERR, "hls->InitForWrite() failed");
423 SetPlayerContext(nullptr);
424 return REENCODE_ERROR;
425 }
426
427 if (video_frame_rate > 30)
428 {
429 halfFramerate = true;
430 avfw->SetFramerate(video_frame_rate/2);
431
432 if (avfw2)
433 avfw2->SetFramerate(video_frame_rate/2);
434
435 hlsSegmentSize = (int)(segmentSize * video_frame_rate / 2);
436 }
437 else
438 {
439 avfw->SetFramerate(video_frame_rate);
440
441 if (avfw2)
442 avfw2->SetFramerate(video_frame_rate);
443
444 hlsSegmentSize = (int)(segmentSize * video_frame_rate);
445 }
446
447 avfw->SetKeyFrameDist(30);
448 if (avfw2)
449 avfw2->SetKeyFrameDist(30);
450
451 hls->AddSegment();
452 avfw->SetFilename(hls->GetCurrentFilename());
453 if (avfw2)
454 avfw2->SetFilename(hls->GetCurrentFilename(true));
455 }
456 else
457 {
458 avfw->SetContainer(m_cmdContainer);
459 avfw->SetVideoCodec(m_cmdVideoCodec);
460 avfw->SetAudioCodec(m_cmdAudioCodec);
461 avfw->SetFilename(outputname);
462 avfw->SetFramerate(video_frame_rate);
463 avfw->SetKeyFrameDist(30);
464 }
465
466 int threads = gCoreContext->GetNumSetting("HTTPLiveStreamThreads", 2);
467 QString preset = gCoreContext->GetSetting("HTTPLiveStreamPreset", "veryfast");
468 QString tune = gCoreContext->GetSetting("HTTPLiveStreamTune", "film");
469
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));
473
474 avfw->SetThreadCount(threads);
475 avfw->SetEncodingPreset(preset);
476 avfw->SetEncodingTune(tune);
477
478 if (avfw2)
479 avfw2->SetThreadCount(1);
480
481 if (!avfw->Init())
482 {
483 LOG(VB_GENERAL, LOG_ERR, "avfw->Init() failed");
484 SetPlayerContext(nullptr);
485 return REENCODE_ERROR;
486 }
487
488 if (!avfw->OpenFile())
489 {
490 LOG(VB_GENERAL, LOG_ERR, "avfw->OpenFile() failed");
491 SetPlayerContext(nullptr);
492 return REENCODE_ERROR;
493 }
494
495 if (avfw2 && !avfw2->Init())
496 {
497 LOG(VB_GENERAL, LOG_ERR, "avfw2->Init() failed");
498 SetPlayerContext(nullptr);
499 return REENCODE_ERROR;
500 }
501
502 if (avfw2 && !avfw2->OpenFile())
503 {
504 LOG(VB_GENERAL, LOG_ERR, "avfw2->OpenFile() failed");
505 SetPlayerContext(nullptr);
506 return REENCODE_ERROR;
507 }
508
509 arb->m_audioFrameSize = avfw->GetAudioFrameSize() * arb->m_channels * 2;
510 }
511
512 if (honorCutList && !deleteMap.empty())
513 {
514 if (cleanCut)
515 {
516 // Have the player seek only part of the way
517 // through a cut, and then use the cutter to
518 // discard the rest
519 cutter = std::make_unique<Cutter>();
520 cutter->SetCutList(deleteMap, m_ctx);
521 player->SetCutList(cutter->AdjustedCutList());
522 }
523 else
524 {
525 // Have the player apply the cut list
526 player->SetCutList(deleteMap);
527 }
528 }
529
530 player->InitForTranscode();
531 if (player->IsErrored())
532 {
533 LOG(VB_GENERAL, LOG_ERR,
534 "Unable to initialize MythPlayer for Transcode");
535 SetPlayerContext(nullptr);
536 return REENCODE_ERROR;
537 }
538
539 // must come after InitForTranscode - which creates the VideoOutput instance
540 if (m_hlsMode && player->GetVideoOutput())
541 player->GetVideoOutput()->SetDeinterlacing(true, false, DEINT_CPU | DEINT_MEDIUM);
542
543 MythVideoFrame frame;
544 // Do not use padding when compressing to RTjpeg or when in fifomode.
545 // The RTjpeg compressor doesn't know how to handle strides different to
546 // video width.
547 bool nonAligned = vidsetting == "RTjpeg" || !fifodir.isEmpty();
548 bool rescale = (video_width != newWidth) || (video_height != newHeight) || nonAligned;
549
550 if (rescale)
551 {
552 if (nonAligned)
553 {
554 // Set a stride identical to actual width, to ease fifo post-conversion process.
555 // 1080i/p video is actually 1088 because of the 16x16 blocks so
556 // we have to fudge the output size here. nuvexport knows how to handle
557 // this and as of right now it is the only app that uses the fifo ability.
559 video_width, video_height == 1080 ? 1088 : video_height, 0 /* aligned */);
560 uint8_t* newbuffer = MythVideoFrame::GetAlignedBuffer(newSize);
561 if (!newbuffer)
562 return REENCODE_ERROR;
563 frame.Init(FMT_YV12, newbuffer, newSize, video_width, video_height, nullptr, 0);
564 }
565 else
566 {
567 frame.Init(FMT_YV12, newWidth, newHeight);
568 }
569 }
570
571 if (!fifodir.isEmpty())
572 {
573 AudioPlayer *aplayer = player->GetAudio();
574 const char *audio_codec_name {nullptr};
575
576 switch(aplayer->GetCodec())
577 {
578 case AV_CODEC_ID_AC3:
579 audio_codec_name = "ac3";
580 break;
581 case AV_CODEC_ID_EAC3:
582 audio_codec_name = "eac3";
583 break;
584 case AV_CODEC_ID_DTS:
585 audio_codec_name = "dts";
586 break;
587 case AV_CODEC_ID_TRUEHD:
588 audio_codec_name = "truehd";
589 break;
590 case AV_CODEC_ID_MP3:
591 audio_codec_name = "mp3";
592 break;
593 case AV_CODEC_ID_MP2:
594 audio_codec_name = "mp2";
595 break;
596 case AV_CODEC_ID_AAC:
597 audio_codec_name = "aac";
598 break;
599 case AV_CODEC_ID_AAC_LATM:
600 audio_codec_name = "aac_latm";
601 break;
602 default:
603 audio_codec_name = "unknown";
604 }
605
606 if (!arb->m_passthru)
607 audio_codec_name = "raw";
608
609 // If cutlist is used then get info on first uncut frame
610 if (honorCutList && fifo_info)
611 {
612 bool is_key = false;
613 int did_ff = 0;
614 player->TranscodeGetNextFrame(did_ff, is_key, true);
615
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();
621 }
622
623 // Display details of the format of the fifo data.
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,
637 QString("FifoAudioSampleRate %1").arg(arb->m_eff_audiorate));
638
639 if(fifo_info)
640 {
641 // Request was for just the format of fifo data, not for
642 // the actual transcode, so stop here.
643 unlink(outputname.toLocal8Bit().constData());
644 SetPlayerContext(nullptr);
645 return REENCODE_OK;
646 }
647
648 QString audfifo = fifodir + QString("/audout");
649 QString vidfifo = fifodir + QString("/vidout");
650 int audio_size = arb->m_eff_audiorate * arb->m_bytes_per_frame;
651 // framecontrol is true if we want to enforce fifo sync.
652 if (framecontrol)
653 LOG(VB_GENERAL, LOG_INFO, "Enforcing sync on fifos");
654 m_fifow = new MythFIFOWriter(2, framecontrol);
655
656 if (!m_fifow->FIFOInit(0, QString("video"), vidfifo, frame.m_bufferSize, 50) ||
657 !m_fifow->FIFOInit(1, QString("audio"), audfifo, audio_size, 25))
658 {
659 LOG(VB_GENERAL, LOG_ERR,
660 "Error initializing fifo writer. Aborting");
661 unlink(outputname.toLocal8Bit().constData());
662 SetPlayerContext(nullptr);
663 return REENCODE_ERROR;
664 }
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)
669 .arg(arb->m_eff_audiorate));
670 LOG(VB_GENERAL, LOG_INFO, "Created fifos. Waiting for connection.");
671 }
672
673 frm_dir_map_t::iterator dm_iter;
674
675 int did_ff = 0;
676
677 long curFrameNum = 0;
678 frame.m_frameNumber = 1;
679 long totalAudio = 0;
680 int dropvideo = 0;
681 // timecode of the last read video frame in input time
682 std::chrono::milliseconds lasttimecode = 0ms;
683 // timecode of the last write video frame in input or output time
684 std::chrono::milliseconds lastWrittenTime = 0ms;
685 // delta between the same video frame in input and output due to applying the cut list
686 std::chrono::milliseconds timecodeOffset = 0ms;
687
688 float rateTimeConv = arb->m_eff_audiorate / 1000.0F;
689 float vidFrameTime = 1000.0F / video_frame_rate;
690 auto vidFrameTimeMs = millisecondsFromFloat(vidFrameTime);
691 int wait_recover = 0;
692 MythVideoOutput *videoOutput = player->GetVideoOutput();
693 bool is_key = false;
694 AVFrame imageIn;
695 AVFrame imageOut;
696 struct SwsContext *scontext = nullptr;
697
698 if (m_fifow)
699 LOG(VB_GENERAL, LOG_INFO, "Dumping Video and Audio data to fifos");
700 else if (m_hlsMode)
701 LOG(VB_GENERAL, LOG_INFO, "Transcoding for HTTP Live Streaming");
702 else if (m_avfMode)
703 LOG(VB_GENERAL, LOG_INFO, "Transcoding to libavformat container");
704 else
705 LOG(VB_GENERAL, LOG_INFO, "Transcoding Video and Audio");
706
707 auto *videoBuffer =
708 new VideoDecodeBuffer(player, videoOutput, honorCutList);
709 MThreadPool::globalInstance()->start(videoBuffer, "VideoDecodeBuffer");
710
711 QElapsedTimer flagTime;
712 flagTime.start();
713
714 if (cutter)
715 cutter->Activate(vidFrameTime * rateTimeConv, total_frame_count);
716
717 bool stopSignalled = false;
718 MythVideoFrame *lastDecode = nullptr;
719
720 if (hls)
721 {
722 hls->UpdateStatus(kHLSStatusRunning);
723 hls->UpdateStatusMessage("Transcoding");
724 }
725
726 while ((!stopSignalled) &&
727 (lastDecode = videoBuffer->GetFrame(did_ff, is_key)))
728 {
729 float new_aspect = lastDecode->m_aspect;
730
731 if (cutter)
732 cutter->NewFrame(lastDecode->m_frameNumber);
733
734// frame timecode is on input time base
735 frame.m_timecode = lastDecode->m_timecode;
736
737 // if the timecode jumps backwards just use the last frame's timecode plus the duration of a frame
738 if (frame.m_timecode < lasttimecode)
739 frame.m_timecode = lasttimecode + vidFrameTimeMs;
740
741 if (m_fifow)
742 {
743 MythAVUtil::FillAVFrame(&imageIn, lastDecode);
744 MythAVUtil::FillAVFrame(&imageOut, &frame);
745
746 scontext = sws_getCachedContext(scontext,
747 lastDecode->m_width, lastDecode->m_height, MythAVUtil::FrameTypeToPixelFormat(lastDecode->m_type),
749 SWS_FAST_BILINEAR, nullptr, nullptr, nullptr);
750 // Typically, wee aren't rescaling per say, we're just correcting the stride set by the decoder.
751 // However, it allows to properly handle recordings that see their resolution change half-way.
752 sws_scale(scontext, imageIn.data, imageIn.linesize, 0,
753 lastDecode->m_height, imageOut.data, imageOut.linesize);
754
755 totalAudio += arb->GetSamples(frame.m_timecode);
756 std::chrono::milliseconds audbufTime = millisecondsFromFloat(totalAudio / rateTimeConv);
757 std::chrono::milliseconds auddelta = frame.m_timecode - audbufTime;
758 std::chrono::milliseconds vidTime = millisecondsFromFloat(curFrameNum * vidFrameTime);
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)
763 {
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;
773 wait_recover = 0;
774 }
775 else if (delta >= 500ms && delta < 10s)
776 {
777 if (wait_recover == 0)
778 {
779 dropvideo = 5;
780 wait_recover = 6;
781 }
782 else if (wait_recover == 1)
783 {
784 // Video is badly lagging. Try to catch up.
785 int count = 0;
786 while (delta > vidFrameTimeMs)
787 {
788 if (!cutter || !cutter->InhibitDummyFrame())
789 m_fifow->FIFOWrite(0, frame.m_buffer, frame.m_bufferSize);
790
791 count++;
792 delta -= vidFrameTimeMs;
793 }
794 QString msg = QString("Added %1 blank video frames")
795 .arg(count);
796 LOG(VB_GENERAL, LOG_INFO, msg);
797 curFrameNum += count;
798 dropvideo = 0;
799 wait_recover = 0;
800 }
801 else
802 {
803 wait_recover--;
804 }
805 }
806 else
807 {
808 dropvideo = 0;
809 wait_recover = 0;
810 }
811
812#if 0
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()));
820#endif
821 AudioBuffer *ab = nullptr;
822 while ((ab = arb->GetData(frame.m_timecode)) != nullptr)
823 {
824 if (!cutter ||
825 !cutter->InhibitUseAudioFrames(ab->m_frames, &totalAudio))
826 m_fifow->FIFOWrite(1, ab->data(), ab->size());
827
828 delete ab;
829 }
830
831 if (dropvideo < 0)
832 {
833 if (cutter && cutter->InhibitDropFrame())
834 m_fifow->FIFOWrite(0, frame.m_buffer, frame.m_bufferSize);
835
836 LOG(VB_GENERAL, LOG_INFO, "Dropping video frame");
837 dropvideo++;
838 curFrameNum--;
839 }
840 else
841 {
842 if (!cutter || !cutter->InhibitUseVideoFrame())
843 m_fifow->FIFOWrite(0, frame.m_buffer, frame.m_bufferSize);
844
845 if (dropvideo)
846 {
847 if (!cutter || !cutter->InhibitDummyFrame())
848 m_fifow->FIFOWrite(0, frame.m_buffer, frame.m_bufferSize);
849
850 curFrameNum++;
851 dropvideo--;
852 }
853 }
854 videoOutput->DoneDisplayingFrame(lastDecode);
855 player->GetCC608Reader()->FlushTxtBuffers();
856 lasttimecode = frame.m_timecode;
857 }
858 else
859 {
860 if (did_ff == 1)
861 {
862 did_ff = 2;
863 timecodeOffset += (frame.m_timecode - lasttimecode -
864 millisecondsFromFloat(vidFrameTime));
865 }
866
867 if (video_aspect != new_aspect)
868 {
869 video_aspect = new_aspect;
870 }
871
872
873 QSize buf_size4 = player->GetVideoBufferSize();
874
875 if (video_width != buf_size4.width() ||
876 video_height != buf_size4.height())
877 {
878 video_width = buf_size4.width();
879 video_height = buf_size4.height();
880
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));
885 }
886
887 if (rescale)
888 {
889 MythAVUtil::FillAVFrame(&imageIn, lastDecode);
890 MythAVUtil::FillAVFrame(&imageOut, &frame);
891
892 int bottomBand = (lastDecode->m_height == 1088) ? 8 : 0;
893 scontext = sws_getCachedContext(scontext,
894 lastDecode->m_width, lastDecode->m_height, MythAVUtil::FrameTypeToPixelFormat(lastDecode->m_type),
896 SWS_FAST_BILINEAR, nullptr, nullptr, nullptr);
897
898 sws_scale(scontext, imageIn.data, imageIn.linesize, 0,
899 lastDecode->m_height - bottomBand,
900 imageOut.data, imageOut.linesize);
901 }
902
903 // audio is fully decoded, so we need to reencode it
904 AudioBuffer *ab = nullptr;
905 while ((ab = arb->GetData(lastWrittenTime)) != nullptr)
906 {
907 auto *buf = (unsigned char *)ab->data();
908 if (m_avfMode)
909 {
910 if (did_ff != 1)
911 {
912 std::chrono::milliseconds tc = ab->m_time - timecodeOffset;
913 avfw->WriteAudioFrame(buf, audioFrame, tc);
914
915 if (avfw2)
916 {
917 if ((avfw2->GetTimecodeOffset() == -1ms) &&
918 (avfw->GetTimecodeOffset() != -1ms))
919 {
920 avfw2->SetTimecodeOffset(
921 avfw->GetTimecodeOffset());
922 }
923
924 tc = ab->m_time - timecodeOffset;
925 avfw2->WriteAudioFrame(buf, audioFrame, tc);
926 }
927
928 ++audioFrame;
929 }
930 }
931 delete ab;
932 }
933
934 if (!m_avfMode)
935 {
936 LOG(VB_GENERAL, LOG_ERR,
937 "AVFormat mode not set.");
938 return REENCODE_ERROR;
939 }
940 lasttimecode = frame.m_timecode;
941 frame.m_timecode -= timecodeOffset;
942
943 if (m_avfMode)
944 {
945 if (halfFramerate && !skippedLastFrame)
946 {
947 skippedLastFrame = true;
948 }
949 else
950 {
951 skippedLastFrame = false;
952
953 if ((hls) &&
954 (avfw->GetFramesWritten()) &&
955 (hlsSegmentFrames > hlsSegmentSize) &&
956 (avfw->NextFrameIsKeyFrame()))
957 {
958 hls->AddSegment();
959 avfw->ReOpen(hls->GetCurrentFilename());
960
961 if (avfw2)
962 avfw2->ReOpen(hls->GetCurrentFilename(true));
963
964 hlsSegmentFrames = 0;
965 }
966
967 if (avfw->WriteVideoFrame(rescale ? &frame : lastDecode) > 0)
968 {
969 lastWrittenTime = frame.m_timecode + timecodeOffset;
970 if (hls)
971 ++hlsSegmentFrames;
972 }
973
974 }
975 }
976 }
977 if (MythDate::current() > statustime)
978 {
979 if (m_showProgress)
980 {
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)));
985 }
986
987 if (hls && hls->CheckStop())
988 {
989 hls->UpdateStatus(kHLSStatusStopping);
990 stopSignalled = true;
991 }
992
993 statustime = MythDate::current().addSecs(5);
994 }
995 if (MythDate::current() > curtime)
996 {
997 if (honorCutList && m_proginfo && !m_avfMode &&
999 {
1000 LOG(VB_GENERAL, LOG_NOTICE,
1001 "Transcoding aborted, cutlist updated");
1002
1003 unlink(outputname.toLocal8Bit().constData());
1004 SetPlayerContext(nullptr);
1005 if (videoBuffer)
1006 videoBuffer->stop();
1008 }
1009
1010 if ((jobID >= 0) || (VERBOSE_LEVEL_CHECK(VB_GENERAL, LOG_INFO)))
1011 {
1013 {
1014 LOG(VB_GENERAL, LOG_NOTICE,
1015 "Transcoding STOPped by JobQueue");
1016
1017 unlink(outputname.toLocal8Bit().constData());
1018 SetPlayerContext(nullptr);
1019 if (videoBuffer)
1020 videoBuffer->stop();
1021 if (hls)
1022 {
1023 hls->UpdateStatus(kHLSStatusStopped);
1024 hls->UpdateStatusMessage("Transcoding Stopped");
1025 }
1026 return REENCODE_STOPPED;
1027 }
1028
1029 float flagFPS = 0.0;
1030 float elapsed = flagTime.elapsed() / 1000.0F;
1031 if (elapsed != 0.0F)
1032 flagFPS = curFrameNum / elapsed;
1033
1034 total_frame_count = player->GetCurrentFrameCount();
1035 int percentage = curFrameNum * 100 / total_frame_count;
1036
1037 if (hls)
1038 hls->UpdatePercentComplete(percentage);
1039
1040 if (jobID >= 0)
1041 {
1043 QObject::tr("%1% Completed @ %2 fps.")
1044 .arg(percentage).arg(flagFPS));
1045 }
1046 else
1047 {
1048 LOG(VB_GENERAL, LOG_INFO,
1049 QString("mythtranscode: %1% Completed @ %2 fps.")
1050 .arg(percentage).arg(flagFPS));
1051 }
1052
1053 }
1054 curtime = MythDate::current().addSecs(20);
1055 }
1056
1057 curFrameNum++;
1058 frame.m_frameNumber = 1 + (curFrameNum << 1);
1059
1060 player->DiscardVideoFrame(lastDecode);
1061 }
1062
1063 sws_freeContext(scontext);
1064
1065 if (!m_fifow)
1066 {
1067 if (avfw)
1068 avfw->CloseFile();
1069
1070 if (avfw2)
1071 avfw2->CloseFile();
1072
1073 if (!m_avfMode && m_proginfo)
1074 {
1079 }
1080 } else {
1081 m_fifow->FIFODrain();
1082 }
1083
1084 if (hls)
1085 {
1086 if (!stopSignalled)
1087 {
1088 hls->UpdateStatus(kHLSStatusCompleted);
1089 hls->UpdateStatusMessage("Transcoding Completed");
1090 hls->UpdatePercentComplete(100);
1091 }
1092 else
1093 {
1094 hls->UpdateStatus(kHLSStatusStopped);
1095 hls->UpdateStatusMessage("Transcoding Stopped");
1096 }
1097 }
1098
1099 if (videoBuffer)
1100 videoBuffer->stop();
1101
1102 SetPlayerContext(nullptr);
1103
1104 return REENCODE_OK;
1105}
1106
1107/* vim: set expandtab tabstop=4 shiftwidth=4: */
1108
AVFrame AVFrame
@ FORMAT_NONE
@ FORMAT_S16
std::chrono::milliseconds m_time
char * data(void) const
int size(void) const
AVCodecID GetCodec(void) const
Definition: audioplayer.h:58
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
Definition: decoderbase.h:182
static enum JobCmds GetJobCmd(int jobID)
Definition: jobqueue.cpp:1465
static bool ChangeJobComment(int jobID, const QString &comment="")
Definition: jobqueue.cpp:1010
static bool IsJobRunning(int jobType, uint chanid, const QDateTime &recstartts)
Definition: jobqueue.cpp:1086
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.
Definition: mythavutil.cpp:199
static AVPixelFormat FrameTypeToPixelFormat(VideoFrameType Type)
Definition: mythavutil.cpp:28
QString GetSetting(const QString &key, const QString &defaultval="")
int GetNumSetting(const QString &key, int defaultval=0)
void FIFODrain(void)
bool FIFOInit(uint Id, const QString &Desc, const QString &Name, long Size, int NumBufs)
void FIFOWrite(uint Id, void *Buffer, long Size)
QString GetLastError(void) const
static MythMediaBuffer * Create(const QString &Filename, bool Write, bool UseReadAhead=true, std::chrono::milliseconds Timeout=kDefaultOpenTimeout, bool StreamOnly=false)
Creates a RingBuffer instance.
long long m_frameNumber
Definition: mythframe.h:128
VideoFrameType m_type
Definition: mythframe.h:118
static size_t GetBufferSize(VideoFrameType Type, int Width, int Height, int Aligned=MYTH_WIDTH_ALIGNMENT)
Definition: mythframe.cpp:412
size_t m_bufferSize
Definition: mythframe.h:123
void Init(VideoFrameType Type, int Width, int Height, const VideoFrameTypes *RenderFormats=nullptr)
Definition: mythframe.cpp:42
std::chrono::milliseconds m_timecode
Definition: mythframe.h:130
uint8_t * m_buffer
Definition: mythframe.h:119
static uint8_t * GetAlignedBuffer(size_t Size)
Definition: mythframe.cpp:430
float m_aspect
Definition: mythframe.h:126
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.
Definition: programinfo.h:68
void ClearMarkupFlag(MarkTypes type) const
Definition: programinfo.h:652
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.
Definition: programinfo.h:413
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)
Definition: transcode.cpp:134
int m_cmdHeight
Definition: transcode.h:67
QString m_cmdVideoCodec
Definition: transcode.h:65
Transcode(ProgramInfo *pginfo)
Definition: transcode.cpp:50
MythMediaBuffer * m_outBuffer
Definition: transcode.h:54
void SetPlayerContext(PlayerContext *player_ctx)
Definition: transcode.cpp:125
~Transcode() override
Definition: transcode.cpp:56
int m_cmdAudioBitrate
Definition: transcode.h:69
bool m_hlsMode
Definition: transcode.h:59
bool m_avfMode
Definition: transcode.h:58
int m_hlsStreamID
Definition: transcode.h:60
bool m_hlsDisableAudioOnly
Definition: transcode.h:61
PlayerContext * m_ctx
Definition: transcode.h:53
int m_cmdWidth
Definition: transcode.h:66
ProgramInfo * m_proginfo
Definition: transcode.h:50
int m_hlsMaxSegments
Definition: transcode.h:62
MythPlayer * GetPlayer(void)
Definition: transcode.h:47
RecordingProfile * m_recProfile
Definition: transcode.h:51
bool GetProfile(const QString &profileName, const QString &encodingType, int height, int frameRate)
Definition: transcode.cpp:64
bool m_showProgress
Definition: transcode.h:56
MythFIFOWriter * m_fifow
Definition: transcode.h:55
int m_cmdBitrate
Definition: transcode.h:68
QString m_cmdContainer
Definition: transcode.h:63
QString m_cmdAudioCodec
Definition: transcode.h:64
@ kTrackTypeAudio
Definition: decoderbase.h:29
@ kHLSStatusStarting
@ kHLSStatusStopping
@ kHLSStatusStopped
@ kHLSStatusCompleted
@ kHLSStatusRunning
@ JOB_COMMFLAG
Definition: jobqueue.h:79
@ JOB_STOP
Definition: jobqueue.h:54
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.
Definition: mythchrono.h:91
int jobID
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
@ DEINT_MEDIUM
Definition: mythframe.h:70
@ DEINT_CPU
Definition: mythframe.h:72
@ FMT_YV12
Definition: mythframe.h:23
static bool VERBOSE_LEVEL_CHECK(uint64_t mask, LogLevel_t level)
Definition: mythlogging.h:29
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
PlayerFlags
Definition: mythplayer.h:65
@ kVideoIsNull
Definition: mythplayer.h:73
@ kNoITV
Definition: mythplayer.h:75
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:15
const QString kTranscoderInUseID
@ MARK_KEYFRAME
Definition: programtypes.h:61
@ MARK_GOP_BYFRAME
Definition: programtypes.h:63
@ MARK_UPDATED_CUT
Definition: programtypes.h:52
@ MARK_DURATION_MS
Definition: programtypes.h:73
@ MARK_GOP_START
Definition: programtypes.h:60
QMap< uint64_t, MarkTypes > frm_dir_map_t
Frame # -> Mark map.
Definition: programtypes.h:117
@ REENCODE_STOPPED
Definition: transcodedefs.h:9
@ REENCODE_CUTLIST_CHANGE
Definition: transcodedefs.h:6
@ REENCODE_OK
Definition: transcodedefs.h:7
@ REENCODE_ERROR
Definition: transcodedefs.h:8
RemoteEncoder * RemoteGetExistingRecorder(const ProgramInfo *pginfo)