diff --git a/ChangeLog b/ChangeLog index 8e8b245..2d92071 100644 --- a/ChangeLog +++ b/ChangeLog @@ -2,6 +2,7 @@ User johns Date: Release Version 0.5.0 + Audio/Video sync rewrite, trick-speed support moved to video. Faster VdpauBlackSurface version. Fix bug: VideoSetPts wrong position for multi frame packets. @@ -13,7 +14,6 @@ Date: Mon Mar 26 20:45:54 CEST 2012 User johns Date: Fri Mar 23 18:43:20 CET 2012 - Audio/Video sync rewrite, trick-speed support moved to video. Add optional argument (display) to ATTA svdrp commmand. Wakeup display to show OSD for remote learning mode. Support switching the primary device with svdrp. diff --git a/softhddev.c b/softhddev.c index c0ca0d6..3dec80f 100644 --- a/softhddev.c +++ b/softhddev.c @@ -1130,8 +1130,11 @@ void SetVolumeDevice(int volume) #include // portable atomic_t +#ifdef DEBUG uint32_t VideoSwitch; ///< debug video switch ticks +#endif static volatile char NewVideoStream; ///< flag new video stream +static volatile char ClosingVideoStream; ///< flag closing video stream static VideoHwDecoder *MyHwDecoder; ///< video hw decoder static VideoDecoder *MyVideoDecoder; ///< video decoder static enum CodecID VideoCodecID; ///< current codec id @@ -1150,8 +1153,7 @@ static atomic_t VideoPacketsFilled; ///< how many of the buffer is used static volatile char VideoClearBuffers; ///< clear video buffers static volatile char VideoClearClose; ///< clear video buffers upto close static volatile char SkipVideo; ///< skip video -static volatile char VideoTrickSpeed; ///< current trick speed -static volatile char VideoTrickCounter; ///< current trick speed counter +static volatile char CurrentTrickSpeed; ///< current trick speed #ifdef DEBUG static int VideoMaxPacketSize; ///< biggest used packet buffer @@ -1340,6 +1342,10 @@ void FixPacketForFFMpeg(VideoDecoder * MyVideoDecoder, AVPacket * avpkt) /** ** Decode from PES packet ringbuffer. +** +** @retval 0 packet decoded +** @retval 1 stream paused +** @retval -1 empty stream */ int VideoDecode(void) { @@ -1360,19 +1366,13 @@ int VideoDecode(void) VideoClearBuffers = 0; return 1; } - if (VideoTrickSpeed) { - if (VideoTrickCounter++ < VideoTrickSpeed * 2) { - usleep(5 * 1000); - return 1; - } - VideoTrickCounter = 0; - } filled = atomic_read(&VideoPacketsFilled); if (!filled) { return -1; } - if (VideoClearClose) { + // clearing for normal channel switch has no advantage + if (VideoClearClose /*|| ClosingVideoStream */ ) { int f; // flush buffers, if close is in the queue @@ -1388,6 +1388,7 @@ int VideoDecode(void) break; } } + ClosingVideoStream = 0; } avpkt = &VideoPacketRb[VideoPacketRead]; @@ -1396,11 +1397,13 @@ int VideoDecode(void) // switch ((int)(size_t) avpkt->priv) { case CODEC_ID_NONE: + ClosingVideoStream = 0; if (last_codec_id != CODEC_ID_NONE) { last_codec_id = CODEC_ID_NONE; CodecVideoClose(MyVideoDecoder); goto skip; } + // FIXME: look if more close are in the queue // size can be zero goto skip; case CODEC_ID_MPEG2VIDEO: @@ -1450,6 +1453,11 @@ int VideoDecode(void) } } + if (ClosingVideoStream) { // closing don't sync + avpkt->pts = AV_NOPTS_VALUE; + avpkt->dts = AV_NOPTS_VALUE; + } + if (last_codec_id == CODEC_ID_MPEG2VIDEO) { FixPacketForFFMpeg(MyVideoDecoder, avpkt); } else { @@ -1618,7 +1626,7 @@ int PlayVideo(const uint8_t * data, int size) return 0; } if (NewVideoStream) { // channel switched - Debug(3, "video: new stream %d\n", GetMsTicks() - VideoSwitch); + Debug(3, "video: new stream %dms\n", GetMsTicks() - VideoSwitch); // FIXME: hack to test results if (atomic_read(&VideoPacketsFilled) >= VIDEO_PACKET_MAX - 1) { Debug(3, "video: new video stream lost\n"); @@ -1627,6 +1635,9 @@ int PlayVideo(const uint8_t * data, int size) } VideoNextPacket(CODEC_ID_NONE); VideoCodecID = CODEC_ID_NONE; + // clear clock until new stream starts + VideoSetClock(MyHwDecoder, AV_NOPTS_VALUE); + ClosingVideoStream = 1; NewVideoStream = 0; } // must be a PES start code @@ -1638,13 +1649,14 @@ int PlayVideo(const uint8_t * data, int size) if (data[3] == PES_PADDING_STREAM) { // from DVD plugin return size; } - n = data[8]; // header size - if (size < 9 + n + 4) { // wrong size + n = data[8]; // header size + if (size <= 9 + n) { // wrong size if (size == 9 + n) { Warning(_("[softhddev] empty video packet\n")); } else { - Error(_("[softhddev] invalid video packet %d bytes\n"), size); + Error(_("[softhddev] invalid video packet %d/%d bytes\n"), 9 + n, + size); } return size; } @@ -1653,7 +1665,6 @@ int PlayVideo(const uint8_t * data, int size) return 0; } // get pts/dts - pts = AV_NOPTS_VALUE; if (data[7] & 0x80) { pts = @@ -1665,10 +1676,12 @@ int PlayVideo(const uint8_t * data, int size) l = size - 9 - n; z = 0; while (!*check) { // count leading zeros - if (--l < 2) { + if (l < 3) { Warning(_("[softhddev] empty video packet %d bytes\n"), size); - return size; + z = 0; + break; } + --l; ++check; ++z; } @@ -1677,7 +1690,7 @@ int PlayVideo(const uint8_t * data, int size) if ((data[6] & 0xC0) == 0x80 && z > 2 && check[0] == 0x01 && check[1] == 0x09) { if (VideoCodecID == CODEC_ID_H264) { - if (VideoTrickSpeed && pts != (int64_t) AV_NOPTS_VALUE) { + if (CurrentTrickSpeed && pts != (int64_t) AV_NOPTS_VALUE) { // H264 NAL End of Sequence static uint8_t seq_end_h264[] = { 0x00, 0x00, 0x00, 0x01, 0x0A }; @@ -1716,7 +1729,6 @@ int PlayVideo(const uint8_t * data, int size) return size; } // this happens when vdr sends incomplete packets - if (VideoCodecID == CODEC_ID_NONE) { Debug(3, "video: not detected\n"); return size; @@ -1836,7 +1848,9 @@ int SetPlayMode(int play_mode) if (MyVideoDecoder) { // tell video parser we have new stream if (VideoCodecID != CODEC_ID_NONE) { NewVideoStream = 1; +#ifdef DEBUG VideoSwitch = GetMsTicks(); +#endif } } if (MyAudioDecoder) { // tell audio parser we have new stream @@ -1844,14 +1858,39 @@ int SetPlayMode(int play_mode) NewAudioStream = 1; } } - if (play_mode == 2 || play_mode == 3) { - Debug(3, "softhddev: FIXME: audio only, silence video errors\n"); + switch (play_mode) { + case 1: // audio/video from player + break; + case 2: // audio only + Debug(3, "softhddev: FIXME: audio only, silence video errors\n"); + VideoSetClock(MyHwDecoder, AV_NOPTS_VALUE); + break; + case 3: // audio only, black screen + Debug(3, "softhddev: FIXME: audio only, silence video errors\n"); + VideoSetClock(MyHwDecoder, AV_NOPTS_VALUE); + break; + case 4: // video only + break; } + Play(); return 1; } +/** +** Gets the current System Time Counter, which can be used to +** synchronize audio, video and subtitles. +*/ +int64_t GetSTC(void) +{ + if (MyHwDecoder) { + return VideoGetClock(MyHwDecoder); + } + Error(_("softhddev: %s called without hw decoder\n"), __FUNCTION__); + return AV_NOPTS_VALUE; +} + /** ** Set trick play speed. ** @@ -1862,8 +1901,12 @@ int SetPlayMode(int play_mode) */ void TrickSpeed(int speed) { - VideoTrickSpeed = speed; - VideoTrickCounter = 0; + CurrentTrickSpeed = speed; + if (MyHwDecoder) { + VideoSetTrickSpeed(MyHwDecoder, speed); + } else { + Error(_("softhddev: %s called without hw decoder\n"), __FUNCTION__); + } StreamFreezed = 0; } @@ -1892,9 +1935,7 @@ void Clear(void) */ void Play(void) { - VideoTrickSpeed = 0; - VideoTrickCounter = 0; - StreamFreezed = 0; + TrickSpeed(0); // normal play SkipAudio = 0; AudioPlay(); } @@ -2018,6 +2059,9 @@ void StillPicture(const uint8_t * data, int size) ** The dvd plugin is using this correct. ** ** @param timeout timeout to become ready in ms +** +** @retval true if ready +** @retval false if busy */ int Poll(int timeout) { @@ -2329,7 +2373,6 @@ void SoftHdDeviceExit(void) StopVideo(); CodecExit(); - //VideoPacketExit(); if (ConfigStartX11Server) { Debug(3, "x-setup: Stop x11 server\n"); diff --git a/softhddev.h b/softhddev.h index 50d7538..78862d6 100644 --- a/softhddev.h +++ b/softhddev.h @@ -51,6 +51,8 @@ extern "C" /// C plugin set play mode extern int SetPlayMode(int); + /// C plugin get current system time counter + extern int64_t GetSTC(void); /// C plugin set trick speed extern void TrickSpeed(int); /// C plugin clears all video and audio data from the device diff --git a/softhddevice.cpp b/softhddevice.cpp index f699b47..8be2e9d 100644 --- a/softhddevice.cpp +++ b/softhddevice.cpp @@ -1085,7 +1085,7 @@ int64_t cSoftHdDevice::GetSTC(void) { //dsyslog("[softhddev]%s:\n", __FUNCTION__); - return::VideoGetClock(); + return::GetSTC(); } /** diff --git a/video.c b/video.c index 9039098..f07282f 100644 --- a/video.c +++ b/video.c @@ -234,6 +234,9 @@ typedef struct _video_module_ const enum PixelFormat *); void (*const RenderFrame) (VideoHwDecoder *, const AVCodecContext *, const AVFrame *); + void (*const SetClock) (VideoHwDecoder *, int64_t); + int64_t(*const GetClock) (const VideoHwDecoder *); + void (*const SetTrickSpeed) (const VideoHwDecoder *, int); uint8_t *(*const GrabOutput)(int *, int *, int *); void (*const SetBackground) (uint32_t); void (*const SetVideoMode) (void); @@ -345,7 +348,9 @@ static xcb_atom_t WmDeleteWindowAtom; ///< WM delete message atom static xcb_atom_t NetWmState; ///< wm-state message atom static xcb_atom_t NetWmStateFullscreen; ///< fullscreen wm-state message atom +#ifdef DEBUG extern uint32_t VideoSwitch; ///< ticks for channel switch +#endif extern void AudioVideoReady(void); ///< tell audio video is ready #ifdef USE_VIDEO_THREAD @@ -391,6 +396,7 @@ static void VideoSetPts(int64_t * pts_p, int interlaced, const AVFrame * frame) // update video clock if (*pts_p != (int64_t) AV_NOPTS_VALUE) { *pts_p += interlaced ? 40 * 90 : 20 * 90; + //Info("video: %s +pts\n", Timestamp2String(*pts_p)); } //av_opt_ptr(avcodec_get_frame_class(), frame, "best_effort_timestamp"); //pts = frame->best_effort_timestamp; @@ -1320,12 +1326,12 @@ struct _vaapi_decoder_ atomic_t SurfacesFilled; ///< how many of the buffer is used int SurfaceField; ///< current displayed field - int DropNextFrame; ///< flag drop next frame - int DupNextFrame; ///< flag duplicate next frame + int TrickSpeed; ///< current trick speed + int TrickCounter; ///< current trick speed counter struct timespec FrameTime; ///< time of last display int64_t PTS; ///< video PTS clock - int StartCounter; ///< number of start frames + int SyncCounter; ///< counter to sync frames int FramesDuped; ///< number of frames duplicated int FramesMissed; ///< number of frames missed int FramesDropped; ///< number of frames dropped @@ -1349,6 +1355,46 @@ static void VaapiReleaseSurface(VaapiDecoder *, VASurfaceID); // VA-API Functions //---------------------------------------------------------------------------- +//---------------------------------------------------------------------------- + +/// +/// Output video messages. +/// +/// Reduce output. +/// +/// @param level message level (Error, Warning, Info, Debug, ...) +/// @param format printf format string (NULL to flush messages) +/// @param ... printf arguments +/// +/// @returns true, if message shown +/// +static int VaapiMessage(int level, const char *format, ...) +{ + if (SysLogLevel > level || DebugLevel > level) { + static const char *last_format; + static char buf[256]; + va_list ap; + + va_start(ap, format); + if (format != last_format) { // don't repeat same message + last_format = format; + if (buf[0]) { // print last repeated message + syslog(LOG_ERR, "%s", buf); + buf[0] = '\0'; + } + + if (format) { + vsyslog(LOG_ERR, format, ap); + } + va_end(ap); + return 1; + } + vsnprintf(buf, sizeof(buf), format, ap); + va_end(ap); + } + return 0; +} + // Surfaces ------------------------------------------------------------- /// @@ -1577,9 +1623,9 @@ static void VaapiReleaseSurface(VaapiDecoder * decoder, VASurfaceID surface) /// static void VaapiPrintFrames(const VaapiDecoder * decoder) { - Debug(3, "video/vaapi: %d missed, %d duped, %d dropped frames of %d\n", + Debug(3, "video/vaapi: %d missed, %d duped, %d dropped frames of %d,%d\n", decoder->FramesMissed, decoder->FramesDuped, decoder->FramesDropped, - decoder->FrameCounter); + decoder->FrameCounter, decoder->FramesDisplayed); #ifndef DEBUG (void)decoder; #endif @@ -1801,11 +1847,10 @@ static void VaapiCleanup(VaapiDecoder * decoder) } decoder->SurfaceRead = 0; decoder->SurfaceWrite = 0; + decoder->SurfaceField = 0; - decoder->SurfaceField = 1; - - //decoder->FrameCounter = 0; - decoder->StartCounter = 0; + decoder->FrameCounter = 0; + decoder->FramesDisplayed = 0; decoder->PTS = AV_NOPTS_VALUE; VideoDeltaPTS = 0; } @@ -1868,6 +1913,8 @@ static void VaapiDelHwDecoder(VaapiDecoder * decoder) free(decoder); } +#ifdef DEBUG // currently unused, keep it for later + static VAProfile VaapiFindProfile(const VAProfile * profiles, unsigned n, VAProfile profile); static VAEntrypoint VaapiFindEntrypoint(const VAEntrypoint * entrypoints, @@ -1992,6 +2039,8 @@ static void Vaapi1080i(void) fprintf(stderr, "done\n"); } +#endif + /// /// VA-API setup. /// @@ -2184,7 +2233,7 @@ static enum PixelFormat Vaapi_get_format(VaapiDecoder * decoder, int e; VAConfigAttrib attrib; - Debug(3, "video: new stream format %d\n", GetMsTicks() - VideoSwitch); + Debug(3, "video: new stream format %dms\n", GetMsTicks() - VideoSwitch); // create initial black surface and display VaapiBlackSurface(decoder); @@ -3987,7 +4036,7 @@ static void VaapiRenderFrame(VaapiDecoder * decoder, decoder->Interlaced = interlaced; decoder->TopFieldFirst = frame->top_field_first; - decoder->SurfaceField = 1; + decoder->SurfaceField = 0; } // update aspect ratio changes #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53,60,100) @@ -4171,74 +4220,45 @@ static void VaapiRenderFrame(VaapiDecoder * decoder, } /// -/// Advance displayed frame. +/// Advance displayed frame of decoder. /// -static void VaapiAdvanceFrame(void) +/// @param decoder VA-API hw decoder +/// +static void VaapiAdvanceDecoderFrame(VaapiDecoder * decoder) { - int i; - - // show any frame as fast as possible - // we keep always the last frame in the ring buffer - - for (i = 0; i < VaapiDecoderN; ++i) { - VaapiDecoder *decoder; + // next surface, if complete frame is displayed (1 -> 0) + if (decoder->SurfaceField) { VASurfaceID surface; int filled; - decoder = VaapiDecoders[i]; filled = atomic_read(&decoder->SurfacesFilled); - - // 0 -> 1 - // 1 -> 0 + advance - if (VideoDeinterlace[decoder->Resolution] < VideoDeinterlaceSoftBob - && decoder->Interlaced) { - // FIXME: first frame is never shown - if (decoder->SurfaceField) { - if (filled > 1) { - decoder->SurfaceField = 0; - } - } else { - decoder->SurfaceField = 1; - return; - } - } - - if (filled > 1) { - decoder->SurfaceRead = (decoder->SurfaceRead + 1) - % VIDEO_SURFACES_MAX; - atomic_dec(&decoder->SurfacesFilled); - - // wait for rendering finished - surface = decoder->SurfacesRb[decoder->SurfaceRead]; - if (vaSyncSurface(decoder->VaDisplay, surface) - != VA_STATUS_SUCCESS) { - Error(_("video/vaapi: vaSyncSurface failed\n")); - } - // debug duplicate frames - } else if (filled == 1) { - static int last_warned_frame; - + // FIXME: this should check the caller + // check decoder, if new surface is available + if (filled <= 1) { + // keep use of last surface ++decoder->FramesDuped; - decoder->DropNextFrame = 0; // FIXME: don't warn after stream start, don't warn during pause - if (last_warned_frame != decoder->FrameCounter) { - Warning(_ - ("video: display buffer empty, duping frame (%d/%d) %d\n"), - decoder->FramesDuped, decoder->FrameCounter, - VideoGetBuffers()); - } - last_warned_frame = decoder->FrameCounter; - if (!(decoder->FramesDisplayed % 300)) { - VaapiPrintFrames(decoder); - } - // wait for rendering finished - surface = decoder->SurfacesRb[decoder->SurfaceRead]; - if (vaSyncSurface(decoder->VaDisplay, surface) - != VA_STATUS_SUCCESS) { - Error(_("video/vaapi: vaSyncSurface failed\n")); - } + Error(_("video: display buffer empty, duping frame (%d/%d) %d\n"), + decoder->FramesDuped, decoder->FrameCounter, + VideoGetBuffers()); + return; } + // wait for rendering finished + surface = decoder->SurfacesRb[decoder->SurfaceRead]; + if (vaSyncSurface(decoder->VaDisplay, surface) != VA_STATUS_SUCCESS) { + Error(_("video/vaapi: vaSyncSurface failed\n")); + } + + decoder->SurfaceRead = (decoder->SurfaceRead + 1) % VIDEO_SURFACES_MAX; + atomic_dec(&decoder->SurfacesFilled); + + // progressiv oder software deinterlacer + decoder->SurfaceField = !decoder->Interlaced + || VideoDeinterlace[decoder->Resolution] + >= VideoDeinterlaceSoftBob; + return; } + decoder->SurfaceField = 1; } /// @@ -4273,6 +4293,7 @@ static void VaapiDisplayFrame(void) // no surface availble show black with possible osd if (!filled) { VaapiBlackSurface(decoder); + VaapiMessage(3, "video/vaapi: black surface displayed\n"); continue; } @@ -4306,9 +4327,11 @@ static void VaapiDisplayFrame(void) put2 = put1; } clock_gettime(CLOCK_REALTIME, &nowtime); + // FIXME: 31 only correct for 50Hz if ((nowtime.tv_sec - decoder->FrameTime.tv_sec) * 1000 * 1000 * 1000 + (nowtime.tv_nsec - - decoder->FrameTime.tv_nsec) > 30 * 1000 * 1000) { + decoder->FrameTime.tv_nsec) > 31 * 1000 * 1000) { + // FIXME: ignore still-frame, trick-speed Debug(3, "video/vaapi: time/frame too long %ldms\n", ((nowtime.tv_sec - decoder->FrameTime.tv_sec) * 1000 * 1000 * 1000 + (nowtime.tv_nsec - @@ -4333,11 +4356,62 @@ static void VaapiDisplayFrame(void) } /// -/// Sync and display surface. +/// Set VA-API decoder video clock. +/// +/// @param decoder VA-API hardware decoder +/// @param pts audio presentation timestamp +/// +void VaapiSetClock(VaapiDecoder * decoder, int64_t pts) +{ + decoder->PTS = pts; +} + +/// +/// Get VA-API decoder video clock. /// /// @param decoder VA-API decoder /// -static void VaapiSyncDisplayFrame(VaapiDecoder * decoder) +static int64_t VaapiGetClock(const VaapiDecoder * decoder) +{ + // pts is the timestamp of the latest decoded frame + if (decoder->PTS == (int64_t) AV_NOPTS_VALUE) { + return AV_NOPTS_VALUE; + } + // subtract buffered decoded frames + if (decoder->Interlaced) { + return decoder->PTS - + 20 * 90 * (2 * atomic_read(&decoder->SurfacesFilled) + - decoder->SurfaceField); + } + return decoder->PTS - 20 * 90 * (atomic_read(&decoder->SurfacesFilled) + + 2); +} + +/// +/// Set trick play speed. +/// +/// @param decoder VA-API decoder +/// @param speed trick speed (0 = normal) +/// +static void VaapiSetTrickSpeed(VaapiDecoder * decoder, int speed) +{ + decoder->TrickSpeed = speed; + decoder->TrickCounter = 0; +} + +/// +/// Sync decoder output to audio. +/// +/// trick-speed show frame times +/// still-picture show frame until new frame arrives +/// 60hz-mode repeat every 5th picture +/// video>audio slow down video by duplicating frames +/// video