日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

librtmp读包阻塞问题修复

發布時間:2023/12/31 编程问答 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 librtmp读包阻塞问题修复 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

近期項目中有遇到播放器在使用librtmp播放rtmp碼流時,在弱網環境中,會出現讀包block很久的情況,甚至斷網會直接阻塞讀包線程,導致播放器無法退出造成ANR。librtmp目前社區已無人維護,所以無法通過升級第三方庫來試圖解決此問題,只能自己啃源碼了。

附上rtmp鏈接

https://github.com/ossrs/librtmp

閱讀librtmp源碼得知,讀包有2個地方會造成死鎖問題,這兩個API分別是:

int RTMP_Read(RTMP *r, char *buf, int size) int RTMP_ReadPacket(RTMP *r, RTMPPacket *packet)

首先看第一個:

RTMP_Read

????????Read_1_Packet

????????????????RTMP_GetNextMediaPacket

int RTMP_GetNextMediaPacket(RTMP *r, RTMPPacket *packet) {int bHasMediaPacket = 0;// 這里會死循環去讀包,并且讀不到數據也出不了循環while (!bHasMediaPacket && RTMP_IsConnected(r)&& RTMP_ReadPacket(r, packet)){if (!RTMPPacket_IsReady(packet) || !packet->m_nBodySize){continue;}bHasMediaPacket = RTMP_ClientPacket(r, packet);if (!bHasMediaPacket){RTMPPacket_Free(packet);}else if (r->m_pausing == 3){if (packet->m_nTimeStamp <= r->m_mediaStamp){bHasMediaPacket = 0; #ifdef _DEBUGRTMP_Log(RTMP_LOGDEBUG,"Skipped type: %02X, size: %d, TS: %d ms, abs TS: %d, pause: %d ms",packet->m_packetType, packet->m_nBodySize,packet->m_nTimeStamp, packet->m_hasAbsTimestamp,r->m_mediaStamp); #endifRTMPPacket_Free(packet);continue;}r->m_pausing = 0;}}if (bHasMediaPacket)r->m_bPlaying = TRUE;else if (r->m_sb.sb_timedout && !r->m_pausing)r->m_pauseStamp = r->m_mediaChannel < r->m_channelsAllocatedIn ?r->m_channelTimestamp[r->m_mediaChannel] : 0;return bHasMediaPacket; }

第二個則是RTMP_ReadPacket:

RTMP_ReadPacket

????????ReadN

????????????????RTMPSockBuf_Fill

int RTMPSockBuf_Fill(RTMPSockBuf *sb) {int nBytes;if (!sb->sb_size)sb->sb_start = sb->sb_buf;while (1){nBytes = sizeof(sb->sb_buf) - 1 - sb->sb_size - (sb->sb_start - sb->sb_buf); #if defined(CRYPTO) && !defined(NO_SSL)if (sb->sb_ssl){nBytes = TLS_read(sb->sb_ssl, sb->sb_start + sb->sb_size, nBytes);}else #endif{// 直接調用系統的recv方法風險很大nBytes = recv(sb->sb_socket, sb->sb_start + sb->sb_size, nBytes, 0);}if (nBytes != -1){sb->sb_size += nBytes;}else{int sockerr = GetSockError();RTMP_Log(RTMP_LOGDEBUG, "%s, recv returned %d. GetSockError(): %d (%s)",__FUNCTION__, nBytes, sockerr, strerror(sockerr));if (sockerr == EINTR && !RTMP_ctrlC)continue;if (sockerr == EWOULDBLOCK || sockerr == EAGAIN){sb->sb_timedout = TRUE;nBytes = 0;}}break;}return nBytes; }

在fill socket buffer的方法中會直接調用recv方法下載數據,這是風險極高的操作,弱網環境中會導致recv返回很慢,斷網狀態更是直接會卡在recv里無法返回。

解法方法其實不難,分兩步去做:

1、recv之前我們使用select或者poll去輪訓已經有數據的socket,若返回成功則在去recv即可。建議使用poll來做,因為select有fd的限制,若傳入select方法的socket fd大于當前系統的FD_SETSIZE則會直接導致crash,poll方法則沒有這個限制,或者使用epoll來做效率更高(如果涉及跨IOS開發則不可使用epoll,IOS系統暫時還不支持epoll方法)。

2、因為poll機制如果socket沒有數據則會返回0,通常外層會有一個while循環去一直poll,知道poll到數據返回,那如果弱網或者斷網一直poll不到數據還不是一樣會死循環?這個問題也好解決,我們看一下ffmpeg的做法就能得到答案:

在libavformat/network.c中已有答案:

// 可以看到這里只有3種情況 // 1.poll < 0出錯了,直接返回 // 2.ff_neterrno() : p.revents & (ev | POLLERR | POLLHUP)滿足,說明有數據可讀/寫,返回0,注意,這個方法進行了封裝,poll系統方法返回0是超時,這里返回0才是正常 // 3.其他情況都返回try again int ff_network_wait_fd(int fd, int write) {int ev = write ? POLLOUT : POLLIN;struct pollfd p = { .fd = fd, .events = ev, .revents = 0 };int ret;ret = poll(&p, 1, POLLING_TIME);return ret < 0 ? ff_neterrno() : p.revents & (ev | POLLERR | POLLHUP) ? 0 : AVERROR(EAGAIN); }int ff_network_wait_fd_timeout(int fd, int write, int64_t timeout, AVIOInterruptCB *int_cb) {int ret;int64_t wait_start = 0;// 循環去poll等待數據可讀之后退出whilewhile (1) {// 中斷機制,上層若有退出操作可以通過此中斷異步退出循環if (ff_check_interrupt(int_cb))return AVERROR_EXIT;// poll輪訓一直去等待socket有數據可讀ret = ff_network_wait_fd(fd, write);// 異常情況直接返回錯誤if (ret != AVERROR(EAGAIN))return ret;// 有個超時等待機制,如果太長時間都poll不到數據也要退出if (timeout > 0) {if (!wait_start)wait_start = av_gettime_relative();else if (av_gettime_relative() - wait_start > timeout)return AVERROR(ETIMEDOUT);}} }

所以,我們可以通過異步中斷函數來退出循環,附上我的解法:

rtmp.c int RTMPSockBuf_Fill(RTMPSockBuf *sb, RTMPIOInterruptCB *cb) {int nBytes;if (!sb->sb_size)sb->sb_start = sb->sb_buf;while (1){if (RTMP_Read_Check_Interrupt(cb)){RTMP_Log(RTMP_LOGINFO, "%s, poll data, user interrupt!", __FUNCTION__);break;}nBytes = sizeof(sb->sb_buf) - 1 - sb->sb_size - (sb->sb_start - sb->sb_buf); #if defined(CRYPTO) && !defined(NO_SSL)if (sb->sb_ssl){nBytes = TLS_read(sb->sb_ssl, sb->sb_start + sb->sb_size, nBytes);}else #endif{int ret = RTMP_Poll_Fd(sb->sb_socket);if (ret < 0)return ret;else if (ret == EAGAIN)continue;elsenBytes = recv(sb->sb_socket, sb->sb_start + sb->sb_size, nBytes, 0);}if (nBytes != -1){sb->sb_size += nBytes;}else{int sockerr = GetSockError();RTMP_Log(RTMP_LOGDEBUG, "%s, recv returned %d. GetSockError(): %d (%s)",__FUNCTION__, nBytes, sockerr, strerror(sockerr));if (sockerr == EINTR && !RTMP_ctrlC)continue;if (sockerr == EWOULDBLOCK || sockerr == EAGAIN){sb->sb_timedout = TRUE;nBytes = 0;}}break;}return nBytes; }int RTMP_Read_Check_Interrupt(RTMPIOInterruptCB *cb) {if (cb && cb->callback && cb->callback(cb->opaque))return 1;elsereturn 0; }int RTMP_Poll_Fd(int fd) {short ev = POLLIN;struct pollfd p = { .fd = fd, .events = ev, .revents = 0 };int ret;ret = poll(&p, 1, kPollingTimeMs);return ret < 0 ? ret : p.revents & (ev | POLLERR | POLLHUP) ? 0 : EAGAIN; }

頭文件:

typedef struct RTMP {RTMPIOInterruptCB m_interruptCallback; /* 讀包中斷函數,從調用librtmp的地方傳入 */ }/*** @brief 中斷結構** @param callback: callback方法* @param opaque: 上層傳入實例 */ typedef struct RTMPIOInterruptCB {int (*callback)(void*);void *opaque; } RTMPIOInterruptCB;/* 確認在讀包過程中,上層有沒有退出播放的操作 */ int RTMP_Read_Check_Interrupt(RTMPIOInterruptCB *cb); int RTMP_Poll_Fd(int fd);

注意:

RTMPIOInterruptCB中的callback需要從其他的線程通知才可以,通常的播放器架構,會有一個主線程去處理各種回調的信息還有播放器的各類事件(如start、stop等),可在把callback接到該主線程,如果該主線程接到stop后讓該callback返回true即可正常退出。

總結

以上是生活随笔為你收集整理的librtmp读包阻塞问题修复的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。