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.
|
// 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 (StreamConfig.audioConfiguration == AUDIO_CONFIGURATION_STEREO) {
|
|
||||||
chosenConfig = opusStereoConfig;
|
|
||||||
}
|
|
||||||
else if (StreamConfig.audioConfiguration == AUDIO_CONFIGURATION_51_SURROUND) {
|
|
||||||
if (HighQualitySurroundEnabled) {
|
if (HighQualitySurroundEnabled) {
|
||||||
LC_ASSERT(HighQualitySurroundSupported);
|
LC_ASSERT(HighQualitySurroundSupported);
|
||||||
chosenConfig = opus51HighSurroundConfig;
|
LC_ASSERT(HighQualityOpusConfig.channelCount != 0);
|
||||||
|
LC_ASSERT(HighQualityOpusConfig.streams != 0);
|
||||||
|
chosenConfig = HighQualityOpusConfig;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
chosenConfig = opus51SurroundConfig;
|
LC_ASSERT(NormalQualityOpusConfig.channelCount != 0);
|
||||||
}
|
LC_ASSERT(NormalQualityOpusConfig.streams != 0);
|
||||||
}
|
chosenConfig = NormalQualityOpusConfig;
|
||||||
else {
|
|
||||||
Limelog("Invalid audio configuration: %d\n", StreamConfig.audioConfiguration);
|
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
chosenConfig.samplesPerFrame = 48 * AudioPacketDuration;
|
chosenConfig.samplesPerFrame = 48 * AudioPacketDuration;
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user