Author Topic: Input ogg file is different than output ogg file when channels equal 8 or less  (Read 636 times)

trojannemo

  • Posts: 143
Running into a funny situation with my tool suite. I take multichannel ogg files (up to 18 channels) and do various things with them. Sometimes I mix channels together, sometimes I delete channels, sometimes I do a matrix and combine to stereo and play it back, or pick one of the channels to play that back, etc.

Most of the files i work with are 10 channels are more. But today I came across two 8 channel files and i realized there's a bug either with my code or with BASS. Probably my code, hence why i'm here.

First image is the input file. Second image is the output file. As you can see, the audio has been shifted two channels down. That creates all kinds of problems for me and my users.
(these images are something to my tool suite creates using BASS to visualize the contents of the given .ogg file)




I understand that per Ogg Vorbis spec up to 8 channels is used for 7.1 audio and the channels are rearranged. That's why I had this bit of code that I "thought" was working but I guess not?

Code: [Select]
public int[] ArrangeStreamChannels(int totalChannels, bool isOgg)
        {
            var channels = new int[totalChannels];
            if (isOgg)
            {
                switch (totalChannels)
                {
                    case 3:
                        channels[0] = 0;
                        channels[1] = 2;
                        channels[2] = 1;
                        break;
                    case 5:
                        channels[0] = 0;
                        channels[1] = 2;
                        channels[2] = 1;
                        channels[3] = 3;
                        channels[4] = 4;
                        break;
                    case 6:
                        channels[0] = 0;
                        channels[1] = 2;
                        channels[2] = 1;
                        channels[3] = 4;
                        channels[4] = 5;
                        channels[5] = 3;
                        break;
                    case 7:
                        channels[0] = 0;
                        channels[1] = 2;
                        channels[2] = 1;
                        channels[3] = 4;
                        channels[4] = 5;
                        channels[5] = 6;
                        channels[6] = 3;
                        break;
                    case 8:
                        channels[0] = 0;
                        channels[1] = 2;
                        channels[2] = 1;
                        channels[3] = 4;
                        channels[4] = 5;
                        channels[5] = 6;
                        channels[6] = 7;
                        channels[7] = 3;
                        break;
                    default:
                        goto DoAllChannels;
                }
                return channels;
            }
            DoAllChannels:
            for (var i = 0; i < totalChannels; i++)
            {
                channels[i] = i;
            }
            return channels;
        }

Applying that channel arrangement to the matrix leaves me with the second image as a result.
So I thought, if i'm doing 1:1 exporting just changing the quality of the audio (that's the use case for this scenario) then why not just go 1:1 with the channels. Didn't work.
So then I thought, again, if i'm not rearranging channels, why not skip the step altogether for adding a channel matrix and just create the stream and then output to disk? Didn't work. It's like, once the audio comes in, it's automatically scrambled and I HAVE to fix it. But I don't know how to fix it. Here's the relevant BASS code:

UPDATED CODE BELOW

I'm about to declare this a BUG that can't be fixed. But I figured I would ask here again since you've all been so helpful. Thanks!

EDIT: I'm fairly confident the error is not with the ArrangeChannels code because if I modify that code, then all other applications that rely on it break, including the one that creates the images. The images are 1:1 accurate to what Audacity puts out (not quite in wave form accuracy but channel data and channel order). But if i input the ogg and export the ogg, bam, channels are scrambled. Thoughts?
« Last Edit: 18 Sep '23 - 19:23 by trojannemo »

Ian @ un4seen

  • Administrator
  • Posts: 26137
I understand that per Ogg Vorbis spec up to 8 channels is used for 7.1 audio and the channels are rearranged. That's why I had this bit of code that I "thought" was working but I guess not?

BASS already rearranges the channels to its expected order (as in the STREAMPROC documentation) during decoding, so you shouldn't also do that yourself. Does the channel order still look wrong if you just always apply the "DoAllChannels" code?

trojannemo

  • Posts: 143
When I do that the original/input file gets out of alignment with the channels. The current code works fine with an input file of any amount of channels up to 18. Problem is when outputting file of 8 or less channels. I've messed enough with it to be sure that it's in the encoding process that it gets messed up. but I updated all my bass files, updated oggenc2, updated from .net framework 4.0 to 4.8.1. don't know what else to do.

