Parse Opus parameters from RTSP DESCRIBE response

This commit is contained in:
Cameron Gutman 2020-03-31 20:52:38 -07:00
parent f489c9d725
commit fb60ae6a4d
4 changed files with 161 additions and 48 deletions

View File

@ -26,32 +26,6 @@ static int receivedDataFromPeer;
// for longer than normal. // for longer than normal.
#define RTP_RECV_BUFFER (64 * 1024) #define RTP_RECV_BUFFER (64 * 1024)
#define SAMPLE_RATE 48000
static OPUS_MULTISTREAM_CONFIGURATION opusStereoConfig = {
.sampleRate = SAMPLE_RATE,
.channelCount = 2,
.streams = 1,
.coupledStreams = 1,
.mapping = {0, 1}
};
static OPUS_MULTISTREAM_CONFIGURATION opus51SurroundConfig = {
.sampleRate = SAMPLE_RATE,
.channelCount = 6,
.streams = 4,
.coupledStreams = 2,
.mapping = {0, 4, 1, 5, 2, 3}
};
static OPUS_MULTISTREAM_CONFIGURATION opus51HighSurroundConfig = {
.sampleRate = SAMPLE_RATE,
.channelCount = 6,
.streams = 6,
.coupledStreams = 0,
.mapping = {0, 1, 2, 3, 4, 5}
};
typedef struct _QUEUED_AUDIO_PACKET { typedef struct _QUEUED_AUDIO_PACKET {
// data must remain at the front // data must remain at the front
char data[MAX_PACKET_SIZE]; char data[MAX_PACKET_SIZE];
@ -321,22 +295,16 @@ int startAudioStream(void* audioContext, int arFlags) {
int err; int err;
OPUS_MULTISTREAM_CONFIGURATION chosenConfig; OPUS_MULTISTREAM_CONFIGURATION chosenConfig;
// TODO: Get these from RTSP ANNOUNCE surround-params if (HighQualitySurroundEnabled) {
if (StreamConfig.audioConfiguration == AUDIO_CONFIGURATION_STEREO) { LC_ASSERT(HighQualitySurroundSupported);
chosenConfig = opusStereoConfig; LC_ASSERT(HighQualityOpusConfig.channelCount != 0);
} LC_ASSERT(HighQualityOpusConfig.streams != 0);
else if (StreamConfig.audioConfiguration == AUDIO_CONFIGURATION_51_SURROUND) { chosenConfig = HighQualityOpusConfig;
if (HighQualitySurroundEnabled) {
LC_ASSERT(HighQualitySurroundSupported);
chosenConfig = opus51HighSurroundConfig;
}
else {
chosenConfig = opus51SurroundConfig;
}
} }
else { else {
Limelog("Invalid audio configuration: %d\n", StreamConfig.audioConfiguration); LC_ASSERT(NormalQualityOpusConfig.channelCount != 0);
return -1; LC_ASSERT(NormalQualityOpusConfig.streams != 0);
chosenConfig = NormalQualityOpusConfig;
} }
chosenConfig.samplesPerFrame = 48 * AudioPacketDuration; chosenConfig.samplesPerFrame = 48 * AudioPacketDuration;
@ -411,4 +379,4 @@ int LiGetPendingAudioFrames(void) {
int LiGetPendingAudioDuration(void) { int LiGetPendingAudioDuration(void) {
return LiGetPendingAudioFrames() * AudioPacketDuration; return LiGetPendingAudioFrames() * AudioPacketDuration;
} }

View File

@ -20,6 +20,8 @@ int NegotiatedVideoFormat;
volatile int ConnectionInterrupted; volatile int ConnectionInterrupted;
int HighQualitySurroundSupported; int HighQualitySurroundSupported;
int HighQualitySurroundEnabled; int HighQualitySurroundEnabled;
OPUS_MULTISTREAM_CONFIGURATION NormalQualityOpusConfig;
OPUS_MULTISTREAM_CONFIGURATION HighQualityOpusConfig;
int OriginalVideoBitrate; int OriginalVideoBitrate;
int AudioPacketDuration; int AudioPacketDuration;

View File

@ -22,6 +22,8 @@ extern int NegotiatedVideoFormat;
extern volatile int ConnectionInterrupted; extern volatile int ConnectionInterrupted;
extern int HighQualitySurroundSupported; extern int HighQualitySurroundSupported;
extern int HighQualitySurroundEnabled; extern int HighQualitySurroundEnabled;
extern OPUS_MULTISTREAM_CONFIGURATION NormalQualityOpusConfig;
extern OPUS_MULTISTREAM_CONFIGURATION HighQualityOpusConfig;
extern int OriginalVideoBitrate; extern int OriginalVideoBitrate;
extern int AudioPacketDuration; extern int AudioPacketDuration;

View File

@ -19,6 +19,16 @@ static SOCKET sock = INVALID_SOCKET;
static ENetHost* client; static ENetHost* client;
static ENetPeer* peer; static ENetPeer* peer;
#define CHAR_TO_INT(x) ((x) - '0')
#define CHAR_IS_DIGIT(x) ((x) >= '0' && (x) <= '9')
#define SWAP_CHANNEL(config, i, j) \
{ \
unsigned char tmp = (config)->mapping[i]; \
(config)->mapping[i] = (config)->mapping[j]; \
(config)->mapping[j] = tmp; \
}
// Create RTSP Option // Create RTSP Option
static POPTION_ITEM createOptionItem(char* option, char* content) static POPTION_ITEM createOptionItem(char* option, char* content)
{ {
@ -435,6 +445,140 @@ static int sendVideoAnnounce(PRTSP_MESSAGE response, int* error) {
return ret; return ret;
} }
static int parseOpusConfigFromParamString(char* paramStr, int channelCount, POPUS_MULTISTREAM_CONFIGURATION opusConfig) {
int i;
// Set channel count (included in the prefix, so not parsed below)
opusConfig->channelCount = channelCount;
// Parse the remaining data from the surround-params value
if (!CHAR_IS_DIGIT(*paramStr)) {
Limelog("Invalid stream count: %c\n", *paramStr);
return -1;
}
opusConfig->streams = CHAR_TO_INT(*paramStr);
paramStr++;
if (!CHAR_IS_DIGIT(*paramStr)) {
Limelog("Invalid coupled stream count: %c\n", *paramStr);
return -2;
}
opusConfig->coupledStreams = CHAR_TO_INT(*paramStr);
paramStr++;
for (i = 0; i < opusConfig->channelCount; i++) {
if (!CHAR_IS_DIGIT(*paramStr)) {
Limelog("Invalid mapping value at %d: %c\n", i, *paramStr);
return -3;
}
opusConfig->mapping[i] = CHAR_TO_INT(*paramStr);
paramStr++;
}
return 0;
}
// Parses the Opus configuration from an RTSP DESCRIBE response
static int parseOpusConfigurations(PRTSP_MESSAGE response) {
memset(&NormalQualityOpusConfig, 0, sizeof(NormalQualityOpusConfig));
memset(&HighQualityOpusConfig, 0, sizeof(HighQualityOpusConfig));
// Sample rate is always 48 KHz
HighQualityOpusConfig.sampleRate = NormalQualityOpusConfig.sampleRate = 48000;
// Stereo doesn't have any surround-params elements in the RTSP data
if (StreamConfig.audioConfiguration == AUDIO_CONFIGURATION_STEREO) {
NormalQualityOpusConfig.channelCount = 2;
NormalQualityOpusConfig.streams = 1;
NormalQualityOpusConfig.coupledStreams = 1;
NormalQualityOpusConfig.mapping[0] = 0;
NormalQualityOpusConfig.mapping[1] = 1;
}
else {
char paramsPrefix[128];
char* paramStart;
int err;
int channelCount;
LC_ASSERT(StreamConfig.audioConfiguration == AUDIO_CONFIGURATION_51_SURROUND);
channelCount = 6;
// Find the correct audio parameter value
sprintf(paramsPrefix, "a=fmtp:97 surround-params=%d", channelCount);
paramStart = strstr(response->payload, paramsPrefix);
if (paramStart) {
// Skip the prefix
paramStart += strlen(paramsPrefix);
// Parse the normal quality Opus config
err = parseOpusConfigFromParamString(paramStart, channelCount, &NormalQualityOpusConfig);
if (err != 0) {
return err;
}
// GFE's normal-quality channel mapping differs from the one our clients use.
// They use FL FR C RL RR LFE, but we use FL FR C LFE RL RR. We'll need
// to swap the mappings to match the expected values.
if (channelCount == 6) {
SWAP_CHANNEL(&NormalQualityOpusConfig, 3, 4);
SWAP_CHANNEL(&NormalQualityOpusConfig, 3, 5);
}
// If this configuration is compatible with high quality mode, we may have another
// matching surround-params value for high quality mode.
paramStart = strstr(paramStart, paramsPrefix);
if (paramStart) {
// Skip the prefix
paramStart += strlen(paramsPrefix);
// Parse the high quality Opus config
err = parseOpusConfigFromParamString(paramStart, channelCount, &HighQualityOpusConfig);
if (err != 0) {
return err;
}
// We can request high quality audio
HighQualitySurroundSupported = 1;
}
}
else {
Limelog("No surround parameters found for channel count: %d\n", channelCount);
// It's unknown whether all GFE versions that supported surround sound included these
// surround sound parameters. In case they didn't, we'll specifically handle 5.1 surround
// sound using a hardcoded configuration like we used to before this parsing code existed.
if (StreamConfig.audioConfiguration == AUDIO_CONFIGURATION_51_SURROUND) {
NormalQualityOpusConfig.channelCount = 6;
NormalQualityOpusConfig.streams = 4;
NormalQualityOpusConfig.coupledStreams = 2;
NormalQualityOpusConfig.mapping[0] = 0;
NormalQualityOpusConfig.mapping[1] = 4;
NormalQualityOpusConfig.mapping[2] = 1;
NormalQualityOpusConfig.mapping[3] = 5;
NormalQualityOpusConfig.mapping[4] = 2;
NormalQualityOpusConfig.mapping[5] = 3;
HighQualityOpusConfig.channelCount = 6;
HighQualityOpusConfig.streams = 6;
HighQualityOpusConfig.coupledStreams = 0;
HighQualityOpusConfig.mapping[0] = 0;
HighQualityOpusConfig.mapping[1] = 1;
HighQualityOpusConfig.mapping[2] = 2;
HighQualityOpusConfig.mapping[3] = 3;
HighQualityOpusConfig.mapping[4] = 4;
HighQualityOpusConfig.mapping[5] = 5;
}
else {
// We don't have a hardcoded fallback mapping, so we have no choice but to fail.
return -4;
}
}
}
return 0;
}
// Perform RTSP Handshake with the streaming server machine as part of the connection process // Perform RTSP Handshake with the streaming server machine as part of the connection process
int performRtspHandshake(void) { int performRtspHandshake(void) {
int ret; int ret;
@ -575,13 +719,10 @@ int performRtspHandshake(void) {
NegotiatedVideoFormat = VIDEO_FORMAT_H264; NegotiatedVideoFormat = VIDEO_FORMAT_H264;
} }
// Check if high bitrate surround sound is available before attempting to request it. // Parse the Opus surround parameters out of the RTSP DESCRIBE response.
// TODO: Parse these surround-params so we can get rid of our hardcoded Opus mappings ret = parseOpusConfigurations(&response);
if (strstr(response.payload, "surround-params=660")) { if (ret != 0) {
HighQualitySurroundSupported = 1; goto Exit;
}
else {
HighQualitySurroundSupported = 0;
} }
freeMessage(&response); freeMessage(&response);