From 831de7855973c206fad7af9b297c4f2739a3f9ea Mon Sep 17 00:00:00 2001
From: Lawrence Rust <lvr@softsystem.co.uk>
Date: Thu, 2 Jun 2011 12:55:13 +0200
Subject: [PATCH 1/2] MythPlayer: Improve low bit rate / high latency stream playback
DVB-S radio programs are low bit rate (64Kbps..256Kbps) and suffer
occasional latency.
MHEG interaction streams are internet sourced and often suffer congestion
resulting in high latency for some packets.
- Fix a number of issues to do with changing program to DVB-S radio and
the lack of video stream causing garbage video display.
- Make FileRingBuffer::safe_read retry if reading from a remote file.
Low bit rate sources such as radio can have considerable latency.
- FileRingBuffer::OpenFile reduce the initial rawbitrate from 8 Mbps to
128 or 256 Kbps, otherwise considerable delay can be caused during
radio program startup.
- Increase the wait period in MythPlayer::OpenFile from 1 to 30 secs
while reading the probe buffer. This allows for typical http stream
startup latency.
Signed-off-by: Lawrence Rust <lvr@softsystem.co.uk>
---
mythtv/libs/libmythtv/avformatdecoder.cpp | 13 ++++
mythtv/libs/libmythtv/fileringbuffer.cpp | 48 +++++++++-----
mythtv/libs/libmythtv/mythplayer.cpp | 106 ++++++++++++++++++-----------
mythtv/libs/libmythtv/tv_play.cpp | 4 +-
4 files changed, 113 insertions(+), 58 deletions(-)
diff --git a/mythtv/libs/libmythtv/avformatdecoder.cpp b/mythtv/libs/libmythtv/avformatdecoder.cpp
index 89cc07b..0f6e4eb 100644
|
a
|
b
|
bool AvFormatDecoder::GetFrame(DecodeType decodetype)
|
| 4313 | 4313 | av_init_packet(pkt); |
| 4314 | 4314 | } |
| 4315 | 4315 | |
| | 4316 | // NB av_read_frame will block until |
| | 4317 | // either a frame is read or an error occurs |
| | 4318 | // so MythPlayer::DecoderLoop will be unable to pause or stop |
| 4316 | 4319 | int retval = 0; |
| 4317 | 4320 | if (!ic || ((retval = av_read_frame(ic, pkt)) < 0)) |
| 4318 | 4321 | { |
| … |
… |
bool AvFormatDecoder::GetFrame(DecodeType decodetype)
|
| 4422 | 4425 | case CODEC_TYPE_AUDIO: |
| 4423 | 4426 | { |
| 4424 | 4427 | if (!ProcessAudioPacket(curstream, pkt, decodetype)) |
| | 4428 | { |
| 4425 | 4429 | have_err = true; |
| | 4430 | if (!hasVideo) |
| | 4431 | { |
| | 4432 | LOG(VB_PLAYBACK, LOG_INFO, LOC + |
| | 4433 | "GetFrame: exiting due to audio decode error"); |
| | 4434 | av_free_packet(pkt); |
| | 4435 | delete pkt; |
| | 4436 | return false; |
| | 4437 | } |
| | 4438 | } |
| 4426 | 4439 | else |
| 4427 | 4440 | GenerateDummyVideoFrames(); |
| 4428 | 4441 | break; |
diff --git a/mythtv/libs/libmythtv/fileringbuffer.cpp b/mythtv/libs/libmythtv/fileringbuffer.cpp
index 944c98d..1449883 100644
|
a
|
b
|
bool FileRingBuffer::OpenFile(const QString &lfilename, uint retry_ms)
|
| 356 | 356 | commserror = false; |
| 357 | 357 | numfailures = 0; |
| 358 | 358 | |
| 359 | | rawbitrate = 8000; |
| | 359 | // The initial bitrate needs to be set with consideration for low bit rate |
| | 360 | // streams (e.g. radio @ 64Kbps) such that fill_min bytes are received |
| | 361 | // in a reasonable time period to enable decoders to peek the first few KB |
| | 362 | // to determine type & settings. |
| | 363 | if (is_local) |
| | 364 | rawbitrate = 256; // Allow for radio |
| | 365 | else |
| | 366 | rawbitrate = 128; // remotefile |
| | 367 | |
| 360 | 368 | CalcReadAheadThresh(); |
| 361 | 369 | |
| 362 | 370 | bool ok = fd2 >= 0 || remotefile; |
| … |
… |
int FileRingBuffer::safe_read(int fd, void *data, uint sz)
|
| 487 | 495 | */ |
| 488 | 496 | int FileRingBuffer::safe_read(RemoteFile *rf, void *data, uint sz) |
| 489 | 497 | { |
| 490 | | int ret = rf->Read(data, sz); |
| 491 | | if (ret < 0) |
| 492 | | { |
| 493 | | LOG(VB_GENERAL, LOG_ERR, LOC + |
| 494 | | "safe_read(RemoteFile* ...): read failed"); |
| 495 | | |
| 496 | | poslock.lockForRead(); |
| 497 | | rf->Seek(internalreadpos - readAdjust, SEEK_SET); |
| 498 | | poslock.unlock(); |
| 499 | | numfailures++; |
| 500 | | } |
| 501 | | else if (ret == 0) |
| | 498 | for (int retries = 0; ; ++retries) |
| 502 | 499 | { |
| 503 | | LOG(VB_FILE, LOG_INFO, LOC + |
| 504 | | "safe_read(RemoteFile* ...): at EOF"); |
| | 500 | int ret = rf->Read(data, sz); |
| | 501 | if (ret > 0) |
| | 502 | return ret; |
| | 503 | else if (ret < 0) |
| | 504 | { |
| | 505 | LOG(VB_GENERAL, LOG_ERR, LOC + |
| | 506 | "safe_read(RemoteFile* ...): read failed"); |
| | 507 | |
| | 508 | poslock.lockForRead(); |
| | 509 | rf->Seek(internalreadpos - readAdjust, SEEK_SET); |
| | 510 | poslock.unlock(); |
| | 511 | numfailures++; |
| | 512 | return ret; |
| | 513 | } |
| | 514 | // Retry for 300mS if liveTV for low bit rate (radio) streams |
| | 515 | else if (!livetvchain || retries >= 5) |
| | 516 | break; |
| | 517 | |
| | 518 | usleep(60000); |
| 505 | 519 | } |
| 506 | 520 | |
| 507 | | return ret; |
| | 521 | LOG(VB_FILE, LOG_INFO, LOC + |
| | 522 | "safe_read(RemoteFile* ...): at EOF"); |
| | 523 | return 0; |
| 508 | 524 | } |
| 509 | 525 | |
| 510 | 526 | long long FileRingBuffer::GetReadPosition(void) const |
diff --git a/mythtv/libs/libmythtv/mythplayer.cpp b/mythtv/libs/libmythtv/mythplayer.cpp
index 5e93d4f..59fa3b8 100644
|
a
|
b
|
bool MythPlayer::Pause(void)
|
| 363 | 363 | |
| 364 | 364 | bool MythPlayer::Play(float speed, bool normal, bool unpauseaudio) |
| 365 | 365 | { |
| 366 | | pauseLock.lock(); |
| | 366 | QMutexLocker locker(&pauseLock); |
| 367 | 367 | LOG(VB_PLAYBACK, LOG_INFO, LOC + |
| 368 | 368 | QString("Play(%1, normal %2, unpause audio %3)") |
| 369 | 369 | .arg(speed,5,'f',1).arg(normal).arg(unpauseaudio)); |
| … |
… |
bool MythPlayer::Play(float speed, bool normal, bool unpauseaudio)
|
| 371 | 371 | if (deleteMap.IsEditing()) |
| 372 | 372 | { |
| 373 | 373 | LOG(VB_GENERAL, LOG_ERR, LOC + "Ignoring Play(), in edit mode."); |
| 374 | | pauseLock.unlock(); |
| 375 | 374 | return false; |
| 376 | 375 | } |
| 377 | 376 | |
| … |
… |
bool MythPlayer::Play(float speed, bool normal, bool unpauseaudio)
|
| 383 | 382 | allpaused = false; |
| 384 | 383 | next_play_speed = speed; |
| 385 | 384 | next_normal_speed = normal; |
| 386 | | pauseLock.unlock(); |
| 387 | 385 | return true; |
| 388 | 386 | } |
| 389 | 387 | |
| … |
… |
int MythPlayer::OpenFile(uint retries)
|
| 902 | 900 | MythTimer peekTimer; peekTimer.start(); |
| 903 | 901 | while (player_ctx->buffer->Peek(testbuf, testreadsize) != testreadsize) |
| 904 | 902 | { |
| 905 | | if (peekTimer.elapsed() > 1000 || bigTimer.elapsed() > timeout) |
| | 903 | // NB need to allow for streams encountering network congestion |
| | 904 | if (peekTimer.elapsed() > 30000 || bigTimer.elapsed() > timeout) |
| 906 | 905 | { |
| 907 | 906 | LOG(VB_GENERAL, LOG_ERR, LOC + |
| 908 | 907 | QString("OpenFile(): Could not read first %1 bytes of '%2'") |
| … |
… |
int MythPlayer::OpenFile(uint retries)
|
| 912 | 911 | return -1; |
| 913 | 912 | } |
| 914 | 913 | LOG(VB_GENERAL, LOG_WARNING, LOC + "OpenFile() waiting on data"); |
| 915 | | usleep(50 * 1000); |
| | 914 | usleep(150 * 1000); |
| 916 | 915 | } |
| 917 | 916 | |
| 918 | 917 | player_ctx->LockPlayingInfo(__FILE__, __LINE__); |
| … |
… |
bool MythPlayer::PrebufferEnoughFrames(int min_buffers)
|
| 2065 | 2064 | // to recover from serious problems if frames get leaked. |
| 2066 | 2065 | DiscardVideoFrames(true); |
| 2067 | 2066 | } |
| 2068 | | if (waited_for > 20000) // 20 seconds |
| | 2067 | if (waited_for > 30000) // 30 seconds for internet streamed media |
| 2069 | 2068 | { |
| 2070 | 2069 | LOG(VB_GENERAL, LOG_ERR, LOC + |
| 2071 | 2070 | "Waited too long for decoder to fill video buffers. Exiting.."); |
| … |
… |
void MythPlayer::DisplayNormalFrame(bool check_prebuffer)
|
| 2109 | 2108 | SetBuffering(false); |
| 2110 | 2109 | |
| 2111 | 2110 | // retrieve the next frame |
| 2112 | | videoOutput->StartDisplayingFrame(); |
| | 2111 | bool const bDisplayFrame = videoOutput->ValidVideoFrames() > 0; |
| | 2112 | if (bDisplayFrame) |
| | 2113 | videoOutput->StartDisplayingFrame(); |
| | 2114 | |
| 2113 | 2115 | VideoFrame *frame = videoOutput->GetLastShownFrame(); |
| 2114 | 2116 | |
| 2115 | 2117 | // Check aspect ratio |
| … |
… |
void MythPlayer::DisplayNormalFrame(bool check_prebuffer)
|
| 2118 | 2120 | // Player specific processing (dvd, bd, mheg etc) |
| 2119 | 2121 | PreProcessNormalFrame(); |
| 2120 | 2122 | |
| 2121 | | // handle scan type changes |
| 2122 | | AutoDeint(frame); |
| 2123 | | detect_letter_box->SwitchTo(frame); |
| | 2123 | if (GetTrackCount(kTrackTypeVideo)) |
| | 2124 | { |
| | 2125 | // handle scan type changes |
| | 2126 | AutoDeint(frame); |
| | 2127 | detect_letter_box->SwitchTo(frame); |
| | 2128 | } |
| 2124 | 2129 | |
| 2125 | 2130 | FrameScanType ps = m_scan; |
| 2126 | 2131 | if (kScan_Detect == m_scan || kScan_Ignore == m_scan) |
| … |
… |
void MythPlayer::DisplayNormalFrame(bool check_prebuffer)
|
| 2133 | 2138 | osdLock.unlock(); |
| 2134 | 2139 | |
| 2135 | 2140 | AVSync(frame, 0); |
| 2136 | | videoOutput->DoneDisplayingFrame(frame); |
| | 2141 | if (bDisplayFrame) |
| | 2142 | videoOutput->DoneDisplayingFrame(frame); |
| 2137 | 2143 | } |
| 2138 | 2144 | |
| 2139 | 2145 | void MythPlayer::PreProcessNormalFrame(void) |
| … |
… |
void MythPlayer::PreProcessNormalFrame(void)
|
| 2142 | 2148 | // handle Interactive TV |
| 2143 | 2149 | if (GetInteractiveTV()) |
| 2144 | 2150 | { |
| 2145 | | osdLock.lock(); |
| 2146 | | itvLock.lock(); |
| | 2151 | QMutexLocker lk1(&osdLock); |
| | 2152 | |
| 2147 | 2153 | if (osd && videoOutput->GetOSDPainter()) |
| 2148 | 2154 | { |
| | 2155 | QMutexLocker lk2(&itvLock); |
| | 2156 | |
| 2149 | 2157 | InteractiveScreen *window = |
| 2150 | 2158 | (InteractiveScreen*)osd->GetWindow(OSD_WIN_INTERACT); |
| 2151 | 2159 | if ((interactiveTV->ImageHasChanged() || !itvVisible) && window) |
| … |
… |
void MythPlayer::PreProcessNormalFrame(void)
|
| 2153 | 2161 | interactiveTV->UpdateOSD(window, videoOutput->GetOSDPainter()); |
| 2154 | 2162 | itvVisible = true; |
| 2155 | 2163 | } |
| | 2164 | // Hide the iTV window if OSD is active otherwise OSD messages |
| | 2165 | // can be hidden |
| | 2166 | if (window && itvVisible && GetTrackCount(kTrackTypeVideo) == 0) |
| | 2167 | window->SetVisible(!osd->IsVisible()); |
| 2156 | 2168 | } |
| 2157 | | itvLock.unlock(); |
| 2158 | | osdLock.unlock(); |
| 2159 | 2169 | } |
| 2160 | 2170 | #endif // USING_MHEG |
| 2161 | 2171 | } |
| … |
… |
bool MythPlayer::VideoLoop(void)
|
| 2301 | 2311 | DisplayPauseFrame(); |
| 2302 | 2312 | } |
| 2303 | 2313 | else |
| 2304 | | DisplayNormalFrame(); |
| | 2314 | DisplayNormalFrame(GetTrackCount(kTrackTypeVideo)); |
| 2305 | 2315 | |
| 2306 | 2316 | if (FlagIsSet(kVideoIsNull) && decoder) |
| 2307 | 2317 | decoder->UpdateFramesPlayed(); |
| … |
… |
void MythPlayer::SwitchToProgram(void)
|
| 2401 | 2411 | int newid = -1; |
| 2402 | 2412 | ProgramInfo *pginfo = player_ctx->tvchain->GetSwitchProgram(d1, d2, newid); |
| 2403 | 2413 | if (!pginfo) |
| | 2414 | { |
| | 2415 | LOG(VB_GENERAL, LOG_ERR, LOC + "SwitchToProgram - No ProgramInfo"); |
| 2404 | 2416 | return; |
| | 2417 | } |
| 2405 | 2418 | |
| 2406 | 2419 | bool newIsDummy = player_ctx->tvchain->GetCardType(newid) == "DUMMY"; |
| 2407 | 2420 | |
| … |
… |
void MythPlayer::JumpToProgram(void)
|
| 2503 | 2516 | long long nextpos = player_ctx->tvchain->GetJumpPos(); |
| 2504 | 2517 | ProgramInfo *pginfo = player_ctx->tvchain->GetSwitchProgram(d1, d2, newid); |
| 2505 | 2518 | if (!pginfo) |
| | 2519 | { |
| | 2520 | LOG(VB_GENERAL, LOG_ERR, LOC + "JumpToProgram - No ProgramInfo"); |
| 2506 | 2521 | return; |
| | 2522 | } |
| 2507 | 2523 | |
| 2508 | 2524 | bool newIsDummy = player_ctx->tvchain->GetCardType(newid) == "DUMMY"; |
| 2509 | 2525 | SetPlayingInfo(*pginfo); |
| 2510 | 2526 | |
| 2511 | 2527 | Pause(); |
| 2512 | | ChangeSpeed(); |
| 2513 | 2528 | ResetCaptions(); |
| 2514 | 2529 | player_ctx->tvchain->SetProgram(*pginfo); |
| 2515 | 2530 | player_ctx->buffer->Reset(true); |
| … |
… |
void MythPlayer::EventLoop(void)
|
| 2703 | 2718 | player_ctx->tvchain->JumpToNext(true, 1); |
| 2704 | 2719 | JumpToProgram(); |
| 2705 | 2720 | } |
| 2706 | | else if ((!allpaused || GetEof()) && player_ctx->tvchain && |
| 2707 | | (decoder && !decoder->GetWaitForChange())) |
| | 2721 | else if ((!allpaused || GetEof()) && |
| | 2722 | decoder && !decoder->GetWaitForChange() && |
| | 2723 | player_ctx->tvchain && player_ctx->tvchain->NeedsToSwitch()) |
| 2708 | 2724 | { |
| 2709 | 2725 | // Switch to the next program in livetv |
| 2710 | | if (player_ctx->tvchain->NeedsToSwitch()) |
| 2711 | | SwitchToProgram(); |
| | 2726 | SwitchToProgram(); |
| 2712 | 2727 | } |
| 2713 | 2728 | |
| 2714 | 2729 | // Jump to the next program in livetv |
| … |
… |
void MythPlayer::AudioEnd(void)
|
| 2864 | 2879 | |
| 2865 | 2880 | bool MythPlayer::PauseDecoder(void) |
| 2866 | 2881 | { |
| 2867 | | decoderPauseLock.lock(); |
| | 2882 | QMutexLocker locker(&decoderPauseLock); |
| 2868 | 2883 | if (is_current_thread(decoderThread)) |
| 2869 | 2884 | { |
| | 2885 | pauseDecoder = false; |
| 2870 | 2886 | decoderPaused = true; |
| 2871 | 2887 | decoderThreadPause.wakeAll(); |
| 2872 | | decoderPauseLock.unlock(); |
| 2873 | | return decoderPaused; |
| | 2888 | return true; |
| 2874 | 2889 | } |
| 2875 | 2890 | |
| 2876 | | int tries = 0; |
| 2877 | 2891 | pauseDecoder = true; |
| 2878 | | while (decoderThread && !killdecoder && (tries++ < 100) && |
| 2879 | | !decoderThreadPause.wait(&decoderPauseLock, 100)) |
| | 2892 | int tries = 0; |
| | 2893 | while (!decoderPaused && decoderThread && !killdecoder && (tries++ < 10) && |
| | 2894 | !decoderThreadPause.wait(locker.mutex(), 100)) |
| 2880 | 2895 | { |
| 2881 | 2896 | LOG(VB_GENERAL, LOG_WARNING, LOC + "Waited 100ms for decoder to pause"); |
| 2882 | 2897 | } |
| 2883 | 2898 | pauseDecoder = false; |
| 2884 | | decoderPauseLock.unlock(); |
| 2885 | 2899 | return decoderPaused; |
| 2886 | | } |
| | 2900 | } |
| 2887 | 2901 | |
| 2888 | 2902 | void MythPlayer::UnpauseDecoder(void) |
| 2889 | 2903 | { |
| 2890 | | decoderPauseLock.lock(); |
| | 2904 | QMutexLocker locker(&decoderPauseLock); |
| 2891 | 2905 | |
| 2892 | 2906 | if (is_current_thread(decoderThread)) |
| 2893 | 2907 | { |
| | 2908 | unpauseDecoder = false; |
| 2894 | 2909 | decoderPaused = false; |
| 2895 | 2910 | decoderThreadUnpause.wakeAll(); |
| 2896 | | decoderPauseLock.unlock(); |
| 2897 | 2911 | return; |
| 2898 | 2912 | } |
| 2899 | 2913 | |
| 2900 | | int tries = 0; |
| 2901 | 2914 | unpauseDecoder = true; |
| 2902 | | while (decoderThread && !killdecoder && (tries++ < 100) && |
| 2903 | | !decoderThreadUnpause.wait(&decoderPauseLock, 100)) |
| | 2915 | int tries = 0; |
| | 2916 | while (decoderPaused && decoderThread && !killdecoder && (tries++ < 10) && |
| | 2917 | !decoderThreadUnpause.wait(locker.mutex(), 100)) |
| 2904 | 2918 | { |
| 2905 | 2919 | LOG(VB_GENERAL, LOG_WARNING, LOC + |
| 2906 | 2920 | "Waited 100ms for decoder to unpause"); |
| 2907 | 2921 | } |
| 2908 | 2922 | unpauseDecoder = false; |
| 2909 | | decoderPauseLock.unlock(); |
| 2910 | 2923 | } |
| 2911 | 2924 | |
| 2912 | 2925 | void MythPlayer::DecoderStart(bool start_paused) |
| … |
… |
void MythPlayer::DecoderEnd(void)
|
| 2932 | 2945 | SetPlaying(false); |
| 2933 | 2946 | killdecoder = true; |
| 2934 | 2947 | int tries = 0; |
| 2935 | | while (decoderThread && !decoderThread->wait(100) && (tries++ < 50)) |
| | 2948 | while (decoderThread && !decoderThread->wait(100) && (tries++ < 20)) |
| 2936 | 2949 | LOG(VB_PLAYBACK, LOG_INFO, LOC + |
| 2937 | 2950 | "Waited 100ms for decoder loop to stop"); |
| 2938 | 2951 | |
| … |
… |
void MythPlayer::DecoderEnd(void)
|
| 2944 | 2957 | |
| 2945 | 2958 | void MythPlayer::DecoderPauseCheck(void) |
| 2946 | 2959 | { |
| 2947 | | if (is_current_thread(decoderThread)) |
| | 2960 | if (!is_current_thread(decoderThread)) |
| | 2961 | return; |
| | 2962 | |
| | 2963 | QMutexLocker locker(&decoderPauseLock); |
| | 2964 | |
| | 2965 | if (pauseDecoder) |
| 2948 | 2966 | { |
| 2949 | | if (pauseDecoder) |
| 2950 | | PauseDecoder(); |
| 2951 | | if (unpauseDecoder) |
| 2952 | | UnpauseDecoder(); |
| | 2967 | pauseDecoder = false; |
| | 2968 | decoderPaused = true; |
| | 2969 | decoderThreadPause.wakeAll(); |
| | 2970 | } |
| | 2971 | |
| | 2972 | if (unpauseDecoder) |
| | 2973 | { |
| | 2974 | unpauseDecoder = false; |
| | 2975 | decoderPaused = false; |
| | 2976 | decoderThreadUnpause.wakeAll(); |
| 2953 | 2977 | } |
| 2954 | 2978 | } |
| 2955 | 2979 | |
diff --git a/mythtv/libs/libmythtv/tv_play.cpp b/mythtv/libs/libmythtv/tv_play.cpp
index 3bc2fec..c206ab9 100644
|
a
|
b
|
void TV::ChangeChannel(PlayerContext *ctx, uint chanid, const QString &chan)
|
| 7092 | 7092 | if (ctx->prevChan.empty()) |
| 7093 | 7093 | ctx->PushPreviousChannel(); |
| 7094 | 7094 | |
| 7095 | | PauseAudioUntilBuffered(ctx); |
| | 7095 | if (ctx->player) |
| | 7096 | ctx->player->GetAudio()->Pause(true); |
| 7096 | 7097 | PauseLiveTV(ctx); |
| 7097 | 7098 | |
| 7098 | 7099 | ctx->LockDeletePlayer(__FILE__, __LINE__); |
| … |
… |
void TV::ChangeChannel(PlayerContext *ctx, uint chanid, const QString &chan)
|
| 7109 | 7110 | ctx->player->GetAudio()->Reset(); |
| 7110 | 7111 | |
| 7111 | 7112 | UnpauseLiveTV(ctx, chanid && GetQueuedChanID()); |
| | 7113 | PauseAudioUntilBuffered(ctx); |
| 7112 | 7114 | |
| 7113 | 7115 | if (oldinputname != ctx->recorder->GetInput()) |
| 7114 | 7116 | UpdateOSDInput(ctx); |