Ian @ un4seen

  • Administrator
  • Posts: 26137
oggenc(2) will rearrange the channels from the BASS/WAVE order to the Vorbis order, so you shouldn't need to do any rearranging for that yourself either, unless you're trying to counteract it?

It looks like you're only setting a matrix when the mixer output has more than 8 channels, and otherwise leaving the default matrix? The default matrix may rearrange channels. Try adding the BASS_MIXER_NOSPEAKER flag to your BASS_Mixer_StreamCreate call to prevent any rearranging by default.

trojannemo

  • Posts: 143
This is the result with adding no speaker flag



This is the latest code. Still funky behavior with the output ogg file. As you noticed I've tried only doing a matrix when it's over 8 channels, and that didn't work. So it's back to how i've always had it, doing a matrix for any amount of channels. This code works if the resulting file has 9 or more channels. So i'm convinced it's an issue with the ogg 7.1 spec.

Code: [Select]
public bool DoMoggDownmix(nTools nautilus, DTAParser Parser, string mogg, int channels, bool DeleteCrowd)
        {
            const int BassBuffer = 1000;

            //initialize BASS.NET
            if (!Bass.BASS_Init(-1, 44100, BASSInit.BASS_DEVICE_DEFAULT, IntPtr.Zero))
            {
                MessageBox.Show("Error initializing BASS.NET:\n" + Bass.BASS_ErrorGetCode().ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);               
            }
            Bass.BASS_SetConfig(BASSConfig.BASS_CONFIG_BUFFER, BassBuffer);
            Bass.BASS_SetConfig(BASSConfig.BASS_CONFIG_UPDATEPERIOD, 50);

            // create a decoder for the OGG file
            var BassStream = Bass.BASS_StreamCreateFile(nautilus.GetOggStreamIntPtr(), 0L, nautilus.PlayingSongOggData.Length, BASSFlag.BASS_STREAM_DECODE | BASSFlag.BASS_SAMPLE_FLOAT);
            var channel_info = Bass.BASS_ChannelGetInfo(BassStream);

            // create a mixer with same frequency rate as the input file
            var BassMixer = BassMix.BASS_Mixer_StreamCreate(channel_info.freq, channels, BASSFlag.BASS_STREAM_DECODE | BASSFlag.BASS_MIXER_NOSPEAKER | BASSFlag.BASS_MIXER_END);
            BassMix.BASS_Mixer_StreamAddChannel(BassMixer, BassStream, BASSFlag.BASS_MIXER_MATRIX);

            //get and apply channel matrix
            var matrix = GetDownmixChannelMatrix(Parser.Songs[0], channel_info.chans, channels, DeleteCrowd);
            BassMix.BASS_Mixer_ChannelSetMatrix(BassStream, matrix);

            var ogg = mogg.Replace(".mogg", ".ogg");
            var Tools = new NemoTools();
            Tools.DeleteFile(ogg);
            var cmd = "bin\\oggenc2.exe -q3 - -o\"" + ogg + "\"";
            BassEnc.BASS_Encode_Start(BassMixer, cmd, BASSEncode.BASS_ENCODE_FP_24BIT | BASSEncode.BASS_ENCODE_AUTOFREE, null, IntPtr.Zero);
            while (true)
            {
                var buffer = new byte[20000];
                var c = Bass.BASS_ChannelGetData(BassMixer, buffer, buffer.Length);
                if (c < 0) break;
            }

            Bass.BASS_ChannelStop(BassMixer);
            Bass.BASS_StreamFree(BassMixer);
            Bass.BASS_Free();
            nautilus.ReleaseStreamHandle();

            return File.Exists(ogg);
        }

radio42

  • Posts: 4839
As Ian said, what is the output, if you remove the ChannelMatix mixing from your code?

Ian @ un4seen

  • Administrator
  • Posts: 26137
