diff --git a/samples/Samples.h b/samples/Samples.h index e409a9b5dd..5c79428ad7 100644 --- a/samples/Samples.h +++ b/samples/Samples.h @@ -15,6 +15,7 @@ extern "C" { #define NUMBER_OF_H264_FRAME_FILES 1500 #define NUMBER_OF_H265_FRAME_FILES 1500 #define NUMBER_OF_OPUS_FRAME_FILES 618 +#define NUMBER_OF_AAC_FRAME_FILES 206 #define DEFAULT_FPS_VALUE 25 #define DEFAULT_VIDEO_HEIGHT_PIXELS 720 #define DEFAULT_VIDEO_WIDTH_PIXELS 1280 @@ -41,9 +42,10 @@ extern "C" { #define DEFAULT_AUDIO_OPUS_BYTE_RATE (DEFAULT_AUDIO_OPUS_SAMPLE_RATE_HZ * DEFAULT_AUDIO_OPUS_CHANNELS * DEFAULT_AUDIO_OPUS_BITS_PER_SAMPLE) / 8 #define DEFAULT_AUDIO_AAC_BYTE_RATE (DEFAULT_AUDIO_AAC_SAMPLE_RATE_HZ * DEFAULT_AUDIO_AAC_CHANNELS * DEFAULT_AUDIO_AAC_BITS_PER_SAMPLE) / 8 -#define SAMPLE_AUDIO_FRAME_DURATION (20 * HUNDREDS_OF_NANOS_IN_A_MILLISECOND) -#define SAMPLE_STATS_DURATION (60 * HUNDREDS_OF_NANOS_IN_A_SECOND) -#define SAMPLE_VIDEO_FRAME_DURATION (HUNDREDS_OF_NANOS_IN_A_SECOND / DEFAULT_FPS_VALUE) +#define SAMPLE_AUDIO_FRAME_DURATION (20 * HUNDREDS_OF_NANOS_IN_A_MILLISECOND) +#define SAMPLE_AUDIO_AAC_FRAME_DURATION (64 * HUNDREDS_OF_NANOS_IN_A_MILLISECOND) +#define SAMPLE_STATS_DURATION (60 * HUNDREDS_OF_NANOS_IN_A_SECOND) +#define SAMPLE_VIDEO_FRAME_DURATION (HUNDREDS_OF_NANOS_IN_A_SECOND / DEFAULT_FPS_VALUE) #define SAMPLE_PRE_GENERATE_CERT TRUE #define SAMPLE_PRE_GENERATE_CERT_PERIOD (1000 * HUNDREDS_OF_NANOS_IN_A_MILLISECOND) diff --git a/src/source/Include_i.h b/src/source/Include_i.h index 2b9e2bfc28..dafd11d126 100644 --- a/src/source/Include_i.h +++ b/src/source/Include_i.h @@ -161,6 +161,7 @@ STATUS generateJSONSafeString(PCHAR, UINT32); #include "Rtp/Codecs/RtpH264Payloader.h" #include "Rtp/Codecs/RtpH265Payloader.h" #include "Rtp/Codecs/RtpOpusPayloader.h" +#include "Rtp/Codecs/RtpAacPayloader.h" #include "Rtp/Codecs/RtpG711Payloader.h" #include "Metrics/Metrics.h" diff --git a/src/source/PeerConnection/PeerConnection.c b/src/source/PeerConnection/PeerConnection.c index 965eaac757..8d0dee7ced 100644 --- a/src/source/PeerConnection/PeerConnection.c +++ b/src/source/PeerConnection/PeerConnection.c @@ -1548,6 +1548,11 @@ STATUS addTransceiver(PRtcPeerConnection pPeerConnection, PRtcMediaStreamTrack p clockRate = OPUS_CLOCKRATE; break; + case RTC_CODEC_AAC: + depayFunc = depayAacFromRtpPayload; + clockRate = AAC_CLOCKRATE; + break; + case RTC_CODEC_MULAW: case RTC_CODEC_ALAW: depayFunc = depayG711FromRtpPayload; @@ -1624,6 +1629,8 @@ STATUS addSupportedCodec(PRtcPeerConnection pPeerConnection, RTC_CODEC rtcCodec) CHK_STATUS(hashTablePut(pKvsPeerConnection->pCodecTable, rtcCodec, DEFAULT_PAYLOAD_MULAW)); } else if (rtcCodec == RTC_CODEC_ALAW) { CHK_STATUS(hashTablePut(pKvsPeerConnection->pCodecTable, rtcCodec, DEFAULT_PAYLOAD_ALAW)); + } else if (rtcCodec == RTC_CODEC_AAC) { + CHK_STATUS(hashTablePut(pKvsPeerConnection->pCodecTable, rtcCodec, DEFAULT_PAYLOAD_AAC)); } else { CHK_STATUS(hashTablePut(pKvsPeerConnection->pCodecTable, rtcCodec, 0)); } diff --git a/src/source/PeerConnection/Rtp.c b/src/source/PeerConnection/Rtp.c index a78e116688..d316fa0b44 100644 --- a/src/source/PeerConnection/Rtp.c +++ b/src/source/PeerConnection/Rtp.c @@ -267,6 +267,11 @@ STATUS writeFrame(PRtcRtpTransceiver pRtcRtpTransceiver, PFrame pFrame) rtpTimestamp = CONVERT_TIMESTAMP_TO_RTP(OPUS_CLOCKRATE, pFrame->presentationTs); break; + case RTC_CODEC_AAC: + rtpPayloadFunc = createPayloadForAac; + rtpTimestamp = CONVERT_TIMESTAMP_TO_RTP(AAC_CLOCKRATE, pFrame->presentationTs); + break; + case RTC_CODEC_MULAW: case RTC_CODEC_ALAW: rtpPayloadFunc = createPayloadForG711; diff --git a/src/source/PeerConnection/SessionDescription.c b/src/source/PeerConnection/SessionDescription.c index 215bdb6a96..85ed1a7753 100644 --- a/src/source/PeerConnection/SessionDescription.c +++ b/src/source/PeerConnection/SessionDescription.c @@ -137,6 +137,7 @@ STATUS setPayloadTypesForOffer(PHashTable codecTable) CHK_STATUS(hashTableUpsert(codecTable, RTC_CODEC_ALAW, DEFAULT_PAYLOAD_ALAW)); CHK_STATUS(hashTableUpsert(codecTable, RTC_CODEC_VP8, DEFAULT_PAYLOAD_VP8)); CHK_STATUS(hashTableUpsert(codecTable, RTC_CODEC_OPUS, DEFAULT_PAYLOAD_OPUS)); + CHK_STATUS(hashTableUpsert(codecTable, RTC_CODEC_AAC, DEFAULT_PAYLOAD_AAC)); CHK_STATUS(hashTableUpsert(codecTable, RTC_CODEC_H264_PROFILE_42E01F_LEVEL_ASYMMETRY_ALLOWED_PACKETIZATION_MODE, DEFAULT_PAYLOAD_H264)); CHK_STATUS(hashTableUpsert(codecTable, RTC_CODEC_H265, DEFAULT_PAYLOAD_H265)); @@ -215,6 +216,12 @@ STATUS setPayloadTypesFromOffer(PHashTable codecTable, PHashTable rtxTable, PSes CHK_STATUS(hashTableUpsert(codecTable, RTC_CODEC_OPUS, parsedPayloadType)); } + CHK_STATUS(hashTableContains(codecTable, RTC_CODEC_AAC, &supportCodec)); + if (supportCodec && (end = STRSTR(attributeValue, AAC_VALUE)) != NULL) { + CHK_STATUS(STRTOUI64(attributeValue, end - 1, 10, &parsedPayloadType)); + CHK_STATUS(hashTableUpsert(codecTable, RTC_CODEC_AAC, parsedPayloadType)); + } + CHK_STATUS(hashTableContains(codecTable, RTC_CODEC_VP8, &supportCodec)); if (supportCodec && (end = STRSTR(attributeValue, VP8_VALUE)) != NULL) { CHK_STATUS(STRTOUI64(attributeValue, end - 1, 10, &parsedPayloadType)); @@ -715,6 +722,12 @@ STATUS populateSingleMediaSection(PKvsPeerConnection pKvsPeerConnection, PKvsRtp CHK_ERR(amountWritten > 0, STATUS_INTERNAL_ERROR, "Full Opus fmtp could not be written"); attributeCount++; } + } else if (pRtcMediaStreamTrack->codec == RTC_CODEC_AAC) { + STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeName, "rtpmap"); + amountWritten = SNPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, + SIZEOF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue), "%" PRId64 " AAC/16000", payloadType); + CHK_ERR(amountWritten > 0, STATUS_INTERNAL_ERROR, "Full Aac rtpmap could not be written"); + attributeCount++; } else if (pRtcMediaStreamTrack->codec == RTC_CODEC_VP8) { STRCPY(pSdpMediaDescription->sdpAttributes[attributeCount].attributeName, "rtpmap"); amountWritten = SNPRINTF(pSdpMediaDescription->sdpAttributes[attributeCount].attributeValue, @@ -1272,6 +1285,9 @@ STATUS findTransceiversByRemoteDescription(PKvsPeerConnection pKvsPeerConnection } else if (STRSTR(attributeValue, OPUS_VALUE) != NULL) { supportCodec = TRUE; rtcCodec = RTC_CODEC_OPUS; + } else if (STRSTR(attributeValue, AAC_VALUE) != NULL) { + supportCodec = TRUE; + rtcCodec = RTC_CODEC_AAC; } else if (STRSTR(attributeValue, MULAW_VALUE) != NULL) { supportCodec = TRUE; rtcCodec = RTC_CODEC_MULAW; @@ -1416,7 +1432,7 @@ STATUS setReceiversSsrc(PSessionDescription pRemoteSessionDescription, PDoubleLi codec = pKvsRtpTransceiver->sender.track.codec; isVideoCodec = (codec == RTC_CODEC_VP8 || codec == RTC_CODEC_H264_PROFILE_42E01F_LEVEL_ASYMMETRY_ALLOWED_PACKETIZATION_MODE || codec == RTC_CODEC_H265); - isAudioCodec = (codec == RTC_CODEC_MULAW || codec == RTC_CODEC_ALAW || codec == RTC_CODEC_OPUS); + isAudioCodec = (codec == RTC_CODEC_MULAW || codec == RTC_CODEC_ALAW || codec == RTC_CODEC_OPUS || codec == RTC_CODEC_AAC); if (pKvsRtpTransceiver->jitterBufferSsrc == 0 && ((isVideoCodec && isVideoMediaSection) || (isAudioCodec && isAudioMediaSection))) { diff --git a/src/source/PeerConnection/SessionDescription.h b/src/source/PeerConnection/SessionDescription.h index a7e5ac4903..5b74864e75 100644 --- a/src/source/PeerConnection/SessionDescription.h +++ b/src/source/PeerConnection/SessionDescription.h @@ -32,6 +32,7 @@ extern "C" { #define H264_VALUE "H264/90000" #define H265_VALUE "H265/90000" #define OPUS_VALUE "opus/48000" +#define AAC_VALUE "AAC/16000" #define VP8_VALUE "VP8/90000" #define MULAW_VALUE "PCMU/8000" #define ALAW_VALUE "PCMA/8000" @@ -44,6 +45,7 @@ extern "C" { #define DEFAULT_PAYLOAD_MULAW (UINT64) 0 #define DEFAULT_PAYLOAD_ALAW (UINT64) 8 #define DEFAULT_PAYLOAD_OPUS (UINT64) 111 +#define DEFAULT_PAYLOAD_AAC (UINT64) 96 #define DEFAULT_PAYLOAD_VP8 (UINT64) 96 #define DEFAULT_PAYLOAD_H264 (UINT64) 125 #define DEFAULT_PAYLOAD_H265 (UINT64) 127 @@ -77,6 +79,7 @@ extern "C" { #define VIDEO_CLOCKRATE (UINT64) 90000 #define OPUS_CLOCKRATE (UINT64) 48000 +#define AAC_CLOCKRATE (UINT64) 16000 #define PCM_CLOCKRATE (UINT64) 8000 // https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01 diff --git a/src/source/Rtp/Codecs/RtpAacPayloader.c b/src/source/Rtp/Codecs/RtpAacPayloader.c new file mode 100644 index 0000000000..edc5f2c146 --- /dev/null +++ b/src/source/Rtp/Codecs/RtpAacPayloader.c @@ -0,0 +1,75 @@ +#define LOG_CLASS "RtpAacPayloader" + +#include "../../Include_i.h" + +STATUS createPayloadForAac(UINT32 mtu, PBYTE aacFrame, UINT32 aacFrameLength, PBYTE payloadBuffer, PUINT32 pPayloadLength, PUINT32 pPayloadSubLength, + PUINT32 pPayloadSubLenSize) +{ + UNUSED_PARAM(mtu); + ENTERS(); + STATUS retStatus = STATUS_SUCCESS; + UINT32 payloadLength = 0; + UINT32 payloadSubLenSize = 0; + BOOL sizeCalculationOnly = (payloadBuffer == NULL); + + CHK(aacFrame != NULL && pPayloadSubLenSize != NULL && pPayloadLength != NULL && (sizeCalculationOnly || pPayloadSubLength != NULL), + STATUS_NULL_ARG); + + payloadLength = aacFrameLength; + payloadSubLenSize = 1; + + // Only return size if given buffer is NULL + CHK(!sizeCalculationOnly, retStatus); + CHK(payloadLength <= *pPayloadLength && payloadSubLenSize <= *pPayloadSubLenSize, STATUS_BUFFER_TOO_SMALL); + + MEMCPY(payloadBuffer, aacFrame, aacFrameLength); + pPayloadSubLength[0] = aacFrameLength; + +CleanUp: + if (STATUS_FAILED(retStatus) && sizeCalculationOnly) { + payloadLength = 0; + payloadSubLenSize = 0; + } + + if (pPayloadSubLenSize != NULL && pPayloadLength != NULL) { + *pPayloadLength = payloadLength; + *pPayloadSubLenSize = payloadSubLenSize; + } + + LEAVES(); + return retStatus; +} + +STATUS depayAacFromRtpPayload(PBYTE pRawPacket, UINT32 packetLength, PBYTE pAacData, PUINT32 pAacLength, PBOOL pIsStart) +{ + ENTERS(); + STATUS retStatus = STATUS_SUCCESS; + UINT32 aacLength = 0; + BOOL sizeCalculationOnly = (pAacData == NULL); + + CHK(pRawPacket != NULL && pAacLength != NULL, STATUS_NULL_ARG); + CHK(packetLength > 0, retStatus); + + aacLength = packetLength; + + CHK(!sizeCalculationOnly, retStatus); + CHK(aacLength <= *pAacLength, STATUS_BUFFER_TOO_SMALL); + + MEMCPY(pAacData, pRawPacket, aacLength); + +CleanUp: + if (STATUS_FAILED(retStatus) && sizeCalculationOnly) { + aacLength = 0; + } + + if (pAacLength != NULL) { + *pAacLength = aacLength; + } + + if (pIsStart != NULL) { + *pIsStart = TRUE; + } + + LEAVES(); + return retStatus; +} diff --git a/src/source/Rtp/Codecs/RtpAacPayloader.h b/src/source/Rtp/Codecs/RtpAacPayloader.h new file mode 100644 index 0000000000..08afe7a088 --- /dev/null +++ b/src/source/Rtp/Codecs/RtpAacPayloader.h @@ -0,0 +1,21 @@ +/******************************************* +AAC RTP Payloader include file +*******************************************/ +#ifndef __KINESIS_VIDEO_WEBRTC_CLIENT_RTPAACPAYLOADER_H +#define __KINESIS_VIDEO_WEBRTC_CLIENT_RTPAACPAYLOADER_H + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +// https://www.rfc-editor.org/rfc/rfc6416 + +STATUS createPayloadForAac(UINT32, PBYTE, UINT32, PBYTE, PUINT32, PUINT32, PUINT32); +STATUS depayAacFromRtpPayload(PBYTE, UINT32, PBYTE, PUINT32, PBOOL); + +#ifdef __cplusplus +} +#endif +#endif //__KINESIS_VIDEO_WEBRTC_CLIENT_RTPAACPAYLOADER_H diff --git a/tst/RtpFunctionalityTest.cpp b/tst/RtpFunctionalityTest.cpp index 7a114035c9..26e2343bf6 100644 --- a/tst/RtpFunctionalityTest.cpp +++ b/tst/RtpFunctionalityTest.cpp @@ -414,6 +414,61 @@ TEST_F(RtpFunctionalityTest, packingUnpackingVerifySameOpusFrame) MEMFREE(depayload); } +TEST_F(RtpFunctionalityTest, packingUnpackingVerifySameAacFrame) +{ + BYTE payload[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05}; + PBYTE depayload = (PBYTE) MEMALLOC(1500); // This is more than max mtu + UINT32 depayloadSize = 1500; + UINT32 payloadLen = 6; + PayloadArray payloadArray; + UINT32 newPayloadSubLen = 0; + + payloadArray.maxPayloadLength = 0; + payloadArray.maxPayloadSubLenSize = 0; + payloadArray.payloadBuffer = NULL; + payloadArray.payloadSubLength = NULL; + + // First call for payload size and sub payload length size + EXPECT_EQ(STATUS_SUCCESS, + createPayloadForAac(DEFAULT_MTU_SIZE_BYTES, (PBYTE) &payload, payloadLen, NULL, &payloadArray.payloadLength, NULL, + &payloadArray.payloadSubLenSize)); + + if (payloadArray.payloadLength > payloadArray.maxPayloadLength) { + if (payloadArray.payloadBuffer != NULL) { + MEMFREE(payloadArray.payloadBuffer); + } + payloadArray.payloadBuffer = (PBYTE) MEMALLOC(payloadArray.payloadLength); + payloadArray.maxPayloadLength = payloadArray.payloadLength; + } + if (payloadArray.payloadSubLenSize > payloadArray.maxPayloadSubLenSize) { + if (payloadArray.payloadSubLength != NULL) { + MEMFREE(payloadArray.payloadSubLength); + } + payloadArray.payloadSubLength = (PUINT32) MEMALLOC(payloadArray.payloadSubLenSize * SIZEOF(UINT32)); + payloadArray.maxPayloadSubLenSize = payloadArray.payloadSubLenSize; + } + + // Second call with actual buffer to fill in data + EXPECT_EQ(STATUS_SUCCESS, + createPayloadForAac(DEFAULT_MTU_SIZE_BYTES, (PBYTE) &payload, payloadLen, payloadArray.payloadBuffer, &payloadArray.payloadLength, + payloadArray.payloadSubLength, &payloadArray.payloadSubLenSize)); + + EXPECT_EQ(1, payloadArray.payloadSubLenSize); + EXPECT_EQ(6, payloadArray.payloadSubLength[0]); + + EXPECT_EQ(STATUS_SUCCESS, depayAacFromRtpPayload(payloadArray.payloadBuffer, payloadArray.payloadSubLength[0], NULL, &newPayloadSubLen, NULL)); + EXPECT_EQ(6, newPayloadSubLen); + + newPayloadSubLen = depayloadSize; + EXPECT_EQ(STATUS_SUCCESS, + depayAacFromRtpPayload(payloadArray.payloadBuffer, payloadArray.payloadSubLength[0], depayload, &newPayloadSubLen, NULL)); + EXPECT_TRUE(MEMCMP(payload, depayload, newPayloadSubLen) == 0); + + MEMFREE(payloadArray.payloadBuffer); + MEMFREE(payloadArray.payloadSubLength); + MEMFREE(depayload); +} + TEST_F(RtpFunctionalityTest, packingUnpackingVerifySameShortG711Frame) { BYTE payload[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05};