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); |