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