summaryrefslogtreecommitdiff
path: root/omxdevice.c
diff options
context:
space:
mode:
Diffstat (limited to 'omxdevice.c')
-rw-r--r--omxdevice.c284
1 files changed, 181 insertions, 103 deletions
diff --git a/omxdevice.c b/omxdevice.c
index f1ac178..3e25121 100644
--- a/omxdevice.c
+++ b/omxdevice.c
@@ -17,22 +17,19 @@
#include <string.h>
-// latency target for transfer mode in PTS ticks (90kHz) -> 500ms
-#define LATENCY_TARGET 45000LL
-// latency window for validation where closed loop will be active (+/- 4s)
-#define LATENCY_WINDOW 360000LL
-
#define S(x) ((int)(floor(x * pow(2, 16))))
// trick speeds as defined in vdr/dvbplayer.c
-int cOmxDevice::s_speeds[2][8] = {
+const int cOmxDevice::s_playbackSpeeds[eNumDirections][eNumPlaybackSpeeds] = {
{ S(0.0f), S( 0.125f), S( 0.25f), S( 0.5f), S( 1.0f), S( 2.0f), S( 4.0f), S( 12.0f) },
{ S(0.0f), S(-0.125f), S(-0.25f), S(-0.5f), S(-1.0f), S(-2.0f), S(-4.0f), S(-12.0f) }
};
-// speed correction factors for live mode, taken from omxplayer
-int cOmxDevice::s_speedCorrections[5] = {
- S(0.990f), S(0.999f), S(1.000f), S(1.001), S(1.010)
+// speed correction factors for live mode
+// HDMI specification allows a tolerance of 1000ppm, however on the Raspberry Pi
+// it's limited to 175ppm to avoid audio drops one some A/V receivers
+const int cOmxDevice::s_liveSpeeds[eNumLiveSpeeds] = {
+ S(0.999f), S(0.99985f), S(1.000f), S(1.00015), S(1.001)
};
const uchar cOmxDevice::PesVideoHeader[14] = {
@@ -46,7 +43,8 @@ cOmxDevice::cOmxDevice(void (*onPrimaryDevice)(void)) :
m_audio(new cRpiAudioDecoder(m_omx)),
m_mutex(new cMutex()),
m_videoCodec(cVideoCodec::eInvalid),
- m_speed(eNormal),
+ m_liveSpeed(eNoCorrection),
+ m_playbackSpeed(eNormal),
m_direction(eForward),
m_hasVideo(false),
m_hasAudio(false),
@@ -55,7 +53,11 @@ cOmxDevice::cOmxDevice(void (*onPrimaryDevice)(void)) :
m_trickRequest(0),
m_audioPts(0),
m_videoPts(0),
- m_latency(0)
+ m_audioId(0),
+ m_latencySamples(0),
+ m_latencyTarget(0),
+ m_posMaxCorrections(0),
+ m_negMaxCorrections(0)
{
}
@@ -92,7 +94,6 @@ int cOmxDevice::Init(void)
int cOmxDevice::DeInit(void)
{
cRpiSetup::SetVideoSetupChangedCallback(0);
-
if (m_audio->DeInit() < 0)
{
ELOG("failed to deinitialize audio!");
@@ -158,6 +159,15 @@ bool cOmxDevice::SetPlayMode(ePlayMode PlayMode)
switch (PlayMode)
{
case pmNone:
+ FlushStreams(true);
+ if (m_hasVideo)
+ m_omx->StopVideo();
+
+ m_hasAudio = false;
+ m_hasVideo = false;
+ m_videoCodec = cVideoCodec::eInvalid;
+ break;
+
if (m_hasAudio)
{
m_audio->Reset();
@@ -180,7 +190,7 @@ bool cOmxDevice::SetPlayMode(ePlayMode PlayMode)
case pmAudioOnly:
case pmAudioOnlyBlack:
case pmVideoOnly:
- m_speed = eNormal;
+ m_playbackSpeed = eNormal;
m_direction = eForward;
break;
@@ -198,6 +208,7 @@ void cOmxDevice::StillPicture(const uchar *Data, int Length)
cDevice::StillPicture(Data, Length);
else
{
+ DBG("StillPicture()");
int pesLength = 0;
uchar *pesPacket = 0;
@@ -213,7 +224,6 @@ void cOmxDevice::StillPicture(const uchar *Data, int Length)
memcpy(pesPacket, PesVideoHeader, sizeof(PesVideoHeader));
memcpy(pesPacket + sizeof(PesVideoHeader), Data, Length);
- PesSetPts(pesPacket, 1);
}
else
codec = ParseVideoCodec(Data + PesPayloadOffset(Data),
@@ -223,34 +233,31 @@ void cOmxDevice::StillPicture(const uchar *Data, int Length)
return;
m_mutex->Lock();
-
- // manually restart clock and wait for video only
- m_omx->StopClock();
- m_omx->SetClockScale(s_speeds[eForward][eNormal]);
- m_omx->StartClock(true, false);
+ m_playbackSpeed = eNormal;
+ m_direction = eForward;
// to get a picture displayed, PlayVideo() needs to be called
- // 4x for MPEG2 and 13x for H264... ?
- int repeat = codec == cVideoCodec::eMPEG2 ? 4 : 13;
-
+ // 4x for MPEG2 and 10x for H264... ?
+ int repeat = codec == cVideoCodec::eMPEG2 ? 4 : 10;
while (repeat--)
{
int length = pesPacket ? pesLength : Length;
const uchar *data = pesPacket ? pesPacket : Data;
- // play every single PES packet, rise EOS flag on last
+ // play every single PES packet, rise ENDOFFRAME flag on last
while (PesLongEnough(length))
{
int pktLen = PesHasLength(data) ? PesLength(data) : length;
// skip non-video packets as they may occur in PES recordings
if ((data[3] & 0xf0) == 0xe0)
- PlayVideo(data, pktLen, !repeat && (pktLen == length));
+ PlayVideo(data, pktLen, pktLen == length);
data += pktLen;
length -= pktLen;
}
}
+ SubmitEOS();
m_mutex->Unlock();
if (pesPacket)
@@ -260,24 +267,21 @@ void cOmxDevice::StillPicture(const uchar *Data, int Length)
int cOmxDevice::PlayAudio(const uchar *Data, int Length, uchar Id)
{
- if (m_skipAudio)
- return Length;
-
m_mutex->Lock();
if (!m_hasAudio)
{
m_hasAudio = true;
+ m_audioId = Id;
m_omx->SetClockReference(cOmx::eClockRefAudio);
- // actually, clock should be restarted anyway, but if video is already
- // present, decoder will get stuck after clock restart and raises a
- // buffer stall
if (!m_hasVideo)
{
- FlushStreams();
- m_omx->SetClockScale(s_speeds[m_direction][m_speed]);
+ DBG("audio first");
+ m_omx->SetClockScale(s_playbackSpeeds[m_direction][m_playbackSpeed]);
m_omx->StartClock(m_hasVideo, m_hasAudio);
+ if (Transferring())
+ ResetLatency();
}
}
@@ -293,7 +297,14 @@ int cOmxDevice::PlayAudio(const uchar *Data, int Length, uchar Id)
}
if (Transferring() && pts)
+ {
+ if (m_audioId != Id)
+ {
+ ResetLatency();
+ m_audioId = Id;
+ }
UpdateLatency(pts);
+ }
int ret = Length;
int length = Length - PesPayloadOffset(Data);
@@ -314,12 +325,11 @@ int cOmxDevice::PlayAudio(const uchar *Data, int Length, uchar Id)
if (!m_audio->WriteData(data, length, pts))
ret = 0;
}
-
m_mutex->Unlock();
return ret;
}
-int cOmxDevice::PlayVideo(const uchar *Data, int Length, bool EndOfStream)
+int cOmxDevice::PlayVideo(const uchar *Data, int Length, bool EndOfFrame)
{
m_mutex->Lock();
int ret = Length;
@@ -346,7 +356,7 @@ int cOmxDevice::PlayVideo(const uchar *Data, int Length, bool EndOfStream)
if (cRpiSetup::IsVideoCodecSupported(codec))
{
videoRestart = true;
- m_omx->SetVideoCodec(codec, cOmx::eArbitraryStreamSection);
+ m_omx->SetVideoCodec(codec);
DLOG("set video codec to %s", cVideoCodec::Str(codec));
}
else
@@ -355,10 +365,17 @@ int cOmxDevice::PlayVideo(const uchar *Data, int Length, bool EndOfStream)
if (videoRestart)
{
+ if (!m_hasAudio)
+ {
+ DBG("video first");
+ m_omx->SetClockReference(cOmx::eClockRefVideo);
+ }
+
m_hasVideo = true;
- FlushStreams();
- m_omx->SetClockScale(s_speeds[m_direction][m_speed]);
+ m_omx->SetClockScale(s_playbackSpeeds[m_direction][m_playbackSpeed]);
m_omx->StartClock(m_hasVideo, m_hasAudio);
+ if (Transferring())
+ ResetLatency();
}
if (m_hasVideo)
@@ -366,13 +383,8 @@ int cOmxDevice::PlayVideo(const uchar *Data, int Length, bool EndOfStream)
int64_t pts = PesHasPts(Data) ? PesGetPts(Data) : 0;
// keep track of direction in case of trick speed
- if (m_trickRequest && pts)
- {
- if (m_videoPts)
- PtsTracker(PtsDiff(m_videoPts, pts));
-
- m_videoPts = pts;
- }
+ if (m_trickRequest && pts && m_videoPts)
+ PtsTracker(PtsDiff(m_videoPts, pts));
if (!m_hasAudio && Transferring() && pts)
UpdateLatency(pts);
@@ -393,8 +405,8 @@ int cOmxDevice::PlayVideo(const uchar *Data, int Length, bool EndOfStream)
Length -= buf->nFilledLen;
Data += buf->nFilledLen;
- if (EndOfStream && !Length)
- buf->nFlags |= OMX_BUFFERFLAG_EOS;
+ if (EndOfFrame && !Length)
+ buf->nFlags |= OMX_BUFFERFLAG_ENDOFFRAME;
if (!m_omx->EmptyVideoBuffer(buf))
{
@@ -411,11 +423,20 @@ int cOmxDevice::PlayVideo(const uchar *Data, int Length, bool EndOfStream)
pts = 0;
}
}
-
m_mutex->Unlock();
return ret;
}
+bool cOmxDevice::SubmitEOS(void)
+{
+ DBG("SubmitEOS()");
+ OMX_BUFFERHEADERTYPE *buf = m_omx->GetVideoBuffer(0);
+ if (buf)
+ buf->nFlags = /*OMX_BUFFERFLAG_ENDOFFRAME | */ OMX_BUFFERFLAG_EOS;
+
+ return m_omx->EmptyVideoBuffer(buf);
+}
+
int64_t cOmxDevice::GetSTC(void)
{
return m_omx->GetSTC();
@@ -475,8 +496,8 @@ void cOmxDevice::Clear(void)
m_mutex->Lock();
FlushStreams();
- m_omx->SetClockScale(s_speeds[m_direction][m_speed]);
- m_omx->StartClock(m_hasVideo, m_hasAudio);
+ m_hasAudio = false;
+ m_hasVideo = false;
m_mutex->Unlock();
cDevice::Clear();
@@ -487,10 +508,9 @@ void cOmxDevice::Play(void)
DBG("Play()");
m_mutex->Lock();
- m_speed = eNormal;
+ m_playbackSpeed = eNormal;
m_direction = eForward;
- m_omx->SetClockScale(s_speeds[m_direction][m_speed]);
- m_skipAudio = false;
+ m_omx->SetClockScale(s_playbackSpeeds[m_direction][m_playbackSpeed]);
m_mutex->Unlock();
cDevice::Play();
@@ -501,7 +521,7 @@ void cOmxDevice::Freeze(void)
DBG("Freeze()");
m_mutex->Lock();
- m_omx->SetClockScale(s_speeds[eForward][ePause]);
+ m_omx->SetClockScale(s_playbackSpeeds[eForward][ePause]);
m_mutex->Unlock();
cDevice::Freeze();
@@ -534,9 +554,9 @@ void cOmxDevice::TrickSpeed(int Speed)
void cOmxDevice::ApplyTrickSpeed(int trickSpeed, bool forward)
{
- bool flush = HasIBPTrickSpeed();
+ m_direction = forward ? eForward : eBackward;
+ m_playbackSpeed =
- m_speed =
// slow forward
trickSpeed == 8 ? eSlowest :
trickSpeed == 4 ? eSlower :
@@ -552,42 +572,17 @@ void cOmxDevice::ApplyTrickSpeed(int trickSpeed, bool forward)
trickSpeed == 48 ? eSlower :
trickSpeed == 24 ? eSlow : eNormal;
- m_direction = forward ? eForward : eBackward;
-
- // we only need to flush when IBP trick mode has changed,
- // for the other transitions VDR will call Clear() if necessary
- flush ^= HasIBPTrickSpeed();
-
- // if there is video to play, we're going to skip audio
- // but first, we need to flush audio
- if (!m_skipAudio && m_hasVideo && !HasIBPTrickSpeed())
- {
- m_audio->Reset();
- m_omx->FlushAudio();
- m_skipAudio = true;
- }
-
- if (flush)
- FlushStreams();
-
- m_omx->SetClockScale(s_speeds[m_direction][m_speed]);
-
- if (flush)
- m_omx->StartClock(m_hasVideo, !m_skipAudio);
+ m_omx->SetClockScale(s_playbackSpeeds[m_direction][m_playbackSpeed]);
DBG("ApplyTrickSpeed(%s, %s)",
- SpeedStr(m_speed), DirectionStr(m_direction));
-}
-
-bool cOmxDevice::HasIBPTrickSpeed(void)
-{
- // IBP trick speed only supported at first fast forward speed or
- // for audio only recordings at every speed
- return m_direction == eForward && (m_speed <= eFast || !m_hasVideo);
+ PlaybackSpeedStr(m_playbackSpeed), DirectionStr(m_direction));
+ return;
}
void cOmxDevice::PtsTracker(int64_t ptsDiff)
{
+ DBG("PtsTracker(%lld)", ptsDiff);
+
if (ptsDiff < 0)
--m_playDirection;
else if (ptsDiff > 0)
@@ -600,26 +595,108 @@ void cOmxDevice::PtsTracker(int64_t ptsDiff)
}
}
+bool cOmxDevice::HasIBPTrickSpeed(void)
+{
+ return !m_hasVideo;
+}
+
void cOmxDevice::UpdateLatency(int64_t pts)
{
- // calculate and validate latency
- uint64_t latency = pts - m_omx->GetSTC();
- if (abs(latency > LATENCY_WINDOW))
+ if (!pts || !m_omx->IsClockRunning())
+ return;
+
+ int64_t stc = m_omx->GetSTC();
+ if (!stc || pts <= stc)
+ return;
+
+ for (int i = LATENCY_FILTER_SIZE - 1; i > 0; i--)
+ m_latency[i] = m_latency[i - 1];
+ m_latency[0] = (pts - stc) / 90;
+
+ if (m_latencySamples < LATENCY_FILTER_SIZE - 1)
+ {
+ m_latencySamples++;
return;
+ }
+
+#ifdef DEBUG_LATENCY
+ eLiveSpeed oldSpeed = m_liveSpeed;
+#endif
+ int average = 0;
+
+ for (int i = 0; i < LATENCY_FILTER_SIZE; i++)
+ average += m_latency[i];
+ average = average / LATENCY_FILTER_SIZE;
+
+ if (!m_latencyTarget)
+ m_latencyTarget = 1.4f * average;
+
+ if (average > 2.0f * m_latencyTarget)
+ {
+ if (m_liveSpeed < ePosMaxCorrection)
+ {
+ m_liveSpeed = ePosMaxCorrection;
+ m_posMaxCorrections++;
+ DLOG("latency too big, speeding up...");
+ }
+ }
+ else if (average < 0.5f * m_latencyTarget)
+ {
+ if (m_liveSpeed > eNegMaxCorrection)
+ {
+ m_liveSpeed = eNegMaxCorrection;
+ m_negMaxCorrections++;
+ DLOG("latency too small, slowing down...");
+ }
+ }
+ else if (average > 1.1f * m_latencyTarget)
+ {
+ if (m_liveSpeed < ePosMaxCorrection)
+ m_liveSpeed = ePosCorrection;
+ }
+ else if (average < 0.9f * m_latencyTarget)
+ {
+ if (m_liveSpeed > eNegMaxCorrection)
+ m_liveSpeed = eNegCorrection;
+ }
+ else if (average > m_latencyTarget)
+ {
+ if (m_liveSpeed < eNoCorrection)
+ m_liveSpeed = eNoCorrection;
+ }
+ else if (average < m_latencyTarget)
+ {
+ if (m_liveSpeed > eNoCorrection)
+ m_liveSpeed = eNoCorrection;
+ } else
+ m_liveSpeed = eNoCorrection;
- m_latency = (7 * m_latency + latency) >> 3;
- eSpeedCorrection corr = eNoCorrection;
+ m_omx->SetClockScale(s_liveSpeeds[m_liveSpeed]);
- if (m_latency < 0.5f * LATENCY_TARGET)
- corr = eNegMaxCorrection;
- else if (m_latency < 0.9f * LATENCY_TARGET)
- corr = eNegCorrection;
- else if (m_latency > 2.0f * LATENCY_TARGET)
- corr = ePosMaxCorrection;
- else if (m_latency > 1.1f * LATENCY_TARGET)
- corr = ePosCorrection;
+#ifdef DEBUG_LATENCY
+ if (oldSpeed != m_liveSpeed)
+ {
+ DLOG("%s%s latency = %4dms, target = %4dms, corr = %s, "
+ "max neg/pos corr = %d/%d",
+ m_hasAudio ? "A" : "-", m_hasVideo ? "V" : "-",
+ average, m_latencyTarget,
+ m_liveSpeed == eNegMaxCorrection ? "--| " :
+ m_liveSpeed == eNegCorrection ? " -| " :
+ m_liveSpeed == eNoCorrection ? " | " :
+ m_liveSpeed == ePosCorrection ? " |+ " :
+ m_liveSpeed == ePosMaxCorrection ? " |++" : " ? ",
+ m_negMaxCorrections, m_posMaxCorrections);
+ }
+#endif
+}
- m_omx->SetClockScale(s_speedCorrections[corr]);
+void cOmxDevice::ResetLatency(void)
+{
+ m_latencySamples = - LATENCY_FILTER_PREROLL;
+ m_latencyTarget = 0;
+ m_liveSpeed = eNoCorrection;
+ m_posMaxCorrections = 0;
+ m_negMaxCorrections = 0;
}
void cOmxDevice::HandleBufferStall()
@@ -628,7 +705,7 @@ void cOmxDevice::HandleBufferStall()
m_mutex->Lock();
FlushStreams();
- m_omx->SetClockScale(s_speeds[m_direction][m_speed]);
+ m_omx->SetClockScale(s_playbackSpeeds[m_direction][m_playbackSpeed]);
m_omx->StartClock(m_hasVideo, m_hasAudio);
m_mutex->Unlock();
@@ -637,11 +714,14 @@ void cOmxDevice::HandleBufferStall()
void cOmxDevice::HandleEndOfStream()
{
DBG("HandleEndOfStream()");
+ m_mutex->Lock();
// flush pipes and restart clock after still image
FlushStreams();
- m_omx->SetClockScale(s_speeds[eForward][ePause]);
+ m_omx->SetClockScale(s_playbackSpeeds[m_direction][m_playbackSpeed]);
m_omx->StartClock(m_hasVideo, m_hasAudio);
+
+ m_mutex->Unlock();
}
void cOmxDevice::HandleStreamStart()
@@ -701,8 +781,6 @@ void cOmxDevice::FlushStreams(bool flushVideoRender)
m_audio->Reset();
m_omx->FlushAudio();
}
-
- m_omx->SetCurrentReferenceTime(0);
}
void cOmxDevice::SetVolumeDevice(int Volume)