iOS推流一般是用aac+h264封装成flv后,推流rtmp协议
大致的流程图我简单画了下
在使用过程中发现,如果主播推流网络卡了M分钟(很常见),这时候编码后的数据如果不丢的话,还会把老的(编码后的)数据陆陆续续推上去(可能要N分钟才能推完),这时候播放器有丢包处理,X分钟后恢复正常,但是体验其实不算太好,因为推流端没丢包,本来不该推上去的老数据,还是推上去了,这样播放器即使丢包了,还是会看到老数据,会造成几个不好的结果:
- 播放器过了很久才恢复到实时。
- 无谓的带宽浪费了。
- 在那段恢复的时间内,延迟大。
所以,要丢包,编码出来的包,如果队列很大,说明推不上去,那这时候,应该丢掉一部分。
我的思路是: 根据包的队列或者buffer, 换算成时间单位s, 如果>3s, 就丢掉一半,或者丢的只剩3s。
根据pts为阀值来判断,丢掉某个pts之前的数据。
丢包的时候,视频包要注意一下,很老的I帧包可以丢,但是临近阀值的时候,就不能丢,丢到I帧前一帧。
如果不能按照pts丢,那就只能用size大小来丢帧。
这个策略和播放的丢包其实是一样的。
Size大小丢帧:
//当前帧的size追加到m_bufferSize
increaseBuffer(size);
//如果超过了最大缓冲buf,且又是视频I帧,置一下标志位
if(m_bufferSize > kMaxSendbufferSize && isKeyframe) {
m_clearing = true;
//Todo 记录丢帧次数,满足条件就重连
}
m_networkQueue.enqueue([=]() {
size_t tosend = size;
uint8_t* p ;
buf->read(&p, size);
//如果没到max_buf,则视频I/P帧,音频I帧都输出,如果超过max_buf,只写视频I帧
while(tosend > 0 && !this->m_ending && (!this->m_clearing || this->m_sentKeyframe == packetTime)) {
this->m_clearing = false;
size_t sent = m_streamSession->write(p, tosend);
p += sent;
tosend -= sent;
this->m_throughputSession.addSentBytesSample(sent);
if( sent == 0 ) {
dispatch_semaphore_wait(m_networkWaitSemaphore, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)));
}
}
this->increaseBuffer(-int64_t(size)); //m_bufferSize -= size
消息回调的函数中,把重连的消息抛上去
setClientState(kClientStateBlock);