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