[Vorbis-dev] 5.1 surround channel coupling
Sebastian Olter
qduaty at gmail.com
Thu Feb 22 06:02:09 PST 2007
> May I ask what this "pan filter" is?
It's an Ambisonic encoder which uses Furse-Malham equations to convert
WAVE-EX files (the format itself is a set of speaker feeds) or a .wav
file produced by an A52 decoder (mplayer is preferable as it does not
truncate the LFE channel), into a set of Ambisonic channels, up to 2nd
order. It follows the .AMB file specification, so the channel mapping
depends on their number. It basically acts as a pan effect (assuming
the Ambisonic stream is played back without decoding), thus I call it
so to not confuse people not involved in Ambisonics.
The patch applies to vorbis-tools-1.1.1. It includes an option parser,
so the actual input setup can be programmed by the user and can be any
multichannel source, with up to 255 channels placed on a sphere.
Some parts require discussion (such as stereo downmix - currently it
creates a stereo image that is completely flat, i.e. without any
depth), so I finally decided to post it here. Patch and enjoy. If you
encounter a trouble with linux, try to remove #ifdefs over my
reimplementation of strsep() for windows(R).
[file "pan.c"]
/* Ambisonic encoder module for OggEnc
* Copyright (C) 2007 Sebastian Olter
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301, USA.
*/
#include "pan.h"
#include <stdlib.h>
#include <math.h>
#ifdef WIN32
#include <windows.h>
#endif
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
const char *ambichan[] = {
NULL,
"W",
"WY",
"WXY",
"WXYZ",
"WXYRS",
"WXYZRS",
NULL,
NULL,
"WXYZRSTUV"
};
#if defined(_WIN32)
static char* strsep(char** haystack, const char *needle)
{
char *c, *tmp, *p;
if (haystack == NULL)
return NULL;
for (c = needle; *c != '\0'; c++)
{
p = *haystack;
do
p++;
while ((*p != '\0') && (*p != *c));
tmp = *haystack;
if (*p != '\0')
{
*p = '\0';
*haystack = p + 1;
}
else
*haystack = NULL;
return tmp;
}
return NULL;
}
#endif
typedef struct
{
int inch, outch, nbufs;
float matrix[255][9]; /* inch x outch */
audio_read_func real_reader;
void *real_readdata;
float **bufs;
}
Pan;
static float furse_malham(float A, float E, char channel)
{
switch (channel)
{
case 'W':
return 1./sqrt(2);
case 'X':
return cos(A) * cos(E);
case 'Y':
return sin(A) * cos(E);
case 'Z':
return sin(E);
case 'R':
return 1.5 * sin(E) * sin(E) - 0.5;
case 'S':
return cos(A) * sin(2 * E);
case 'T':
return sin(A) * sin(2 * E);
case 'U':
return cos(2 * A) * cos(E) * cos(E);
case 'V':
return sin(2 * A) * cos(E) * cos(E);
default:
return 1.;
}
}
static int setup_pan_coeffs(oe_enc_opt *opt, const char* channels_in_s)
{
Pan* pan = opt->readdata;
int inch = 0;
float A, E;
const char *outch;
char *p, *channels_in = malloc(strlen(channels_in_s) + 1);
strcpy(channels_in, channels_in_s);
do
{
p = strsep(&channels_in, ",");
if (strcmp(p, "lfe"))
{
A = strtod(p, NULL) * 2. * M_PI / 360.;
strsep(&p, ":");
E = (p) ? (strtod(p, NULL) * 2. * M_PI / 360.) : 0.;
for (outch = ambichan[opt->channels]; outch <
ambichan[opt->channels] + opt->channels; outch++)
pan->matrix[inch][outch - ambichan[opt->channels]] =
furse_malham(A, E, *outch);
}
else
{
A = E = 0.;
pan->matrix[inch][0] = 1.;
}
inch++;
}
while (channels_in);
pan->inch = inch;
pan->outch = opt->channels;
return inch;
}
static long read_pan(void *data, float **buffer, int samples)
{
Pan* pan = data;
long in_samples = pan->real_reader(pan->real_readdata, pan->bufs, samples);
int i, x, y;
float W, Y; // for stereo downmix
for (i=0; i < in_samples; i++)
{
for (y = 0; y < pan->outch; y++)
{
buffer[y][i] = 0.;
for (x = 0; x < pan->inch; x++)
buffer[y][i] += pan->bufs[x][i] * pan->matrix[x][y] / pan->inch;
}
if(pan->outch == 2)
{
W = buffer[0][i];
Y = buffer[1][i];
buffer[0][i] = W + Y;
buffer[1][i] = W - Y;
}
}
return in_samples;
}
void setup_pan(oe_enc_opt *opt, const char* channels_in, int channels_out)
{
char *chan;
int i, inch = opt->channels;
Pan *pan = calloc(1, sizeof(Pan));
if(!ambichan[channels_out])
{
fprintf(stderr,"No suitable Ambisonic configuration for %d
channels. Panning disabled.\n",channels_out);
return;
}
chan = malloc(strlen(ambichan[channels_out]) + 1);
pan->real_reader = opt->read_samples;
pan->real_readdata = opt->readdata;
opt->read_samples = read_pan;
opt->readdata = pan;
if (!strcmp(channels_in, "itu5.1"))
channels_in = "30,330,0,lfe,125,235";
else if (!strcmp(channels_in, "5.1ac3"))
channels_in = "30,330,125,235,0,lfe";
opt->channels = channels_out;
setup_pan_coeffs(opt, channels_in);
pan->bufs = malloc(inch * sizeof(float*));
for (i = 0; i < inch; i++)
pan->bufs[i] = malloc(4096 * sizeof(float));
pan->nbufs = inch;
pan->inch = min(pan->inch, inch); /* Avoid reading from
non-existing buffers */
fprintf(stderr, "Enabling Ambisonic encoder %dch->", pan->inch);
switch(channels_out)
{
case 1:
fprintf(stderr, "mono\n");
break;
case 2:
fprintf(stderr, "stereo\n");
break;
default:
fprintf(stderr, "%s\n", ambichan[channels_out]);
break;
}
free(chan);
}
void clear_pan(oe_enc_opt *opt)
{
int i;
Pan* pan = opt->readdata;
opt->read_samples = pan->real_reader;
opt->readdata = pan->real_readdata;
for (i = 0; i < pan->nbufs; i++)
free(pan->bufs[i]);
free(pan->bufs);
free(pan);
}
/*ends here*/
[file "pan.h"]
/* Ambisonic encoder module for OggEnc, header for pan.c */
#include "encode.h"
void setup_pan(oe_enc_opt *opt, const char* channels_in, int channels_out);
void clear_pan(oe_enc_opt *opt);
/*ends here*/
[PATCH for oggenc 1.0.2]
diff -b oggenc/audio.c downloads.xiph.org/vorbis-tools-1.1.1/oggenc/audio.c
389c389
< if(len!=16 && len!=18)
---
> if(len!=16 && len!=18 && len != 40) // 40 is wave-ex
415c415
< if(format.format == 1)
---
> if(format.format == 1 || format.format == -2) // -2 is wave-ex
diff -b oggenc/encode.c downloads.xiph.org/vorbis-tools-1.1.1/oggenc/encode.c
162,163d161
< struct ovectl_ratemanage2_arg ai;
< vorbis_encode_ctl(&vi, OV_ECTL_RATEMANAGE2_GET, &ai);
177a176,178
> struct ovectl_ratemanage2_arg ai;
> vorbis_encode_ctl(&vi, OV_ECTL_RATEMANAGE2_GET, &ai);
>
diff -b oggenc/encode.h downloads.xiph.org/vorbis-tools-1.1.1/oggenc/encode.h
87a88,91
> /* Ambisonics */
> char *channels_in;
> int channels_out;
>
diff -b oggenc/oggenc.c downloads.xiph.org/vorbis-tools-1.1.1/oggenc/oggenc.c
26c26
<
---
> #include "pan.h"
33a34,35
> {"sources",1,0,0},
> {"sinks",1,0,0},
82c84
< NULL, 0, -1,-1,-1,.3,-1,0, 0,0.f, 0};
---
> NULL, 0, -1,-1,-1,.3,-1,0, 0,0.f, NULL, 3, 0};
188a191
>
321a325,327
> if(opt.channels_in)
> setup_pan(&enc_opts, opt.channels_in, opt.channels_out);
>
340d345
<
357a363,364
> if(opt.channels_out > 0)
> clear_pan(&enc_opts);
359a367
>
431a440,456
> " Ambisonics:\n"
> " --sources=string Input setup:\n"
> " <preset>\n"
> " <angle1>[:elevation1][,angle2[:elevation2]]...\n"
> " This option enables ambisonic encoding. Input channels are\n"
> " mixed together to produce an ambisonic output, using\n"
> " Furse-Malham equations. Input channels not listed here\n"
> " are omitted. Currently two presets are available:\n"
> " \"itu5.1\" and \"5.1ac3\". They mean WAVE-EX and AC3 channel\n"
> " mapping, respectively. Use them with -q0 to encode\n"
> " a ~140 kbps stream.\n"
> " --sinks=number Total number of Ambisonic channels to be produced. Number\n"
> " of channels determines what setup we use, see .amb file\n"
> " format specification. Currently 3, 4, 5, 6 and 9-channel\n"
> " Ambisonic configurations are supported. 1 and 2 channels\n"
> " enable downmix.\n"
> "\n"
592c617,631
< if(!strcmp(long_options[option_index].name, "managed")) {
---
> if(!strcmp(long_options[option_index].name, "sinks")) {
> if(sscanf(optarg, "%d", &opt->channels_out) != 1) {
> fprintf(stderr, _("WARNING: Bad number of output channels: \"%s\". Pan filter disabled.\n"), optarg);
> opt->channels_out = 0;
> }
> else if(opt->channels_out < 1 || opt->channels_out == 7 || opt->channels_out == 8 || opt->channels_out > 9) {
> fprintf(stderr, _("WARNING: no %s-channel Ambisonic setup. Assuming 2nd order, full sphere.\n"), optarg);
> opt->channels_out = 9;
> }
> }
> else if(!strcmp(long_options[option_index].name, "sources")) {
> opt->channels_in = malloc(strlen(optarg) + 1);
> strcpy(opt->channels_in, optarg);
> }
> else if(!strcmp(long_options[option_index].name, "managed")) {
Only in downloads.xiph.org/vorbis-tools-1.1.1/oggenc: pan.c
Only in downloads.xiph.org/vorbis-tools-1.1.1/oggenc: pan.h
/*ends here*/
More information about the Vorbis-dev
mailing list