blob: 1f35526de051a43490fd2343f31c448cae1a8d7f [file] [log] [blame]
/*
* Copyright (c) 2011 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "rtp_sender_h264.h"
#include "rtp_utility.h"
namespace webrtc {
RTPSenderH264::RTPSenderH264(RTPSenderInterface* rtpSender) :
// H264
_rtpSender(*rtpSender),
_h264Mode(H264_SINGLE_NAL_MODE),
_h264SendPPS_SPS(true),
_h264SVCPayloadType(-1),
_h264SVCRelaySequenceNumber(0),
_h264SVCRelayTimeStamp(0),
_h264SVCRelayLayerComplete(false),
_useHighestSendLayer(false),
_highestDependencyLayerOld(MAX_NUMBER_OF_TEMPORAL_ID-1),
_highestDependencyQualityIDOld(MAX_NUMBER_OF_DEPENDENCY_QUALITY_ID-1),
_highestDependencyLayer(0),
_highestDependencyQualityID(0),
_highestTemporalLayer(0)
{
}
RTPSenderH264::~RTPSenderH264()
{
}
WebRtc_Word32
RTPSenderH264::Init()
{
_h264SendPPS_SPS = true;
_h264Mode = H264_SINGLE_NAL_MODE;
return 0;
}
/*
multi-session
3 modes supported
NI-T timestamps
NI-TC timestamps/CS-DON
NI-C CS-DON
Non-interleaved timestamp based mode (NI-T)
Non-interleaved cross-session decoding order number (CS-DON) based mode (NI-C)
Non-interleaved combined timestamp and CS-DON mode (NI-TC)
NOT supported Interleaved CS-DON (I-C) mode.
NI-T and NI-TC modes both use timestamps to recover the decoding
order. In order to be able to do so, it is necessary for the RTP
packet stream to contain data for all sampling instances of a given
RTP session in all enhancement RTP sessions that depend on the given
RTP session. The NI-C and I-C modes do not have this limitation,
and use the CS-DON values as a means to explicitly indicate decoding
order, either directly coded in PACSI NAL units, or inferred from
them using the packetization rules. It is noted that the NI-TC mode
offers both alternatives and it is up to the receiver to select
which one to use.
*/
bool
RTPSenderH264::AddH264SVCNALUHeader(const H264_SVC_NALUHeader& svc,
WebRtc_UWord8* databuffer,
WebRtc_Word32& curByte) const
{
// +---------------+---------------+---------------+
// |0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7|
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |R|I| PRID |N| DID | QID | TID |U|D|O| RR|
// +---------------+---------------+---------------+
// R - Reserved for future extensions (MUST be 1). Receivers SHOULD ignore the value of R.
// I - Is layer representation an IDR layer (1) or not (0).
// PRID - Priority identifier for the NAL unit.
// N - Specifies whether inter-layer prediction may be used for decoding the coded slice (1) or not (0).
// DID - Indicates the WebRtc_Word32er-layer coding dependency level of a layer representation.
// QID - Indicates the quality level of an MGS layer representation.
// TID - Indicates the temporal level of a layer representation.
// U - Use only reference base pictures during the WebRtc_Word32er prediction process (1) or not (0).
// D - Discardable flag.
// O - Output_flag. Affects the decoded picture output process as defined in Annex C of [H.264].
// RR - Reserved_three_2bits (MUST be '11'). Receivers SHOULD ignore the value of RR.
// Add header data
databuffer[curByte++] = (svc.r << 7) + (svc.idr << 6) + (svc.priorityID & 0x3F);
databuffer[curByte++] = (svc.interLayerPred << 7) + (svc.dependencyID << 4) + (svc.qualityID & 0x0F);
databuffer[curByte++] = (svc.temporalID << 5) + (svc.useRefBasePic << 4) + (svc.discardable << 3) +
(svc.output << 2) + (svc.rr & 0x03);
return true;
}
WebRtc_Word32
RTPSenderH264::AddH264PACSINALU(const bool firstPacketInNALU,
const bool lastPacketInNALU,
const H264_PACSI_NALU& pacsi,
const H264_SVC_NALUHeader& svc,
const WebRtc_UWord16 DONC,
WebRtc_UWord8* databuffer,
WebRtc_Word32& curByte) const
{
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |F|NRI|Type(30) | SVC NAL unit header |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |X|Y|T|A|P|C|S|E| TL0PICIDX (o.)| IDRPICID (o.) |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | DONC (o.) | NAL unit size 1 |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | |
// | SEI NAL unit 1 |
// | |
// | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | | NAL unit size 2 | |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
// | |
// | SEI NAL unit 2 |
// | +-+-+-+-+-+-+-+-+-+-+
// | |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// If present, MUST be first NAL unit in aggregation packet + there MUST be at least
// one additional unit in the same packet! The RTPHeader and payload header are set as if the 2nd NAL unit
// (first non-PACSI NAL unit) is encapsulated in the same packet.
// contains scalability info common for all remaining NAL units.
// todo add API to configure this required for multisession
const bool addDONC = false;
if (svc.length == 0 || pacsi.NALlength == 0)
{
return 0;
}
WebRtc_Word32 startByte = curByte;
// NAL unit header
databuffer[curByte++] = 30; // NRI will be added later
// Extended SVC header
AddH264SVCNALUHeader(svc, databuffer, curByte);
// Flags
databuffer[curByte++] = (pacsi.X << 7) +
(pacsi.Y << 6) +
(addDONC << 5) +
(pacsi.A << 4) +
(pacsi.P << 3) +
(pacsi.C << 2) +
firstPacketInNALU?(pacsi.S << 1):0 +
lastPacketInNALU?(pacsi.E):0;
// Optional fields
if (pacsi.Y)
{
databuffer[curByte++] = pacsi.TL0picIDx;
databuffer[curByte++] = (WebRtc_UWord8)(pacsi.IDRpicID >> 8);
databuffer[curByte++] = (WebRtc_UWord8)(pacsi.IDRpicID);
}
// Decoding order number
if (addDONC) // pacsi.T
{
databuffer[curByte++] = (WebRtc_UWord8)(DONC >> 8);
databuffer[curByte++] = (WebRtc_UWord8)(DONC);
}
// SEI NALU
if(firstPacketInNALU) // IMPROVEMENT duplicate it to make sure it arrives...
{
// we only set this for NALU 0 to make sure we send it only once per frame
for (WebRtc_UWord32 i = 0; i < pacsi.numSEINALUs; i++)
{
// NALU size
databuffer[curByte++] = (WebRtc_UWord8)(pacsi.seiMessageLength[i] >> 8);
databuffer[curByte++] = (WebRtc_UWord8)(pacsi.seiMessageLength[i]);
// NALU data
memcpy(databuffer + curByte, pacsi.seiMessageData[i], pacsi.seiMessageLength[i]);
curByte += pacsi.seiMessageLength[i];
}
}
return curByte - startByte;
}
WebRtc_Word32
RTPSenderH264::SetH264RelaySequenceNumber(const WebRtc_UWord16 seqNum)
{
_h264SVCRelaySequenceNumber = seqNum;
return 0;
}
WebRtc_Word32
RTPSenderH264::SetH264RelayCompleteLayer(const bool complete)
{
_h264SVCRelayLayerComplete = complete;
return 0;
}
/*
12 Filler data
The only restriction of filler data NAL units within an
access unit is that they shall not precede the first VCL
NAL unit with the same access unit.
*/
WebRtc_Word32
RTPSenderH264::SendH264FillerData(const WebRtcRTPHeader* rtpHeader,
const WebRtc_UWord16 bytesToSend,
const WebRtc_UWord32 ssrc)
{
WebRtc_UWord16 fillerLength = bytesToSend - 12 - 1;
if (fillerLength > WEBRTC_IP_PACKET_SIZE - 12 - 1)
{
return 0;
}
if (fillerLength == 0)
{
// do not send an empty packet, will not reach JB
fillerLength = 1;
}
// send codec valid data, H.264 has defined data which is binary 1111111
WebRtc_UWord8 dataBuffer[WEBRTC_IP_PACKET_SIZE];
dataBuffer[0] = static_cast<WebRtc_UWord8>(0x80); // version 2
dataBuffer[1] = rtpHeader->header.payloadType;
ModuleRTPUtility::AssignUWord16ToBuffer(dataBuffer+2, _rtpSender.IncrementSequenceNumber()); // get the current SequenceNumber and add by 1 after returning
ModuleRTPUtility::AssignUWord32ToBuffer(dataBuffer+4, rtpHeader->header.timestamp);
ModuleRTPUtility::AssignUWord32ToBuffer(dataBuffer+8, rtpHeader->header.ssrc);
// set filler NALU type
dataBuffer[12] = 12; // NRI field = 0, type 12
// fill with 0xff
memset(dataBuffer + 12 + 1, 0xff, fillerLength);
return _rtpSender.SendToNetwork(dataBuffer,
fillerLength,
12 + 1);
}
WebRtc_Word32
RTPSenderH264::SendH264FillerData(const WebRtc_UWord32 captureTimestamp,
const WebRtc_UWord8 payloadType,
const WebRtc_UWord32 bytes
)
{
const WebRtc_UWord16 rtpHeaderLength = _rtpSender.RTPHeaderLength();
WebRtc_UWord16 maxLength = _rtpSender.MaxPayloadLength() - FECPacketOverhead() - _rtpSender.RTPHeaderLength();
WebRtc_Word32 bytesToSend=bytes;
WebRtc_UWord16 fillerLength=0;
WebRtc_UWord8 dataBuffer[WEBRTC_IP_PACKET_SIZE];
while(bytesToSend>0)
{
fillerLength=maxLength;
if(fillerLength<maxLength)
{
fillerLength = (WebRtc_UWord16) bytesToSend;
}
bytesToSend-=fillerLength;
if (fillerLength > WEBRTC_IP_PACKET_SIZE - 12 - 1)
{
return 0;
}
if (fillerLength == 0)
{
// do not send an empty packet, will not reach JB
fillerLength = 1;
}
// send paded data
// correct seq num, time stamp and payloadtype
_rtpSender.BuildRTPheader(dataBuffer, payloadType, false,captureTimestamp, true, true);
// set filler NALU type
dataBuffer[12] = 12; // NRI field = 0, type 12
// send codec valid data, H.264 has defined data which is binary 1111111
// fill with 0xff
memset(dataBuffer + 12 + 1, 0xff, fillerLength-1);
if( _rtpSender.SendToNetwork(dataBuffer,
fillerLength,
12)<0)
{
return -1;;
}
}
return 0;
}
WebRtc_Word32
RTPSenderH264::SendH264SVCRelayPacket(const WebRtcRTPHeader* rtpHeader,
const WebRtc_UWord8* incomingRTPPacket,
const WebRtc_UWord16 incomingRTPPacketSize,
const WebRtc_UWord32 ssrc,
const bool higestLayer)
{
if (rtpHeader->header.sequenceNumber != (WebRtc_UWord16)(_h264SVCRelaySequenceNumber + 1))
{
// not continous, signal loss
_rtpSender.IncrementSequenceNumber();
}
_h264SVCRelaySequenceNumber = rtpHeader->header.sequenceNumber;
if (rtpHeader->header.timestamp != _h264SVCRelayTimeStamp)
{
// new frame
_h264SVCRelayLayerComplete = false;
}
if (rtpHeader->header.timestamp == _h264SVCRelayTimeStamp &&
_h264SVCRelayLayerComplete)
{
// sanity, end of layer already sent
// Could happened for fragmented packet with missing PACSI info (PACSI packet reorded and received after packet it belongs to)
// fragmented packet has no layer info set (default info 0)
return 0;
}
_h264SVCRelayTimeStamp = rtpHeader->header.timestamp;
// re-packetize H.264-SVC packets
// we keep the timestap unchanged
// make a copy and only change the SSRC and seqNum
WebRtc_UWord8 dataBuffer[WEBRTC_IP_PACKET_SIZE];
memcpy(dataBuffer, incomingRTPPacket, incomingRTPPacketSize);
// _sequenceNumber initiated in Init()
// _ssrc initiated in constructor
// re-write payload type
if(_h264SVCPayloadType != -1)
{
dataBuffer[1] &= kRtpMarkerBitMask;
dataBuffer[1] += _h264SVCPayloadType;
}
// _sequenceNumber will not work for re-ordering by NACK from original sender
// engine responsible for this
ModuleRTPUtility::AssignUWord16ToBuffer(dataBuffer+2, _rtpSender.IncrementSequenceNumber()); // get the current SequenceNumber and add by 1 after returning
//ModuleRTPUtility::AssignUWord32ToBuffer(dataBuffer+8, ssrc);
// how do we know it's the last relayed packet in a frame?
// 1) packets arrive in order, the engine manages that
// 2) highest layer that we relay
// 3) the end bit is set for the highest layer
if(higestLayer && rtpHeader->type.Video.codecHeader.H264.relayE)
{
// set marker bit
dataBuffer[1] |= kRtpMarkerBitMask;
// set relayed layer as complete
_h264SVCRelayLayerComplete = true;
}
return _rtpSender.SendToNetwork(dataBuffer,
incomingRTPPacketSize - rtpHeader->header.headerLength,
rtpHeader->header.headerLength);
}
WebRtc_Word32
RTPSenderH264::SendH264_STAP_A(const FrameType frameType,
const H264Info* ptrH264Info,
WebRtc_UWord16 &idxNALU,
const WebRtc_Word8 payloadType,
const WebRtc_UWord32 captureTimeStamp,
bool& switchToFUA,
WebRtc_Word32 &payloadBytesToSend,
const WebRtc_UWord8*& data,
const WebRtc_UWord16 rtpHeaderLength)
{
const WebRtc_Word32 H264_NALU_LENGTH = 2;
WebRtc_UWord16 h264HeaderLength = 1; // normal header length
WebRtc_UWord16 maxPayloadLengthSTAP_A = _rtpSender.MaxPayloadLength() -
FECPacketOverhead() - rtpHeaderLength -
h264HeaderLength - H264_NALU_LENGTH;
WebRtc_Word32 dataOffset = rtpHeaderLength + h264HeaderLength;
WebRtc_UWord8 NRI = 0;
WebRtc_UWord16 payloadBytesInPacket = 0;
WebRtc_UWord8 dataBuffer[WEBRTC_IP_PACKET_SIZE];
if (ptrH264Info->payloadSize[idxNALU] > maxPayloadLengthSTAP_A)
{
// we need to fragment NAL switch to mode FU-A
switchToFUA = true;
} else
{
// combine as many NAL units in every IP packet
do
{
if(!_h264SendPPS_SPS)
{
// don't send NALU of type 7 and 8 SPS and PPS
if(ptrH264Info->type[idxNALU] == 7 || ptrH264Info->type[idxNALU] == 8)
{
payloadBytesToSend -= ptrH264Info->payloadSize[idxNALU] + ptrH264Info->startCodeSize[idxNALU];
data += ptrH264Info->payloadSize[idxNALU] + ptrH264Info->startCodeSize[idxNALU];
idxNALU++;
continue;
}
}
if(ptrH264Info->payloadSize[idxNALU] + payloadBytesInPacket <= maxPayloadLengthSTAP_A)
{
if(ptrH264Info->NRI[idxNALU] > NRI)
{
NRI = ptrH264Info->NRI[idxNALU];
}
// put NAL size into packet
dataBuffer[dataOffset] = (WebRtc_UWord8)(ptrH264Info->payloadSize[idxNALU] >> 8);
dataOffset++;
dataBuffer[dataOffset] = (WebRtc_UWord8)(ptrH264Info->payloadSize[idxNALU] & 0xff);
dataOffset++;
// Put payload in packet
memcpy(&dataBuffer[dataOffset], &data[ptrH264Info->startCodeSize[idxNALU]], ptrH264Info->payloadSize[idxNALU]);
dataOffset += ptrH264Info->payloadSize[idxNALU];
data += ptrH264Info->payloadSize[idxNALU] + ptrH264Info->startCodeSize[idxNALU];
payloadBytesInPacket += (WebRtc_UWord16)(ptrH264Info->payloadSize[idxNALU] + H264_NALU_LENGTH);
payloadBytesToSend -= ptrH264Info->payloadSize[idxNALU] + ptrH264Info->startCodeSize[idxNALU];
} else
{
// we don't fitt the next NALU in this packet
break;
}
idxNALU++;
}while(payloadBytesToSend);
}
// sanity
// don't send empty packets
if (payloadBytesInPacket)
{
// add RTP header
_rtpSender.BuildRTPheader(dataBuffer, payloadType, (payloadBytesToSend==0)?true:false, captureTimeStamp);
dataBuffer[rtpHeaderLength] = 24 + NRI; // STAP-A == 24
WebRtc_UWord16 payloadLength = payloadBytesInPacket + h264HeaderLength;
if(-1 == SendVideoPacket(frameType, dataBuffer, payloadLength, rtpHeaderLength))
{
return -1;
}
}
return 0;
} // end STAP-A
// STAP-A for H.264 SVC
WebRtc_Word32
RTPSenderH264::SendH264_STAP_A_PACSI(const FrameType frameType,
const H264Info* ptrH264Info,
WebRtc_UWord16 &idxNALU,
const WebRtc_Word8 payloadType,
const WebRtc_UWord32 captureTimeStamp,
bool& switchToFUA,
WebRtc_Word32 &payloadBytesToSend,
const WebRtc_UWord8*& data,
const WebRtc_UWord16 rtpHeaderLength,
WebRtc_UWord16& decodingOrderNumber)
{
const WebRtc_Word32 H264_NALU_LENGTH = 2;
WebRtc_UWord16 h264HeaderLength = 1; // normal header length
WebRtc_UWord16 maxPayloadLengthSTAP_A = _rtpSender.MaxPayloadLength() - FECPacketOverhead() - rtpHeaderLength - h264HeaderLength - H264_NALU_LENGTH;
WebRtc_Word32 dataOffset = rtpHeaderLength + h264HeaderLength;
WebRtc_UWord8 NRI = 0;
WebRtc_UWord16 payloadBytesInPacket = 0;
WebRtc_UWord8 dataBuffer[WEBRTC_IP_PACKET_SIZE];
bool firstNALUNotIDR = true; //delta
// Put PACSI NAL unit into packet
WebRtc_Word32 lengthPACSI = 0;
WebRtc_UWord32 PACSI_NALlength = ptrH264Info->PACSI[idxNALU].NALlength;
if (PACSI_NALlength > maxPayloadLengthSTAP_A)
{
return -1;
}
dataBuffer[dataOffset++] = (WebRtc_UWord8)(PACSI_NALlength >> 8);
dataBuffer[dataOffset++] = (WebRtc_UWord8)(PACSI_NALlength & 0xff);
// end bit will be updated later, since another NALU in this packet might be the last
WebRtc_Word32 lengthPASCINALU = AddH264PACSINALU(true,
false,
ptrH264Info->PACSI[idxNALU],
ptrH264Info->SVCheader[idxNALU],
decodingOrderNumber,
dataBuffer,
dataOffset);
if (lengthPASCINALU <= 0)
{
return -1;
}
decodingOrderNumber++;
lengthPACSI = H264_NALU_LENGTH + lengthPASCINALU;
maxPayloadLengthSTAP_A -= (WebRtc_UWord16)lengthPACSI;
if (ptrH264Info->payloadSize[idxNALU] > maxPayloadLengthSTAP_A)
{
// we need to fragment NAL switch to mode FU-A
switchToFUA = true;
return 0;
}
if(!ptrH264Info->SVCheader[idxNALU].idr)
{
firstNALUNotIDR = true;
}
WebRtc_UWord32 layer = (ptrH264Info->SVCheader[idxNALU].dependencyID << 16)+
(ptrH264Info->SVCheader[idxNALU].qualityID << 8) +
ptrH264Info->SVCheader[idxNALU].temporalID;
{
// combine as many NAL units in every IP packet, with the same priorityID
// Improvement we could allow several very small MGS NALU from different layers to be sent in one packet
do
{
if(!_h264SendPPS_SPS)
{
// Don't send NALU of type 7 and 8 SPS and PPS,
// they could be signaled outofband
if(ptrH264Info->type[idxNALU] == 7 || ptrH264Info->type[idxNALU] == 8)
{
payloadBytesToSend -= ptrH264Info->payloadSize[idxNALU] + ptrH264Info->startCodeSize[idxNALU];
data += ptrH264Info->payloadSize[idxNALU] + ptrH264Info->startCodeSize[idxNALU];
idxNALU++;
continue;
}
}
// don't send NALU type 6 (SEI message) not allowed when we send it in PACSI
if(ptrH264Info->type[idxNALU] == 6)
{
// SEI NALU Don't send, not allowed when we send it in PACSI
payloadBytesToSend -= ptrH264Info->payloadSize[idxNALU] + ptrH264Info->startCodeSize[idxNALU];
data += ptrH264Info->payloadSize[idxNALU] + ptrH264Info->startCodeSize[idxNALU];
idxNALU++;
continue;
}
const WebRtc_UWord32 layerNALU = (ptrH264Info->SVCheader[idxNALU].dependencyID << 16)+
(ptrH264Info->SVCheader[idxNALU].qualityID << 8) +
ptrH264Info->SVCheader[idxNALU].temporalID;
// we need to break on a new layer
if( ptrH264Info->payloadSize[idxNALU] + payloadBytesInPacket <= maxPayloadLengthSTAP_A &&
layerNALU == layer)
{
if(ptrH264Info->NRI[idxNALU] > NRI)
{
NRI = ptrH264Info->NRI[idxNALU];
}
// put NAL size into packet
dataBuffer[dataOffset] = (WebRtc_UWord8)(ptrH264Info->payloadSize[idxNALU] >> 8);
dataOffset++;
dataBuffer[dataOffset] = (WebRtc_UWord8)(ptrH264Info->payloadSize[idxNALU] & 0xff);
dataOffset++;
// Put payload in packet
memcpy(&dataBuffer[dataOffset], &data[ptrH264Info->startCodeSize[idxNALU]], ptrH264Info->payloadSize[idxNALU]);
dataOffset += ptrH264Info->payloadSize[idxNALU];
data += ptrH264Info->payloadSize[idxNALU] + ptrH264Info->startCodeSize[idxNALU];
payloadBytesInPacket += (WebRtc_UWord16)(ptrH264Info->payloadSize[idxNALU] + H264_NALU_LENGTH);
payloadBytesToSend -= ptrH264Info->payloadSize[idxNALU] + ptrH264Info->startCodeSize[idxNALU];
} else
{
// we don't fitt the next NALU in this packet or,
// it's the next layer
// check if we should send this NALU
// based on the layer
if(_useHighestSendLayer && layerNALU != layer)
{
// we don't send this NALU due to it's a new layer
// check if we should send the next or if this is the last
const WebRtc_UWord8 dependencyQualityID = (ptrH264Info->SVCheader[idxNALU].dependencyID << 4) + ptrH264Info->SVCheader[idxNALU].qualityID;
bool highestLayer;
if(SendH264SVCLayer(frameType,
ptrH264Info->SVCheader[idxNALU].temporalID,
dependencyQualityID,
highestLayer) == false)
{
// will trigger markerbit and stop sending this frame
payloadBytesToSend = 0;
}
}
break;
}
idxNALU++;
}while(payloadBytesToSend);
}
// sanity, don't send empty packets
if (payloadBytesInPacket)
{
// add RTP header
_rtpSender.BuildRTPheader(dataBuffer, payloadType, (payloadBytesToSend==0)?true:false, captureTimeStamp);
dataBuffer[rtpHeaderLength] = 24 + NRI; // STAP-A == 24
// NRI for PACSI
dataBuffer[rtpHeaderLength + H264_NALU_LENGTH + 1] &= 0x1f; // zero out NRI field
dataBuffer[rtpHeaderLength + H264_NALU_LENGTH + 1] |= NRI;
if(ptrH264Info->PACSI[idxNALU-1].E)
{
// update end bit
dataBuffer[rtpHeaderLength + H264_NALU_LENGTH + 5] |= 0x01;
}
if(firstNALUNotIDR)
{
// we have to check if any of the NALU in this packet is an IDR NALU
bool setIBit = false;
for(int i = 0; i < idxNALU; i++)
{
if(ptrH264Info->SVCheader[i].idr)
{
setIBit = true;
break;
}
}
if(setIBit)
{
// update I bit
dataBuffer[rtpHeaderLength + H264_NALU_LENGTH + 2] |= 0x40;
}
}
const WebRtc_UWord16 payloadLength = payloadBytesInPacket + h264HeaderLength + (WebRtc_UWord16)lengthPACSI;
if(-1 == SendVideoPacket(frameType,
dataBuffer,
payloadLength,
rtpHeaderLength,
layer==0))
{
return -1;
}
}
return 0;
} // end STAP-A
WebRtc_Word32
RTPSenderH264::SendH264_FU_A(const FrameType frameType,
const H264Info* ptrH264Info,
WebRtc_UWord16 &idxNALU,
const WebRtc_Word8 payloadType,
const WebRtc_UWord32 captureTimeStamp,
WebRtc_Word32 &payloadBytesToSend,
const WebRtc_UWord8*& data,
const WebRtc_UWord16 rtpHeaderLength,
WebRtc_UWord16& decodingOrderNumber,
const bool sendSVCPACSI)
{
// FUA for the rest of the frame
WebRtc_UWord16 maxPayloadLength = _rtpSender.MaxPayloadLength() - FECPacketOverhead() - rtpHeaderLength;
WebRtc_UWord8 dataBuffer[WEBRTC_IP_PACKET_SIZE];
WebRtc_UWord32 payloadBytesRemainingInNALU = ptrH264Info->payloadSize[idxNALU];
bool isBaseLayer=false;
if(payloadBytesRemainingInNALU > maxPayloadLength)
{
// we need to fragment NALU
const WebRtc_UWord16 H264_FUA_LENGTH = 2; // FU-a H.264 header is 2 bytes
if(sendSVCPACSI)
{
SendH264_SinglePACSI(frameType,
ptrH264Info,
idxNALU,
payloadType,
captureTimeStamp,
true,
false);
WebRtc_UWord32 layer = (ptrH264Info->SVCheader[idxNALU].dependencyID << 16)+
(ptrH264Info->SVCheader[idxNALU].qualityID << 8) +
ptrH264Info->SVCheader[idxNALU].temporalID;
isBaseLayer=(layer==0);
}
// First packet
_rtpSender.BuildRTPheader(dataBuffer,payloadType, false, captureTimeStamp);
WebRtc_UWord16 maxPayloadLengthFU_A = maxPayloadLength - H264_FUA_LENGTH ;
WebRtc_UWord8 fuaIndc = 28 + ptrH264Info->NRI[idxNALU];
dataBuffer[rtpHeaderLength] = fuaIndc; // FU-A indicator
dataBuffer[rtpHeaderLength+1] = (WebRtc_UWord8)(ptrH264Info->type[idxNALU] + 0x80)/*start*/; // FU-A header
memcpy(&dataBuffer[rtpHeaderLength + H264_FUA_LENGTH], &data[ptrH264Info->startCodeSize[idxNALU]+1], maxPayloadLengthFU_A);
WebRtc_UWord16 payloadLength = maxPayloadLengthFU_A + H264_FUA_LENGTH;
if(-1 == SendVideoPacket(frameType, dataBuffer, payloadLength, rtpHeaderLength, isBaseLayer))
{
return -1;
}
//+1 is from the type that is coded into the FU-a header
data += maxPayloadLengthFU_A + 1 + ptrH264Info->startCodeSize[idxNALU]; // inc data ptr
payloadBytesToSend -= maxPayloadLengthFU_A+1+ptrH264Info->startCodeSize[idxNALU];
payloadBytesRemainingInNALU -= maxPayloadLengthFU_A+1;
// all non first/last packets
while(payloadBytesRemainingInNALU > maxPayloadLengthFU_A)
{
if(sendSVCPACSI)
{
SendH264_SinglePACSI(frameType,
ptrH264Info,
idxNALU,
payloadType,
captureTimeStamp,
false,
false);
}
// prepare next header
_rtpSender.BuildRTPheader(dataBuffer, payloadType, false, captureTimeStamp);
dataBuffer[rtpHeaderLength] = (WebRtc_UWord8)fuaIndc; // FU-A indicator
dataBuffer[rtpHeaderLength+1] = ptrH264Info->type[idxNALU]; // FU-A header
memcpy(&dataBuffer[rtpHeaderLength+H264_FUA_LENGTH], data, maxPayloadLengthFU_A);
payloadLength = maxPayloadLengthFU_A + H264_FUA_LENGTH;
if(-1 == SendVideoPacket(frameType, dataBuffer, payloadLength, rtpHeaderLength,isBaseLayer))
{
return -1;
}
data += maxPayloadLengthFU_A; // inc data ptr
payloadBytesToSend -= maxPayloadLengthFU_A;
payloadBytesRemainingInNALU -= maxPayloadLengthFU_A;
dataBuffer[rtpHeaderLength] = fuaIndc; // FU-A indicator
dataBuffer[rtpHeaderLength+1] = ptrH264Info->type[idxNALU]; // FU-A header
}
if(sendSVCPACSI)
{
SendH264_SinglePACSI(frameType,
ptrH264Info,
idxNALU,
payloadType,
captureTimeStamp,
false,
true); // last packet in NALU
if(_useHighestSendLayer && idxNALU+1 < ptrH264Info->numNALUs)
{
// not last NALU in frame
// check if it's the the next layer should not be sent
// check if we should send the next or if this is the last
const WebRtc_UWord8 dependencyQualityID = (ptrH264Info->SVCheader[idxNALU+1].dependencyID << 4) +
ptrH264Info->SVCheader[idxNALU+1].qualityID;
bool highestLayer;
if(SendH264SVCLayer(frameType,
ptrH264Info->SVCheader[idxNALU+1].temporalID,
dependencyQualityID,
highestLayer) == false)
{
// will trigger markerbit and stop sending this frame
payloadBytesToSend = payloadBytesRemainingInNALU;
}
}
}
// last packet in NALU
_rtpSender.BuildRTPheader(dataBuffer, payloadType,(payloadBytesToSend == (WebRtc_Word32)payloadBytesRemainingInNALU)?true:false, captureTimeStamp);
dataBuffer[rtpHeaderLength+1] = ptrH264Info->type[idxNALU] + 0x40/*stop*/; // FU-A header
memcpy(&dataBuffer[rtpHeaderLength+H264_FUA_LENGTH], data, payloadBytesRemainingInNALU);
payloadLength = (WebRtc_UWord16)payloadBytesRemainingInNALU + H264_FUA_LENGTH;
payloadBytesToSend -= payloadBytesRemainingInNALU;
if(payloadBytesToSend != 0)
{
data += payloadBytesRemainingInNALU; // inc data ptr
}
idxNALU++;
if(-1 == SendVideoPacket(frameType, dataBuffer, payloadLength, rtpHeaderLength,isBaseLayer))
{
return -1;
}
} else
{
// send NAL unit in singel mode
return SendH264_SingleMode(frameType,
ptrH264Info,
idxNALU,
payloadType,
captureTimeStamp,
payloadBytesToSend,
data,
rtpHeaderLength,
sendSVCPACSI);
}
// end FU-a
return 0;
}
WebRtc_Word32
RTPSenderH264::SendH264_SingleMode(const FrameType frameType,
const H264Info* ptrH264Info,
WebRtc_UWord16 &idxNALU,
const WebRtc_Word8 payloadType,
const WebRtc_UWord32 captureTimeStamp,
WebRtc_Word32 &payloadBytesToSend,
const WebRtc_UWord8*& data,
const WebRtc_UWord16 rtpHeaderLength,
WebRtc_UWord16& decodingOrderNumber,
const bool sendSVCPACSI)
{
// no H.264 header lenght in single mode
// we use WEBRTC_IP_PACKET_SIZE instead of the configured MTU since it's better to send fragmented UDP than not to send
const WebRtc_UWord16 maxPayloadLength = WEBRTC_IP_PACKET_SIZE - _rtpSender.PacketOverHead() - FECPacketOverhead() - rtpHeaderLength;
WebRtc_UWord8 dataBuffer[WEBRTC_IP_PACKET_SIZE];
bool isBaseLayer=false;
if(ptrH264Info->payloadSize[idxNALU] > maxPayloadLength)
{
return -3;
}
if(!_h264SendPPS_SPS)
{
// don't send NALU of type 7 and 8 SPS and PPS
if(ptrH264Info->type[idxNALU] == 7 || ptrH264Info->type[idxNALU] == 8)
{
payloadBytesToSend -= ptrH264Info->payloadSize[idxNALU] + ptrH264Info->startCodeSize[idxNALU];
data += ptrH264Info->payloadSize[idxNALU] + ptrH264Info->startCodeSize[idxNALU];
idxNALU++;
return 0;
}
}
if(sendSVCPACSI)
{
SendH264_SinglePACSI(frameType,
ptrH264Info,
idxNALU,
payloadType,
captureTimeStamp,
true,
true);
WebRtc_UWord32 layer = (ptrH264Info->SVCheader[idxNALU].dependencyID << 16)+
(ptrH264Info->SVCheader[idxNALU].qualityID << 8) +
ptrH264Info->SVCheader[idxNALU].temporalID;
isBaseLayer=(layer==0);
}
// Put payload in packet
memcpy(&dataBuffer[rtpHeaderLength], &data[ptrH264Info->startCodeSize[idxNALU]], ptrH264Info->payloadSize[idxNALU]);
WebRtc_UWord16 payloadBytesInPacket = (WebRtc_UWord16)ptrH264Info->payloadSize[idxNALU];
payloadBytesToSend -= ptrH264Info->payloadSize[idxNALU] + ptrH264Info->startCodeSize[idxNALU]; // left to send
//
_rtpSender.BuildRTPheader(dataBuffer,payloadType,(payloadBytesToSend ==0)?true:false, captureTimeStamp);
dataBuffer[rtpHeaderLength] &= 0x1f; // zero out NRI field
dataBuffer[rtpHeaderLength] |= ptrH264Info->NRI[idxNALU]; // nri
if(payloadBytesToSend > 0)
{
data += ptrH264Info->payloadSize[idxNALU] + ptrH264Info->startCodeSize[idxNALU];
}
idxNALU++;
if(-1 == SendVideoPacket(frameType, dataBuffer, payloadBytesInPacket, rtpHeaderLength,isBaseLayer))
{
return -1;
}
return 0;
}
WebRtc_Word32
RTPSenderH264::SendH264_SinglePACSI(const FrameType frameType,
const H264Info* ptrH264Info,
const WebRtc_UWord16 idxNALU,
const WebRtc_Word8 payloadType,
const WebRtc_UWord32 captureTimeStamp,
const bool firstPacketInNALU,
const bool lastPacketInNALU);
{
// Send PACSI in single mode
WebRtc_UWord8 dataBuffer[WEBRTC_IP_PACKET_SIZE];
WebRtc_UWord16 rtpHeaderLength = (WebRtc_UWord16)_rtpSender.BuildRTPheader(dataBuffer, payloadType,false, captureTimeStamp);
WebRtc_Word32 dataOffset = rtpHeaderLength;
WebRtc_Word32 lengthPASCINALU = AddH264PACSINALU(firstPacketInNALU,
lastPacketInNALU,
ptrH264Info->PACSI[idxNALU],
ptrH264Info->SVCheader[idxNALU],
decodingOrderNumber,
dataBuffer,
dataOffset);
if (lengthPASCINALU <= 0)
{
return -1;
}
decodingOrderNumber++;
WebRtc_UWord16 payloadBytesInPacket = (WebRtc_UWord16)lengthPASCINALU;
// Set payload header (first payload byte co-serves as the payload header)
dataBuffer[rtpHeaderLength] &= 0x1f; // zero out NRI field
dataBuffer[rtpHeaderLength] |= ptrH264Info->NRI[idxNALU]; // nri
const WebRtc_UWord32 layer = (ptrH264Info->SVCheader[idxNALU].dependencyID << 16)+
(ptrH264Info->SVCheader[idxNALU].qualityID << 8) +
ptrH264Info->SVCheader[idxNALU].temporalID;
if (-1 == SendVideoPacket(frameType, dataBuffer, payloadBytesInPacket, rtpHeaderLength,layer==0))
{
return -1;
}
return 0;
}
WebRtc_Word32
RTPSenderH264::SendH264SVC(const FrameType frameType,
const WebRtc_Word8 payloadType,
const WebRtc_UWord32 captureTimeStamp,
const WebRtc_UWord8* payloadData,
const WebRtc_UWord32 payloadSize,
H264Information& h264Information,
WebRtc_UWord16& decodingOrderNumber)
{
WebRtc_Word32 payloadBytesToSend = payloadSize;
const WebRtc_UWord16 rtpHeaderLength = _rtpSender.RTPHeaderLength();
const H264Info* ptrH264Info = NULL;
if (h264Information.GetInfo(payloadData,payloadSize, ptrH264Info) == -1)
{
return -1;
}
if(_useHighestSendLayer)
{
// we need to check if we should drop the frame
// it could be a temporal layer (aka a temporal frame)
const WebRtc_UWord8 dependencyQualityID = (ptrH264Info->SVCheader[0].dependencyID << 4) + ptrH264Info->SVCheader[0].qualityID;
bool dummyHighestLayer;
if(SendH264SVCLayer(frameType,
ptrH264Info->SVCheader[0].temporalID,
dependencyQualityID,
dummyHighestLayer) == false)
{
// skip send this frame
return 0;
}
}
WebRtc_UWord16 idxNALU = 0;
while (payloadBytesToSend > 0)
{
bool switchToFUA = false;
if (SendH264_STAP_A_PACSI(frameType,
ptrH264Info,
idxNALU,
payloadType,
captureTimeStamp,
switchToFUA,
payloadBytesToSend,
payloadData,
rtpHeaderLength,
decodingOrderNumber) != 0)
{
return -1;
}
if(switchToFUA)
{
// FU_A for this NALU
if (SendH264_FU_A(frameType,
ptrH264Info,
idxNALU,
payloadType,
captureTimeStamp,
payloadBytesToSend,
payloadData,
rtpHeaderLength,
true) != 0)
{
return -1;
}
}
}
return 0;
}
WebRtc_Word32
RTPSenderH264::SetH264PacketizationMode(const H264PacketizationMode mode)
{
_h264Mode = mode;
return 0;
}
WebRtc_Word32
RTPSenderH264::SetH264SendModeNALU_PPS_SPS(const bool dontSend)
{
_h264SendPPS_SPS = !dontSend;
return 0;
}
bool
RTPSenderH264::SendH264SVCLayer(const FrameType frameType,
const WebRtc_UWord8 temporalID,
const WebRtc_UWord8 dependencyQualityID,
bool& higestLayer)
{
WebRtc_UWord8 dependencyID = dependencyQualityID >> 4;
// keyframe required to switch between dependency layers not quality and temporal
if( _highestDependencyLayer != _highestDependencyLayerOld)
{
// we want to switch dependency layer
if(frameType == kVideoFrameKey)
{
// key frame we can change layer if it's correct layer
if(_highestDependencyLayer > _highestDependencyLayerOld)
{
// we want to switch up
// does this packet belong to a new layer?
if( dependencyID > _highestDependencyLayerOld &&
dependencyID <= _highestDependencyLayer)
{
_highestDependencyLayerOld = dependencyID;
_highestDependencyQualityIDOld = _highestDependencyQualityID;
if( dependencyID == _highestDependencyLayer &&
dependencyQualityID == _highestDependencyQualityID)
{
higestLayer = true;
}
// relay
return true;
}
}
if(_highestDependencyLayer < _highestDependencyLayerOld)
{
// we want to switch down
// does this packet belong to a low layer?
if( dependencyID <= _highestDependencyLayer)
{
_highestDependencyLayerOld = dependencyID;
_highestDependencyQualityIDOld = _highestDependencyQualityID;
if( dependencyID == _highestDependencyLayer &&
dependencyQualityID == _highestDependencyQualityID)
{
higestLayer = true;
}
// relay
return true;
}
}
} else
{
// Delta frame and we are waiting to switch dependency layer
if(_highestDependencyLayer > _highestDependencyLayerOld)
{
// we want to switch up to a higher dependency layer
// use old setting until we get a key-frame
// filter based on old dependency
// we could have allowed to add a MGS layer lower than the dependency ID
// but then we can't know the highest layer relayed we assume that the user
// will add one layer at a time
if( _highestTemporalLayer < temporalID ||
_highestDependencyLayerOld < dependencyID ||
_highestDependencyQualityIDOld < dependencyQualityID)
{
// drop
return false;
}
// highest layer based on old
if( dependencyID == _highestDependencyLayerOld &&
dependencyQualityID == _highestDependencyQualityIDOld)
{
higestLayer = true;
}
} else
{
// we want to switch down to a lower dependency layer,
// use old setting, done bellow
// drop all temporal layers while waiting for the key-frame
if(temporalID > 0)
{
// drop
return false;
}
// we can't drop a lower MGS layer since this might depend on it
// however we can drop MGS layers larger than dependecyQualityId
// with dependency from old and quality 0
if( _highestDependencyLayerOld < dependencyID ||
(_highestDependencyQualityIDOld & 0xf0) < dependencyQualityID)
{
// drop
return false;
}
if( dependencyID == _highestDependencyLayerOld &&
dependencyQualityID == (_highestDependencyQualityIDOld & 0xf0))
{
higestLayer = true;
}
}
}
} else
{
// filter based on current state
if( _highestTemporalLayer < temporalID ||
_highestDependencyLayer < dependencyID ||
_highestDependencyQualityID < dependencyQualityID)
{
// drop
return false;
}
if( dependencyID == _highestDependencyLayer &&
dependencyQualityID == _highestDependencyQualityID)
{
higestLayer = true;
}
}
return true;
}
WebRtc_Word32
RTPSenderH264::SetHighestSendLayer(const WebRtc_UWord8 dependencyQualityLayer,
const WebRtc_UWord8 temporalLayer)
{
const WebRtc_UWord8 dependencyLayer = (dependencyQualityLayer >> 4);
if(_highestDependencyLayerOld != _highestDependencyLayer)
{
// we have not switched to the new dependency yet
} else
{
if(_highestDependencyLayer == dependencyLayer)
{
// no change of dependency
// switch now _highestDependencyQualityIDOld
_highestDependencyQualityIDOld = dependencyQualityLayer;
}else
{
// change of dependency, update _highestDependencyQualityIDOld store as old
_highestDependencyQualityIDOld = _highestDependencyQualityID;
}
}
_useHighestSendLayer = true;
_highestDependencyLayer = dependencyLayer;
_highestDependencyQualityID = dependencyQualityLayer;
_highestTemporalLayer = temporalLayer;
return 0;
}
WebRtc_Word32
RTPSenderH264::HighestSendLayer(WebRtc_UWord8& dependencyQualityLayer,
WebRtc_UWord8& temporalLayer)
{
if (!_useHighestSendLayer)
{
// No information set
return -1;
}
dependencyQualityLayer = _highestDependencyQualityID;
temporalLayer = _highestTemporalLayer;
return 0;
}
/*
* H.264
*/
WebRtc_Word32
RTPSenderH264::SendH264(const FrameType frameType,
const WebRtc_Word8 payloadType,
const WebRtc_UWord32 captureTimeStamp,
const WebRtc_UWord8* payloadData,
const WebRtc_UWord32 payloadSize,
H264Information& h264Information)
{
WebRtc_Word32 payloadBytesToSend = payloadSize;
const WebRtc_UWord8* data = payloadData;
bool switchToFUA = false;
const WebRtc_UWord16 rtpHeaderLength = _rtpSender.RTPHeaderLength();
const H264Info* ptrH264Info = NULL;
if (h264Information.GetInfo(payloadData,payloadSize, ptrH264Info) == -1)
{
return -1;
}
WebRtc_UWord16 idxNALU = 0;
WebRtc_UWord16 DONCdummy = 0;
while (payloadBytesToSend > 0)
{
switch(_h264Mode)
{
case H264_NON_INTERLEAVED_MODE:
if(!switchToFUA)
{
if(SendH264_STAP_A(frameType,
ptrH264Info,
idxNALU,
payloadType,
captureTimeStamp,
switchToFUA,
payloadBytesToSend,
data,
rtpHeaderLength) != 0)
{
return -1;
}
}
else
{
// FUA for the rest of the frame
if(SendH264_FU_A(frameType,
ptrH264Info,
idxNALU,
payloadType,
captureTimeStamp,
payloadBytesToSend,
data,
rtpHeaderLength,
DONCdummy) != 0)
{
return -1;
}
// try to go back to STAP_A
switchToFUA = false;
}
break;
case H264_SINGLE_NAL_MODE:
{
// modeSingleU
if(SendH264_SingleMode(frameType,
ptrH264Info,
idxNALU,
payloadType,
captureTimeStamp,
payloadBytesToSend,
data,
rtpHeaderLength,
DONCdummy) != 0)
{
return -1;
}
break;
}
case H264_INTERLEAVED_MODE:
// not supported
assert(false);
return -1;
}
}
return 0;
}
} // namespace webrtc