Permalink
Fetching contributors…
Cannot retrieve contributors at this time
4551 lines (3831 sloc) 119 KB
#include <cstdio>
#include <cstdlib>
#include <unistd.h>
#include <fcntl.h>
#include <cassert>
#include <cerrno>
#include <sys/time.h>
#include <ctime>
#include <cmath>
#include <qapplication.h>
#include <qstringlist.h>
#include <qmap.h>
#include <sched.h>
#include <sys/ioctl.h>
#include <iostream>
using namespace std;
#include "config.h"
#include "mythdbcon.h"
#include "dialogbox.h"
#include "NuppelVideoPlayer.h"
#include "audiooutput.h"
#include "recordingprofile.h"
#include "osdtypes.h"
#include "remoteutil.h"
#include "programinfo.h"
#include "mythcontext.h"
#include "fifowriter.h"
#include "filtermanager.h"
#include "decoderbase.h"
#include "nuppeldecoder.h"
#include "avformatdecoder.h"
#include "jobqueue.h"
#include "NuppelVideoRecorder.h"
extern "C" {
#include "vbitext/vbi.h"
#include "vsync.h"
}
#include "remoteencoder.h"
#include "videoout_null.h"
#ifdef USING_IVTV
#include "videoout_ivtv.h"
#include "ivtvdecoder.h"
#endif
#ifdef USING_DIRECTX
#include "videoout_dx.h"
#undef GetFreeSpace
#undef GetFileSize
#endif
#ifdef USING_XVMC
#include "videoout_xvmc.h" // for hasIDCTAcceleration() call
#endif
#ifdef CONFIG_DARWIN
extern "C" {
int isnan(double);
}
#endif
NuppelVideoPlayer::NuppelVideoPlayer(ProgramInfo *info)
: decoder_lock(true)
{
m_playbackinfo = NULL;
parentWidget = NULL;
if (info)
m_playbackinfo = new ProgramInfo(*info);
playing = false;
decoder_thread_alive = true;
filename = "output.nuv";
prebuffering_lock.lock();
prebuffering = false;
prebuffering_wait.wakeAll();
prebuffering_lock.unlock();
vbimode = ' ';
QString mypage = gContext->GetSetting("VBIpageNr", "888");
bool valid = false;
if (mypage)
vbipagenr = mypage.toInt(&valid, 16) << 16;
else
vbipagenr = 0x08880000;
video_height = 0;
video_width = 0;
video_size = 0;
text_size = 0;
video_aspect = 1.33333;
m_scan = kScan_Detect;
m_double_framerate = false;
m_can_double = false;
video_frame_rate = 29.97;
prevtc = 0;
forceVideoOutput = kVideoOutput_Default;
decoder = NULL;
transcoding = false;
bookmarkseek = 0;
eof = 0;
keyframedist = 30;
wtxt = rtxt = 0;
embedid = 0;
embx = emby = embw = embh = -1;
nvr_enc = NULL;
paused = false;
actuallypaused = false;
video_actually_paused = false;
audiodevice = "/dev/dsp";
audioOutput = NULL;
ringBuffer = NULL;
weMadeBuffer = false;
osd = NULL;
commDetect = NULL;
audio_bits = -1;
audio_channels = 2;
audio_samplerate = 44100;
audio_stretchfactor = 1.0;
editmode = false;
resetvideo = false;
hasFullPositionMap = false;
framesPlayed = 0;
totalLength = 0;
totalFrames = 0;
play_speed = 1.0;
normal_speed = true;
ffrew_skip = 1;
next_play_speed = 1.0;
next_normal_speed = true;
videobuf_retries = 0;
disablevideo = disableaudio = false;
setpipplayer = pipplayer = NULL;
needsetpipplayer = false;
videoFilterList = "";
videoFilters = NULL;
FiltMan = new FilterManager;
videoOutput = NULL;
watchingrecording = false;
exactseeks = false;
autocommercialskip = 0;
commercialskipmethod = gContext->GetNumSetting("CommercialSkipMethod",
COMM_DETECT_BLANKS);
commrewindamount = gContext->GetNumSetting("CommRewindAmount",0);
commnotifyamount = gContext->GetNumSetting("CommNotifyAmount",0);
lastCommSkipDirection = 0;
lastCommSkipStart = 0;
m_DeintSetting = gContext->GetNumSetting("Deinterlace", 0);
timedisplay = NULL;
seekamount = 30;
seekamountpos = 4;
deleteframe = 0;
hasdeletetable = false;
hasblanktable = false;
hascommbreaktable = false;
dialogname = "";
for (int i = 0; i <= MAXTBUFFER; i++)
{
txtbuffers[i].len = 0;
txtbuffers[i].timecode = 0;
txtbuffers[i].type = 0;
txtbuffers[i].buffer = NULL;
}
killvideo = false;
pausevideo = false;
cc = false;
ccmode = CC_CC1;
ccline = "";
cccol = 0;
ccrow = 0;
limitKeyRepeat = false;
warplbuff = NULL;
warprbuff = NULL;
warpbuffsize = 0;
videosync = NULL;
errored = false;
}
NuppelVideoPlayer::~NuppelVideoPlayer(void)
{
if (audioOutput)
delete audioOutput;
if (m_playbackinfo)
delete m_playbackinfo;
if (weMadeBuffer)
delete ringBuffer;
if (osd)
delete osd;
if (commDetect)
delete commDetect;
for (int i = 0; i < MAXTBUFFER; i++)
{
if (txtbuffers[i].buffer)
delete [] txtbuffers[i].buffer;
}
if (decoder)
delete decoder;
if (FiltMan)
delete FiltMan;
if (videoFilters)
delete videoFilters;
if (videoOutput)
delete videoOutput;
if (videosync)
delete videosync;
}
void NuppelVideoPlayer::SetWatchingRecording(bool mode)
{
watchingrecording = mode;
if (decoder)
decoder->setWatchingRecording(mode);
}
void NuppelVideoPlayer::SetRecorder(RemoteEncoder *recorder)
{
nvr_enc = recorder;
if (decoder)
decoder->setRecorder(recorder);
}
void NuppelVideoPlayer::Pause(bool waitvideo)
{
decoder_lock.lock();
next_play_speed = 0.0;
next_normal_speed = false;
decoder_lock.unlock();
if (!actuallypaused)
{
while (!decoderThreadPaused.wait(4000))
{
if (eof)
return;
VERBOSE(VB_IMPORTANT, "Waited too long for decoder to pause");
}
}
//cout << "stopping other threads" << endl;
PauseVideo(waitvideo);
if (audioOutput)
audioOutput->Pause(true);
if (ringBuffer)
ringBuffer->Pause();
if (decoder && videoOutput)
{
//cout << "updating frames played" << endl;
if (disablevideo || forceVideoOutput == kVideoOutput_IVTV)
decoder->UpdateFramesPlayed();
else
framesPlayed = videoOutput->GetFramesPlayed();
}
}
bool NuppelVideoPlayer::Play(float speed, bool normal, bool unpauseaudio)
{
//cout << "starting other threads" << endl;
UnpauseVideo();
if (audioOutput && unpauseaudio)
audioOutput->Pause(false);
if (ringBuffer)
ringBuffer->Unpause();
decoder_lock.lock();
next_play_speed = speed;
next_normal_speed = normal;
if (normal)
audio_stretchfactor = speed;
decoder_lock.unlock();
return true;
}
bool NuppelVideoPlayer::GetPause(void)
{
return (actuallypaused &&
(ringBuffer == NULL || ringBuffer->isPaused()) &&
(audioOutput == NULL || audioOutput->GetPause()) &&
GetVideoPause());
}
inline bool NuppelVideoPlayer::GetVideoPause(void)
{
return video_actually_paused;
}
void NuppelVideoPlayer::PauseVideo(bool wait)
{
video_actually_paused = false;
pausevideo = true;
if (wait && !video_actually_paused)
{
while (!videoThreadPaused.wait(1000))
{
if (eof)
return;
VERBOSE(VB_IMPORTANT, "Waited too long for video out to pause");
}
}
}
void NuppelVideoPlayer::UnpauseVideo(void)
{
pausevideo = false;
}
void NuppelVideoPlayer::setPrebuffering(bool prebuffer)
{
prebuffering_lock.lock();
if (prebuffer != prebuffering)
{
prebuffering = prebuffer;
if (audioOutput && !paused)
audioOutput->Pause(prebuffering);
}
if (!prebuffering)
prebuffering_wait.wakeAll();
prebuffering_lock.unlock();
}
void NuppelVideoPlayer::ForceVideoOutputType(VideoOutputType type)
{
forceVideoOutput = type;
}
bool NuppelVideoPlayer::InitVideo(void)
{
InitFilters();
if (disablevideo)
{
videoOutput = new VideoOutputNull();
if (!videoOutput->Init(video_width, video_height, video_aspect,
0, 0, 0, 0, 0, 0))
{
errored = true;
return false;
}
}
else
{
QWidget *widget = parentWidget;
if (!widget)
{
MythMainWindow *window = gContext->GetMainWindow();
assert(window);
QObject *playbackwin = window->child("video playback window");
QWidget *widget = (QWidget *)playbackwin;
if (!widget)
{
cerr << "Couldn't find 'tv playback' widget\n";
widget = window->currentWidget();
assert(widget);
}
}
videoOutput = VideoOutput::InitVideoOut(forceVideoOutput);
if (!videoOutput)
{
errored = true;
return false;
}
if (gContext->GetNumSetting("DecodeExtraAudio", 0))
decoder->setLowBuffers();
if (!videoOutput->Init(video_width, video_height, video_aspect,
widget->winId(), 0, 0, widget->width(),
widget->height(), 0))
{
errored = true;
return false;
}
#ifdef USING_XVMC
// We must tell the AvFormatDecoder whether we are using
// an IDCT or MC pixel format.
if (kVideoOutput_XvMC == forceVideoOutput)
{
bool idct = videoOutput->hasIDCTAcceleration();
decoder->SetPixelFormat((idct) ? PIX_FMT_XVMC_MPEG2_IDCT :
PIX_FMT_XVMC_MPEG2_MC);
}
#endif
}
if (embedid > 0)
{
videoOutput->EmbedInWidget(embedid, embx, emby, embw, embh);
}
return true;
}
void NuppelVideoPlayer::ReinitOSD(void)
{
if (osd)
{
int dispx = 0, dispy = 0;
int dispw = video_width, disph = video_height;
videoOutput->GetVisibleSize(dispx, dispy, dispw, disph);
osd->Reinit(video_width, video_height, frame_interval,
dispx, dispy, dispw, disph);
}
}
void NuppelVideoPlayer::ReinitVideo(void)
{
InitFilters();
videofiltersLock.lock();
videoOutput->InputChanged(video_width, video_height, video_aspect);
ReinitOSD();
videofiltersLock.unlock();
ClearAfterSeek();
}
void NuppelVideoPlayer::ReinitAudio(void)
{
if (audioOutput)
{
audioOutput->Reconfigure(audio_bits, audio_channels, audio_samplerate);
audioOutput->SetStretchFactor(audio_stretchfactor);
}
}
static inline QString toQString(FrameScanType scan) {
switch (scan) {
case kScan_Ignore: return QString("Ignore Scan");
case kScan_Detect: return QString("Detect Scan");
case kScan_Interlaced: return QString("Interlaced Scan");
case kScan_Progressive: return QString("Progressive Scan");
default: return QString("Unknown Scan");
}
}
FrameScanType NuppelVideoPlayer::detectInterlace(FrameScanType newScan,
FrameScanType scan,
float fps, int video_height)
{
QString dbg = QString("detectInterlace(") + toQString(newScan) +
QString(", ") + toQString(scan) + QString(", ") + QString("%1").arg(fps) +
QString(", ") + QString("%1").arg(video_height) + QString(") ->");
if (kScan_Ignore != newScan || kScan_Detect == scan)
{
// The scanning mode should be decoded from the stream, but if it
// isn't, we have to guess.
scan = kScan_Interlaced; // default to interlaced
if (720 == video_height) // ATSC 720p
scan = kScan_Progressive;
else if (fps > 45) // software deinterlacing
scan = kScan_Progressive;
if (kScan_Detect != newScan)
scan = newScan;
};
VERBOSE(VB_PLAYBACK, dbg+toQString(scan));
return scan;
}
void NuppelVideoPlayer::SetKeyframeDistance(int keyframedistance)
{
if (keyframedistance > 0)
keyframedist = keyframedistance;
}
void NuppelVideoPlayer::SetVideoParams(int width, int height, double fps,
int keyframedistance, float aspect,
FrameScanType scan, bool reinit)
{
if (width == 0 || height == 0 || isnan(aspect) || isnan(fps))
return;
if (video_width == width && video_height == height &&
aspect == video_aspect && fps == video_frame_rate)
{
return;
}
if (width > 0)
video_width = width;
if (height > 0)
video_height = height;
if (fps > 0 && fps < 120)
{
video_frame_rate = fps;
float temp_speed = (play_speed == 0.0) ? audio_stretchfactor : play_speed;
frame_interval = (int)(1000000.0 / video_frame_rate / temp_speed);
}
video_size = video_height * video_width * 3 / 2;
if (keyframedistance > 0)
keyframedist = keyframedistance;
if (aspect > 0.0f)
{
video_aspect = aspect;
if (commDetect)
commDetect->SetVideoParams(aspect);
}
if (reinit)
ReinitVideo();
videofiltersLock.lock();
m_scan = detectInterlace(scan, m_scan, video_frame_rate, video_height);
VERBOSE(VB_PLAYBACK, QString("Interlaced: %1 video_height: %2 fps: %3")
.arg(toQString(m_scan)).arg(video_height).arg(fps));
// Set up deinterlacing in the video output method
m_double_framerate = false;
if (videoOutput)
videoOutput->SetupDeinterlace(false);
if (m_scan == kScan_Interlaced && m_DeintSetting) {
if (videoOutput && videoOutput->SetupDeinterlace(true)) {
if (videoOutput->NeedsDoubleFramerate())
{
m_double_framerate = true;
m_can_double = true;
}
}
}
// Make sure video sync can do it
if (videosync != NULL && m_double_framerate)
{
videosync->SetFrameInterval(frame_interval, m_double_framerate);
if (!videosync->isInterlaced()) {
VERBOSE(VB_IMPORTANT, "Video sync method can't support double "
"framerate (refresh rate too low for bob deint)");
m_scan = kScan_Ignore;
m_double_framerate = false;
m_can_double = false;
if (videoOutput)
videoOutput->SetupDeinterlace(false);
}
}
videofiltersLock.unlock();
}
void NuppelVideoPlayer::SetFileLength(int total, int frames)
{
totalLength = total;
totalFrames = frames;
}
int NuppelVideoPlayer::OpenFile(bool skipDsp)
{
if (!skipDsp)
{
if (!ringBuffer)
{
cerr << "Warning: Old player ringbuf creation\n";
ringBuffer = new RingBuffer(filename, false);
weMadeBuffer = true;
livetv = false;
}
else
livetv = ringBuffer->LiveMode();
if (!ringBuffer->IsOpen())
{
fprintf(stderr, "File not found: %s\n",
ringBuffer->GetFilename().ascii());
return -1;
}
}
ringBuffer->Start();
long long startpos = ringBuffer->GetTotalReadPosition();
decoder = NULL;
char testbuf[2048];
if (ringBuffer->Read(testbuf, 2048) != 2048)
{
cerr << "Couldn't read file: " << ringBuffer->GetFilename() << endl;
return -1;
}
ringBuffer->Seek(startpos, SEEK_SET);
if (decoder)
delete decoder;
if (NuppelDecoder::CanHandle(testbuf))
decoder = new NuppelDecoder(this, m_playbackinfo);
#ifdef USING_IVTV
else if (!disablevideo && IvtvDecoder::CanHandle(testbuf,
ringBuffer->GetFilename()))
{
decoder = new IvtvDecoder(this, m_playbackinfo);
disableaudio = true; // no audio with ivtv.
audio_bits = 16;
}
#endif
else if (AvFormatDecoder::CanHandle(testbuf, ringBuffer->GetFilename()))
decoder = new AvFormatDecoder(this, m_playbackinfo);
if (!decoder)
{
VERBOSE(VB_IMPORTANT,
QString("NVP: Couldn't find a matching decoder for: %1").
arg(ringBuffer->GetFilename()));
return -1;
}
else if (decoder->IsErrored())
{
VERBOSE(VB_IMPORTANT,
"NVP: NuppelDecoder encountered error during creation.");
delete decoder;
return -1;
}
decoder->setExactSeeks(exactseeks);
decoder->setLiveTVMode(livetv);
decoder->setWatchingRecording(watchingrecording);
decoder->setRecorder(nvr_enc);
decoder->setTranscoding(transcoding);
eof = 0;
text_size = 8 * (sizeof(teletextsubtitle) + VT_WIDTH);
int ret;
if ((ret = decoder->OpenFile(ringBuffer, disablevideo, testbuf)) < 0)
{
cerr << "Couldn't open decoder for: " << ringBuffer->GetFilename()
<< endl;
return -1;
}
if (audio_bits == -1)
disableaudio = true;
if (ret > 0)
{
hasFullPositionMap = true;
LoadCutList();
if (!deleteMap.isEmpty())
{
hasdeletetable = true;
deleteIter = deleteMap.begin();
}
}
bookmarkseek = GetBookmark();
commDetect = new CommDetect(video_width, video_height, video_frame_rate,
commercialskipmethod);
commDetect->SetAggressiveDetection(
gContext->GetNumSetting("AggressiveCommDetect", 1));
return 0;
}
void NuppelVideoPlayer::InitFilters(void)
{
VideoFrameType itmp = FMT_YV12;
VideoFrameType otmp = FMT_YV12;
int btmp;
videofiltersLock.lock();
if (videoFilters)
delete videoFilters;
postfilt_width = video_width;
postfilt_height = video_height;
videoFilters = FiltMan->LoadFilters(videoFilterList, itmp, otmp,
postfilt_width, postfilt_height, btmp);
videofiltersLock.unlock();
}
int NuppelVideoPlayer::tbuffer_numvalid(void)
{
/* thread safe, returns number of valid slots in the text buffer */
int ret;
text_buflock.lock();
if (wtxt >= rtxt)
ret = wtxt - rtxt;
else
ret = MAXTBUFFER - (rtxt - wtxt);
text_buflock.unlock();
return ret;
}
int NuppelVideoPlayer::tbuffer_numfree(void)
{
return MAXTBUFFER - tbuffer_numvalid() - 1;
/* There's one wasted slot, because the case when rtxt = wtxt is
interpreted as an empty buffer. So the fullest the buffer can be is
MAXTBUFFER - 1. */
}
VideoFrame *NuppelVideoPlayer::GetNextVideoFrame(void)
{
return videoOutput->GetNextFreeFrame();
}
void NuppelVideoPlayer::ReleaseNextVideoFrame(VideoFrame *buffer,
long long timecode)
{
buffer->timecode = timecode;
videoOutput->ReleaseFrame(buffer);
}
void NuppelVideoPlayer::DiscardVideoFrame(VideoFrame *buffer)
{
videoOutput->DiscardFrame(buffer);
}
void NuppelVideoPlayer::DrawSlice(VideoFrame *frame, int x, int y, int w, int h)
{
videoOutput->DrawSlice(frame, x, y, w, h);
}
void NuppelVideoPlayer::AddTextData(char *buffer, int len,
long long timecode, char type)
{
if (cc)
{
if (!tbuffer_numfree())
{
cerr << "text buffer overflow\n";
return;
}
if (len > text_size)
len = text_size;
txtbuffers[wtxt].timecode = timecode;
txtbuffers[wtxt].type = type;
txtbuffers[wtxt].len = len;
memset(txtbuffers[wtxt].buffer, 0, text_size);
memcpy(txtbuffers[wtxt].buffer, buffer, len);
text_buflock.lock();
wtxt = (wtxt+1) % MAXTBUFFER;
text_buflock.unlock();
}
}
bool NuppelVideoPlayer::GetFrame(int onlyvideo, bool unsafe)
{
#ifdef USING_IVTV
if (forceVideoOutput != kVideoOutput_IVTV)
#endif
{
if (!videoOutput->EnoughFreeFrames() && !unsafe)
{
//cout << "waiting for video buffer to drain.\n";
setPrebuffering(false);
if (!videoOutput->availableVideoBuffersWait()->wait(10))
{
if (++videobuf_retries >= 200)
{
VERBOSE(VB_IMPORTANT, "Timed out waiting for free video buffers.");
if (videosync==NULL && tryingVideoSync)
{
VERBOSE(VB_IMPORTANT, "Attempting video sync thread restart");
//pthread_create(&output_video, NULL, kickoffOutputVideoLoop, this);
}
videobuf_retries = 0;
}
return false;
}
}
videobuf_retries = 0;
}
if (!decoder->GetFrame(onlyvideo))
return false;
#ifdef USING_IVTV
if (forceVideoOutput != kVideoOutput_IVTV)
#endif
{
if (videoOutput->EnoughDecodedFrames())
setPrebuffering(false);
}
if (ffrew_skip != 1)
{
bool stop;
long long real_skip;
if (ffrew_skip > 0)
{
long long delta = decoder->GetFramesRead() - framesPlayed;
real_skip = CalcMaxFFTime(ffrew_skip + delta) - delta;
decoder->DoFastForward(decoder->GetFramesRead() + real_skip, false);
stop = (CalcMaxFFTime(100) < 100);
}
else
{
real_skip = (-decoder->GetFramesRead() > ffrew_skip) ?
-decoder->GetFramesRead() : ffrew_skip;
decoder->DoRewind(decoder->GetFramesRead() + real_skip, false);
stop = framesPlayed <= keyframedist;
}
if (stop)
Play(audio_stretchfactor, true, true);
}
return true;
}
VideoFrame *NuppelVideoPlayer::GetCurrentFrame(int &w, int &h)
{
w = video_width;
h = video_height;
VideoFrame *retval = NULL;
vidExitLock.lock();
if (videoOutput)
retval = videoOutput->GetLastShownFrame();
if (!retval)
vidExitLock.unlock();
return retval;
}
void NuppelVideoPlayer::ReleaseCurrentFrame(VideoFrame *frame)
{
if (frame)
vidExitLock.unlock();
}
void NuppelVideoPlayer::EmbedInWidget(WId wid, int x, int y, int w, int h)
{
if (videoOutput)
{
videoOutput->EmbedInWidget(wid, x, y, w, h);
}
else
{
embedid = wid;
embx = x;
emby = y;
embw = w;
embh = h;
}
}
void NuppelVideoPlayer::StopEmbedding(void)
{
if (videoOutput)
videoOutput->StopEmbedding();
}
void NuppelVideoPlayer::ToggleCC(char mode, int arg)
{
QString msg;
vbimode = mode;
if (cc && !arg)
{
cc = false;
ResetCC();
if (mode == 1)
msg = QObject::tr("TXT off");
else
msg = QObject::tr("CC off");
}
else
{
cc = true;
if (mode == 1)
{
// TODO: teletext page select
//if (arg)
// vbipagenr = (arg & 0xffff) << 16;
//msg = QString("%1%2").arg(QObject::tr("TXT pg")).arg(vbipagenr);
msg = QObject::tr("TXT on");
}
else if (mode == 2)
{
cc = true;
switch (arg)
{
case 0: break; // same mode
case 2: ccmode = CC_CC2; break;
case 3: ccmode = CC_CC3; break;
case 4: ccmode = CC_CC4; break;
case 5: ccmode = CC_TXT1; break;
case 6: ccmode = CC_TXT2; break;
case 7: ccmode = CC_TXT3; break;
case 8: ccmode = CC_TXT4; break;
default: ccmode = CC_CC1;
}
ResetCC();
switch (ccmode)
{
case CC_CC2 :
msg = QString("%1%2").arg(QObject::tr("CC")).arg(2);
break;
case CC_CC3 :
msg = QString("%1%2").arg(QObject::tr("CC")).arg(3);
break;
case CC_CC4 :
msg = QString("%1%2").arg(QObject::tr("CC")).arg(4);
break;
case CC_TXT1:
msg = QString("%1%2").arg(QObject::tr("TXT")).arg(1);
break;
case CC_TXT2:
msg = QString("%1%2").arg(QObject::tr("TXT")).arg(2);
break;
case CC_TXT3:
msg = QString("%1%2").arg(QObject::tr("TXT")).arg(3);
break;
case CC_TXT4:
msg = QString("%1%2").arg(QObject::tr("TXT")).arg(4);
break;
default :
msg = QString("%1%2").arg(QObject::tr("CC")).arg(1);
break;
}
}
else
msg = QString(QObject::tr("CC/TXT disabled"));
}
if (osd)
osd->SetSettingsText(msg, 3);
}
void NuppelVideoPlayer::ShowText(void)
{
VideoFrame *last = videoOutput->GetLastShownFrame();
// check if subtitles need to be updated on the OSD
if (osd && tbuffer_numvalid() && txtbuffers[rtxt].timecode &&
(last && txtbuffers[rtxt].timecode <= last->timecode))
{
if (txtbuffers[rtxt].type == 'T')
{
// display full page of teletext
//
// all formatting is always defined in the page itself,
// if scrolling is needed for live closed captions this
// is handled by the broadcaster:
// the pages are then very often transmitted (sometimes as often as
// every 2 frames) with small differences between them
unsigned char *inpos = txtbuffers[rtxt].buffer;
int pagenr;
memcpy(&pagenr, inpos, sizeof(int));
inpos += sizeof(int);
if (pagenr == vbipagenr)
{
// show teletext subtitles
osd->ClearAllCCText();
(*inpos)++;
while (*inpos)
{
struct teletextsubtitle st;
memcpy(&st, inpos, sizeof(st));
inpos += sizeof(st);
printf("%s\n", (char*) inpos);
QString s((const char*) inpos);
osd->AddCCText(s, st.row, st.col, st.fg, true);
inpos += st.len;
}
}
}
else if (txtbuffers[rtxt].type == 'C')
{
UpdateCC(txtbuffers[rtxt].buffer);
}
text_buflock.lock();
if (rtxt != wtxt) // if a seek occurred, rtxt == wtxt, in this case do
// nothing
rtxt = (rtxt + 1) % MAXTBUFFER;
text_buflock.unlock();
}
}
void NuppelVideoPlayer::ResetCC(void)
{
ccline = "";
cccol = 0;
ccrow = 0;
if (osd)
osd->ClearAllCCText();
}
void NuppelVideoPlayer::UpdateCC(unsigned char *inpos)
{
struct ccsubtitle subtitle;
memcpy(&subtitle, inpos, sizeof(subtitle));
inpos += sizeof(ccsubtitle);
// skip undisplayed streams
if ((subtitle.resumetext & CC_MODE_MASK) != ccmode)
return;
if (subtitle.row == 0)
subtitle.row = 1;
if (subtitle.clr)
{
//printf ("erase displayed memory\n");
ResetCC();
if (!subtitle.len)
return;
}
// if (subtitle.len || !subtitle.clr)
{
unsigned char *end = inpos + subtitle.len;
int row = 0;
int linecont = (subtitle.resumetext & CC_LINE_CONT);
vector<ccText*> *ccbuf = new vector<ccText*>;
vector<ccText*>::iterator ccp;
ccText *tmpcc = NULL;
int replace = linecont;
int scroll = 0;
bool scroll_prsv = false;
int scroll_yoff = 0;
int scroll_ymax = 15;
do
{
if (linecont)
{
// append to last line; needs to be redrawn
replace = 1;
// backspace into existing line if needed
int bscnt = 0;
while ((inpos < end) && *inpos != 0 && (char)*inpos == '\b')
{
bscnt++;
inpos++;
}
if (bscnt)
ccline.remove(ccline.length() - bscnt, bscnt);
}
else
{
// new line: count spaces to calculate column position
row++;
cccol = 0;
ccline = "";
while ((inpos < end) && *inpos != 0 && (char)*inpos == ' ')
{
inpos++;
cccol++;
}
}
ccrow = subtitle.row;
unsigned char *cur = inpos;
//null terminate at EOL
while (cur < end && *cur != '\n' && *cur != 0)
cur++;
*cur = 0;
if (*inpos != 0 || linecont)
{
if (linecont)
ccline += QString::fromUtf8((const char *)inpos, -1);
else
ccline = QString::fromUtf8((const char *)inpos, -1);
tmpcc = new ccText();
tmpcc->text = ccline;
tmpcc->x = cccol;
tmpcc->y = ccrow;
tmpcc->color = 0;
tmpcc->teletextmode = false;
ccbuf->push_back(tmpcc);
#if 0
if (ccbuf->size() > 4)
{
printf("CC overflow: ");
printf("%d %d %s\n", cccol, ccrow, ccline.ascii());
}
#endif
}
subtitle.row++;
inpos = cur + 1;
linecont = 0;
} while (inpos < end);
// adjust row position
if (subtitle.resumetext & CC_TXT_MASK)
{
// TXT mode
// - can use entire 15 rows
// - scroll up when reaching bottom
if (ccrow > 15)
{
if (row)
scroll = ccrow - 15;
if (tmpcc)
tmpcc->y = 15;
}
}
else if (subtitle.rowcount == 0 || row > 1)
{
// multi-line text
// - fix display of old (badly-encoded) files
if (ccrow > 15)
{
ccp = ccbuf->begin();
for (; ccp != ccbuf->end(); ccp++)
{
tmpcc = *ccp;
tmpcc->y -= (ccrow - 15);
}
}
}
else
{
// scrolling text
// - scroll up previous lines if adding new line
// - if caption is at bottom, row address is for last
// row
// - if caption is at top, row address is for first row (?)
if (subtitle.rowcount > 4)
subtitle.rowcount = 4;
if (ccrow < subtitle.rowcount)
{
ccrow = subtitle.rowcount;
if (tmpcc)
tmpcc->y = ccrow;
}
if (row)
{
scroll = row;
scroll_prsv = true;
scroll_yoff = ccrow - subtitle.rowcount;
scroll_ymax = ccrow;
}
}
if (osd)
osd->UpdateCCText(ccbuf, replace, scroll,
scroll_prsv, scroll_yoff, scroll_ymax);
delete ccbuf;
}
}
#define MAXWARPDIFF 0.0005 // Max amount the warpfactor can change in 1 frame
#define WARPMULTIPLIER 1000000000 // How much do we multiply the warp by when
//storing it in an integer
#define WARPAVLEN (video_frame_rate * 600) // How long to average the warp over
#define WARPCLIP 0.1 // How much we allow the warp to deviate from 1
// (normal speed)
#define MAXDIVERGE 3 // Maximum number of frames of A/V divergence allowed
// before dropping or extending video frames to
// compensate
#define DIVERGELIMIT 30 // A/V divergence above this amount is clipped
// to avoid bad stream data causing huge pauses
float NuppelVideoPlayer::WarpFactor(void)
{
// Calculate a new warp factor
float divergence;
float rate;
float newwarp = 1;
float warpdiff;
// Number of frames the audio is out by
divergence = (float)avsync_avg / (float)frame_interval;
// Number of frames divergence is changing by per frame
rate = (float)(avsync_avg - avsync_oldavg) / (float)frame_interval;
avsync_oldavg = avsync_avg;
newwarp = warpfactor_avg * (1 + ((divergence + rate) / 125));
// Clip the amount changed so we don't get big frequency variations
warpdiff = newwarp / warpfactor;
if (warpdiff > (1 + MAXWARPDIFF))
newwarp = warpfactor * (1 + MAXWARPDIFF);
else if (warpdiff < (1 - MAXWARPDIFF))
newwarp = warpfactor * (1 - MAXWARPDIFF);
warpfactor = newwarp;
// Clip final warp factor
if (warpfactor < (1 - WARPCLIP))
warpfactor = 1 - WARPCLIP;
else if (warpfactor > (1 + (WARPCLIP * 2)))
warpfactor = 1 + (WARPCLIP * 2);
// Keep a 10 minute average
warpfactor_avg = (warpfactor + (warpfactor_avg * (WARPAVLEN - 1))) /
WARPAVLEN;
//cerr << "Divergence: " << divergence << " Rate: " << rate
//<< " Warpfactor: " << warpfactor << " warpfactor_avg: "
//<< warpfactor_avg << endl;
return divergence;
}
void NuppelVideoPlayer::InitAVSync(void)
{
videosync->Start();
avsync_adjustment = 0;
if (usevideotimebase)
{
warpfactor_avg = gContext->GetNumSetting("WarpFactor", 0);
if (warpfactor_avg)
warpfactor_avg /= WARPMULTIPLIER;
else
warpfactor_avg = 1;
// Reset the warpfactor if it's obviously bogus
if (warpfactor_avg < (1 - WARPCLIP))
warpfactor_avg = 1;
if (warpfactor_avg > (1 + (WARPCLIP * 2)) )
warpfactor_avg = 1;
warpfactor = warpfactor_avg;
}
refreshrate = videoOutput->GetRefreshRate();
if (refreshrate <= 0)
refreshrate = frame_interval;
vsynctol = refreshrate / 4;
if (!disablevideo)
{
if (usevideotimebase)
VERBOSE(VB_PLAYBACK, "Using video as timebase");
else
VERBOSE(VB_PLAYBACK, "Using audio as timebase");
QString timing_type = videosync->getName();
QString msg = QString("Video timing method: %1").arg(timing_type);
VERBOSE(VB_GENERAL, msg);
msg = QString("Refresh rate: %1, frame interval: %2")
.arg(refreshrate).arg(frame_interval);
VERBOSE(VB_PLAYBACK, msg);
nice(-19);
}
}
void NuppelVideoPlayer::AVSync(void)
{
float diverge;
VideoFrame *buffer = videoOutput->GetLastShownFrame();
if (!buffer)
{
VERBOSE(VB_IMPORTANT, "No video buffer");
return;
}
if (normal_speed)
diverge = WarpFactor();
else
diverge = 0;
// If video is way ahead of audio, drop some frames until we're
// close again.
if (diverge < -MAXDIVERGE)
{
if (diverge < -DIVERGELIMIT)
diverge = -DIVERGELIMIT;
VERBOSE(VB_PLAYBACK, QString("A/V diverged by %1 frames, dropping "
"frame to keep audio in sync").arg(diverge));
lastsync = true;
}
else if (!disablevideo)
{
if (videoOutput->IsErrored())
{ // this check prevents calling prepareframe
VERBOSE(VB_IMPORTANT, "NVP: Error condition detected "
"in videoOutput before PrepareFrame(), aborting playback.");
errored = true;
return;
}
// if we get here, we're actually going to do video output
if (buffer)
{
if (m_double_framerate)
videoOutput->PrepareFrame(buffer, kScan_Interlaced);
else
videoOutput->PrepareFrame(buffer, kScan_Ignore);
}
videosync->WaitForFrame(avsync_adjustment);
videoOutput->Show(m_scan);
if (videoOutput->IsErrored())
{
VERBOSE(VB_IMPORTANT, "NVP: Error condition detected "
"in videoOutput after Show(), aborting playback.");
errored = true;
return;
}
if (m_double_framerate)
{
if (forceVideoOutput != kVideoOutput_XvMC)
{
// XvMC does not need the frame again
if (buffer)
videoOutput->PrepareFrame(buffer, kScan_Intr2ndField);
}
// Display the second field
videosync->AdvanceTrigger();
videosync->WaitForFrame(0);
videoOutput->Show(kScan_Intr2ndField);
}
}
else
{
videosync->WaitForFrame(0);
}
if (output_jmeter && output_jmeter->RecordCycleTime())
{
//cerr << "avsync_delay: " << avsync_delay / 1000
// << ", avsync_avg: " << avsync_avg / 1000
// << ", warpfactor: " << warpfactor
// << ", warpfactor_avg: " << warpfactor_avg << endl;
}
videosync->AdvanceTrigger();
avsync_adjustment = 0;
if (diverge > MAXDIVERGE)
{
if (diverge > DIVERGELIMIT)
diverge = DIVERGELIMIT;
// Audio is way ahead of the video - cut the frame rate to
// half speed until it is within normal range
VERBOSE(VB_PLAYBACK, QString("A/V diverged by %1 frames, extending "
"frame to keep audio in sync").arg(diverge));
avsync_adjustment = frame_interval;
lastsync = true;
}
if (audioOutput && normal_speed)
{
// ms, same scale as timecodes
lastaudiotime = audioOutput->GetAudiotime();
if (lastaudiotime != 0 && buffer->timecode != 0)
{ // lastaudiotime = 0 after a seek
// The time at the start of this frame (ie, now) is given by
// last->timecode
int delta = buffer->timecode - prevtc - (frame_interval / 1000);
prevtc = buffer->timecode;
//cerr << delta << " ";
// If the timecode is off by a frame (dropped frame) wait to sync
if (delta > (int) frame_interval / 1200 &&
delta < (int) frame_interval / 1000 * 3)
{
//cerr << "+ ";
videosync->AdvanceTrigger();
if (m_double_framerate)
videosync->AdvanceTrigger();
}
avsync_delay = (buffer->timecode - lastaudiotime) * 1000; // uSecs
avsync_avg = (avsync_delay + (avsync_avg * 3)) / 4;
if (!usevideotimebase)
{
/* If the audio time codes and video diverge, shift
the video by one interlaced field (1/2 frame) */
if (!lastsync)
{
if (avsync_avg > frame_interval * 3 / 2)
{
avsync_adjustment = refreshrate;
lastsync = true;
}
else if (avsync_avg < 0 - frame_interval * 3 / 2)
{
avsync_adjustment = -refreshrate;
lastsync = true;
}
}
else
lastsync = false;
}
}
else
{
avsync_avg = 0;
avsync_oldavg = 0;
}
}
}
void NuppelVideoPlayer::ShutdownAVSync(void)
{
if (usevideotimebase)
{
gContext->SaveSetting("WarpFactor",
(int)(warpfactor_avg * WARPMULTIPLIER));
if (warplbuff)
{
free(warplbuff);
warplbuff = NULL;
}
if (warprbuff)
{
free(warprbuff);
warprbuff = NULL;
}
warpbuffsize = 0;
}
}
void NuppelVideoPlayer::OutputVideoLoop(void)
{
lastaudiotime = 0;
delay = 0;
avsync_delay = 0;
avsync_avg = 0;
avsync_oldavg = 0;
delay_clipping = false;
refreshrate = 0;
lastsync = false;
usevideotimebase = gContext->GetNumSetting("UseVideoTimebase", 0);
if ((print_verbose_messages & VB_PLAYBACK) != 0)
output_jmeter = new Jitterometer("video_output", 100);
else
output_jmeter = NULL;
refreshrate = frame_interval;
float temp_speed = (play_speed == 0.0) ? audio_stretchfactor : play_speed;
int fr_int = (int)(1000000.0 / video_frame_rate / temp_speed);
if (disablevideo)
{
videosync = new USleepVideoSync(fr_int, 0, false);
}
else
{
// Set up deinterlacing in the video output method
m_double_framerate = false;
if (m_scan == kScan_Interlaced && m_DeintSetting) {
if (videoOutput && videoOutput->SetupDeinterlace(true)) {
if (videoOutput->NeedsDoubleFramerate())
{
m_double_framerate = true;
m_can_double = true;
}
}
}
videosync = VideoSync::BestMethod(
fr_int, videoOutput->GetRefreshRate(), m_double_framerate);
// Make sure video sync can do it
if (videosync != NULL && m_double_framerate)
{
videosync->SetFrameInterval(frame_interval, m_double_framerate);
if (!videosync->isInterlaced()) {
m_scan = kScan_Ignore;
m_double_framerate = false;
m_can_double = false;
if (videoOutput)
videoOutput->SetupDeinterlace(false);
}
}
}
InitAVSync();
videosync->Start();
while (!killvideo)
{
if (needsetpipplayer)
{
pipplayer = setpipplayer;
needsetpipplayer = false;
}
if (pausevideo)
{
if (!video_actually_paused)
videoOutput->UpdatePauseFrame();
if (resetvideo)
{
videoOutput->UpdatePauseFrame();
resetvideo = false;
}
video_actually_paused = true;
videoThreadPaused.wakeAll();
if (videoOutput->IsErrored())
{
errored = true;
continue;
}
videofiltersLock.lock();
videoOutput->ProcessFrame(NULL, osd, videoFilters, pipplayer);
videofiltersLock.unlock();
videoOutput->PrepareFrame(NULL, kScan_Ignore);
//printf("video waiting for unpause\n");
usleep(frame_interval);
videoOutput->Show(kScan_Ignore);
videosync->Start();
continue;
}
video_actually_paused = false;
resetvideo = false;
prebuffering_lock.lock();
if (prebuffering)
{
VERBOSE(VB_PLAYBACK, "waiting for prebuffer...");
if (!prebuffering_wait.wait(&prebuffering_lock,
frame_interval * 4 / 1000))
VERBOSE(VB_PLAYBACK, "prebuffer wait timed out..");
prebuffering_lock.unlock();
videosync->Start();
continue;
}
prebuffering_lock.unlock();
if (!videoOutput->EnoughPrebufferedFrames())
{
VERBOSE(VB_GENERAL, "prebuffering pause");
setPrebuffering(true);
continue;
}
videoOutput->StartDisplayingFrame();
VideoFrame *frame = videoOutput->GetLastShownFrame();
if (cc)
ShowText();
videofiltersLock.lock();
videoOutput->ProcessFrame(frame, osd, videoFilters, pipplayer);
videofiltersLock.unlock();
AVSync();
videoOutput->DoneDisplayingFrame();
//sched_yield();
}
vidExitLock.lock();
delete videoOutput;
videoOutput = NULL;
vidExitLock.unlock();
ShutdownAVSync();
}
#ifdef USING_IVTV
void NuppelVideoPlayer::IvtvVideoLoop(void)
{
refreshrate = frame_interval;
int delay = frame_interval;
VideoOutputIvtv *vidout = (VideoOutputIvtv *)videoOutput;
vidout->SetFPS(GetFrameRate());
while (!killvideo)
{
if (needsetpipplayer)
{
pipplayer = setpipplayer;
needsetpipplayer = false;
}
resetvideo = false;
video_actually_paused = pausevideo;
if (pausevideo)
{
videoThreadPaused.wakeAll();
videofiltersLock.lock();
videoOutput->ProcessFrame(NULL, osd, videoFilters, pipplayer);
videofiltersLock.unlock();
}
else
{
if (cc)
ShowText();
videofiltersLock.lock();
videoOutput->ProcessFrame(NULL, osd, videoFilters, pipplayer);
videofiltersLock.unlock();
}
usleep(delay);
}
// no need to lock this, we don't have any video frames
delete videoOutput;
videoOutput = NULL;
}
#endif
void *NuppelVideoPlayer::kickoffOutputVideoLoop(void *player)
{
NuppelVideoPlayer *nvp = (NuppelVideoPlayer *)player;
#ifdef USING_IVTV
if (nvp->forceVideoOutput == kVideoOutput_IVTV)
nvp->IvtvVideoLoop();
else
#endif
nvp->OutputVideoLoop();
return NULL;
}
bool NuppelVideoPlayer::FastForward(float seconds)
{
if (!videoOutput)
return false;
if (fftime == 0)
fftime = (int)(seconds * video_frame_rate);
return fftime > CalcMaxFFTime(fftime);
}
bool NuppelVideoPlayer::Rewind(float seconds)
{
if (!videoOutput)
return false;
if (rewindtime == 0)
rewindtime = (int)(seconds * video_frame_rate);
return rewindtime >= framesPlayed;
}
void NuppelVideoPlayer::SkipCommercials(int direction)
{
if (skipcommercials == 0)
skipcommercials = direction;
}
void NuppelVideoPlayer::ResetPlaying(void)
{
ClearAfterSeek();
framesPlayed = 0;
decoder->Reset();
}
void NuppelVideoPlayer::StartPlaying(void)
{
consecutive_blanks = 0;
killplayer = false;
framesPlayed = 0;
if (OpenFile() < 0)
return;
if (!disableaudio || (forceVideoOutput == kVideoOutput_IVTV &&
!gContext->GetNumSetting("PVR350InternalAudioOnly")))
{
bool setVolume = gContext->GetNumSetting("MythControlsVolume", 1);
audioOutput = AudioOutput::OpenAudio(audiodevice, audio_bits,
audio_channels, audio_samplerate,
AUDIOOUTPUT_VIDEO,
setVolume);
DialogBox *dialog = NULL;
if (audioOutput == NULL)
{
qApp->lock();
dialog = new DialogBox(gContext->GetMainWindow(),
QObject::tr("Unable to create AudioOutput."));
}
else if (audioOutput->GetError() != QString::null)
{
qApp->lock();
dialog = new DialogBox(gContext->GetMainWindow(),
audioOutput->GetError());
}
if (dialog != NULL)
{
dialog->AddButton(QObject::tr("Continue WITHOUT AUDIO!"));
dialog->AddButton(QObject::tr("Return to menu."));
int ret = dialog->exec();
delete dialog;
qApp->unlock();
if (audioOutput)
{
delete audioOutput;
audioOutput = NULL;
}
disableaudio = true;
if (ret == 2)
return;
}
}
if (!InitVideo())
{
qApp->lock();
DialogBox *dialog = new DialogBox(gContext->GetMainWindow(),
QObject::tr("Unable to initialize video."));
dialog->AddButton(QObject::tr("Return to menu."));
dialog->exec();
delete dialog;
qApp->unlock();
if (audioOutput)
{
delete audioOutput;
audioOutput = NULL;
}
disableaudio = true;
return;
}
if (!disablevideo)
{
int dispx = 0, dispy = 0, dispw = video_width, disph = video_height;
videoOutput->GetVisibleSize(dispx, dispy, dispw, disph);
osd = new OSD(video_width, video_height, frame_interval,
dispx, dispy, dispw, disph);
if (forceVideoOutput != kVideoOutput_Default &&
forceVideoOutput != kVideoOutput_IVTV)
osd->DisableFade();
}
playing = true;
rewindtime = fftime = 0;
skipcommercials = 0;
// in the player, pretend we use Blank-frame detection so we can
// utilize the blank-frame info from the recorder if no commercial
// skip list exists.
if (commDetect)
commDetect->SetCommDetectMethod(COMM_DETECT_BLANKS);
for (int i = 0; i < MAXTBUFFER; i++)
txtbuffers[i].buffer = new unsigned char[text_size];
ClearAfterSeek();
/* This thread will fill the video and audio buffers, it does all CPU
intensive operations. We fork two other threads which do nothing but
write to the audio and video output devices. These should use a
minimum of CPU. */
pthread_t output_video, decoder_thread;
decoder_thread = pthread_self();
pthread_create(&output_video, NULL, kickoffOutputVideoLoop, this);
if (!disablevideo)
{
// Request that the video output thread run with realtime priority.
// If mythyv/mythfrontend was installed SUID root, this will work.
#ifndef CONFIG_DARWIN
gContext->addPrivRequest(MythPrivRequest::MythRealtime, &output_video);
#endif
// Use realtime prio for decoder thread as well
//gContext->addPrivRequest(MythPrivRequest::MythRealtime, &decoder_thread);
}
int pausecheck = 0;
if (bookmarkseek > 30)
{
GetFrame(audioOutput == NULL || !normal_speed);
bool seeks = exactseeks;
decoder->setExactSeeks(false);
fftime = bookmarkseek;
DoFastForward();
fftime = 0;
decoder->setExactSeeks(seeks);
if (gContext->GetNumSetting("ClearSavedPosition", 1))
m_playbackinfo->SetBookmark(0);
}
LoadBlankList();
if (!blankMap.isEmpty())
{
hasblanktable = true;
blankIter = blankMap.begin();
}
LoadCommBreakList();
if (!commBreakMap.isEmpty())
{
hascommbreaktable = true;
commBreakIter = commBreakMap.begin();
}
SetCommBreakIter();
while (!eof && !killplayer && !errored)
{
if (nvr_enc && nvr_enc->GetErrorStatus())
{
errored = killplayer = true;
break;
}
if (play_speed != next_play_speed)
{
decoder_lock.lock();
play_speed = next_play_speed;
normal_speed = next_normal_speed;
//cout << "changing speed to " << play_speed << endl;
if (play_speed == 0.0)
DoPause();
else
DoPlay();
decoder_lock.unlock();
}
if (rewindtime < 0)
rewindtime = 0;
if (fftime < 0)
fftime = 0;
if (paused)
{
decoderThreadPaused.wakeAll();
pausecheck++;
if (!(pausecheck % 20))
{
if (livetv && ringBuffer->GetFreeSpace() < -1000)
{
Play();
printf("forced unpause\n");
}
pausecheck = 0;
}
if (rewindtime > 0)
{
DoRewind();
GetFrame(audioOutput == NULL || !normal_speed);
resetvideo = true;
while (resetvideo)
usleep(50);
rewindtime = 0;
continue;
}
else if (fftime > 0)
{
fftime = CalcMaxFFTime(fftime);
DoFastForward();
GetFrame(audioOutput == NULL || !normal_speed);
resetvideo = true;
while (resetvideo)
usleep(50);
fftime = 0;
continue;
}
else
{
//printf("startplaying waiting for unpause\n");
usleep(500);
continue;
}
}
if (rewindtime > 0 && ffrew_skip == 1)
{
PauseVideo();
if (rewindtime >= 1)
DoRewind();
UnpauseVideo();
while (GetVideoPause())
usleep(50);
rewindtime = 0;
}
if (fftime > 0 && ffrew_skip == 1)
{
fftime = CalcMaxFFTime(fftime);
PauseVideo();
if (fftime >= 5)
DoFastForward();
if (eof)
continue;
UnpauseVideo();
while (GetVideoPause())
usleep(50);
fftime = 0;
}
if (skipcommercials != 0 && ffrew_skip == 1)
{
PauseVideo();
DoSkipCommercials(skipcommercials);
UnpauseVideo();
while (GetVideoPause())
usleep(50);
skipcommercials = 0;
continue;
}
GetFrame(audioOutput == NULL || !normal_speed);
if (disablevideo || forceVideoOutput == kVideoOutput_IVTV)
decoder->UpdateFramesPlayed();
else
framesPlayed = videoOutput->GetFramesPlayed();
if (ffrew_skip != 1)
continue;
if (!hasdeletetable && autocommercialskip)
AutoCommercialSkip();
if (hasdeletetable && deleteIter.data() == 1 &&
framesPlayed >= deleteIter.key())
{
++deleteIter;
if (deleteIter.key() == totalFrames)
eof = 1;
else
{
PauseVideo();
JumpToFrame(deleteIter.key());
UnpauseVideo();
while (GetVideoPause())
usleep(50);
}
}
}
decoderThreadPaused.wakeAll();
killvideo = true;
pthread_join(output_video, NULL);
// need to make sure video has exited first.
if (audioOutput)
delete audioOutput;
audioOutput = NULL;
/*
if (!disablevideo)
{
// Reset to default scheduling
struct sched_param sp = {0};
int status = pthread_setschedparam(decoder_thread, SCHED_OTHER, &sp);
if (status)
perror("pthread_setschedparam");
}
*/
playing = false;
if (IsErrored())
{
qApp->lock();
DialogBox *dialog =
new DialogBox(gContext->GetMainWindow(),
QObject::tr("Error was encountered while displaying video."));
dialog->AddButton(QObject::tr("Return to Menu"));
dialog->exec();
delete dialog;
qApp->unlock();
}
}
void NuppelVideoPlayer::SetAudioParams(int bps, int channels, int samplerate)
{
audio_bits = bps;
audio_channels = channels;
audio_samplerate = samplerate;
}
void NuppelVideoPlayer::SetEffDsp(int dsprate)
{
if (audioOutput)
audioOutput->SetEffDsp(dsprate);
}
void NuppelVideoPlayer::SetTranscoding(bool value)
{
transcoding = value;
if (decoder)
decoder->setTranscoding(value);
};
void NuppelVideoPlayer::AddAudioData(char *buffer, int len, long long timecode)
{
if (audioOutput)
{
if (usevideotimebase)
{
int samples;
short int * newbuffer;
float incount = 0;
int outcount;
int samplesize;
int newsamples;
int newlen;
samplesize = audio_channels * audio_bits / 8;
samples = len / samplesize;
newsamples = (int)(samples / warpfactor);
newlen = newsamples * samplesize;
// We use the left warp buffer to store the new data.
// If it isn't big enough it is resized.
if ((warpbuffsize < newlen) || (! warplbuff))
{
if (warprbuff)
{
// Make sure this isn't allocated since we're only
// resizing 1 buffer.
free(warprbuff);
warprbuff = NULL;
}
newbuffer = (short int *)realloc(warplbuff, newlen);
if (!newbuffer)
{
cerr << "Couldn't allocate warped audio buffer!" << endl;
return;
}
warplbuff = newbuffer;
warpbuffsize = newlen;
}
else
newbuffer = warplbuff;
for (incount = 0, outcount = 0;
(incount < samples) && (outcount < newsamples);
outcount++, incount += warpfactor)
{
memcpy(((char *)newbuffer) + (outcount * samplesize),
buffer + (((int)incount) * samplesize), samplesize);
}
samples = outcount;
if (!audioOutput->AddSamples((char *)newbuffer, samples, timecode))
VERBOSE(VB_IMPORTANT, "Audio buffer overflow, audio data lost!");
}
else
{
if (!audioOutput->AddSamples(buffer, len /
(audio_channels * audio_bits / 8),
timecode))
VERBOSE(VB_IMPORTANT, "Audio buffer overflow, audio data lost!");
}
}
}
void NuppelVideoPlayer::AddAudioData(short int *lbuffer, short int *rbuffer,
int samples, long long timecode)
{
if (audioOutput)
{
char *buffers[] = {(char *)lbuffer, (char *)rbuffer};
if (usevideotimebase)
{
short int *newlbuffer;
short int *newrbuffer;
float incount = 0;
int outcount;
int newlen;
int newsamples;
newsamples = (int)(samples / warpfactor);
newlen = newsamples * sizeof(short int);
// We resize the buffers if they aren't big enough
if ((warpbuffsize < newlen) || (!warplbuff) || (!warprbuff))
{
newlbuffer = (short int *)realloc(warplbuff, newlen);
if (!newlbuffer)
{
cerr << "Couldn't allocate left warped audio buffer!\n";
return;
}
warplbuff = newlbuffer;
newrbuffer = (short int *)realloc(warprbuff, newlen);
if (!newrbuffer)
{
cerr << "Couldn't allocate right warped audio buffer!\n";
return;
}
warprbuff = newrbuffer;
warpbuffsize = newlen;
}
else
{
newlbuffer = warplbuff;
newrbuffer = warprbuff;
}
buffers[0] = (char *)newlbuffer;
buffers[1] = (char *)newrbuffer;
for (incount = 0, outcount = 0;
(incount < samples) && (outcount < newsamples);
outcount++, incount += warpfactor)
{
newlbuffer[outcount] = lbuffer[(int)incount];
newrbuffer[outcount] = rbuffer[(int)incount];
}
samples = outcount;
}
if (!audioOutput->AddSamples(buffers, samples, timecode))
VERBOSE(VB_IMPORTANT, "Audio buffer overflow, audio data lost!");
}
}
void NuppelVideoPlayer::SetBookmark(void)
{
if (livetv)
return;
if (!m_playbackinfo || !osd)
return;
m_playbackinfo->SetBookmark(framesPlayed);
osd->SetSettingsText(QObject::tr("Position Saved"), 1);
}
void NuppelVideoPlayer::ClearBookmark(void)
{
if (livetv)
return;
if (!m_playbackinfo || !osd)
return;
m_playbackinfo->SetBookmark(0);
osd->SetSettingsText(QObject::tr("Position Cleared"), 1);
}
long long NuppelVideoPlayer::GetBookmark(void)
{
if (!m_playbackinfo)
return 0;
return m_playbackinfo->GetBookmark();
}
void NuppelVideoPlayer::DoPause(void)
{
bool skip_changed;
#ifdef USING_IVTV
if (forceVideoOutput == kVideoOutput_IVTV)
{
VideoOutputIvtv *vidout = (VideoOutputIvtv *)videoOutput;
vidout->Pause();
}
#endif
skip_changed = (ffrew_skip != 1);
ffrew_skip = 1;
if (skip_changed)
{
//cout << "handling skip change" << endl;
videoOutput->SetPrebuffering(ffrew_skip == 1);
decoder->setExactSeeks(exactseeks && ffrew_skip == 1);
decoder->DoFastForward(framesPlayed + ffrew_skip);
ClearAfterSeek();
}
float temp_speed = audio_stretchfactor;
frame_interval = (int)(1000000.0 * ffrew_skip / video_frame_rate / temp_speed);
VERBOSE(VB_PLAYBACK, QString("rate: %1 speed: %2 skip: %3 = interval %4")
.arg(video_frame_rate).arg(temp_speed)
.arg(ffrew_skip).arg(frame_interval));
if (osd && forceVideoOutput != kVideoOutput_IVTV)
osd->SetFrameInterval(frame_interval);
if (videosync != NULL)
videosync->SetFrameInterval(frame_interval, m_double_framerate);
//cout << "setting paused" << endl << endl;
paused = actuallypaused = true;
}
void NuppelVideoPlayer::DoPlay(void)
{
bool skip_changed;
if (play_speed > 0.0 && play_speed <= 3.0)
{
skip_changed = (ffrew_skip != 1);
ffrew_skip = 1;
}
else
{
skip_changed = true;
ffrew_skip = int(ceil(4.0 * fabs(play_speed) / keyframedist)) * keyframedist;
if (play_speed < 0.0)
ffrew_skip = -ffrew_skip;
}
//cout << "skip changed to " << ffrew_skip << endl;
if (skip_changed)
{
//cout << "handling skip change" << endl;
videoOutput->SetPrebuffering(ffrew_skip == 1);
#ifdef USING_IVTV
if (forceVideoOutput == kVideoOutput_IVTV)
{
VideoOutputIvtv *vidout = (VideoOutputIvtv *)videoOutput;
vidout->NextPlay(play_speed / ffrew_skip, normal_speed,
(ffrew_skip == 1) ? 2 : 0);
}
#endif
decoder->setExactSeeks(exactseeks && ffrew_skip == 1);
decoder->DoRewind(framesPlayed);
ClearAfterSeek();
}
float temp_speed = play_speed;
frame_interval = (int)(1000000.0 * ffrew_skip / video_frame_rate / temp_speed);
VERBOSE(VB_PLAYBACK, QString("rate: %1 speed: %2 skip: %3 = interval %4")
.arg(video_frame_rate).arg(temp_speed)
.arg(ffrew_skip).arg(frame_interval));
if (osd && forceVideoOutput != kVideoOutput_IVTV)
osd->SetFrameInterval(frame_interval);
if (videoOutput && videosync != NULL)
{
videosync->SetFrameInterval(frame_interval, m_double_framerate);
// If using bob deinterlace, turn on or off if we
// changed to or from synchronous playback speed.
videofiltersLock.lock();
if (m_double_framerate)
{
if (!normal_speed || play_speed < 0.99 || play_speed > 1.01)
{
m_double_framerate = false;
m_scan = kScan_Ignore;
videosync->SetFrameInterval(frame_interval, false);
videoOutput->SetupDeinterlace(false);
}
}
else if (m_can_double && !m_double_framerate)
{
if (normal_speed && play_speed > 0.99 && play_speed < 1.01)
{
m_scan = kScan_Interlaced;
videosync->SetFrameInterval(frame_interval, true);
videoOutput->SetupDeinterlace(true);
if (videoOutput->NeedsDoubleFramerate())
m_double_framerate = true;
}
}
videofiltersLock.unlock();
}
#ifdef USING_IVTV
if (forceVideoOutput == kVideoOutput_IVTV)
{
VideoOutputIvtv *vidout = (VideoOutputIvtv *)videoOutput;
vidout->Play(play_speed / ffrew_skip, normal_speed,
(ffrew_skip == 1) ? 2 : 0);
}
#endif
if (normal_speed && audioOutput)
{
audioOutput->SetStretchFactor(play_speed);
#ifdef USING_DIRECTX
audioOutput->Reset();
#endif
}
//cout << "setting unpaused" << endl << endl;
paused = actuallypaused = false;
}
bool NuppelVideoPlayer::DoRewind(void)
{
long long number = rewindtime + 1;
long long desiredFrame = framesPlayed - number;
if (!editmode && hasdeletetable && IsInDelete(desiredFrame))
{
QMap<long long, int>::Iterator it = deleteMap.begin();
while (it != deleteMap.end())
{
if (desiredFrame > it.key())
++it;
else
break;
}
if (it != deleteMap.begin() && it != deleteMap.end())
{
long long over = it.key() - desiredFrame;
--it;
desiredFrame = it.key() - over;
}
}
if (desiredFrame < 0)
desiredFrame = 0;
limitKeyRepeat = false;
if (desiredFrame < video_frame_rate)
limitKeyRepeat = true;
if (paused && !editmode)
decoder->setExactSeeks(true);
decoder->DoRewind(desiredFrame);
decoder->setExactSeeks(exactseeks);
ClearAfterSeek();
return true;
}
long long NuppelVideoPlayer::CalcMaxFFTime(long long ff)
{
long long maxtime = (long long)(1.0 * video_frame_rate);
if (livetv || (watchingrecording && nvr_enc && nvr_enc->IsValidRecorder()))
maxtime = (long long)(3.0 * video_frame_rate);
long long ret = ff;
limitKeyRepeat = false;
if (livetv || (watchingrecording && nvr_enc && nvr_enc->IsValidRecorder()))
{
long long behind = nvr_enc->GetFramesWritten() - framesPlayed;
if (behind < maxtime) // if we're close, do nothing
ret = 0;
else if (behind - ff <= maxtime)
ret = behind - maxtime;
if (behind < maxtime * 3)
limitKeyRepeat = true;
}
else
{
if (totalFrames > 0)
{
long long behind = totalFrames - framesPlayed;
if (behind < maxtime)
ret = 0;
else if (behind - ff <= maxtime * 2)
ret = behind - maxtime * 2;
}
}
return ret;
}
bool NuppelVideoPlayer::DoFastForward(void)
{
long long number = fftime - 1;
long long desiredFrame = framesPlayed + number;
if (paused && !editmode)
decoder->setExactSeeks(true);
decoder->DoFastForward(desiredFrame);
decoder->setExactSeeks(exactseeks);
ClearAfterSeek();
return true;
}
void NuppelVideoPlayer::JumpToFrame(long long frame)
{
bool exactstore = exactseeks;
decoder->setExactSeeks(true);
fftime = rewindtime = 0;
if (frame > framesPlayed)
{
fftime = frame - framesPlayed;
DoFastForward();
fftime = 0;
}
else if (frame < framesPlayed)
{
rewindtime = framesPlayed - frame;
DoRewind();
rewindtime = 0;
}
decoder->setExactSeeks(exactstore);
}
int NuppelVideoPlayer::SkipTooCloseToEnd(int frames)
{
if ((livetv) ||
(watchingrecording && nvr_enc && nvr_enc->IsValidRecorder()))
{
if (nvr_enc->GetFramesWritten() < (framesPlayed + frames))
return 1;
}
else if ((totalFrames) && (totalFrames < (framesPlayed + frames)))
{
return 1;
}
return 0;
}
void NuppelVideoPlayer::ClearAfterSeek(void)
{
videoOutput->ClearAfterSeek();
for (int i = 0; i < MAXTBUFFER; i++)
txtbuffers[i].timecode = 0;
ResetCC();
wtxt = 0;
rtxt = 0;
setPrebuffering(true);
if (audioOutput)
audioOutput->Reset();
if (osd)
osd->ClearAllCCText();
SetDeleteIter();
SetBlankIter();
SetCommBreakIter();
}
void NuppelVideoPlayer::SetDeleteIter(void)
{
deleteIter = deleteMap.begin();
if (hasdeletetable)
{
while (deleteIter != deleteMap.end())
{
if ((framesPlayed + 2) > deleteIter.key())
{
++deleteIter;
}
else
break;
}
if (deleteIter != deleteMap.begin())
--deleteIter;
if (deleteIter.data() == 0)
++deleteIter;
}
}
void NuppelVideoPlayer::SetBlankIter(void)
{
blankIter = blankMap.begin();
if (hasblanktable)
{
while (blankIter != blankMap.end())
{
if ((framesPlayed + 2) > blankIter.key())
{
++blankIter;
}
else
break;
}
if (blankIter != blankMap.begin())
--blankIter;
}
}
void NuppelVideoPlayer::SetCommBreakIter(void)
{
commBreakIter = commBreakMap.begin();
if (hascommbreaktable)
{
while (commBreakIter != commBreakMap.end())
{
if ((framesPlayed + 2) > commBreakIter.key())
{
commBreakIter++;
}
else
break;
}
}
}
void NuppelVideoPlayer::SetAutoCommercialSkip(int autoskip)
{
SetCommBreakIter();
autocommercialskip = autoskip;
}
bool NuppelVideoPlayer::EnableEdit(void)
{
editmode = false;
if (!hasFullPositionMap || !m_playbackinfo || !osd)
return false;
bool alreadyediting = false;
alreadyediting = m_playbackinfo->IsEditing();
if (alreadyediting)
return false;
if (GetPause())
osd->EndPause();
editmode = true;
Pause();
while (!GetPause())
usleep(50);
seekamount = keyframedist;
seekamountpos = 3;
dialogname = "";
QMap<QString, QString> infoMap;
m_playbackinfo->ToMap(infoMap);
osd->SetText("editmode", infoMap, -1);
UpdateEditSlider();
UpdateTimeDisplay();
UpdateSeekAmount(true);
if (hasdeletetable)
{
if (deleteMap.contains(0))
deleteMap.erase(0);
if (deleteMap.contains(totalFrames))
deleteMap.erase(totalFrames);
QMap<long long, int>::Iterator it;
for (it = deleteMap.begin(); it != deleteMap.end(); ++it)
AddMark(it.key(), it.data());
}
m_playbackinfo->SetEditing(true);
return editmode;
}
void NuppelVideoPlayer::DisableEdit(void)
{
editmode = false;
if (!m_playbackinfo)
return;
QMap<long long, int>::Iterator i = deleteMap.begin();
for (; i != deleteMap.end(); ++i)
osd->HideEditArrow(i.key(), i.data());
osd->HideSet("editmode");
timedisplay = NULL;
SaveCutList();
LoadCutList();
if (!deleteMap.isEmpty())
{
hasdeletetable = true;
SetDeleteIter();
}
else
{
hasdeletetable = false;
}
m_playbackinfo->SetEditing(false);
}
bool NuppelVideoPlayer::DoKeypress(QKeyEvent *e)
{
bool handled = false;
QStringList actions;
gContext->GetMainWindow()->TranslateKeyPress("TV Editing", e, actions);
if (dialogname != "")
{
for (unsigned int i = 0; i < actions.size() && !handled; i++)
{
QString action = actions[i];
handled = true;
if (action == "UP")
osd->DialogUp(dialogname);
else if (action == "DOWN")
osd->DialogDown(dialogname);
else if (action == "SELECT" || action == "ESCAPE")
{
if (action == "ESCAPE")
osd->DialogAbort(dialogname);
osd->TurnDialogOff(dialogname);
HandleResponse();
}
else
handled = false;
}
return true;
}
bool retval = true;
bool exactstore = exactseeks;
decoder->setExactSeeks(true);
for (unsigned int i = 0; i < actions.size() && !handled; i++)
{
QString action = actions[i];
handled = true;
if (action == "SELECT")
HandleSelect();
else if (action == "LEFT")
{
if (seekamount > 0)
{
rewindtime = seekamount;
while (rewindtime != 0)
usleep(50);
UpdateEditSlider();
}
else
HandleArbSeek(false);
UpdateTimeDisplay();
}
else if (action == "RIGHT")
{
if (seekamount > 0)
{
fftime = seekamount;
while (fftime != 0)
usleep(50);
UpdateEditSlider();
}
else
HandleArbSeek(true);
UpdateTimeDisplay();
}
else if (action == "UP")
{
UpdateSeekAmount(true);
UpdateTimeDisplay();
}
else if (action == "DOWN")
{
UpdateSeekAmount(false);
UpdateTimeDisplay();
}
else if (action == "CLEARMAP")
{
QMap<long long, int>::Iterator it;
for (it = deleteMap.begin(); it != deleteMap.end(); ++it)
osd->HideEditArrow(it.key(), it.data());
deleteMap.clear();
UpdateEditSlider();
}
else if (action == "INVERTMAP")
{
QMap<long long, int>::Iterator it;
for (it = deleteMap.begin(); it != deleteMap.end(); ++it)
ReverseMark(it.key());
UpdateEditSlider();
UpdateTimeDisplay();
}
else if (action == "LOADCOMMSKIP")
{
if (hascommbreaktable)
{
QMap<long long, int>::Iterator it;
for (it = commBreakMap.begin(); it != commBreakMap.end(); ++it)
{
if (!deleteMap.contains(it.key()))
{
if (it.data() == MARK_COMM_START)
AddMark(it.key(), 1);
else
AddMark(it.key(), 0);
}
}
UpdateEditSlider();
UpdateTimeDisplay();
}
}
else if (action == "PREVCUT")
{
int old_seekamount = seekamount;
seekamount = -2;
HandleArbSeek(false);
seekamount = old_seekamount;
UpdateTimeDisplay();
}
else if (action == "NEXTCUT")
{
int old_seekamount = seekamount;
seekamount = -2;
HandleArbSeek(true);
seekamount = old_seekamount;
UpdateTimeDisplay();
}
#define FFREW_MULTICOUNT 10
else if (action == "BIGJUMPREW")
{
if (seekamount > 0)
rewindtime = seekamount * FFREW_MULTICOUNT;
else
{
int fps = (int)ceil(video_frame_rate);
rewindtime = fps * FFREW_MULTICOUNT / 2;
}
while (rewindtime != 0)
usleep(50);
UpdateEditSlider();
UpdateTimeDisplay();
}
else if (action == "BIGJUMPFWD")
{
if (seekamount > 0)
fftime = seekamount * FFREW_MULTICOUNT;
else
{
int fps = (int)ceil(video_frame_rate);
fftime = fps * FFREW_MULTICOUNT / 2;
}
while (fftime != 0)
usleep(50);
UpdateEditSlider();
UpdateTimeDisplay();
}
else if (action == "ESCAPE" || action == "MENU" ||
action == "TOGGLEEDIT")
{
DisableEdit();
retval = false;
}
else
handled = false;
}
decoder->setExactSeeks(exactstore);
return retval;
}
int NuppelVideoPlayer::GetLetterbox(void)
{
if (videoOutput)
return videoOutput->GetLetterbox();
return false;
}
void NuppelVideoPlayer::ToggleLetterbox(int letterboxMode)
{
if (videoOutput)
{
videoOutput->ToggleLetterbox(letterboxMode);
ReinitOSD();
}
}
void NuppelVideoPlayer::Zoom(int direction)
{
if (videoOutput)
{
videoOutput->Zoom(direction);
ReinitOSD();
}
}
void NuppelVideoPlayer::ExposeEvent(void)
{
if (videoOutput)
videoOutput->ExposeEvent();
}
void NuppelVideoPlayer::UpdateSeekAmount(bool up)
{
if (seekamountpos > 0 && !up)
seekamountpos--;
if (seekamountpos < 9 && up)
seekamountpos++;
QString text = "";
int fps = (int)ceil(video_frame_rate);
switch (seekamountpos)
{
case 0: text = QObject::tr("cut point"); seekamount = -2; break;
case 1: text = QObject::tr("keyframe"); seekamount = -1; break;
case 2: text = QObject::tr("1 frame"); seekamount = 1; break;
case 3: text = QObject::tr("0.5 seconds"); seekamount = fps / 2; break;
case 4: text = QObject::tr("1 second"); seekamount = fps; break;
case 5: text = QObject::tr("5 seconds"); seekamount = fps * 5; break;
case 6: text = QObject::tr("20 seconds"); seekamount = fps * 20; break;
case 7: text = QObject::tr("1 minute"); seekamount = fps * 60; break;
case 8: text = QObject::tr("5 minutes"); seekamount = fps * 300; break;
case 9: text = QObject::tr("10 minutes"); seekamount = fps * 600; break;
default: text = QObject::tr("error"); seekamount = fps; break;
}
QMap<QString, QString> infoMap;
infoMap["seekamount"] = text;
osd->SetText("editmode", infoMap, -1);
}
void NuppelVideoPlayer::UpdateTimeDisplay(void)
{
int hours = 0;
int mins = 0;
int secs = 0;
int frames = 0;
int fps = (int)ceil(video_frame_rate);
hours = (framesPlayed / fps) / 60 / 60;
mins = (framesPlayed / fps) / 60 - (hours * 60);
secs = (framesPlayed / fps) - (mins * 60) - (hours * 60 * 60);
frames = framesPlayed - ((secs * fps) + (mins * 60 * fps) +
(hours * 60 * 60 * fps));
char timestr[128];
sprintf(timestr, "%1d:%02d:%02d.%02d",
hours, mins, secs, frames);
char framestr[128];
sprintf(framestr, "%lld", framesPlayed);
QString cutmarker = " ";
if (IsInDelete(framesPlayed))
cutmarker = QObject::tr("cut");
QMap<QString, QString> infoMap;
infoMap["timedisplay"] = timestr;
infoMap["framedisplay"] = framestr;
infoMap["cutindicator"] = cutmarker;
osd->SetText("editmode", infoMap, -1);
}
void NuppelVideoPlayer::HandleSelect(void)
{
bool deletepoint = false;
QMap<long long, int>::Iterator i;
int direction = 0;
for (i = deleteMap.begin(); i != deleteMap.end(); ++i)
{
long long pos = framesPlayed - i.key();
if (pos < 0)
pos = 0 - pos;
if (pos < (int)ceil(20 * video_frame_rate))
{
deletepoint = true;
deleteframe = i.key();
direction = i.data();
break;
}
}
if (deletepoint)
{
QString message = QObject::tr("You are close to an existing cut point. Would you "
"like to:");
QString option1 = QObject::tr("Delete this cut point");
QString option2 = QObject::tr("Move this cut point to the current position");
QString option3 = QObject::tr("Flip directions - delete to the ");
if (direction == 0)
option3 += QObject::tr("right");
else
option3 += QObject::tr("left");
QString option4 = QObject::tr("Cancel");
dialogname = "deletemark";
dialogtype = 0;
QStringList options;
options += option1;
options += option2;
options += option3;
options += option4;
osd->NewDialogBox(dialogname, message, options, -1);
}
else
{
QString message = QObject::tr("Insert a new cut point?");
QString option1 = QObject::tr("Delete before this frame");
QString option2 = QObject::tr("Delete after this frame");
QString option3 = QObject::tr("Cancel");
dialogname = "addmark";
dialogtype = 1;
QStringList options;
options += option1;
options += option2;
options += option3;
osd->NewDialogBox(dialogname, message, options, -1);
}
}
void NuppelVideoPlayer::HandleResponse(void)
{
int result = osd->GetDialogResponse(dialogname);
dialogname = "";
if (dialogtype == 0)
{
int type = deleteMap[deleteframe];
switch (result)
{
case 1:
DeleteMark(deleteframe);
break;
case 2:
DeleteMark(deleteframe);
AddMark(framesPlayed, type);
break;
case 3:
ReverseMark(deleteframe);
break;
default:
break;
}
}
else if (dialogtype == 1)
{
switch (result)
{
case 1:
AddMark(framesPlayed, 0);
break;
case 2:
AddMark(framesPlayed, 1);
break;
case 3: case 0: default:
break;
}
}
UpdateEditSlider();
UpdateTimeDisplay();
}
void NuppelVideoPlayer::UpdateEditSlider(void)
{
osd->DoEditSlider(deleteMap, framesPlayed, totalFrames);
}
void NuppelVideoPlayer::AddMark(long long frames, int type)
{
deleteMap[frames] = type;
osd->ShowEditArrow(frames, totalFrames, type);
}
void NuppelVideoPlayer::DeleteMark(long long frames)
{
osd->HideEditArrow(frames, deleteMap[frames]);
deleteMap.remove(frames);
}
void NuppelVideoPlayer::ReverseMark(long long frames)
{
osd->HideEditArrow(frames, deleteMap[frames]);
if (deleteMap[frames] == 0)
deleteMap[frames] = 1;
else
deleteMap[frames] = 0;
osd->ShowEditArrow(frames, totalFrames, deleteMap[frames]);
}
void NuppelVideoPlayer::HandleArbSeek(bool right)
{
if (seekamount == -2)
{
QMap<long long, int>::Iterator i = deleteMap.begin();
long long framenum = -1;
if (right)
{
for (; i != deleteMap.end(); ++i)
{
if (i.key() > framesPlayed)
{
framenum = i.key();
break;
}
}
if (framenum == -1)
framenum = totalFrames;
fftime = framenum - framesPlayed;
while (fftime > 0)
usleep(50);
}
else
{
for (; i != deleteMap.end(); ++i)
{
if (i.key() >= framesPlayed)
break;
framenum = i.key();
}
if (framenum == -1)
framenum = 0;
rewindtime = framesPlayed - framenum;
while (rewindtime > 0)
usleep(50);
}
}
else
{
if (right)
{
decoder->setExactSeeks(false);
fftime = (long long)(keyframedist * 1.1);
while (fftime > 0)
usleep(50);
}
else
{
decoder->setExactSeeks(false);
rewindtime = 2;
while (rewindtime > 0)
usleep(50);
}
}
UpdateEditSlider();
}
bool NuppelVideoPlayer::IsInDelete(long long testframe)
{
long long startpos = 0;
long long endpos = 0;
bool first = true;
bool indelete = false;
bool ret = false;
QMap<long long, int>::Iterator i;
for (i = deleteMap.begin(); i != deleteMap.end(); ++i)
{
if (ret)
break;
long long frame = i.key();
int direction = i.data();
if (direction == 0 && !indelete && first)
{
startpos = 0;
endpos = frame;
first = false;
if (startpos <= testframe && endpos >= testframe)
ret = true;
}
else if (direction == 0)
{
endpos = frame;
indelete = false;
first = false;
if (startpos <= testframe && endpos >= testframe)
ret = true;
}
else if (direction == 1 && !indelete)
{
startpos = frame;
indelete = true;
first = false;
}
first = false;
}
if (indelete && testframe >= startpos)
ret = true;
return ret;
}
void NuppelVideoPlayer::SaveCutList(void)
{
if (!m_playbackinfo)
return;
long long startpos = 0;
long long endpos = 0;
bool first = true;
bool indelete = false;
long long lastpos = -1;
int lasttype = -1;
QMap<long long, int>::Iterator i;
for (i = deleteMap.begin(); i != deleteMap.end();)
{
long long frame = i.key();
int direction = i.data();
if (direction == 0 && !indelete && first)
{
deleteMap[0] = 1;
startpos = 0;
endpos = frame;
}
else if (direction == 0)
{
endpos = frame;
indelete = false;
first = false;
}
else if (direction == 1 && !indelete)
{
startpos = frame;
indelete = true;
first = false;
}
if (direction == lasttype)
{
if (direction == 0)
{
deleteMap.remove(lastpos);
++i;
}
else
{
++i;
deleteMap.remove(frame);
}
}
else
++i;
lastpos = frame;
lasttype = direction;
}
if (indelete)
deleteMap[totalFrames] = 0;
m_playbackinfo->SetMarkupFlag(MARK_UPDATED_CUT, true);
m_playbackinfo->SetCutList(deleteMap);
}
void NuppelVideoPlayer::LoadCutList(void)
{
if (!m_playbackinfo)
return;
m_playbackinfo->GetCutList(deleteMap);
}
void NuppelVideoPlayer::LoadBlankList(void)
{
if (!m_playbackinfo)
return;
m_playbackinfo->GetBlankFrameList(blankMap);
}
void NuppelVideoPlayer::LoadCommBreakList(void)
{
if (!m_playbackinfo)
return;
m_playbackinfo->GetCommBreakList(commBreakMap);
}
char *NuppelVideoPlayer::GetScreenGrab(int secondsin, int &bufflen, int &vw,
int &vh)
{
disablevideo = true;
if (OpenFile() < 0)
return NULL;
if (!decoder)
return NULL;
if (!hasFullPositionMap)
return NULL;
if (!InitVideo())
{
VERBOSE(VB_IMPORTANT, "NVP: Unable to initialize video for screen grab.");
return NULL;
}
for (int i = 0; i < MAXTBUFFER; i++)
txtbuffers[i].buffer = new unsigned char[text_size];
ClearAfterSeek();
int number = (int)(secondsin * video_frame_rate);
if (number >= totalFrames)
number = totalFrames / 2;
GetFrame(1, true);
fftime = number;
DoFastForward();
fftime = 0;
GetFrame(1, true);
VideoFrame *frame = videoOutput->GetLastDecodedFrame();
if (!frame)
{
bufflen = 0;
vw = vh = 0;
return NULL;
}
unsigned char *data = frame->buf;
if (!data)
{
bufflen = 0;
vw = vh = 0;
return NULL;
}
AVPicture orig, retbuf;
avpicture_fill(&orig, data, PIX_FMT_YUV420P, video_width, video_height);
avpicture_deinterlace(&orig, &orig, PIX_FMT_YUV420P, video_width,
video_height);
bufflen = video_width * video_height * 4;
unsigned char *outputbuf = new unsigned char[bufflen];
avpicture_fill(&retbuf, outputbuf, PIX_FMT_RGBA32, video_width,
video_height);
img_convert(&retbuf, PIX_FMT_RGBA32, &orig, PIX_FMT_YUV420P,
video_width, video_height);
vw = video_width;
vh = video_height;
return (char *)outputbuf;
}
QString NuppelVideoPlayer::GetEncodingType(void)
{
return decoder->GetEncodingType();
}
bool NuppelVideoPlayer::GetRawAudioState(void)
{
return decoder->GetRawAudioState();
}
void NuppelVideoPlayer::TranscodeWriteText(void (*func)
(void *, unsigned char *, int, int, int), void * ptr)
{
while (tbuffer_numvalid())
{
int pagenr = 0;
unsigned char *inpos = txtbuffers[rtxt].buffer;
if (txtbuffers[rtxt].type == 'T')
{
memcpy(&pagenr, inpos, sizeof(int));
inpos += sizeof(int);
txtbuffers[rtxt].len -= sizeof(int);
}
func(ptr, inpos, txtbuffers[rtxt].len,
txtbuffers[rtxt].timecode, pagenr);
rtxt = (rtxt + 1) % MAXTBUFFER;
}
}
void NuppelVideoPlayer::InitForTranscode(bool copyaudio, bool copyvideo)
{
// Are these really needed?
playing = true;
keyframedist = 30;
warpfactor = 1;
warpfactor_avg = 1;
if (!InitVideo())
{
VERBOSE(VB_IMPORTANT, "NVP: Unable to initialize video for transcode.");
playing = false;
return;
}
for (int i = 0; i < MAXTBUFFER; i++)
txtbuffers[i].buffer = new unsigned char[text_size];
framesPlayed = 0;
ClearAfterSeek();
if (copyvideo)
decoder->SetRawVideoState(true);
if (copyaudio)
decoder->SetRawAudioState(true);
decoder->setExactSeeks(true);
decoder->setLowBuffers();
}
bool NuppelVideoPlayer::TranscodeGetNextFrame(QMap<long long, int>::Iterator &dm_iter,
int *did_ff, bool *is_key, bool honorCutList)
{
if (dm_iter == NULL && honorCutList)
dm_iter = deleteMap.begin();
if (!decoder->GetFrame(0))
return false;
if (eof)
return false;
if (honorCutList && (!deleteMap.isEmpty()))
{
if (videoOutput->GetLastDecodedFrame()->frameNumber >= dm_iter.key())
{
while((dm_iter.data() == 1) && (dm_iter != deleteMap.end()))
{
QString msg = QString("Fast-Forwarding from %1")
.arg((int)dm_iter.key());
dm_iter++;
msg += QString(" to %1").arg((int)dm_iter.key());
VERBOSE(VB_GENERAL, msg);
decoder->DoFastForward(dm_iter.key());
decoder->ClearStoredData();
ClearAfterSeek();
decoder->GetFrame(0);
*did_ff = 1;
}
while((dm_iter.data() == 0) && (dm_iter != deleteMap.end()))
{
dm_iter++;
}
}
}
if (eof)
return false;
*is_key = decoder->isLastFrameKey();
return true;
}
long NuppelVideoPlayer::UpdateStoredFrameNum(long curFrameNum)
{
return decoder->UpdateStoredFrameNum(curFrameNum);
}
bool NuppelVideoPlayer::WriteStoredData(RingBuffer *outRingBuffer,
bool writevideo, long timecodeOffset)
{
if (writevideo && !decoder->GetRawVideoState())
writevideo = false;
decoder->WriteStoredData(outRingBuffer, writevideo, timecodeOffset);
return writevideo;
}
bool NuppelVideoPlayer::LastFrameIsBlank(void)
{
return FrameIsBlank(videoOutput->GetLastDecodedFrame());
}
int NuppelVideoPlayer::FlagCommercials(bool showPercentage, bool fullSpeed,
bool inJobQueue)
{
int comms_found = 0;
int percentage = 0;
int jobID = -1;
bool flaggingPaused = false;
float flagFPS = 0.0;
float elapsed = 0.0;
long usecPerFrame = 0;
long usecSleep = 0;
struct timeval startTime;
struct timeval endTime;
VideoFrame *currentFrame;
long long myTotalFrames = 0;
killplayer = false;
framesPlayed = 0;
blankMap.clear();
commBreakMap.clear();
disablevideo = true;
if (ringBuffer && !ringBuffer->IsOpen())
return(254);
if (inJobQueue)
{
jobID = JobQueue::GetJobID(JOB_COMMFLAG,
m_playbackinfo->chanid,
m_playbackinfo->startts);
JobQueue::ChangeJobStatus(jobID, JOB_RUNNING,
"0% " + QObject::tr("Completed"));
}
QTime flagTime;
flagTime.start();
if (m_playbackinfo->recendts < QDateTime::currentDateTime())
SetWatchingRecording(false);
// if we're running flagging during recording sleep a while to let the
// recorder get a little ways ahead of us. We need this buffer in order
// to run logo detection. flagging will be allowed to catch back up
// later.
if (watchingrecording)
{
int i = 0;
int initialSleep = 30;
int additionalSleep = 0;
int curCmd;
if (commercialskipmethod & COMM_DETECT_LOGO)
additionalSleep = LOGO_SECONDS_NEEDED;
if (jobID != -1)
{
JobQueue::ChangeJobStatus(jobID, JOB_RUNNING,
QObject::tr("Building Detection Buffer"));
}
while(i < (initialSleep + additionalSleep))
{
if ((jobID != -1) &&
((i % 5) == 0))
{
curCmd = JobQueue::GetJobCmd(jobID);
if (curCmd == JOB_STOP)
return(255);
}
if (i == initialSleep)
flagTime.start();
sleep(1);
i++;
}
}
if (OpenFile() < 0)
return(254);
if (watchingrecording)
commDetect->SetWatchingRecording(true, nvr_enc, this);
m_playbackinfo->SetCommFlagged(COMM_FLAG_PROCESSING);
if (!InitVideo())
{
VERBOSE(VB_IMPORTANT,
"NVP: Unable to initialize video for FlagCommercials.");
playing = false;
m_playbackinfo->SetCommFlagged(COMM_FLAG_NOT_FLAGGED);
return(254);
}
for (int i = 0; i < MAXTBUFFER; i++)
txtbuffers[i].buffer = new unsigned char[text_size];
ClearAfterSeek();
commDetect->SetCommSkipAllBlanks(
gContext->GetNumSetting("CommSkipAllBlanks", 1));
if ((commercialskipmethod & COMM_DETECT_LOGO) &&
((totalLength == 0) || (totalLength > LOGO_SECONDS_NEEDED)))
{
if (inJobQueue)
{
JobQueue::ChangeJobStatus(jobID, JOB_RUNNING,
QObject::tr("Searching for Logo"));
}
if (showPercentage)
{
printf( "Finding Logo" );
fflush( stdout );
}
commDetect->SearchForLogo(this, fullSpeed);
if (showPercentage)
{
printf( "\b\b\b\b\b\b\b\b\b\b\b\b "
"\b\b\b\b\b\b\b\b\b\b\b\b" );
fflush( stdout );
}
}
// the meat of the offline commercial detection code, scan through whole
// file looking for indications of commercial breaks
GetFrame(1,true);
usecPerFrame = (long)(1.0 / video_frame_rate * 1000000);
if (!watchingrecording)
myTotalFrames = totalFrames;
else
myTotalFrames = (long long)(video_frame_rate *
(m_playbackinfo->recendts.toTime_t()
- m_playbackinfo->recstartts.toTime_t()));
if (showPercentage)
{
if (myTotalFrames)
printf( "%3d%%/ ", 0 );
else
printf( "%6lld/ ", 0LL );
}
while (!eof)
{
if (watchingrecording)
gettimeofday(&startTime, NULL);
currentFrame = videoOutput->GetLastDecodedFrame();
if ((jobID != -1) &&
(((currentFrame->frameNumber % 500) == 0)) ||
(((currentFrame->frameNumber % 100) == 0) && (watchingrecording)))
{
int curCmd;
curCmd = JobQueue::GetJobCmd(jobID);
if (curCmd == JOB_STOP)
{
playing = false;
killplayer = true;
m_playbackinfo->SetCommFlagged(COMM_FLAG_NOT_FLAGGED);
return(255);
}
else if (curCmd == JOB_PAUSE)
{
if (!flaggingPaused && jobID != -1)
{
JobQueue::ChangeJobStatus(jobID, JOB_PAUSED,
QObject::tr("Paused"));
flaggingPaused = true;
}
sleep(1);
continue;
}
}
if (flaggingPaused && jobID != -1)
{
JobQueue::ChangeJobStatus(jobID, JOB_RUNNING,
QObject::tr("Restarting"));
JobQueue::ChangeJobFlags(jobID, JOB_RUN);
flaggingPaused = false;
}
// sleep a little so we don't use all cpu even if we're niced
if (!fullSpeed && !watchingrecording)
usleep(10000);
if (((jobID != -1) &&
((currentFrame->frameNumber % 500) == 0)) ||
((showPercentage || watchingrecording) &&
(currentFrame->frameNumber % 100) == 0))
{
elapsed = flagTime.elapsed() / 1000.0;
if (elapsed)
flagFPS = currentFrame->frameNumber / elapsed;
else
flagFPS = 0.0;
if (myTotalFrames)
percentage = currentFrame->frameNumber * 100 / myTotalFrames;
else
percentage = 0;
if (percentage > 100)
percentage = 100;
if (showPercentage)
{
if (myTotalFrames)
{
printf( "\b\b\b\b\b\b\b\b\b\b\b" );
printf( "%3d%%/%3dfps", percentage, (int)flagFPS );
}
else
{
printf( "\b\b\b\b\b\b\b\b\b\b\b\b\b" );
printf( "%6lld/%3dfps", currentFrame->frameNumber,
(int)flagFPS);
}
fflush( stdout );
}
if ((jobID != -1) &&
(((currentFrame->frameNumber % 500) == 0)) ||
(((currentFrame->frameNumber % 100) == 0) &&
(watchingrecording)))
{
if (myTotalFrames)
{
JobQueue::ChangeJobComment(jobID, QObject::tr(
"%1% Completed @ %2 fps.")
.arg(percentage)
.arg(flagFPS));
}
else
{
JobQueue::ChangeJobComment(jobID, QObject::tr(
"%1 Frames Completed @ %2 fps.")
.arg((long)
currentFrame->frameNumber)
.arg(flagFPS));
}
}
}
commDetect->ProcessNextFrame(currentFrame, currentFrame->frameNumber);
GetFrame(1,true);
if (watchingrecording)
{
qApp->processEvents();
usecPerFrame = (long)(1.0 / video_frame_rate * 1000000);
gettimeofday(&endTime, NULL);
usecSleep = usecPerFrame -
(((endTime.tv_sec - startTime.tv_sec) * 1000000) +
(endTime.tv_usec - startTime.tv_usec));
elapsed = flagTime.elapsed() / 1000.0;
if (elapsed)
{
flagFPS = currentFrame->frameNumber / elapsed;
if ((flagFPS < (video_frame_rate - 1.0)) &&
(usecSleep > 0))
usecSleep = (long)(usecSleep * 0.33);
else if (flagFPS > (video_frame_rate * 0.99))
usecSleep = (long)(usecPerFrame * 1.5);
}
if (usecSleep > 0)
usleep(usecSleep);
}
}
if (showPercentage)
{
elapsed = flagTime.elapsed() / 1000.0;
if (elapsed)
flagFPS = currentFrame->frameNumber / elapsed;
else
flagFPS = 0.0;
if (myTotalFrames)
printf( "\b\b\b\b\b\b \b\b\b\b\b\b" );
else
printf( "\b\b\b\b\b\b\b\b\b\b\b\b\b "
"\b\b\b\b\b\b\b\b\b\b\b\b\b" );
}
if (commercialskipmethod & COMM_DETECT_BLANKS)
{
commDetect->GetBlankFrameMap(blankMap);
if (blankMap.size())
{
m_playbackinfo->SetBlankFrameList(blankMap);
}
}
commDetect->GetCommBreakMap(commBreakMap);
if (commBreakMap.size())
{
m_playbackinfo->SetMarkupFlag(MARK_UPDATED_CUT, true);
m_playbackinfo->SetCommBreakList(commBreakMap);
}
comms_found = commBreakMap.size() / 2;
playing = false;
killplayer = true;
if (!hasFullPositionMap)
decoder->SetPositionMap();
m_playbackinfo->SetCommFlagged(COMM_FLAG_DONE);
if (jobID != -1)
{
elapsed = flagTime.elapsed() / 1000.0;
if (myTotalFrames && (elapsed > 0.0))
{
flagFPS = myTotalFrames / elapsed;
JobQueue::ChangeJobStatus(jobID, JOB_STOPPING,
QObject::tr("Completed, %1 FPS")
.arg(flagFPS) + ", "
+ QString("%1").arg(comms_found) + " "
+ QObject::tr("Commercial Breaks Found"));
}
else
{
JobQueue::ChangeJobStatus(jobID, JOB_STOPPING,
QObject::tr("Completed") + ", "
+ QString("%1").arg(comms_found) + " "
+ QObject::tr("Commercial Breaks Found"));
}
}
return(comms_found);
}
bool NuppelVideoPlayer::RebuildSeekTable(bool showPercentage, StatusCallback cb, void* cbData)
{
int percentage = 0;
long long myFramesPlayed = 0;
bool looped = false;
killplayer = false;
framesPlayed = 0;
disablevideo = true;
if (OpenFile() < 0)
return(0);
// clear out any existing seektables
m_playbackinfo->ClearPositionMap(MARK_KEYFRAME);
m_playbackinfo->ClearPositionMap(MARK_GOP_START);
m_playbackinfo->ClearPositionMap(MARK_GOP_BYFRAME);
playing = true;
if (!InitVideo())
{
VERBOSE(VB_IMPORTANT, "NVP: Unable to initialize video for RebuildSeekTable.");
playing = false;
return 0;
}
for (int i = 0; i < MAXTBUFFER; i++)
txtbuffers[i].buffer = new unsigned char[text_size];
ClearAfterSeek();
QTime flagTime;
flagTime.start();
GetFrame(-1,true);
if (showPercentage)
{
if (totalFrames)
printf(" ");
else
printf(" ");
fflush( stdout );
}
while (!eof)
{
looped = true;
myFramesPlayed++;
if ((myFramesPlayed % 100) == 0)
{
if (totalFrames)
{
int flagFPS;
float elapsed = flagTime.elapsed() / 1000.0;
if (elapsed)
flagFPS = (int)(myFramesPlayed / elapsed);
else
flagFPS = 0;
percentage = myFramesPlayed * 100 / totalFrames;
if (cb)
(*cb)(percentage, cbData);
if (showPercentage)