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
25#include "libmythtv/deletemap.h"
27#include "libmythtv/jobqueue.h"
32
33// MythTranscode
34#include "audioreencodebuffer.h"
35#include "cutter.h"
36#include "mythtranscodeplayer.h"
37#include "transcode.h"
38#include "videodecodebuffer.h"
39
40extern "C" {
41#include "libavcodec/avcodec.h"
42#include "libswscale/swscale.h"
43}
44
45#define LOC QString("Transcode: ")
46
48 m_proginfo(pginfo),
49 m_recProfile(new RecordingProfile("Transcoders"))
50{
51}
52
54{
55 SetPlayerContext(nullptr);
56 delete m_outBuffer;
57 delete m_fifow;
58 delete m_recProfile;
59}
60
61bool Transcode::GetProfile(const QString& profileName, const QString& encodingType,
62 int height, int frameRate)
63{
64 if (profileName.toLower() == "autodetect")
65 {
66 if (height == 1088)
67 height = 1080;
68
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";
74
75 bool result = false;
76 LOG(VB_GENERAL, LOG_NOTICE,
77 QString("Transcode: Looking for autodetect profile: %1")
78 .arg(autoProfileName));
79 result = m_recProfile->loadByGroup(autoProfileName, "Transcoders");
80
81 if (!result && encodingType == "MPEG-2")
82 {
83 result = m_recProfile->loadByGroup("MPEG2", "Transcoders");
84 autoProfileName = "MPEG2";
85 }
86 if (!result && (encodingType == "MPEG-4" || encodingType == "RTjpeg"))
87 {
88 result = m_recProfile->loadByGroup("RTjpeg/MPEG4",
89 "Transcoders");
90 autoProfileName = "RTjpeg/MPEG4";
91 }
92 if (!result)
93 {
94 LOG(VB_GENERAL, LOG_ERR,
95 QString("Transcode: Couldn't find profile for : %1")
96 .arg(encodingType));
97
98 return false;
99 }
100
101 LOG(VB_GENERAL, LOG_NOTICE,
102 QString("Transcode: Using autodetect profile: %1")
103 .arg(autoProfileName));
104 }
105 else
106 {
107 bool isNum = false;
108 int profileID = profileName.toInt(&isNum);
109 // If a bad profile is specified, there will be trouble
110 if (isNum && profileID > 0)
111 {
112 m_recProfile->loadByID(profileID);
113 }
114 else if (!m_recProfile->loadByGroup(profileName, "Transcoders"))
115 {
116 LOG(VB_GENERAL, LOG_ERR, QString("Couldn't find profile #: %1")
117 .arg(profileName));
118 return false;
119 }
120 }
121 return true;
122}
123
125{
126 if (player_ctx == m_ctx)
127 return;
128
129 delete m_ctx;
130 m_ctx = player_ctx;
131}
132
133int Transcode::TranscodeFile(const QString &inputname,
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,
139 frm_dir_map_t &deleteMap,
140 int AudioTrackNo,
141 bool passthru)
142{
143 QDateTime curtime = MythDate::current();
144 QDateTime statustime = curtime;
145 int audioFrame = 0;
146 std::unique_ptr<Cutter> cutter = nullptr;
147 std::unique_ptr<MythAVFormatWriter> avfw = nullptr;
148
149 if (jobID >= 0)
150 JobQueue::ChangeJobComment(jobID, "0% " + QObject::tr("Completed"));
151
152 if (!m_avfMode && fifodir.isEmpty())
153 {
154 LOG(VB_GENERAL, LOG_ERR, "No output mode is set.");
155 return REENCODE_ERROR;
156 }
157
158 // Input setup
159 auto *player_ctx = new PlayerContext(kTranscoderInUseID);
160 player_ctx->SetPlayingInfo(m_proginfo);
161 MythMediaBuffer *rb = MythMediaBuffer::Create(inputname, false, false);
162 if (!rb || !rb->GetLastError().isEmpty())
163 {
164 LOG(VB_GENERAL, LOG_ERR,
165 QString("Transcoding aborted, error: '%1'")
166 .arg(rb? rb->GetLastError() : ""));
167 delete player_ctx;
168 return REENCODE_ERROR;
169 }
170 player_ctx->SetRingBuffer(rb);
171 player_ctx->SetPlayer(new MythTranscodePlayer(player_ctx, static_cast<PlayerFlags>(kVideoIsNull | kNoITV)));
172 SetPlayerContext(player_ctx);
173 auto * player = qobject_cast<MythTranscodePlayer*>(GetPlayer());
174 if (player == nullptr)
175 {
176 LOG(VB_GENERAL, LOG_ERR,
177 QString("Transcoding aborted, failed to retrieve MythPlayer object"));
178 return REENCODE_ERROR;
179 }
180 if (m_proginfo->GetRecordingEndTime() > curtime)
181 {
182 player_ctx->SetRecorder(RemoteGetExistingRecorder(m_proginfo));
183 player->SetWatchingRecording(true);
184 }
185
186 if (m_showProgress)
187 {
188 statustime = statustime.addSecs(5);
189 }
190
191 AudioOutput *audioOutput = new AudioReencodeBuffer(FORMAT_NONE, 0,
192 passthru);
193 AudioReencodeBuffer *arb = ((AudioReencodeBuffer*)audioOutput);
194 player->GetAudio()->SetAudioOutput(audioOutput);
195 player->SetTranscoding(true);
196
197 if (player->OpenFile() < 0)
198 {
199 LOG(VB_GENERAL, LOG_ERR, "Transcoding aborted, error opening file.");
200 SetPlayerContext(nullptr);
201 return REENCODE_ERROR;
202 }
203
204 if (AudioTrackNo > -1)
205 {
206 LOG(VB_GENERAL, LOG_INFO,
207 QString("Set audiotrack number to %1").arg(AudioTrackNo));
208 player->GetDecoder()->SetTrack(kTrackTypeAudio, AudioTrackNo);
209 }
210
211 long long total_frame_count = player->GetTotalFrameCount();
212 long long new_frame_count = total_frame_count;
213 if (honorCutList && m_proginfo)
214 {
215 LOG(VB_GENERAL, LOG_INFO, "Honoring the cutlist while transcoding");
216
217 frm_dir_map_t::const_iterator it;
218 QString cutStr;
219 long long lastStart = 0;
220
221 if (deleteMap.empty())
222 m_proginfo->QueryCutList(deleteMap);
223
224 for (it = deleteMap.cbegin(); it != deleteMap.cend(); ++it)
225 {
226 if (*it)
227 {
228 if (!cutStr.isEmpty())
229 cutStr += ",";
230 cutStr += QString("%1-").arg((long)it.key());
231 lastStart = it.key();
232 }
233 else
234 {
235 if (cutStr.isEmpty())
236 cutStr += "0-";
237 cutStr += QString("%1").arg((long)it.key());
238 new_frame_count -= (it.key() - lastStart);
239 }
240 }
241 if (cutStr.isEmpty())
242 {
243 cutStr = "Is Empty";
244 }
245 else if (cutStr.endsWith('-') && (total_frame_count > lastStart))
246 {
247 new_frame_count -= (total_frame_count - lastStart);
248 cutStr += QString("%1").arg(total_frame_count);
249 }
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));
255
256 if ((m_proginfo->QueryIsEditing()) ||
258 {
259 LOG(VB_GENERAL, LOG_INFO, "Transcoding aborted, cutlist changed");
260 SetPlayerContext(nullptr);
262 }
264 curtime = curtime.addSecs(60);
265 }
266
267 player->GetAudio()->ReinitAudio();
268
269 QString vidsetting = nullptr;
270
271 QSize buf_size = player->GetVideoBufferSize();
272 int video_width = buf_size.width();
273 int video_height = buf_size.height();
274
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.");
280 }
281
282 DecoderBase* dec = player->GetDecoder();
283 float video_aspect = dec ? dec->GetVideoAspect() : 4.0F / 3.0F;
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;
289
290 if (m_avfMode)
291 {
292 newWidth = m_cmdWidth;
293 newHeight = m_cmdHeight;
294
295 // TODO: is this necessary? It got commented out, but may still be
296 // needed.
297 // int actualHeight = (video_height == 1088 ? 1080 : video_height);
298
299 // If height or width are 0, then we need to calculate them
300 if (newHeight == 0 && newWidth > 0)
301 {
302 newHeight = (int)(1.0F * newWidth / video_aspect);
303 }
304 else if (newWidth == 0 && newHeight > 0)
305 {
306 newWidth = (int)(1.0F * newHeight * video_aspect);
307 }
308 else if (newWidth == 0 && newHeight == 0)
309 {
310 newHeight = 480;
311 newWidth = (int)(1.0F * 480 * video_aspect);
312 if (newWidth > 640)
313 {
314 newWidth = 640;
315 newHeight = (int)(1.0F * 640 / video_aspect);
316 }
317 }
318
319 // make sure dimensions are valid for MPEG codecs
320 newHeight = (newHeight + 15) & ~0xF;
321 newWidth = (newWidth + 15) & ~0xF;
322
323 avfw = std::make_unique<MythAVFormatWriter>();
324 if (!avfw)
325 {
326 LOG(VB_GENERAL, LOG_ERR,
327 "Transcoding aborted, error creating AVFormatWriter.");
328 SetPlayerContext(nullptr);
329 return REENCODE_ERROR;
330 }
331
332 avfw->SetVideoBitrate(m_cmdBitrate);
333 avfw->SetHeight(newHeight);
334 avfw->SetWidth(newWidth);
335 avfw->SetAspect(video_aspect);
336 avfw->SetAudioBitrate(m_cmdAudioBitrate);
337 avfw->SetAudioChannels(arb->m_channels);
338 avfw->SetAudioFrameRate(arb->m_eff_audiorate);
339 avfw->SetAudioFormat(FORMAT_S16);
340
341 {
342 avfw->SetContainer(m_cmdContainer);
343 avfw->SetVideoCodec(m_cmdVideoCodec);
344 avfw->SetAudioCodec(m_cmdAudioCodec);
345 avfw->SetFilename(outputname);
346 avfw->SetFramerate(video_frame_rate);
347 avfw->SetKeyFrameDist(30);
348 }
349
350 int threads = gCoreContext->GetNumSetting("HTTPLiveStreamThreads", 2);
351 QString preset = gCoreContext->GetSetting("HTTPLiveStreamPreset", "veryfast");
352 QString tune = gCoreContext->GetSetting("HTTPLiveStreamTune", "film");
353
354 LOG(VB_GENERAL, LOG_NOTICE,
355 QString("x264 using: %1 threads, '%2' profile and '%3' tune")
356 .arg(QString::number(threads), preset, tune));
357
358 avfw->SetThreadCount(threads);
359 avfw->SetEncodingPreset(preset);
360 avfw->SetEncodingTune(tune);
361
362 if (!avfw->Init())
363 {
364 LOG(VB_GENERAL, LOG_ERR, "avfw->Init() failed");
365 SetPlayerContext(nullptr);
366 return REENCODE_ERROR;
367 }
368
369 if (!avfw->OpenFile())
370 {
371 LOG(VB_GENERAL, LOG_ERR, "avfw->OpenFile() failed");
372 SetPlayerContext(nullptr);
373 return REENCODE_ERROR;
374 }
375
376 arb->m_audioFrameSize = avfw->GetAudioFrameSize() * arb->m_channels * 2;
377 }
378
379 if (honorCutList && !deleteMap.empty())
380 {
381 if (cleanCut)
382 {
383 // Have the player seek only part of the way
384 // through a cut, and then use the cutter to
385 // discard the rest
386 cutter = std::make_unique<Cutter>();
387 cutter->SetCutList(deleteMap, m_ctx);
388 player->SetCutList(cutter->AdjustedCutList());
389 }
390 else
391 {
392 // Have the player apply the cut list
393 player->SetCutList(deleteMap);
394 }
395 }
396
397 player->InitForTranscode();
398 if (player->IsErrored())
399 {
400 LOG(VB_GENERAL, LOG_ERR,
401 "Unable to initialize MythPlayer for Transcode");
402 SetPlayerContext(nullptr);
403 return REENCODE_ERROR;
404 }
405
406 MythVideoFrame frame;
407 // Do not use padding when compressing to RTjpeg or when in fifomode.
408 // The RTjpeg compressor doesn't know how to handle strides different to
409 // video width.
410 bool nonAligned = vidsetting == "RTjpeg" || !fifodir.isEmpty();
411 bool rescale = (video_width != newWidth) || (video_height != newHeight) || nonAligned;
412
413 if (rescale)
414 {
415 if (nonAligned)
416 {
417 // Set a stride identical to actual width, to ease fifo post-conversion process.
418 // 1080i/p video is actually 1088 because of the 16x16 blocks so
419 // we have to fudge the output size here. nuvexport knows how to handle
420 // this and as of right now it is the only app that uses the fifo ability.
422 video_width, video_height == 1080 ? 1088 : video_height, 0 /* aligned */);
423 uint8_t* newbuffer = MythVideoFrame::GetAlignedBuffer(newSize);
424 if (!newbuffer)
425 return REENCODE_ERROR;
426 frame.Init(FMT_YV12, newbuffer, newSize, video_width, video_height, nullptr, 0);
427 }
428 else
429 {
430 frame.Init(FMT_YV12, newWidth, newHeight);
431 }
432 }
433
434 if (!fifodir.isEmpty())
435 {
436 AudioPlayer *aplayer = player->GetAudio();
437 const char *audio_codec_name {nullptr};
438
439 switch(aplayer->GetCodec())
440 {
441 case AV_CODEC_ID_AC3:
442 audio_codec_name = "ac3";
443 break;
444 case AV_CODEC_ID_EAC3:
445 audio_codec_name = "eac3";
446 break;
447 case AV_CODEC_ID_DTS:
448 audio_codec_name = "dts";
449 break;
450 case AV_CODEC_ID_TRUEHD:
451 audio_codec_name = "truehd";
452 break;
453 case AV_CODEC_ID_MP3:
454 audio_codec_name = "mp3";
455 break;
456 case AV_CODEC_ID_MP2:
457 audio_codec_name = "mp2";
458 break;
459 case AV_CODEC_ID_AAC:
460 audio_codec_name = "aac";
461 break;
462 case AV_CODEC_ID_AAC_LATM:
463 audio_codec_name = "aac_latm";
464 break;
465 default:
466 audio_codec_name = "unknown";
467 }
468
469 if (!arb->m_passthru)
470 audio_codec_name = "raw";
471
472 // If cutlist is used then get info on first uncut frame
473 if (honorCutList && fifo_info)
474 {
475 bool is_key = false;
476 int did_ff = 0;
477 player->TranscodeGetNextFrame(did_ff, is_key, true);
478
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();
484 }
485
486 // Display details of the format of the fifo data.
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,
500 QString("FifoAudioSampleRate %1").arg(arb->m_eff_audiorate));
501
502 if(fifo_info)
503 {
504 // Request was for just the format of fifo data, not for
505 // the actual transcode, so stop here.
506 unlink(outputname.toLocal8Bit().constData());
507 SetPlayerContext(nullptr);
508 return REENCODE_OK;
509 }
510
511 QString audfifo = fifodir + QString("/audout");
512 QString vidfifo = fifodir + QString("/vidout");
513 int audio_size = arb->m_eff_audiorate * arb->m_bytes_per_frame;
514 // framecontrol is true if we want to enforce fifo sync.
515 if (framecontrol)
516 LOG(VB_GENERAL, LOG_INFO, "Enforcing sync on fifos");
517 m_fifow = new MythFIFOWriter(2, framecontrol);
518
519 if (!m_fifow->FIFOInit(0, QString("video"), vidfifo, frame.m_bufferSize, 50) ||
520 !m_fifow->FIFOInit(1, QString("audio"), audfifo, audio_size, 25))
521 {
522 LOG(VB_GENERAL, LOG_ERR,
523 "Error initializing fifo writer. Aborting");
524 unlink(outputname.toLocal8Bit().constData());
525 SetPlayerContext(nullptr);
526 return REENCODE_ERROR;
527 }
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)
532 .arg(arb->m_eff_audiorate));
533 LOG(VB_GENERAL, LOG_INFO, "Created fifos. Waiting for connection.");
534 }
535
536 frm_dir_map_t::iterator dm_iter;
537
538 int did_ff = 0;
539
540 long curFrameNum = 0;
541 frame.m_frameNumber = 1;
542 long totalAudio = 0;
543 int dropvideo = 0;
544 // timecode of the last read video frame in input time
545 std::chrono::milliseconds lasttimecode = 0ms;
546 // timecode of the last write video frame in input or output time
547 std::chrono::milliseconds lastWrittenTime = 0ms;
548 // delta between the same video frame in input and output due to applying the cut list
549 std::chrono::milliseconds timecodeOffset = 0ms;
550
551 float rateTimeConv = arb->m_eff_audiorate / 1000.0F;
552 float vidFrameTime = 1000.0F / video_frame_rate;
553 auto vidFrameTimeMs = millisecondsFromFloat(vidFrameTime);
554 int wait_recover = 0;
555 MythVideoOutput *videoOutput = player->GetVideoOutput();
556 bool is_key = false;
557 AVFrame imageIn;
558 AVFrame imageOut;
559 struct SwsContext *scontext = nullptr;
560
561 if (m_fifow)
562 LOG(VB_GENERAL, LOG_INFO, "Dumping Video and Audio data to fifos");
563 else if (m_avfMode)
564 LOG(VB_GENERAL, LOG_INFO, "Transcoding to libavformat container");
565 else
566 LOG(VB_GENERAL, LOG_INFO, "Transcoding Video and Audio");
567
568 auto *videoBuffer =
569 new VideoDecodeBuffer(player, videoOutput, honorCutList);
570 MThreadPool::globalInstance()->start(videoBuffer, "VideoDecodeBuffer");
571
572 QElapsedTimer flagTime;
573 flagTime.start();
574
575 if (cutter)
576 cutter->Activate(vidFrameTime * rateTimeConv, total_frame_count);
577
578 bool stopSignalled = false;
579 MythVideoFrame *lastDecode = nullptr;
580
581 while ((!stopSignalled) &&
582 (lastDecode = videoBuffer->GetFrame(did_ff, is_key)))
583 {
584 float new_aspect = lastDecode->m_aspect;
585
586 if (cutter)
587 cutter->NewFrame(lastDecode->m_frameNumber);
588
589// frame timecode is on input time base
590 frame.m_timecode = lastDecode->m_timecode;
591
592 // if the timecode jumps backwards just use the last frame's timecode plus the duration of a frame
593 if (frame.m_timecode < lasttimecode)
594 frame.m_timecode = lasttimecode + vidFrameTimeMs;
595
596 if (m_fifow)
597 {
598 MythAVUtil::FillAVFrame(&imageIn, lastDecode);
599 MythAVUtil::FillAVFrame(&imageOut, &frame);
600
601 scontext = sws_getCachedContext(scontext,
602 lastDecode->m_width, lastDecode->m_height, MythAVUtil::FrameTypeToPixelFormat(lastDecode->m_type),
604 SWS_FAST_BILINEAR, nullptr, nullptr, nullptr);
605 // Typically, wee aren't rescaling per say, we're just correcting the stride set by the decoder.
606 // However, it allows to properly handle recordings that see their resolution change half-way.
607 sws_scale(scontext, imageIn.data, imageIn.linesize, 0,
608 lastDecode->m_height, imageOut.data, imageOut.linesize);
609
610 totalAudio += arb->GetSamples(frame.m_timecode);
611 std::chrono::milliseconds audbufTime = millisecondsFromFloat(totalAudio / rateTimeConv);
612 std::chrono::milliseconds auddelta = frame.m_timecode - audbufTime;
613 std::chrono::milliseconds vidTime = millisecondsFromFloat(curFrameNum * vidFrameTime);
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)
618 {
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;
628 wait_recover = 0;
629 }
630 else if (delta >= 500ms && delta < 10s)
631 {
632 if (wait_recover == 0)
633 {
634 dropvideo = 5;
635 wait_recover = 6;
636 }
637 else if (wait_recover == 1)
638 {
639 // Video is badly lagging. Try to catch up.
640 int count = 0;
641 while (delta > vidFrameTimeMs)
642 {
643 if (!cutter || !cutter->InhibitDummyFrame())
644 m_fifow->FIFOWrite(0, frame.m_buffer, frame.m_bufferSize);
645
646 count++;
647 delta -= vidFrameTimeMs;
648 }
649 QString msg = QString("Added %1 blank video frames")
650 .arg(count);
651 LOG(VB_GENERAL, LOG_INFO, msg);
652 curFrameNum += count;
653 dropvideo = 0;
654 wait_recover = 0;
655 }
656 else
657 {
658 wait_recover--;
659 }
660 }
661 else
662 {
663 dropvideo = 0;
664 wait_recover = 0;
665 }
666
667#if 0
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()));
675#endif
676 AudioBuffer *ab = nullptr;
677 while ((ab = arb->GetData(frame.m_timecode)) != nullptr)
678 {
679 if (!cutter ||
680 !cutter->InhibitUseAudioFrames(ab->m_frames, &totalAudio))
681 m_fifow->FIFOWrite(1, ab->data(), ab->size());
682
683 delete ab;
684 }
685
686 if (dropvideo < 0)
687 {
688 if (cutter && cutter->InhibitDropFrame())
689 m_fifow->FIFOWrite(0, frame.m_buffer, frame.m_bufferSize);
690
691 LOG(VB_GENERAL, LOG_INFO, "Dropping video frame");
692 dropvideo++;
693 curFrameNum--;
694 }
695 else
696 {
697 if (!cutter || !cutter->InhibitUseVideoFrame())
698 m_fifow->FIFOWrite(0, frame.m_buffer, frame.m_bufferSize);
699
700 if (dropvideo)
701 {
702 if (!cutter || !cutter->InhibitDummyFrame())
703 m_fifow->FIFOWrite(0, frame.m_buffer, frame.m_bufferSize);
704
705 curFrameNum++;
706 dropvideo--;
707 }
708 }
709 videoOutput->DoneDisplayingFrame(lastDecode);
710 player->GetCC608Reader()->FlushTxtBuffers();
711 lasttimecode = frame.m_timecode;
712 }
713 else
714 {
715 if (did_ff == 1)
716 {
717 did_ff = 2;
718 timecodeOffset += (frame.m_timecode - lasttimecode -
719 millisecondsFromFloat(vidFrameTime));
720 }
721
722 if (video_aspect != new_aspect)
723 {
724 video_aspect = new_aspect;
725 }
726
727
728 QSize buf_size4 = player->GetVideoBufferSize();
729
730 if (video_width != buf_size4.width() ||
731 video_height != buf_size4.height())
732 {
733 video_width = buf_size4.width();
734 video_height = buf_size4.height();
735
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));
740 }
741
742 if (rescale)
743 {
744 MythAVUtil::FillAVFrame(&imageIn, lastDecode);
745 MythAVUtil::FillAVFrame(&imageOut, &frame);
746
747 int bottomBand = (lastDecode->m_height == 1088) ? 8 : 0;
748 scontext = sws_getCachedContext(scontext,
749 lastDecode->m_width, lastDecode->m_height, MythAVUtil::FrameTypeToPixelFormat(lastDecode->m_type),
751 SWS_FAST_BILINEAR, nullptr, nullptr, nullptr);
752
753 sws_scale(scontext, imageIn.data, imageIn.linesize, 0,
754 lastDecode->m_height - bottomBand,
755 imageOut.data, imageOut.linesize);
756 }
757
758 // audio is fully decoded, so we need to reencode it
759 AudioBuffer *ab = nullptr;
760 while ((ab = arb->GetData(lastWrittenTime)) != nullptr)
761 {
762 auto *buf = (unsigned char *)ab->data();
763 if (m_avfMode)
764 {
765 if (did_ff != 1)
766 {
767 std::chrono::milliseconds tc = ab->m_time - timecodeOffset;
768 avfw->WriteAudioFrame(buf, audioFrame, tc);
769
770 ++audioFrame;
771 }
772 }
773 delete ab;
774 }
775
776 if (!m_avfMode)
777 {
778 LOG(VB_GENERAL, LOG_ERR,
779 "AVFormat mode not set.");
780 return REENCODE_ERROR;
781 }
782 lasttimecode = frame.m_timecode;
783 frame.m_timecode -= timecodeOffset;
784
785 if (m_avfMode)
786 {
787 if (halfFramerate && !skippedLastFrame)
788 {
789 skippedLastFrame = true;
790 }
791 else
792 {
793 skippedLastFrame = false;
794
795 if (avfw->WriteVideoFrame(rescale ? &frame : lastDecode) > 0)
796 {
797 lastWrittenTime = frame.m_timecode + timecodeOffset;
798 }
799
800 }
801 }
802 }
803 if (MythDate::current() > statustime)
804 {
805 if (m_showProgress)
806 {
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)));
811 }
812
813 statustime = MythDate::current().addSecs(5);
814 }
815 if (MythDate::current() > curtime)
816 {
817 if (honorCutList && m_proginfo && !m_avfMode &&
819 {
820 LOG(VB_GENERAL, LOG_NOTICE,
821 "Transcoding aborted, cutlist updated");
822
823 unlink(outputname.toLocal8Bit().constData());
824 SetPlayerContext(nullptr);
825 if (videoBuffer)
826 videoBuffer->stop();
828 }
829
830 if ((jobID >= 0) || (VERBOSE_LEVEL_CHECK(VB_GENERAL, LOG_INFO)))
831 {
833 {
834 LOG(VB_GENERAL, LOG_NOTICE,
835 "Transcoding STOPped by JobQueue");
836
837 unlink(outputname.toLocal8Bit().constData());
838 SetPlayerContext(nullptr);
839 if (videoBuffer)
840 videoBuffer->stop();
841 return REENCODE_STOPPED;
842 }
843
844 float flagFPS = 0.0;
845 float elapsed = flagTime.elapsed() / 1000.0F;
846 if (elapsed != 0.0F)
847 flagFPS = curFrameNum / elapsed;
848
849 total_frame_count = player->GetCurrentFrameCount();
850 int percentage = curFrameNum * 100 / total_frame_count;
851
852 if (jobID >= 0)
853 {
855 QObject::tr("%1% Completed @ %2 fps.")
856 .arg(percentage).arg(flagFPS));
857 }
858 else
859 {
860 LOG(VB_GENERAL, LOG_INFO,
861 QString("mythtranscode: %1% Completed @ %2 fps.")
862 .arg(percentage).arg(flagFPS));
863 }
864
865 }
866 curtime = MythDate::current().addSecs(20);
867 }
868
869 curFrameNum++;
870 frame.m_frameNumber = 1 + (curFrameNum << 1);
871
872 player->DiscardVideoFrame(lastDecode);
873 }
874
875 sws_freeContext(scontext);
876
877 if (!m_fifow)
878 {
879 if (avfw)
880 avfw->CloseFile();
881
882 if (!m_avfMode && m_proginfo)
883 {
888 }
889 } else {
891 }
892
893 if (videoBuffer)
894 videoBuffer->stop();
895
896 SetPlayerContext(nullptr);
897
898 return REENCODE_OK;
899}
900
901/* vim: set expandtab tabstop=4 shiftwidth=4: */
902
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:1480
static bool ChangeJobComment(int jobID, const QString &comment="")
Definition: jobqueue.cpp:1025
static bool IsJobRunning(int jobType, uint chanid, const QDateTime &recstartts)
Definition: jobqueue.cpp:1101
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:203
static AVPixelFormat FrameTypeToPixelFormat(VideoFrameType Type)
Definition: mythavutil.cpp:32
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:416
size_t m_bufferSize
Definition: mythframe.h:123
void Init(VideoFrameType Type, int Width, int Height, const VideoFrameTypes *RenderFormats=nullptr)
Definition: mythframe.cpp:46
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:434
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:75
void ClearMarkupFlag(MarkTypes type) const
Definition: programinfo.h:661
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:421
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:133
int m_cmdHeight
Definition: transcode.h:59
QString m_cmdVideoCodec
Definition: transcode.h:57
Transcode(ProgramInfo *pginfo)
Definition: transcode.cpp:47
MythMediaBuffer * m_outBuffer
Definition: transcode.h:50
void SetPlayerContext(PlayerContext *player_ctx)
Definition: transcode.cpp:124
~Transcode() override
Definition: transcode.cpp:53
int m_cmdAudioBitrate
Definition: transcode.h:61
bool m_avfMode
Definition: transcode.h:54
PlayerContext * m_ctx
Definition: transcode.h:49
int m_cmdWidth
Definition: transcode.h:58
ProgramInfo * m_proginfo
Definition: transcode.h:46
MythPlayer * GetPlayer(void)
Definition: transcode.h:43
RecordingProfile * m_recProfile
Definition: transcode.h:47
bool GetProfile(const QString &profileName, const QString &encodingType, int height, int frameRate)
Definition: transcode.cpp:61
bool m_showProgress
Definition: transcode.h:52
MythFIFOWriter * m_fifow
Definition: transcode.h:51
int m_cmdBitrate
Definition: transcode.h:60
QString m_cmdContainer
Definition: transcode.h:55
QString m_cmdAudioCodec
Definition: transcode.h:56
@ kTrackTypeAudio
Definition: decoderbase.h:29
@ JOB_COMMFLAG
Definition: jobqueue.h:79
@ JOB_STOP
Definition: jobqueue.h:54
std::chrono::milliseconds millisecondsFromFloat(T value)
Helper function for convert a floating point number to a duration.
Definition: mythchrono.h:80
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
@ 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)