mirror of
https://github.com/moonlight-stream/moonlight-common-c.git
synced 2025-08-18 01:15:46 +00:00
Parse Opus parameters from RTSP DESCRIBE response
This commit is contained in:
parent
f489c9d725
commit
fb60ae6a4d
@ -26,32 +26,6 @@ static int receivedDataFromPeer;
|
||||
// for longer than normal.
|
||||
#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 {
|
||||
// data must remain at the front
|
||||
char data[MAX_PACKET_SIZE];
|
||||
@ -321,22 +295,16 @@ int startAudioStream(void* audioContext, int arFlags) {
|
||||
int err;
|
||||
OPUS_MULTISTREAM_CONFIGURATION chosenConfig;
|
||||
|
||||
// TODO: Get these from RTSP ANNOUNCE surround-params
|
||||
if (StreamConfig.audioConfiguration == AUDIO_CONFIGURATION_STEREO) {
|
||||
chosenConfig = opusStereoConfig;
|
||||
}
|
||||
else if (StreamConfig.audioConfiguration == AUDIO_CONFIGURATION_51_SURROUND) {
|
||||
if (HighQualitySurroundEnabled) {
|
||||
LC_ASSERT(HighQualitySurroundSupported);
|
||||
chosenConfig = opus51HighSurroundConfig;
|
||||
}
|
||||
else {
|
||||
chosenConfig = opus51SurroundConfig;
|
||||
}
|
||||
if (HighQualitySurroundEnabled) {
|
||||
LC_ASSERT(HighQualitySurroundSupported);
|
||||
LC_ASSERT(HighQualityOpusConfig.channelCount != 0);
|
||||
LC_ASSERT(HighQualityOpusConfig.streams != 0);
|
||||
chosenConfig = HighQualityOpusConfig;
|
||||
}
|
||||
else {
|
||||
Limelog("Invalid audio configuration: %d\n", StreamConfig.audioConfiguration);
|
||||
return -1;
|
||||
LC_ASSERT(NormalQualityOpusConfig.channelCount != 0);
|
||||
LC_ASSERT(NormalQualityOpusConfig.streams != 0);
|
||||
chosenConfig = NormalQualityOpusConfig;
|
||||
}
|
||||
|
||||
chosenConfig.samplesPerFrame = 48 * AudioPacketDuration;
|
||||
@ -411,4 +379,4 @@ int LiGetPendingAudioFrames(void) {
|
||||
|
||||
int LiGetPendingAudioDuration(void) {
|
||||
return LiGetPendingAudioFrames() * AudioPacketDuration;
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,8 @@ int NegotiatedVideoFormat;
|
||||
volatile int ConnectionInterrupted;
|
||||
int HighQualitySurroundSupported;
|
||||
int HighQualitySurroundEnabled;
|
||||
OPUS_MULTISTREAM_CONFIGURATION NormalQualityOpusConfig;
|
||||
OPUS_MULTISTREAM_CONFIGURATION HighQualityOpusConfig;
|
||||
int OriginalVideoBitrate;
|
||||
int AudioPacketDuration;
|
||||
|
||||
|
@ -22,6 +22,8 @@ extern int NegotiatedVideoFormat;
|
||||
extern volatile int ConnectionInterrupted;
|
||||
extern int HighQualitySurroundSupported;
|
||||
extern int HighQualitySurroundEnabled;
|
||||
extern OPUS_MULTISTREAM_CONFIGURATION NormalQualityOpusConfig;
|
||||
extern OPUS_MULTISTREAM_CONFIGURATION HighQualityOpusConfig;
|
||||
extern int OriginalVideoBitrate;
|
||||
extern int AudioPacketDuration;
|
||||
|
||||
|
@ -19,6 +19,16 @@ static SOCKET sock = INVALID_SOCKET;
|
||||
static ENetHost* client;
|
||||
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
|
||||
static POPTION_ITEM createOptionItem(char* option, char* content)
|
||||
{
|
||||
@ -435,6 +445,140 @@ static int sendVideoAnnounce(PRTSP_MESSAGE response, int* error) {
|
||||
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
|
||||
int performRtspHandshake(void) {
|
||||
int ret;
|
||||
@ -575,13 +719,10 @@ int performRtspHandshake(void) {
|
||||
NegotiatedVideoFormat = VIDEO_FORMAT_H264;
|
||||
}
|
||||
|
||||
// Check if high bitrate surround sound is available before attempting to request it.
|
||||
// TODO: Parse these surround-params so we can get rid of our hardcoded Opus mappings
|
||||
if (strstr(response.payload, "surround-params=660")) {
|
||||
HighQualitySurroundSupported = 1;
|
||||
}
|
||||
else {
|
||||
HighQualitySurroundSupported = 0;
|
||||
// Parse the Opus surround parameters out of the RTSP DESCRIBE response.
|
||||
ret = parseOpusConfigurations(&response);
|
||||
if (ret != 0) {
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
freeMessage(&response);
|
||||
|
Loading…
x
Reference in New Issue
Block a user