blob: d62f50b4c08867d29692b1ec1a8f5fe8471933d9 [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()
{
}
int32_t
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,
uint8_t* databuffer,
int32_t& 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 int32_t:er-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 int32_t:er 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;
}
int32_t
RTPSenderH264::AddH264PACSINALU(const bool firstPacketInNALU,
const bool lastPacketInNALU,
const H264_PACSI_NALU& pacsi,
const H264_SVC_NALUHeader& svc,
const uint16_t DONC,
uint8_t* databuffer,
int32_t& 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;
}
int32_t 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++] = (uint8_t)(pacsi.IDRpicID >> 8);
databuffer[curByte++] = (uint8_t)(pacsi.IDRpicID);
}
// Decoding order number
if (addDONC) // pacsi.T
{
databuffer[curByte++] = (uint8_t)(DONC >> 8);
databuffer[curByte++] = (uint8_t)(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 (uint32_t i = 0; i < pacsi.numSEINALUs; i++)
{
// NALU size
databuffer[curByte++] = (uint8_t)(pacsi.seiMessageLength[i] >> 8);
databuffer[curByte++] = (uint8_t)(pacsi.seiMessageLength[i]);
// NALU data
memcpy(databuffer + curByte, pacsi.seiMessageData[i], pacsi.seiMessageLength[i]);
curByte += pacsi.seiMessageLength[i];
}
}
return curByte - startByte;
}
int32_t
RTPSenderH264::SetH264RelaySequenceNumber(const uint16_t seqNum)
{
_h264SVCRelaySequenceNumber = seqNum;
return 0;
}
int32_t
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.
*/
int32_t
RTPSenderH264::SendH264FillerData(const WebRtcRTPHeader* rtpHeader,
const uint16_t bytesToSend,
const uint32_t ssrc)
{
uint16_t 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
uint8_t dataBuffer[WEBRTC_IP_PACKET_SIZE];
dataBuffer[0] = static_cast<uint8_t>(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);
}
int32_t
RTPSenderH264::SendH264FillerData(const uint32_t captureTimestamp,
const uint8_t payloadType,
const uint32_t bytes
)
{
const uint16_t rtpHeaderLength = _rtpSender.RTPHeaderLength();
uint16_t maxLength = _rtpSender.MaxPayloadLength() - FECPacketOverhead() - _rtpSender.RTPHeaderLength();
int32_t bytesToSend=bytes;
uint16_t fillerLength=0;
uint8_t dataBuffer[WEBRTC_IP_PACKET_SIZE];
while(bytesToSend>0)
{
fillerLength=maxLength;
if(fillerLength<maxLength)
{
fillerLength = (uint16_t) 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;
}
int32_t
RTPSenderH264::SendH264SVCRelayPacket(const WebRtcRTPHeader* rtpHeader,
const uint8_t* incomingRTPPacket,
const uint16_t incomingRTPPacketSize,
const uint32_t ssrc,
const bool higestLayer)
{
if (rtpHeader->header.sequenceNumber != (uint16_t)(_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
uint8_t 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);
}
int32_t
RTPSenderH264::SendH264_STAP_A(const FrameType frameType,
const H264Info* ptrH264Info,
uint16_t &idxNALU,
const int8_t payloadType,
const uint32_t captureTimeStamp,
bool& switchToFUA,
int32_t &payloadBytesToSend,
const uint8_t*& data,
const uint16_t rtpHeaderLength)
{
const int32_t H264_NALU_LENGTH = 2;
uint16_t h264HeaderLength = 1; // normal header length
uint16_t maxPayloadLengthSTAP_A = _rtpSender.MaxPayloadLength() -
FECPacketOverhead() - rtpHeaderLength -
h264HeaderLength - H264_NALU_LENGTH;
int32_t dataOffset = rtpHeaderLength + h264HeaderLength;
uint8_t NRI = 0;
uint16_t payloadBytesInPacket = 0;
uint8_t 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] = (uint8_t)(ptrH264Info->payloadSize[idxNALU] >> 8);
dataOffset++;
dataBuffer[dataOffset] = (uint8_t)(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 += (uint16_t)(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
uint16_t payloadLength = payloadBytesInPacket + h264HeaderLength;
if(-1 == SendVideoPacket(frameType, dataBuffer, payloadLength, rtpHeaderLength))
{
return -1;
}
}
return 0;
} // end STAP-A
// STAP-A for H.264 SVC
int32_t
RTPSenderH264::SendH264_STAP_A_PACSI(const FrameType frameType,
const H264Info* ptrH264Info,
uint16_t &idxNALU,
const int8_t payloadType,
const uint32_t captureTimeStamp,
bool& switchToFUA,
int32_t &payloadBytesToSend,
const uint8_t*& data,
const uint16_t rtpHeaderLength,
uint16_t& decodingOrderNumber)
{
const int32_t H264_NALU_LENGTH = 2;
uint16_t h264HeaderLength = 1; // normal header length
uint16_t maxPayloadLengthSTAP_A = _rtpSender.MaxPayloadLength() - FECPacketOverhead() - rtpHeaderLength - h264HeaderLength - H264_NALU_LENGTH;
int32_t dataOffset = rtpHeaderLength + h264HeaderLength;
uint8_t NRI = 0;
uint16_t payloadBytesInPacket = 0;
uint8_t dataBuffer[WEBRTC_IP_PACKET_SIZE];
bool firstNALUNotIDR = true; //delta
// Put PACSI NAL unit into packet
int32_t lengthPACSI = 0;
uint32_t PACSI_NALlength = ptrH264Info->PACSI[idxNALU].NALlength;
if (PACSI_NALlength > maxPayloadLengthSTAP_A)
{
return -1;
}
dataBuffer[dataOffset++] = (uint8_t)(PACSI_NALlength >> 8);
dataBuffer[dataOffset++] = (uint8_t)(PACSI_NALlength & 0xff);
// end bit will be updated later, since another NALU in this packet might be the last
int32_t 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 -= (uint16_t)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;
}
uint32_t 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 uint32_t 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] = (uint8_t)(ptrH264Info->payloadSize[idxNALU] >> 8);
dataOffset++;
dataBuffer[dataOffset] = (uint8_t)(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 += (uint16_t)(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 uint8_t 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 uint16_t payloadLength = payloadBytesInPacket + h264HeaderLength + (uint16_t)lengthPACSI;
if(-1 == SendVideoPacket(frameType,
dataBuffer,
payloadLength,
rtpHeaderLength,
layer==0))
{
return -1;
}
}
return 0;
} // end STAP-A
int32_t
RTPSenderH264::SendH264_FU_A(const FrameType frameType,
const H264Info* ptrH264Info,
uint16_t &idxNALU,
const int8_t payloadType,
const uint32_t captureTimeStamp,
int32_t &payloadBytesToSend,
const uint8_t*& data,
const uint16_t rtpHeaderLength,
uint16_t& decodingOrderNumber,
const bool sendSVCPACSI)
{
// FUA for the rest of the frame
uint16_t maxPayloadLength = _rtpSender.MaxPayloadLength() - FECPacketOverhead() - rtpHeaderLength;
uint8_t dataBuffer[WEBRTC_IP_PACKET_SIZE];
uint32_t payloadBytesRemainingInNALU = ptrH264Info->payloadSize[idxNALU];
bool isBaseLayer=false;
if(payloadBytesRemainingInNALU > maxPayloadLength)
{
// we need to fragment NALU
const uint16_t H264_FUA_LENGTH = 2; // FU-a H.264 header is 2 bytes
if(sendSVCPACSI)
{
SendH264_SinglePACSI(frameType,
ptrH264Info,
idxNALU,
payloadType,
captureTimeStamp,
true,
false);
uint32_t 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);
uint16_t maxPayloadLengthFU_A = maxPayloadLength - H264_FUA_LENGTH ;
uint8_t fuaIndc = 28 + ptrH264Info->NRI[idxNALU];
dataBuffer[rtpHeaderLength] = fuaIndc; // FU-A indicator
dataBuffer[rtpHeaderLength+1] = (uint8_t)(ptrH264Info->type[idxNALU] + 0x80)/*start*/; // FU-A header
memcpy(&dataBuffer[rtpHeaderLength + H264_FUA_LENGTH], &data[ptrH264Info->startCodeSize[idxNALU]+1], maxPayloadLengthFU_A);
uint16_t 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] = (uint8_t)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 uint8_t 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 == (int32_t)payloadBytesRemainingInNALU)?true:false, captureTimeStamp);
dataBuffer[rtpHeaderLength+1] = ptrH264Info->type[idxNALU] + 0x40/*stop*/; // FU-A header
memcpy(&dataBuffer[rtpHeaderLength+H264_FUA_LENGTH], data, payloadBytesRemainingInNALU);
payloadLength = (uint16_t)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;
}
int32_t
RTPSenderH264::SendH264_SingleMode(const FrameType frameType,
const H264Info* ptrH264Info,
uint16_t &idxNALU,
const int8_t payloadType,
const uint32_t captureTimeStamp,
int32_t &payloadBytesToSend,
const uint8_t*& data,
const uint16_t rtpHeaderLength,
uint16_t& 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 uint16_t maxPayloadLength = WEBRTC_IP_PACKET_SIZE - _rtpSender.PacketOverHead() - FECPacketOverhead() - rtpHeaderLength;
uint8_t 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);
uint32_t 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]);
uint16_t payloadBytesInPacket = (uint16_t)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;
}
int32_t
RTPSenderH264::SendH264_SinglePACSI(const FrameType frameType,
const H264Info* ptrH264Info,
const uint16_t idxNALU,
const int8_t payloadType,
const uint32_t captureTimeStamp,
const bool firstPacketInNALU,
const bool lastPacketInNALU);
{
// Send PACSI in single mode
uint8_t dataBuffer[WEBRTC_IP_PACKET_SIZE];
uint16_t rtpHeaderLength = (uint16_t)_rtpSender.BuildRTPheader(dataBuffer, payloadType,false, captureTimeStamp);
int32_t dataOffset = rtpHeaderLength;
int32_t lengthPASCINALU = AddH264PACSINALU(firstPacketInNALU,
lastPacketInNALU,
ptrH264Info->PACSI[idxNALU],
ptrH264Info->SVCheader[idxNALU],
decodingOrderNumber,
dataBuffer,
dataOffset);
if (lengthPASCINALU <= 0)
{
return -1;
}
decodingOrderNumber++;
uint16_t payloadBytesInPacket = (uint16_t)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 uint32_t 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;
}
int32_t
RTPSenderH264::SendH264SVC(const FrameType frameType,
const int8_t payloadType,
const uint32_t captureTimeStamp,
const uint8_t* payloadData,
const uint32_t payloadSize,
H264Information& h264Information,
uint16_t& decodingOrderNumber)
{
int32_t payloadBytesToSend = payloadSize;
const uint16_t 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 uint8_t 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;
}
}
uint16_t 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;
}
int32_t
RTPSenderH264::SetH264PacketizationMode(const H264PacketizationMode mode)
{
_h264Mode = mode;
return 0;
}
int32_t
RTPSenderH264::SetH264SendModeNALU_PPS_SPS(const bool dontSend)
{
_h264SendPPS_SPS = !dontSend;
return 0;
}
bool
RTPSenderH264::SendH264SVCLayer(const FrameType frameType,
const uint8_t temporalID,
const uint8_t dependencyQualityID,
bool& higestLayer)
{
uint8_t 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;
}
int32_t
RTPSenderH264::SetHighestSendLayer(const uint8_t dependencyQualityLayer,
const uint8_t temporalLayer)
{
const uint8_t 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;
}
int32_t
RTPSenderH264::HighestSendLayer(uint8_t& dependencyQualityLayer,
uint8_t& temporalLayer)
{
if (!_useHighestSendLayer)
{
// No information set
return -1;
}
dependencyQualityLayer = _highestDependencyQualityID;
temporalLayer = _highestTemporalLayer;
return 0;
}
/*
* H.264
*/
int32_t
RTPSenderH264::SendH264(const FrameType frameType,
const int8_t payloadType,
const uint32_t captureTimeStamp,
const uint8_t* payloadData,
const uint32_t payloadSize,
H264Information& h264Information)
{
int32_t payloadBytesToSend = payloadSize;
const uint8_t* data = payloadData;
bool switchToFUA = false;
const uint16_t rtpHeaderLength = _rtpSender.RTPHeaderLength();
const H264Info* ptrH264Info = NULL;
if (h264Information.GetInfo(payloadData,payloadSize, ptrH264Info) == -1)
{
return -1;
}
uint16_t idxNALU = 0;
uint16_t 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