/***************************************************************************** * ParseNALBlock: parses annexB type NALs * All p_frag blocks are required to start with 0 0 0 1 4-byte startcode *****************************************************************************/ static block_t *ParseNALBlock( decoder_t *p_dec, bool *pb_ts_used, block_t *p_frag ) { decoder_sys_t *p_sys = p_dec->p_sys; block_t *p_pic = NULL; const int i_nal_ref_idc = (p_frag->p_buffer[4] >> 5)&0x03; const int i_nal_type = p_frag->p_buffer[4]&0x1f; const mtime_t i_frag_dts = p_frag->i_dts; const mtime_t i_frag_pts = p_frag->i_pts; if( p_sys->b_slice && ( !p_sys->b_sps || !p_sys->b_pps ) ) { block_ChainRelease( p_sys->p_frame ); msg_Warn( p_dec, "waiting for SPS/PPS" ); /* Reset context */ p_sys->slice.i_frame_type = 0; p_sys->p_frame = NULL; p_sys->b_frame_sps = false; p_sys->b_frame_pps = false; p_sys->b_slice = false; cc_Flush( &p_sys->cc_next ); } if( ( !p_sys->b_sps || !p_sys->b_pps ) && i_nal_type >= NAL_SLICE && i_nal_type <= NAL_SLICE_IDR ) { p_sys->b_slice = true; /* Fragment will be discarded later on */ } else if( i_nal_type >= NAL_SLICE && i_nal_type <= NAL_SLICE_IDR ) { slice_t slice; bool b_new_picture; ParseSlice( p_dec, &b_new_picture, &slice, i_nal_ref_idc, i_nal_type, p_frag ); /* */ if( b_new_picture && p_sys->b_slice ) p_pic = OutputPicture( p_dec ); /* */ p_sys->slice = slice; p_sys->b_slice = true; } else if( i_nal_type == NAL_SPS ) { if( p_sys->b_slice ) p_pic = OutputPicture( p_dec ); p_sys->b_frame_sps = true; PutSPS( p_dec, p_frag ); /* Do not append the SPS because we will insert it on keyframes */ p_frag = NULL; } else if( i_nal_type == NAL_PPS ) { if( p_sys->b_slice ) p_pic = OutputPicture( p_dec ); p_sys->b_frame_pps = true; PutPPS( p_dec, p_frag ); /* Do not append the PPS because we will insert it on keyframes */ p_frag = NULL; } else if( i_nal_type == NAL_AU_DELIMITER || i_nal_type == NAL_SEI || ( i_nal_type >= 13 && i_nal_type <= 18 ) ) { if( p_sys->b_slice ) p_pic = OutputPicture( p_dec ); /* Parse SEI for CC support */ if( i_nal_type == NAL_SEI ) { ParseSei( p_dec, p_frag ); } else if( i_nal_type == NAL_AU_DELIMITER ) { if( p_sys->p_frame && (p_sys->p_frame->i_flags & BLOCK_FLAG_PRIVATE_AUD) ) { block_Release( p_frag ); p_frag = NULL; } else { p_frag->i_flags |= BLOCK_FLAG_PRIVATE_AUD; } } } /* Append the block */ if( p_frag ) block_ChainAppend( &p_sys->p_frame, p_frag ); *pb_ts_used = false; if( p_sys->i_frame_dts <= VLC_TS_INVALID && p_sys->i_frame_pts <= VLC_TS_INVALID ) { p_sys->i_frame_dts = i_frag_dts; p_sys->i_frame_pts = i_frag_pts; *pb_ts_used = true; } return p_pic; }
bool CVideoPlayerVideo::ProcessDecoderOutput(double &frametime, double &pts) { CDVDVideoCodec::VCReturn decoderState = m_pVideoCodec->GetPicture(&m_picture); if (decoderState == CDVDVideoCodec::VC_BUFFER) { return false; } // if decoder was flushed, we need to seek back again to resume rendering if (decoderState == CDVDVideoCodec::VC_FLUSHED) { CLog::Log(LOGDEBUG, "CVideoPlayerVideo - video decoder was flushed"); while (!m_packets.empty()) { CDVDMsgDemuxerPacket* msg = static_cast<CDVDMsgDemuxerPacket*>(m_packets.front().message->Acquire()); m_packets.pop_front(); SendMessage(msg, 10); } m_pVideoCodec->Reset(); m_packets.clear(); //picture.iFlags &= ~DVP_FLAG_ALLOCATED; m_renderManager.DiscardBuffer(); return false; } if (decoderState == CDVDVideoCodec::VC_REOPEN) { while (!m_packets.empty()) { CDVDMsgDemuxerPacket* msg = static_cast<CDVDMsgDemuxerPacket*>(m_packets.front().message->Acquire()); m_packets.pop_front(); SendMessage(msg, 10); } m_pVideoCodec->Reopen(); m_packets.clear(); m_renderManager.DiscardBuffer(); return false; } // if decoder had an error, tell it to reset to avoid more problems if (decoderState == CDVDVideoCodec::VC_ERROR) { CLog::Log(LOGDEBUG, "CVideoPlayerVideo - video decoder returned error"); return false; } if (decoderState == CDVDVideoCodec::VC_EOF) { if (m_syncState == IDVDStreamPlayer::SYNC_STARTING) { SStartMsg msg; msg.player = VideoPlayer_VIDEO; msg.cachetime = DVD_MSEC_TO_TIME(50); msg.cachetotal = DVD_MSEC_TO_TIME(100); msg.timestamp = DVD_NOPTS_VALUE; m_messageParent.Put(new CDVDMsgType<SStartMsg>(CDVDMsg::PLAYER_STARTED, msg)); } return false; } // check for a new picture if (decoderState == CDVDVideoCodec::VC_PICTURE) { bool hasTimestamp = true; m_picture.iDuration = frametime; // validate picture timing, // if both dts/pts invalid, use pts calulated from picture.iDuration // if pts invalid use dts, else use picture.pts as passed if (m_picture.dts == DVD_NOPTS_VALUE && m_picture.pts == DVD_NOPTS_VALUE) { m_picture.pts = pts; hasTimestamp = false; } else if (m_picture.pts == DVD_NOPTS_VALUE) m_picture.pts = m_picture.dts; // use forced aspect if any if (m_fForcedAspectRatio != 0.0f) { m_picture.iDisplayWidth = (int) (m_picture.iDisplayHeight * m_fForcedAspectRatio); if (m_picture.iDisplayWidth > m_picture.iWidth) { m_picture.iDisplayWidth = m_picture.iWidth; m_picture.iDisplayHeight = (int) (m_picture.iDisplayWidth / m_fForcedAspectRatio); } } // set stereo mode if not set by decoder if (m_picture.stereoMode.empty()) { std::string stereoMode; switch(m_processInfo.GetVideoSettings().m_StereoMode) { case RENDER_STEREO_MODE_SPLIT_VERTICAL: stereoMode = "left_right"; if (m_processInfo.GetVideoSettings().m_StereoInvert) stereoMode = "right_left"; break; case RENDER_STEREO_MODE_SPLIT_HORIZONTAL: stereoMode = "top_bottom"; if (m_processInfo.GetVideoSettings().m_StereoInvert) stereoMode = "bottom_top"; break; default: stereoMode = m_hints.stereo_mode; break; } if (!stereoMode.empty() && stereoMode != "mono") { m_picture.stereoMode = stereoMode; } } // if frame has a pts (usually originiating from demux packet), use that if (m_picture.pts != DVD_NOPTS_VALUE) { pts = m_picture.pts; } double extraDelay = 0.0; if (m_picture.iRepeatPicture) { extraDelay = m_picture.iRepeatPicture * m_picture.iDuration; m_picture.iDuration += extraDelay; } m_picture.pts = pts + extraDelay; // guess next frame pts. iDuration is always valid if (m_speed != 0) pts += m_picture.iDuration * m_speed / abs(m_speed); m_outputSate = OutputPicture(&m_picture); if (m_outputSate == OUTPUT_AGAIN) { return true; } else if (m_outputSate == OUTPUT_ABORT) { return false; } else if ((m_outputSate == OUTPUT_DROPPED) && !(m_picture.iFlags & DVP_FLAG_DROPPED)) { m_iDroppedFrames++; m_ptsTracker.Flush(); } if (m_syncState == IDVDStreamPlayer::SYNC_STARTING && m_outputSate != OUTPUT_DROPPED && !(m_picture.iFlags & DVP_FLAG_DROPPED)) { m_syncState = IDVDStreamPlayer::SYNC_WAITSYNC; SStartMsg msg; msg.player = VideoPlayer_VIDEO; msg.cachetime = DVD_MSEC_TO_TIME(50); //! @todo implement msg.cachetotal = DVD_MSEC_TO_TIME(100); //! @todo implement msg.timestamp = hasTimestamp ? (pts + m_renderManager.GetDelay() * 1000) : DVD_NOPTS_VALUE; m_messageParent.Put(new CDVDMsgType<SStartMsg>(CDVDMsg::PLAYER_STARTED, msg)); } frametime = (double)DVD_TIME_BASE / m_fFrameRate; } return true; }
/***************************************************************************** * ParseNALBlock: parses annexB type NALs * All p_frag blocks are required to start with 0 0 0 1 4-byte startcode *****************************************************************************/ static block_t *ParseNALBlock( decoder_t *p_dec, bool *pb_ts_used, block_t *p_frag ) { decoder_sys_t *p_sys = p_dec->p_sys; block_t *p_pic = NULL; bool b_new_picture = false; const int i_nal_type = p_frag->p_buffer[4]&0x1f; const mtime_t i_frag_dts = p_frag->i_dts; const mtime_t i_frag_pts = p_frag->i_pts; if( p_sys->b_slice && (!p_sys->p_active_pps || !p_sys->p_active_sps) ) { msg_Warn( p_dec, "waiting for SPS/PPS" ); /* Reset context */ p_sys->slice.type = H264_SLICE_TYPE_UNKNOWN; p_sys->b_slice = false; DropStoredNAL( p_sys ); /* From SEI */ p_sys->i_dpb_output_delay = 0; p_sys->i_pic_struct = UINT8_MAX; cc_storage_reset( p_sys->p_ccs ); } if( i_nal_type >= H264_NAL_SLICE && i_nal_type <= H264_NAL_SLICE_IDR ) { h264_slice_t newslice; if( i_nal_type == H264_NAL_SLICE_IDR ) { p_sys->b_recovered = true; p_sys->i_recovery_frame_cnt = UINT_MAX; p_sys->i_recoveryfnum = UINT_MAX; } if( ParseSliceHeader( p_dec, p_frag, &newslice ) ) { /* Only IDR carries the id, to be propagated */ if( newslice.i_idr_pic_id == -1 ) newslice.i_idr_pic_id = p_sys->slice.i_idr_pic_id; b_new_picture = IsFirstVCLNALUnit( &p_sys->slice, &newslice ); if( b_new_picture ) { /* Parse SEI for that frame now we should have matched SPS/PPS */ for( block_t *p_sei = p_sys->p_sei; p_sei; p_sei = p_sei->p_next ) { HxxxParse_AnnexB_SEI( p_sei->p_buffer, p_sei->i_buffer, 1 /* nal header */, ParseSeiCallback, p_dec ); } if( p_sys->b_slice ) p_pic = OutputPicture( p_dec ); } /* */ p_sys->slice = newslice; } else { p_sys->p_active_pps = NULL; /* Fragment will be discarded later on */ } p_sys->b_slice = true; } else if( i_nal_type == H264_NAL_SPS ) { if( p_sys->b_slice ) p_pic = OutputPicture( p_dec ); PutSPS( p_dec, p_frag ); p_sys->b_new_sps = true; /* Do not append the SPS because we will insert it on keyframes */ p_frag = NULL; } else if( i_nal_type == H264_NAL_PPS ) { if( p_sys->b_slice ) p_pic = OutputPicture( p_dec ); PutPPS( p_dec, p_frag ); p_sys->b_new_pps = true; /* Do not append the PPS because we will insert it on keyframes */ p_frag = NULL; } else if( i_nal_type == H264_NAL_SEI ) { if( p_sys->b_slice ) p_pic = OutputPicture( p_dec ); block_ChainLastAppend( &p_sys->pp_sei_last, p_frag ); p_frag = NULL; } else if( i_nal_type == H264_NAL_END_OF_SEQ || i_nal_type == H264_NAL_END_OF_STREAM ) { /* Early end of packetization */ block_ChainLastAppend( &p_sys->pp_sei_last, p_frag ); p_frag = NULL; /* important for still pictures/menus */ p_sys->i_next_block_flags |= BLOCK_FLAG_END_OF_SEQUENCE; if( p_sys->b_slice ) p_pic = OutputPicture( p_dec ); } else if( i_nal_type == H264_NAL_AU_DELIMITER || ( i_nal_type >= H264_NAL_PREFIX && i_nal_type <= H264_NAL_RESERVED_18 ) ) { if( p_sys->b_slice ) p_pic = OutputPicture( p_dec ); if( i_nal_type == H264_NAL_AU_DELIMITER ) { if( p_sys->p_frame && (p_sys->p_frame->i_flags & BLOCK_FLAG_PRIVATE_AUD) ) { block_Release( p_frag ); p_frag = NULL; } else { p_frag->i_flags |= BLOCK_FLAG_PRIVATE_AUD; } } } /* Append the block */ if( p_frag ) block_ChainLastAppend( &p_sys->pp_frame_last, p_frag ); *pb_ts_used = false; if( p_sys->i_frame_dts <= VLC_TS_INVALID && p_sys->i_frame_pts <= VLC_TS_INVALID && b_new_picture ) { p_sys->i_frame_dts = i_frag_dts; p_sys->i_frame_pts = i_frag_pts; *pb_ts_used = true; if( i_frag_dts > VLC_TS_INVALID ) date_Set( &p_sys->dts, i_frag_dts ); } if( p_pic && (p_pic->i_flags & BLOCK_FLAG_DROP) ) { block_Release( p_pic ); p_pic = NULL; } return p_pic; }
void CVideoPlayerVideo::Process() { CLog::Log(LOGNOTICE, "running thread: video_thread"); double pts = 0; double frametime = (double)DVD_TIME_BASE / m_fFrameRate; bool bRequestDrop = false; int iDropDirective; bool onlyPrioMsgs = false; m_videoStats.Start(); m_droppingStats.Reset(); m_iDroppedFrames = 0; m_rewindStalled = false; m_outputSate = OUTPUT_NORMAL; while (!m_bStop) { int iQueueTimeOut = (int)(m_stalled ? frametime : frametime * 10) / 1000; int iPriority = 0; if (m_syncState == IDVDStreamPlayer::SYNC_WAITSYNC) iPriority = 1; if (m_paused) iPriority = 1; if (onlyPrioMsgs) { iPriority = 1; iQueueTimeOut = 1; } CDVDMsg* pMsg; MsgQueueReturnCode ret = GetMessage(&pMsg, iQueueTimeOut, iPriority); onlyPrioMsgs = false; if (MSGQ_IS_ERROR(ret)) { CLog::Log(LOGERROR, "Got MSGQ_ABORT or MSGO_IS_ERROR return true"); break; } else if (ret == MSGQ_TIMEOUT) { if (m_outputSate == OUTPUT_AGAIN && m_picture.videoBuffer) { m_outputSate = OutputPicture(&m_picture); if (m_outputSate == OUTPUT_AGAIN) { onlyPrioMsgs = true; continue; } } // don't ask for a new frame if we can't deliver it to renderer else if ((m_speed != DVD_PLAYSPEED_PAUSE || m_processInfo.IsFrameAdvance() || m_syncState != IDVDStreamPlayer::SYNC_INSYNC) && !m_paused) { if (ProcessDecoderOutput(frametime, pts)) { onlyPrioMsgs = true; continue; } } // if we only wanted priority messages, this isn't a stall if (iPriority) continue; //Okey, start rendering at stream fps now instead, we are likely in a stillframe if (!m_stalled) { // squeeze pictures out while (!m_bStop && m_pVideoCodec) { m_pVideoCodec->SetCodecControl(DVD_CODEC_CTRL_DRAIN); if (!ProcessDecoderOutput(frametime, pts)) break; } CLog::Log(LOGINFO, "CVideoPlayerVideo - Stillframe detected, switching to forced %f fps", m_fFrameRate); m_stalled = true; pts += frametime * 4; } // Waiting timed out, output last picture if (m_picture.videoBuffer) { m_picture.pts = pts; m_outputSate = OutputPicture(&m_picture); pts += frametime; } continue; } if (pMsg->IsType(CDVDMsg::GENERAL_SYNCHRONIZE)) { if (static_cast<CDVDMsgGeneralSynchronize*>(pMsg)->Wait(100, SYNCSOURCE_VIDEO)) { CLog::Log(LOGDEBUG, "CVideoPlayerVideo - CDVDMsg::GENERAL_SYNCHRONIZE"); } else SendMessage(pMsg->Acquire(), 1); /* push back as prio message, to process other prio messages */ m_droppingStats.Reset(); } else if (pMsg->IsType(CDVDMsg::GENERAL_RESYNC)) { pts = static_cast<CDVDMsgDouble*>(pMsg)->m_value; m_syncState = IDVDStreamPlayer::SYNC_INSYNC; m_droppingStats.Reset(); m_rewindStalled = false; m_renderManager.ShowVideo(true); CLog::Log(LOGDEBUG, "CVideoPlayerVideo - CDVDMsg::GENERAL_RESYNC(%f)", pts); } else if (pMsg->IsType(CDVDMsg::VIDEO_SET_ASPECT)) { CLog::Log(LOGDEBUG, "CVideoPlayerVideo - CDVDMsg::VIDEO_SET_ASPECT"); m_fForcedAspectRatio = static_cast<float>(*static_cast<CDVDMsgDouble*>(pMsg)); } else if (pMsg->IsType(CDVDMsg::GENERAL_RESET)) { if(m_pVideoCodec) m_pVideoCodec->Reset(); if (m_picture.videoBuffer) { m_picture.videoBuffer->Release(); m_picture.videoBuffer = nullptr; } m_packets.clear(); m_droppingStats.Reset(); m_syncState = IDVDStreamPlayer::SYNC_STARTING; m_renderManager.ShowVideo(false); m_rewindStalled = false; } else if (pMsg->IsType(CDVDMsg::GENERAL_FLUSH)) // private message sent by (CVideoPlayerVideo::Flush()) { bool sync = static_cast<CDVDMsgBool*>(pMsg)->m_value; if(m_pVideoCodec) m_pVideoCodec->Reset(); if (m_picture.videoBuffer) { m_picture.videoBuffer->Release(); m_picture.videoBuffer = nullptr; } m_packets.clear(); pts = 0; m_rewindStalled = false; m_ptsTracker.Flush(); //we need to recalculate the framerate //! @todo this needs to be set on a streamchange instead ResetFrameRateCalc(); m_droppingStats.Reset(); m_stalled = true; if (sync) { m_syncState = IDVDStreamPlayer::SYNC_STARTING; m_renderManager.ShowVideo(false); } m_renderManager.DiscardBuffer(); } else if (pMsg->IsType(CDVDMsg::PLAYER_SETSPEED)) { m_speed = static_cast<CDVDMsgInt*>(pMsg)->m_value; if (m_pVideoCodec) m_pVideoCodec->SetSpeed(m_speed); m_droppingStats.Reset(); } else if (pMsg->IsType(CDVDMsg::GENERAL_STREAMCHANGE)) { CDVDMsgVideoCodecChange* msg(static_cast<CDVDMsgVideoCodecChange*>(pMsg)); while (!m_bStop && m_pVideoCodec) { m_pVideoCodec->SetCodecControl(DVD_CODEC_CTRL_DRAIN); bool cont = ProcessDecoderOutput(frametime, pts); if (!cont) break; } OpenStream(msg->m_hints, msg->m_codec); msg->m_codec = NULL; if (m_picture.videoBuffer) { m_picture.videoBuffer->Release(); m_picture.videoBuffer = nullptr; } } else if (pMsg->IsType(CDVDMsg::VIDEO_DRAIN)) { while (!m_bStop && m_pVideoCodec) { m_pVideoCodec->SetCodecControl(DVD_CODEC_CTRL_DRAIN); if (!ProcessDecoderOutput(frametime, pts)) break; } } else if (pMsg->IsType(CDVDMsg::GENERAL_PAUSE)) { m_paused = static_cast<CDVDMsgBool*>(pMsg)->m_value; CLog::Log(LOGDEBUG, "CVideoPlayerVideo - CDVDMsg::GENERAL_PAUSE: %d", m_paused); } else if (pMsg->IsType(CDVDMsg::PLAYER_REQUEST_STATE)) { SStateMsg msg; msg.player = VideoPlayer_VIDEO; msg.syncState = m_syncState; m_messageParent.Put(new CDVDMsgType<SStateMsg>(CDVDMsg::PLAYER_REPORT_STATE, msg)); } else if (pMsg->IsType(CDVDMsg::DEMUXER_PACKET)) { DemuxPacket* pPacket = static_cast<CDVDMsgDemuxerPacket*>(pMsg)->GetPacket(); bool bPacketDrop = static_cast<CDVDMsgDemuxerPacket*>(pMsg)->GetPacketDrop(); if (m_stalled) { CLog::Log(LOGINFO, "CVideoPlayerVideo - Stillframe left, switching to normal playback"); m_stalled = false; } bRequestDrop = false; iDropDirective = CalcDropRequirement(pts); if ((iDropDirective & DROP_VERYLATE) && m_bAllowDrop && !bPacketDrop) { bRequestDrop = true; } if (iDropDirective & DROP_DROPPED) { m_iDroppedFrames++; m_ptsTracker.Flush(); } if (m_messageQueue.GetDataSize() == 0 || m_speed < 0) { bRequestDrop = false; m_iDroppedRequest = 0; m_iLateFrames = 0; } int codecControl = 0; if (iDropDirective & DROP_BUFFER_LEVEL) codecControl |= DVD_CODEC_CTRL_HURRY; if (m_speed > DVD_PLAYSPEED_NORMAL) codecControl |= DVD_CODEC_CTRL_NO_POSTPROC; if (bPacketDrop) codecControl |= DVD_CODEC_CTRL_DROP; if (bRequestDrop) codecControl |= DVD_CODEC_CTRL_DROP_ANY; if (!m_renderManager.Supports(RENDERFEATURE_ROTATION)) codecControl |= DVD_CODEC_CTRL_ROTATE; m_pVideoCodec->SetCodecControl(codecControl); if (m_pVideoCodec->AddData(*pPacket)) { // buffer packets so we can recover should decoder flush for some reason if (m_pVideoCodec->GetConvergeCount() > 0) { m_packets.emplace_back(pMsg, 0); if (m_packets.size() > m_pVideoCodec->GetConvergeCount() || m_packets.size() * frametime > DVD_SEC_TO_TIME(10)) m_packets.pop_front(); } m_videoStats.AddSampleBytes(pPacket->iSize); if (ProcessDecoderOutput(frametime, pts)) { onlyPrioMsgs = true; } } else { SendMessageBack(pMsg->Acquire()); onlyPrioMsgs = true; } } // all data is used by the decoder, we can safely free it now pMsg->Release(); } }