How are you generating those waveform images, eg. what decoder are you using? Perhaps that decoder isn't applying the Vorbis spec's channel order? That could explain the difference you're seeing, ie. oggenc2 is reordering the channels according to the spec but the decoder isn't reversing that to get back the original order. In that case, perhaps you could pad the output to at least 9 channels to prevent any reordering, by raising the BASS_Mixer_StreamCreate "chans" parameter and adding extra 0-filled rows to the matrix.

In case you would also like to prevent BASS reordering channels for the Vorbis spec while decoding, there was an update posted recently with that option, here:

   www.un4seen.com/forum/?topic=20148.msg140872#msg140872

trojannemo

  • Posts: 143
As Ian said, what is the output, if you remove the ChannelMatix mixing from your code?

This is the output, which I think I have already shared, if i comment out the lines about creating and setting the matrix.



The channels are shifted down.

trojannemo

  • Posts: 143
How are you generating those waveform images, eg. what decoder are you using? Perhaps that decoder isn't applying the Vorbis spec's channel order? That could explain the difference you're seeing, ie. oggenc2 is reordering the channels according to the spec but the decoder isn't reversing that to get back the original order. In that case, perhaps you could pad the output to at least 9 channels to prevent any reordering, by raising the BASS_Mixer_StreamCreate "chans" parameter and adding extra 0-filled rows to the matrix.

In case you would also like to prevent BASS reordering channels for the Vorbis spec while decoding, there was an update posted recently with that option, here:

   www.un4seen.com/forum/?topic=20148.msg140872#msg140872

I'm using BASS like I do for everything else. It works fine with the input 8 channel files and it's worked fine for the last 8+ years with 10+ channel files.

Relevant code here:

