我正在開發一個應用程序,需要將媒體流發佈到rtmp「攝取」網址(如YouTube Live中使用的,或作爲Wowza Streaming Engine的輸入等),我正在使用ffmpeg庫(以編程方式,從C/C++,而不是命令行工具)來處理rtmp圖層。我已經準備好了一個工作版本,但是在將更高帶寬的數據流傳輸到ping服務器較差的服務器時,我發現有些問題。當使用ffmpeg「本地」/內置rtmp實現和librtmp實現時,存在這個問題。使用ffmpeg進行流發佈rtmp:網絡帶寬未得到充分利用
當通過一個良好的網絡(特別是本地Wowza服務器)流式傳輸到本地目標服務器時,我的代碼到目前爲止已經處理了我所投射的每一個流,並設法實時上傳所有內容 - 這很重要,因爲這是專門用於直播的。
但是,當流式傳輸到具有較差ping的遠程服務器時(例如youtube在a.rtmp.youtube.com上發送URL,對於我來說有50 + ms ping),較低的帶寬流可以正常工作,但更高帶寬流的網絡未充分利用 - 例如,對於400kB/s的流,我只看到〜140kB/s的網絡使用率,並且很多幀會延遲/丟棄,這取決於我用來處理網絡的策略推回。
現在,我知道這不是與目標服務器的網絡連接問題,因爲我可以在使用ffmpeg命令行工具到同一目標服務器或使用我的代碼進行流式處理時實時成功上載流到當地的Wowza服務器,然後將該流轉發到YouTube的攝取點。
所以網絡連接不是問題,這個問題似乎與我的代碼一樣。
我計時我的代碼各部分,發現問題出現時,調用av_write_frame/av_interleaved_write_frame(我從來不混&配合他們,我總是在任何特定的構建使用一個版本的一致,這只是我已經試驗了兩者,看看是否有任何區別)有時需要很長時間 - 我看到這些電話有時需要長達500-1000毫秒,但平均「壞情況」在50-100毫秒範圍內。並非所有的通話都需要這麼長時間,大部分時間會立即返回,但這些通話所花的平均時間會比平均持續時間長,所以我沒有再實時上傳。
在我看來,主要的嫌疑人可能是rtmp確認窗口機制,在發送每個N字節之後,數據發送方在發送更多數據之前等待接收確認 - 這將解釋可用網絡帶寬沒有得到充分利用,因爲客戶端只需坐在那裏等待響應(由於較低的ping需要較長的時間),而不是使用可用帶寬。雖然我沒有看過ffmpeg的rtmp/librtmp代碼,看它是否實際實現了這種限制,所以它可能完全是其他的東西。
的應用程序的完整代碼太多,張貼在這裏,但這裏有一些重要的片段:
格式上下文創建:
const int nAVFormatContextCreateError = avformat_alloc_output_context2(&m_pAVFormatContext, nullptr, "flv", m_sOutputUrl.c_str());
流創建:
m_pVideoAVStream = avformat_new_stream(m_pAVFormatContext, nullptr);
m_pVideoAVStream->id = m_pAVFormatContext->nb_streams - 1;
m_pAudioAVStream = avformat_new_stream(m_pAVFormatContext, nullptr);
m_pAudioAVStream->id = m_pAVFormatContext->nb_streams - 1;
視頻流設置:
m_pVideoAVStream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
m_pVideoAVStream->codecpar->codec_id = AV_CODEC_ID_H264;
m_pVideoAVStream->codecpar->width = nWidth;
m_pVideoAVStream->codecpar->height = nHeight;
m_pVideoAVStream->codecpar->format = AV_PIX_FMT_YUV420P;
m_pVideoAVStream->codecpar->bit_rate = 10 * 1000 * 1000;
m_pVideoAVStream->time_base = AVRational { 1, 1000 };
m_pVideoAVStream->codecpar->extradata_size = int(nTotalSizeRequired);
m_pVideoAVStream->codecpar->extradata = (uint8_t*)av_malloc(m_pVideoAVStream->codecpar->extradata_size + AV_INPUT_BUFFER_PADDING_SIZE);
// Fill in the extradata here - I'm sure I'm doing that correctly.
音頻數據流設置:
m_pAudioAVStream->time_base = AVRational { 1, 1000 };
// Let's leave creation of m_pAudioCodecContext out of the scope of this question, I'm quite sure everything is done right there.
const int nAudioCodecCopyParamsError = avcodec_parameters_from_context(m_pAudioAVStream->codecpar, m_pAudioCodecContext);
打開連接:
const int nAVioOpenError = avio_open2(&m_pAVFormatContext->pb, m_sOutputUrl.c_str(), AVIO_FLAG_WRITE);
啓動流:
AVDictionary * pOptions = nullptr;
const int nWriteHeaderError = avformat_write_header(m_pAVFormatContext, &pOptions);
發送視頻幀:
AVPacket pkt = { 0 };
av_init_packet(&pkt);
pkt.dts = nTimestamp;
pkt.pts = nTimestamp;
pkt.duration = nDuration; // I know what I have the wrong duration sometimes, but I don't think that's the issue.
pkt.data = pFrameData;
pkt.size = pFrameDataSize;
pkt.flags = bKeyframe ? AV_PKT_FLAG_KEY : 0;
pkt.stream_index = m_pVideoAVStream->index;
const int nWriteFrameError = av_write_frame(m_pAVFormatContext, &pkt); // This is where too much time is spent.
森ding an audio frame:
AVPacket pkt = { 0 };
av_init_packet(&pkt);
pkt.pts = m_nTimestampMs;
pkt.dts = m_nTimestampMs;
pkt.duration = m_nDurationMs;
pkt.stream_index = m_pAudioAVStream->index;
const int nWriteFrameError = av_write_frame(m_pAVFormatContext, &pkt);
任何想法?我是否在正確的軌道上思考確認窗口?我在做其他事情完全錯誤嗎?