diff options
Diffstat (limited to 'omxdevice.c')
-rw-r--r-- | omxdevice.c | 328 |
1 files changed, 217 insertions, 111 deletions
diff --git a/omxdevice.c b/omxdevice.c index d1893e5..24da8bd 100644 --- a/omxdevice.c +++ b/omxdevice.c @@ -11,6 +11,7 @@ #include <vdr/remux.h> #include <vdr/tools.h> +#include <vdr/skins.h> #include <string.h> @@ -20,10 +21,15 @@ cOmxDevice::cOmxDevice(void (*onPrimaryDevice)(void)) : m_omx(new cOmx()), m_audio(new cAudioDecoder(m_omx)), m_mutex(new cMutex()), - m_state(eNone), - m_audioId(0) -{ -} + m_videoCodec(cVideoCodec::eInvalid), + m_hasVideo(false), + m_hasAudio(false), + m_skipAudio(false), + m_playDirection(0), + m_trickRequest(0), + m_audioPts(0), + m_videoPts(0) +{ } cOmxDevice::~cOmxDevice() { @@ -71,14 +77,12 @@ bool cOmxDevice::CanReplay(void) const { dsyslog("rpihddevice: CanReplay"); // video codec de-initialization done - return (m_state == eNone); + return true; //(m_state == eNone); } bool cOmxDevice::SetPlayMode(ePlayMode PlayMode) { - // in case we were in some trick mode - m_omx->SetClockScale(1.0f); - + m_mutex->Lock(); dsyslog("rpihddevice: SetPlayMode(%s)", PlayMode == pmNone ? "none" : PlayMode == pmAudioVideo ? "Audio/Video" : @@ -86,20 +90,19 @@ bool cOmxDevice::SetPlayMode(ePlayMode PlayMode) PlayMode == pmAudioOnlyBlack ? "Audio only, black" : PlayMode == pmVideoOnly ? "Video only" : "unsupported"); + + // Stop audio / video if play mode is set to pmNone. Start + // is triggered once a packet is going to be played, since + // we don't know what kind of stream we'll get (audio-only, + // video-only or both) after SetPlayMode() - VDR will always + // pass pmAudioVideo as argument. + switch (PlayMode) { case pmNone: - m_mutex->Lock(); - if (HasVideo()) - m_omx->FlushVideo(true); - if (HasAudio()) - { - m_audio->Reset(); - m_omx->FlushAudio(); - } - m_omx->Stop(); - SetState(eNone); - m_mutex->Unlock(); + ResetAudioVideo(true); + m_omx->StopVideo(); + m_videoCodec = cVideoCodec::eInvalid; break; case pmAudioVideo: @@ -107,95 +110,143 @@ bool cOmxDevice::SetPlayMode(ePlayMode PlayMode) case pmAudioOnlyBlack: case pmVideoOnly: break; + + default: + break; } + m_mutex->Unlock(); return true; } +void cOmxDevice::StillPicture(const uchar *Data, int Length) +{ + if (Data[0] == 0x47) + cDevice::StillPicture(Data, Length); + else + { + // to get a picture displayed, PlayVideo() needs to be called + // twice for MPEG2 and 6x for H264... ? + for (int i = 0; i < (m_videoCodec == cVideoCodec::eMPEG2 ? 2 : 6); i++) + PlayVideo(Data, Length, true); + } +} + int cOmxDevice::PlayAudio(const uchar *Data, int Length, uchar Id) { + if (m_skipAudio) + return Length; + m_mutex->Lock(); - int ret = Length; + int64_t pts = PesHasPts(Data) ? PesGetPts(Data) : 0; - // if first packet is audio, we assume audio only - if (State() == eNone) - SetState(eAudioOnly); + if (!m_hasAudio) + { + m_omx->SetClockReference(cOmx::eClockRefAudio); + m_hasAudio = true; + } - if (State() == eAudioOnly || State() == eAudioVideo) + // keep track of direction in case of trick speed + if (m_trickRequest && pts) { - if (m_audio->Poll()) - { - if (m_audioId != Id) - { - m_audioId = Id; - m_audio->Reset(); - } + if (m_audioPts) + PtsTracker(PtsDiff(m_audioPts, pts)); - //dsyslog("A %llu", PesGetPts(Data)); - if (!m_audio->WriteData(Data + PesPayloadOffset(Data), - PesLength(Data) - PesPayloadOffset(Data), - PesHasPts(Data) ? PesGetPts(Data) : 0)) - esyslog("rpihddevice: failed to write data to audio decoder!"); - } - else - ret = 0; + m_audioPts = pts; } + int ret = m_audio->WriteData(Data + PesPayloadOffset(Data), + Length - PesPayloadOffset(Data), pts); + m_mutex->Unlock(); return ret; } -int cOmxDevice::PlayVideo(const uchar *Data, int Length) +int cOmxDevice::PlayVideo(const uchar *Data, int Length, bool singleFrame) { m_mutex->Lock(); int ret = Length; - if (State() == eNone) - SetState(eStartingVideo); + int64_t pts = PesHasPts(Data) ? PesGetPts(Data) : 0; + cVideoCodec::eCodec codec = ParseVideoCodec(Data, Length); + + // video restart after Clear() with same codec + bool videoRestart = (!m_hasVideo && codec == m_videoCodec && + cRpiSetup::IsVideoCodecSupported(codec)); - if (State() == eStartingVideo) + // video restart after SetPlayMode() or codec changed + if (codec != cVideoCodec::eInvalid && codec != m_videoCodec) { - cVideoCodec::eCodec codec = ParseVideoCodec(Data, Length); - if (codec != cVideoCodec::eInvalid) + m_videoCodec = codec; + + if (m_hasVideo) { - if (cRpiSetup::IsVideoCodecSupported(codec)) - { - m_omx->SetVideoCodec(codec); - SetState(eAudioVideo); - dsyslog("rpihddevice: set video codec to %s", - cVideoCodec::Str(codec)); - } - else - { - SetState(eAudioOnly); - esyslog("rpihddevice: %s video codec not supported!", - cVideoCodec::Str(codec)); - } + m_omx->StopVideo(); + m_hasVideo = false; + } + + if (cRpiSetup::IsVideoCodecSupported(codec)) + { + videoRestart = true; + m_omx->SetVideoCodec(codec, cOmx::eArbitraryStreamSection); + dsyslog("rpihddevice: set video codec to %s", + cVideoCodec::Str(codec)); + } + else + Skins.QueueMessage(mtError, tr("video format not supported!")); + } + + if (videoRestart) + { + m_hasVideo = true; + + if (!m_hasAudio) + { + m_omx->SetClockReference(cOmx::eClockRefVideo); + m_omx->SetCurrentReferenceTime(0); + m_omx->SetClockState(cOmx::eClockStateWaitForVideo); } } - if (State() == eVideoOnly || State() == eAudioVideo) + // keep track of direction in case of trick speed + if (m_trickRequest && pts) { - int64_t pts = PesHasPts(Data) ? PesGetPts(Data) : 0; - OMX_BUFFERHEADERTYPE *buf = m_omx->GetVideoBuffer(pts); - if (buf) + if (m_videoPts) + PtsTracker(PtsDiff(m_videoPts, pts)); + + m_videoPts = pts; + } + + if (m_hasVideo) + { + while (Length) { - //dsyslog("V %llu", PesGetPts(Data)); const uchar *payload = Data + PesPayloadOffset(Data); - int length = PesLength(Data) - PesPayloadOffset(Data); - if (length <= buf->nAllocLen) + unsigned int length = PesLength(Data) - PesPayloadOffset(Data); + + OMX_BUFFERHEADERTYPE *buf = m_omx->GetVideoBuffer(pts); + if (buf) { - memcpy(buf->pBuffer, payload, length); buf->nFilledLen = length; + memcpy(buf->pBuffer, payload, length); + + if (singleFrame && Length == PesLength(Data)) + buf->nFlags |= OMX_BUFFERFLAG_ENDOFFRAME; + + if (!m_omx->EmptyVideoBuffer(buf)) + { + ret = 0; + esyslog("rpihddevice: failed to pass buffer to video decoder!"); + break; + } } else - esyslog("rpihddevice: video packet too long for video buffer!"); - - if (!m_omx->EmptyVideoBuffer(buf)) { ret = 0; - esyslog("rpihddevice: failed to pass buffer to video decoder!"); + break; } + Length -= PesLength(Data); + Data += PesLength(Data); } } @@ -203,85 +254,133 @@ int cOmxDevice::PlayVideo(const uchar *Data, int Length) return ret; } -void cOmxDevice::SetState(eState state) -{ - switch (state) - { - case eNone: - m_omx->SetClockState(cOmx::eClockStateStop); - break; - - case eStartingVideo: - break; - - case eAudioOnly: - m_omx->SetClockState(cOmx::eClockStateWaitForAudio); - break; - - case eVideoOnly: - m_omx->SetClockState(cOmx::eClockStateWaitForVideo); - break; - - case eAudioVideo: - m_omx->SetClockState(cOmx::eClockStateWaitForAudioVideo); - break; - } - m_state = state; -} - int64_t cOmxDevice::GetSTC(void) { - //dsyslog("S %llu", m_omx->GetSTC()); return m_omx->GetSTC(); } void cOmxDevice::Play(void) { dsyslog("rpihddevice: Play()"); + + Clear(); + return; + + m_mutex->Lock(); + + m_skipAudio = false; m_omx->SetClockScale(1.0f); + m_omx->SetClockReference(m_hasAudio ? + cOmx::eClockRefAudio : cOmx::eClockRefVideo); + + m_mutex->Unlock(); cDevice::Play(); } void cOmxDevice::Freeze(void) { - dsyslog("rpihddevice: Freeze()"); + m_mutex->Lock(); m_omx->SetClockScale(0.0f); + m_mutex->Unlock(); cDevice::Freeze(); } void cOmxDevice::TrickSpeed(int Speed) { - dsyslog("rpihddevice: TrickSpeed(%d)", Speed); + m_mutex->Lock(); + m_audioPts = 0; + m_videoPts = 0; + m_playDirection = 0; + + // play direction is ambiguous for fast modes, start PTS tracking + if (Speed == 1 || Speed == 3 || Speed == 6) + m_trickRequest = Speed; + else + ApplyTrickSpeed(Speed); + + m_mutex->Unlock(); +} + +void cOmxDevice::ApplyTrickSpeed(int trickSpeed, bool reverse) +{ + float scale = + // slow forward + trickSpeed == 8 ? 0.125f : + trickSpeed == 4 ? 0.25f : + trickSpeed == 2 ? 0.5f : + + // fast for-/backward + trickSpeed == 6 ? (reverse ? -2.0f : 2.0f) : + trickSpeed == 3 ? (reverse ? -4.0f : 4.0f) : + trickSpeed == 1 ? (reverse ? -12.0f : 12.0f) : + + // slow backward + trickSpeed == 63 ? -0.125f : + trickSpeed == 48 ? -0.25f : + trickSpeed == 24 ? -0.5f : 1.0f; + + m_omx->SetClockScale(scale); + m_omx->SetClockReference(cOmx::eClockRefVideo); + + m_trickRequest = 0; + m_skipAudio = true; + + dsyslog("rpihddevice: ApplyTrickSpeed(%.3f, %sward)", + scale, reverse ? "back" : "for"); + +} + +void cOmxDevice::PtsTracker(int64_t ptsDiff) +{ + if (ptsDiff < 0) + --m_playDirection; + else if (ptsDiff > 0) + m_playDirection += 2; + + if (m_playDirection < -2 || m_playDirection > 3) + ApplyTrickSpeed(m_trickRequest, m_playDirection < 0); } bool cOmxDevice::Flush(int TimeoutMs) { dsyslog("rpihddevice: Flush()"); - return true; } void cOmxDevice::Clear(void) { - m_mutex->Lock(); dsyslog("rpihddevice: Clear()"); + m_mutex->Lock(); + ResetAudioVideo(); + m_omx->SetStartTime(0); + + m_mutex->Unlock(); + cDevice::Clear(); +} + +void cOmxDevice::ResetAudioVideo(bool flushVideoRender) +{ m_omx->SetClockScale(1.0f); - m_omx->SetMediaTime(0); + m_skipAudio = false; + m_trickRequest = 0; + m_audioPts = 0; + m_videoPts = 0; - if (HasAudio()) + if (m_hasAudio) { m_audio->Reset(); m_omx->FlushAudio(); } - if (HasVideo()) - m_omx->FlushVideo(false); + if (m_hasVideo) + m_omx->FlushVideo(flushVideoRender); - m_mutex->Unlock(); - cDevice::Clear(); + m_hasAudio = false; + m_hasVideo = false; } + void cOmxDevice::SetVolumeDevice(int Volume) { m_omx->SetVolume(Volume); @@ -293,7 +392,7 @@ bool cOmxDevice::Poll(cPoller &Poller, int TimeoutMs) time.Set(); while (!m_omx->PollVideoBuffers() || !m_audio->Poll()) { - if (time.Elapsed() >= TimeoutMs) + if (time.Elapsed() >= (unsigned)TimeoutMs) return false; cCondWait::SleepMs(5); } @@ -316,6 +415,7 @@ cVideoCodec::eCodec cOmxDevice::ParseVideoCodec(const uchar *data, int length) return cVideoCodec::eInvalid; const uchar *p = data + PesPayloadOffset(data); + for (int i = 0; i < 5; i++) { // find start code prefix - should be right at the beginning of payload @@ -324,17 +424,23 @@ cVideoCodec::eCodec cOmxDevice::ParseVideoCodec(const uchar *data, int length) if (p[i + 3] == 0xb3) // sequence header return cVideoCodec::eMPEG2; + //p[i + 3] = 0xf0 else if (p[i + 3] == 0x09) // slice { + // quick hack for converted mkvs + if (p[i + 4] == 0xf0) + return cVideoCodec::eH264; + switch (p[i + 4] >> 5) { case 0: case 3: case 5: // I frame return cVideoCodec::eH264; - case 1: case 4: case 6: // P frame - case 2: case 7: // B frame + case 2: case 7: // B frame + case 1: case 4: case 6: // P frame default: - return cVideoCodec::eInvalid; +// return cVideoCodec::eInvalid; + return cVideoCodec::eH264; } } return cVideoCodec::eInvalid; |