Skip to content

Commit

Permalink
Multichannel support (#37)
Browse files Browse the repository at this point in the history
* Support for multichannel in scream-pulse

* Support for multichannel

* Multichannel support for scream-alsa, minor changes in scream-pulse.

* Multichannel support for scream-raw

* New protocol support for ScreamReader, multichannel is supported but not channel mapping. Also fixed a null pointer exception if you try to change the volume before the first udp packet is received.

* Increased the number of chunks to fix the occasional hiccups when transmitting at high bitrates
  • Loading branch information
martinellimarco authored and duncanthrax committed Apr 13, 2019
1 parent 3e88ba1 commit 0a6adfe
Show file tree
Hide file tree
Showing 16 changed files with 316 additions and 79 deletions.
116 changes: 100 additions & 16 deletions Receivers/alsa/scream-alsa.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@
#define DEFAULT_MULTICAST_GROUP "239.255.77.77"
#define DEFAULT_PORT 4010

#define MAX_SO_PACKETSIZE 1154
#define CHANNELS 2
#define MAX_CHANNELS 8
#define HEADER_SIZE 5
#define MAX_SO_PACKETSIZE 1152+HEADER_SIZE

#define SNDCHK(call, ret) { \
if (ret < 0) { \
Expand Down Expand Up @@ -106,7 +107,7 @@ static int dump_alsa_info(snd_pcm_t *pcm)
return 0;
}

static int setup_alsa(snd_pcm_t **psnd, snd_pcm_format_t format, unsigned int rate, int verbosity, unsigned int target_latency_ms, const char *output_device)
static int setup_alsa(snd_pcm_t **psnd, snd_pcm_format_t format, unsigned int rate, int verbosity, unsigned int target_latency_ms, const char *output_device, int channels, snd_pcm_chmap_t **channel_map)
{
int ret;
int soft_resample = 1;
Expand All @@ -116,9 +117,21 @@ static int setup_alsa(snd_pcm_t **psnd, snd_pcm_format_t format, unsigned int ra
SNDCHK("snd_pcm_open", ret);

ret = snd_pcm_set_params(*psnd, format, SND_PCM_ACCESS_RW_INTERLEAVED,
CHANNELS, rate, soft_resample, latency);
channels, rate, soft_resample, latency);
SNDCHK("snd_pcm_set_params", ret);

ret = snd_pcm_set_chmap(*psnd, *channel_map);
if (ret == -ENXIO) { // snd_pcm_set_chmap returns -ENXIO if device does not support channel maps at all
if (channels > 2) { // but it's relevant only above 2 channels
fprintf(stderr, "Your device doesn't support channel maps. Channels may be in the wrong order.\n");
// TODO ALSA has a fixed channel order and we have the source channel_map.
// It's possible to reorder the channels in software. Maybe a place to start is the remap_data function in aplay.c
}
}
else {
SNDCHK("snd_pcm_set_chmap", ret);
}

if (verbosity > 0) {
return dump_alsa_info(*psnd);
}
Expand All @@ -134,14 +147,14 @@ static int close_alsa(snd_pcm_t *snd) {
return 0;
}

static int write_frames(snd_pcm_t *snd, unsigned char buf[MAX_SO_PACKETSIZE-2], int total_frames, int bytes_per_sample)
static int write_frames(snd_pcm_t *snd, unsigned char buf[MAX_SO_PACKETSIZE-HEADER_SIZE], int total_frames, int bytes_per_sample, int channels)
{
int i = 0;
int ret;
snd_pcm_sframes_t written;

while (i < total_frames) {
written = snd_pcm_writei(snd, &buf[i * bytes_per_sample * CHANNELS], total_frames - i);
written = snd_pcm_writei(snd, &buf[i * bytes_per_sample * channels], total_frames - i);
if (written < 0) {
ret = snd_pcm_recover(snd, written, 0);
SNDCHK("snd_pcm_recover", ret);
Expand Down Expand Up @@ -170,6 +183,16 @@ int main(int argc, char *argv[])
unsigned int rate = 44100;
unsigned char cur_server_rate = 0;
unsigned char cur_server_size = 0;
unsigned char cur_channels = 2;
unsigned char cur_channel_map_lsb = 0x03; // stereo
unsigned char cur_channel_map_msb = 0;
uint16_t cur_channel_map = (cur_channel_map_msb << 8) | cur_channel_map_lsb;

snd_pcm_chmap_t *channel_map;
channel_map = malloc(sizeof(snd_pcm_chmap_t) + MAX_CHANNELS*sizeof(unsigned int));
channel_map->channels = 2;
channel_map->pos[0] = SND_CHMAP_FL;
channel_map->pos[1] = SND_CHMAP_FR;

// Command line options
int use_unicast = 0;
Expand All @@ -179,7 +202,6 @@ int main(int argc, char *argv[])
int target_latency_ms = 50;
char *output_device ="default";


while ((opt = getopt(argc, argv, "i:g:p:t:vuho:")) != -1) {
switch (opt) {
case 'i':
Expand Down Expand Up @@ -208,13 +230,13 @@ int main(int argc, char *argv[])
show_usage(argv[0]);
}
}

if (optind < argc) {
fprintf(stderr, "Expected argument after options\n");
show_usage(argv[0]);
}

sockfd = socket(AF_INET, SOCK_DGRAM, 0);

memset((void *)&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = use_unicast ? interface : htonl(INADDR_ANY);
Expand All @@ -229,18 +251,22 @@ int main(int argc, char *argv[])
(const void *)&imreq, sizeof(struct ip_mreq));
}

if (setup_alsa(&snd, format, rate, verbosity, target_latency_ms, output_device) == -1) {
if (setup_alsa(&snd, format, rate, verbosity, target_latency_ms, output_device, cur_channels, &channel_map) == -1) {
return -1;
}

for (;;) {
n = recvfrom(sockfd, &buf, MAX_SO_PACKETSIZE, 0, NULL, 0);
if (n < 2) continue;
if (n < HEADER_SIZE) continue;

// Change rate/size?
if (cur_server_rate != buf[0] || cur_server_size != buf[1]) {
if (cur_server_rate != buf[0] || cur_server_size != buf[1] || cur_channels != buf[2] || cur_channel_map_lsb != buf[3] || cur_channel_map_msb != buf[4]) {
cur_server_rate = buf[0];
cur_server_size = buf[1];
cur_channels = buf[2];
cur_channel_map_lsb = buf[3];
cur_channel_map_msb = buf[4];
cur_channel_map = (cur_channel_map_msb << 8) | cur_channel_map_lsb;

rate = ((cur_server_rate >= 128) ? 44100 : 48000) * (cur_server_rate % 128);
switch (cur_server_size) {
Expand All @@ -253,23 +279,81 @@ int main(int argc, char *argv[])
rate = 0;
}

channel_map->channels = cur_channels;
if (cur_channels == 1) {
channel_map->pos[0] = SND_CHMAP_MONO;
}
else {
// k is the key to map a windows SPEAKER_* position to a PA_CHANNEL_POSITION_*
// it goes from 0 (SPEAKER_FRONT_LEFT) up to 10 (SPEAKER_SIDE_RIGHT) following the order in ksmedia.h
// the SPEAKER_TOP_* values are not used
int k = -1;
for (int i=0; i<cur_channels; i++) {
for (int j = k+1; j<=10; j++) {// check the channel map bit by bit from lsb to msb, starting from were we left on the previous step
if ((cur_channel_map >> j) & 0x01) {// if the bit in j position is set then we have the key for this channel
k = j;
break;
}
}
// map the key value to a pulseaudio channel position
switch (k) {
case 0: channel_map->pos[i] = SND_CHMAP_FL; break;
case 1: channel_map->pos[i] = SND_CHMAP_FR; break;
case 2: channel_map->pos[i] = SND_CHMAP_FC; break;
case 3: channel_map->pos[i] = SND_CHMAP_LFE; break;
case 4: channel_map->pos[i] = SND_CHMAP_RL; break;
case 5: channel_map->pos[i] = SND_CHMAP_RR; break;
case 6: channel_map->pos[i] = SND_CHMAP_FLC; break;
case 7: channel_map->pos[i] = SND_CHMAP_FRC; break;
case 8: channel_map->pos[i] = SND_CHMAP_RC; break;
case 9: channel_map->pos[i] = SND_CHMAP_SL; break;
case 10: channel_map->pos[i] = SND_CHMAP_SR; break;
default:
// center is a safe default, at least it's balanced. This shouldn't happen, but it's better to have a fallback
if (verbosity > 0) {
printf("Channel %i coult not be mapped. Falling back to 'center'.\n", i);
}
channel_map->pos[i] = SND_CHMAP_FC;
}
if (verbosity > 0) {
const char *channel_name;
switch (k) {
case 0: channel_name = "Front Left"; break;
case 1: channel_name = "Front Right"; break;
case 2: channel_name = "Front Center"; break;
case 3: channel_name = "LFE / Subwoofer"; break;
case 4: channel_name = "Rear Left"; break;
case 5: channel_name = "Rear Right"; break;
case 6: channel_name = "Front-Left Center"; break;
case 7: channel_name = "Front-Right Center"; break;
case 8: channel_name = "Rear Center"; break;
case 9: channel_name = "Side Left"; break;
case 10: channel_name = "Side Right"; break;
default:
channel_name = "Unknown. Setted to Center.";
}
printf("Channel %i mapped to %s\n", i, channel_name);
}
}
}

if (rate) {
close_alsa(snd);
if (setup_alsa(&snd, format, rate, verbosity, target_latency_ms, output_device) == -1) {
if (setup_alsa(&snd, format, rate, verbosity, target_latency_ms, output_device, cur_channels, &channel_map) == -1) {
if (verbosity > 0)
printf("Unable to set up ALSA with sample rate %u and sample size %hhu, not playing until next format switch.\n", rate, cur_server_size);
printf("Unable to set up ALSA with sample rate %u, sample size %hhu and %u channels, not playing until next format switch.\n", rate, cur_server_size, cur_channels);
snd = NULL;
rate = 0;
}
else {
if (verbosity > 0)
printf("Switched format to sample rate %u and sample size %hhu.\n", rate, cur_server_size);
printf("Switched format to sample rate %u, sample size %hhu and %u channels.\n", rate, cur_server_size, cur_channels);
}
}
}
if (!rate) continue;

samples = (n - 2) / (bytes_per_sample * CHANNELS);
write_frames(snd, &buf[2], samples, bytes_per_sample);
samples = (n - HEADER_SIZE) / (bytes_per_sample * cur_channels);
write_frames(snd, &buf[HEADER_SIZE], samples, bytes_per_sample, cur_channels);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2"/>
</startup>
</configuration>

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<OutputType>WinExe</OutputType>
<RootNamespace>ScreamReader</RootNamespace>
<AssemblyName>ScreamReader</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<TargetFrameworkProfile />
Expand Down Expand Up @@ -38,8 +38,8 @@
<StartupObject />
</PropertyGroup>
<ItemGroup>
<Reference Include="NAudio, Version=1.8.3.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\NAudio.1.8.3\lib\net35\NAudio.dll</HintPath>
<Reference Include="NAudio, Version=1.8.5.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\NAudio.1.8.5\lib\net35\NAudio.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
Expand Down Expand Up @@ -97,7 +97,9 @@
</Compile>
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<None Include="App.config">
<SubType>Designer</SubType>
</None>
</ItemGroup>
<ItemGroup>
<None Include="Resources\if_speaker_volume_103855.png" />
Expand Down

0 comments on commit 0a6adfe

Please sign in to comment.