Code: [Select]
var BassStream = Bass.BASS_StreamCreateFile(nautilus.GetOggStreamIntPtr(), 0L, nautilus.PlayingSongOggData.Length, BASSFlag.BASS_STREAM_DECODE);
            if (BassStream == 0)
            {
                MessageBox.Show("Error processing audio stream:\n" + Bass.BASS_ErrorGetCode(), Text, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                return;
            }
            var length = Bass.BASS_ChannelGetLength(BassStream);
            var duration = Math.Round(Bass.BASS_ChannelBytes2Seconds(BassStream, length), 2);
            var audio_info = Bass.BASS_ChannelGetInfo(BassStream);

try
                    {
                        List<string> TrackNames;
                        List<bool> TrackIsStereo;
                        var splitter = new MoggSplitter();
                        var ArrangedChannels = splitter.ArrangeStreamChannels(audio_info.chans, Path.GetExtension(InputFile) != ".wav");
                        GetTrackNames(out TrackNames, out TrackIsStereo);
                        var height = WaveHeight / audio_info.chans;
                        var top = 0;
                        var index = 0;
                        var maxCount = TrackNames.Any() ? TrackNames.Count : audio_info.chans;
                        for (var i = 0; i < maxCount; i++)
                        {
                            var multiplier = TrackIsStereo.Any() && TrackIsStereo[i] ? 2 : 1;
                            var panel = new Panel();
                            Invoke(new MethodInvoker(delegate { panel.Parent = panelWave; }));
                            panel.Invoke(new MethodInvoker(() => panel.Left = -1));
                            panel.Invoke(new MethodInvoker(() => panel.Top = top - 1));
                            panel.Invoke(new MethodInvoker(() => panel.Width = WaveWidth + 2));
                            panel.Invoke(new MethodInvoker(() => panel.Height = (height * multiplier) + 1));
                            panel.Invoke(new MethodInvoker(() => panel.BackgroundImageLayout = ImageLayout.Stretch));
                            panel.Invoke(new MethodInvoker(() => panel.Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right));
                            if (outlineAudioTracks.Checked && i < maxCount - 1)
                            {
                                panel.Invoke(new MethodInvoker(() => panel.BorderStyle = BorderStyle.FixedSingle));
                            }
                            var map = TrackIsStereo.Any() && TrackIsStereo[i] ? new[] { ArrangedChannels[index], ArrangedChannels[index + 1], -1 } :
                                new[]{ ArrangedChannels[index], -1 };
                            var channel_stream = BassMix.BASS_Split_StreamCreate(BassStream, BASSFlag.BASS_STREAM_DECODE, map);
                            WaveImage = GetNewWaveForm(TrackIsStereo.Any() && TrackIsStereo[i]);
                            if (!WaveImage.RenderStart(channel_stream, false, true))
                            {
                                MessageBox.Show("Error rendering audio stream:\n" + Bass.BASS_ErrorGetCode(), Text, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                                ClearPanels();
                                ClearLabels();
                                return;
                            }
                            var endFrame = WaveImage.Position2Frames((length / audio_info.chans)*multiplier);
                            panel.BackgroundImage = WaveImage.CreateBitmap(WaveWidth, height * multiplier, -1, endFrame, highQualityDrawing.Checked);
                            var font = new Font("Times New Roman", 10f, FontStyle.Bold);
                            var label = new Label();
                            Invoke(new MethodInvoker(delegate { label.Parent = panel; }));
                            label.Invoke(new MethodInvoker(() => label.Visible = labelAudioChannels.Checked));
                            label.Invoke(new MethodInvoker(() => label.Location = new Point(3, 3)));
                            label.Invoke(new MethodInvoker(() => label.BackColor = Color.Transparent));
                            label.Invoke(new MethodInvoker(() => label.ForeColor = Color.White));
                            label.Invoke(new MethodInvoker(() => label.Font = font));
                            label.Invoke(new MethodInvoker(() => label.Text = TrackNames.Count > 0 ? TrackNames[i] : "chan. " + i));
                            ChannelLabels.Add(label);
                            Bass.BASS_StreamFree(channel_stream);
                            ChannelPanels.Add(panel);
                            top += (height*multiplier);
                            index += multiplier;
                        }
                    }

private static WaveForm GetNewWaveForm(bool isStereo)
        {
            var ColorBackground = Color.FromArgb(192, 192, 192);
            var ColorWaveForm = Color.FromArgb(50, 50, 200);
            var ColorLine = Color.FromArgb(50, 50, 200);
            var WaveImage = new WaveForm
            {
                FrameResolution = 0.01f,
                CallbackFrequency = 2000,
                ColorBackground = ColorBackground,
                ColorLeft = ColorWaveForm,
                ColorRight = ColorWaveForm,
                ColorMiddleLeft = ColorLine,
                ColorMiddleRight = ColorLine,
                DrawWaveForm = isStereo ? WaveForm.WAVEFORMDRAWTYPE.Stereo : WaveForm.WAVEFORMDRAWTYPE.Mono,               
            };
            return WaveImage;
        }

The WaveForm is from Bass.Misc.WaveForm

Ian @ un4seen

  • Administrator
  • Posts: 26137
Code: [Select]
                        var ArrangedChannels = splitter.ArrangeStreamChannels(audio_info.chans, Path.GetExtension(InputFile) != ".wav");
...
                            var map = TrackIsStereo.Any() && TrackIsStereo[i] ? new[] { ArrangedChannels[index], ArrangedChannels[index + 1], -1 } :
                                new[]{ ArrangedChannels[index], -1 };

This looks like you're rearranging the channels to counteract the Vorbis spec rearranging that BASS already did? If so, you could avoid the need for that by using the new option I mentioned in the BASS update above. But you will still have the issue of oggenc2 rearranging the channels then, unless you always output more than 8 channels or pre-rearrange the channels to counteract oggenc2 (similar to what you seem to be doing for BASS in the code above).

Another option is to use the BASSenc_OGG add-on instead of oggenc2. That currently has a bug whereby it doesn't apply the Vorbis spec channel order, which seems to be exactly what you want. That will be corrected in the next release, but there will also be an option added to ignore the Vorbis spec channel order.

trojannemo

  • Posts: 143
Another option is to use the BASSenc_OGG add-on instead of oggenc2. That currently has a bug whereby it doesn't apply the Vorbis spec channel order, which seems to be exactly what you want. That will be corrected in the next release, but there will also be an option added to ignore the Vorbis spec channel order.

That worked!  :D 8) I have three instances in the entire 50,000+ lines of code where I rely on oggenc2. Going to eliminate it altogether and just rely on this bass add-on. Sweet. I knew it was something to do with the ogg spec. Glad for that bug  ;D