Author Topic: Encoding at full speed  (Read 3031 times)

Hanuman

  • Posts: 114
Encoding at full speed
« on: 1 Jun '22 - 18:32 »
I've managed to encode a short file into another file. Following the syntax from the sample.

Code: [Select]
BASS_Encode_Start(channel, "output.wav", BASS_ENCODE_PCM, NULL, NULL);
BASS_ChannelPlay(channel, 0); // start the channel playing & encoding

For a large file, the problem is that ChannelPlay starts the stream at play speed, and thus encoding a 3 minutes file takes 3 minutes.

How can I encode at full speed instead of at playback speed?

Chris

  • Posts: 2221
Re: Encoding at full speed
« Reply #1 on: 1 Jun '22 - 20:13 »
with the Bass_Stream_Decode Flag

e.g
Code: [Select]
Channel :=   BASS_StreamCreateFile(FALSE, "afile.wav", 0, 0, Bass_Stream_Decode);
BASS_Encode_Start(channel, "output.wav", BASS_ENCODE_PCM, NULL, NULL);

Hanuman

  • Posts: 114
Re: Encoding at full speed
« Reply #2 on: 1 Jun '22 - 22:27 »
That's the first thing I tried; then I get

Code: [Select]
Bass.ChannelPlay(_chan)
BASS threw an error of type Decode

If I don't call ChannelPlay, then the encoding doesn't start at all.

Then I call
Code: [Select]
_encHandle = BassEnc_Mp3.Start(_chan, null, EncodeFlags.AutoFree, file.Destination)
« Last Edit: 2 Jun '22 - 03:35 by Hanuman »

Ian @ un4seen

  • Administrator
  • Posts: 26222
Re: Encoding at full speed
« Reply #3 on: 2 Jun '22 - 13:10 »
It isn't possible to play a decoding channel with BASS_ChannelPlay. You need to instead repeatedly call BASS_ChannelGetData to process it. For example, like this:

Code: [Select]
while (true) {
BYTE buf[20000]; // processing buffer
int got = BASS_ChannelGetData(channel, buf, sizeof(buf)); // process the decoder
if (got == -1) break; // done
}

Hanuman

  • Posts: 114
Re: Encoding at full speed
« Reply #4 on: 2 Jun '22 - 14:18 »
BASS_ENC only has a single method, BASS_Encode_MP3_StartFile. That method is useless then? So how to do

Got any sample of encoding to MP3 with BASS_ChannelGetData ?

Found BassEnc.EncodeWrite, trying this...

Code: [Select]
_encHandle = BassEnc_Mp3.Start(_chan, null, EncodeFlags.AutoFree, file.Destination).Valid();

var buffer = new byte[20000];
while (true)
{
    var read = Bass.ChannelGetData(_chan, buffer, buffer.Length);
    if (read == -1) { break; }
    BassEnc.EncodeWrite(_encHandle, buffer, read);
}

It "encodes", but the resulting output is distorted.

Doc says this though

Quote
There is usually no need to use this function, as the channel's sample data will automatically be fed to the encoder as it is decoded/played.
« Last Edit: 2 Jun '22 - 14:35 by Hanuman »

Ian @ un4seen

  • Administrator
  • Posts: 26222
Re: Encoding at full speed
« Reply #5 on: 2 Jun '22 - 14:37 »
BASS_ENC only has a single method, BASS_Encode_MP3_StartFile. That method is useless then? So how to do

Got any sample of encoding to MP3 with BASS_ChannelGetData ?

You would still use BASS_Encode_MP3_StartFile to start an MP3 file encoder. The BASS_ChannelGetData loop that I posted would just replace the BASS_ChannelPlay call that you had originally. Please see the CONVERT.C example included in the BASSenc package for a demonstration.

Hanuman

  • Posts: 114
Re: Encoding at full speed
« Reply #6 on: 3 Jun '22 - 03:01 »
Very counter-intuitive API, but it works now. Thanks!

IMO this should be better documented, or a "PlayDecode" function should be provided.

Hanuman

  • Posts: 114
Re: Encoding at full speed
« Reply #7 on: 3 Jun '22 - 14:33 »
This solution is not multi-threaded.

Any way to utilize all CPU cores?

Ian @ un4seen

  • Administrator
  • Posts: 26222
Re: Encoding at full speed
« Reply #8 on: 3 Jun '22 - 16:37 »
It won't be possible to use all cores (unless it's a dual-core system) but you can have the decoder and encoder run in parallel. Decoding is generally very quick though, so I'm not sure doing that will save much time. If you would like to try it anyway, you can do so by setting the BASS_ENCODE_QUEUE flag on the encoder. Note that the "queue" is limited to 6 seconds by default, which means that if the decoder gets more than 6s ahead of the encoder then data may be dropped, but you can remove the limit via the BASS_CONFIG_ENCODE_QUEUE option (before starting the encoder). Alternatively, you could monitor the BASS_ENCODE_COUNT_QUEUE value (via BASS_Encode_GetCount) in the decoding loop to make sure it doesn't exceed the limit. Also note that the encoder will probably still be busy when the decoding loop exits, and you should use BASS_Encode_StopEx (instead of BASS_Encode_Stop) to free it. You can use BASS_Encode_SetNotify to receive a notification (BASS_ENCODE_NOTIFY_FREE) when the encoder finally finishes, or poll BASS_Encode_IsActive. Please see the documentation for details on the mentioned functions/options.

If you happen to be encoding multiple files then another way to use multiple cores is to simply encode multiple files at the same time (each in its own thread).

Hanuman

  • Posts: 114
Re: Encoding at full speed
« Reply #9 on: 4 Jun '22 - 02:19 »
Hum... works for MP3, but WAV is giving me StackOverflow when calling ChannelGetData ??

Code: [Select]
BassEnc.EncodeStart(chan, file.Destination, EncodeFlags.PCM | EncodeFlags.Dither | EncodeFlags.AutoFree, null).Valid();
BassEnc_Mp3.Start(chan, options, EncodeFlags.Dither | EncodeFlags.AutoFree, file.Destination).Valid();
...
var read = Bass.ChannelGetData(chan, buffer, buffer.Length);

Hanuman

  • Posts: 114
Re: Encoding at full speed
« Reply #10 on: 4 Jun '22 - 03:33 »
How do I set the encoding Sample Rate? Encoders have a parameter, but I'd rather convert on BASS side to get sinc-64 sampling quality.

First I'm shifting the pitch with
Code: [Select]
Bass.ChannelSetAttribute(chan, ChannelAttribute.TempoFrequency, freq);
I tried setting the sample rate but it always encodes at 48000
Code: [Select]
Bass.ChannelSetAttribute(chan, ChannelAttribute.Frequency, settings.SampleRate);
According to the doc the --resample encoder parameter is not supported for OGG, Opus and FLAC.
« Last Edit: 4 Jun '22 - 03:38 by Hanuman »

Ian @ un4seen

  • Administrator
  • Posts: 26222
Re: Encoding at full speed
« Reply #11 on: 6 Jun '22 - 12:45 »
Hum... works for MP3, but WAV is giving me StackOverflow when calling ChannelGetData ??

Code: [Select]
BassEnc.EncodeStart(chan, file.Destination, EncodeFlags.PCM | EncodeFlags.Dither | EncodeFlags.AutoFree, null).Valid();
BassEnc_Mp3.Start(chan, options, EncodeFlags.Dither | EncodeFlags.AutoFree, file.Destination).Valid();
...
var read = Bass.ChannelGetData(chan, buffer, buffer.Length);

Please confirm what platform you're running on, and that you're using the latest BASSenc version with BASS_Encode_GetVersion (it should be 0x02041001 or 0x02041000 on Android/iOS). Also... where are you calling BASS_ChannelGetData from and what is the "buffer.Length" value? Are you setting 2 encoders on "chan" or are you just showing the encoder setup in each case? If you are setting 2 encoders, does the problem still happen if you only set the WAV encoder? Please also show how "chan" is created.

How do I set the encoding Sample Rate? Encoders have a parameter, but I'd rather convert on BASS side to get sinc-64 sampling quality.

You can use the BASSmix add-on to change the sample rate. Create a mixer with the wanted rate and plug the decoder into it, and then encode the mixer. Something like this:

Code: [Select]
decoder = BASS_StreamCreateFile(false, filename, 0, 0, BASS_SAMPLE_FLOAT | BASS_STREAM_DECODE); // create decoder
resampler = BASS_Mixer_StreamCreate(freq, chans, BASS_MIXER_END | BASS_STREAM_DECODE); // create mixer with wanted format
BASS_Mixer_StreamAddChannel(resampler, decoder, BASS_MIXER_CHAN_NORAMPIN | BASS_STREAM_AUTOFREE); // plug in the decoder
BASS_Encode_MP3_StartFile(resampler, options, BASS_ENCODE_AUTOFREE, outfile); // set an MP3 encoder on the mixer
while (true) {
BYTE buf[20000]; // processing buffer
int got = BASS_ChannelGetData(resampler, buf, sizeof(buf)); // process the mixer
if (got == -1) break; // done
}
BASS_StreamFree(resampler); // free the mixer (and decoder/encoder due to AUTOFREE)

Please see the documentation for details on the mentioned functions.

Hanuman

  • Posts: 114
Re: Encoding at full speed
« Reply #12 on: 6 Jun '22 - 16:17 »
ManagedBass returns v2.4.16.1 for BassEnc

I'm on Linux (Garuda)

Here's a full encoding sample that crashes

Code: [Select]
var pitch = 432.0 / 440;
var source = "SourceShort.mp3";
var destination = "SourceShort_out.wav";
var speed = 1.0;
var rate = 1.0;

var v = BassEnc.Version;
// Create channel.
Bass.Init();
var chan = Bass.CreateStream(source, Flags: BassFlags.Float | BassFlags.Decode);
var chanInfo = Bass.ChannelGetInfo(chan);
var srcDuration = GetDuration(chan);
_output.WriteLine($"Pitch: {pitch}");
_output.WriteLine($"Source duration: {srcDuration.TotalSeconds:F3} seconds");

// Add tempo effects.
chan = BassFx.TempoCreate(chan, BassFlags.Decode).Valid();
Bass.Configure(Configuration.SRCQuality, 4);

// In BASS, 2x speed is 100 (+100%), whereas our Speed property is 2. Need to convert.
// speed 1=0, 2=100, 3=200, 4=300, .5=-100, .25=-300
var speedShift = speed / pitch;
var tempo = speedShift >= 1 ? -100.0 / speedShift + 100 : 100.0 * speedShift - 100;
var freq = chanInfo.Frequency * rate * pitch;
Bass.ChannelSetAttribute(chan, ChannelAttribute.Tempo, tempo);
Bass.ChannelSetAttribute(chan, ChannelAttribute.TempoFrequency, freq);

// Create encoder.
BassEnc.EncodeStart(chan, destination, EncodeFlags.PCM | EncodeFlags.Dither | EncodeFlags.AutoFree, null);
var length = Bass.ChannelGetLength(chan, PositionFlags.Bytes);

// Process file.
var buffer = new byte[32 * 1024];
while (true)
{
    // Reading data moves the encoder forward, no need to use the buffer ourselves.
    var read = Bass.ChannelGetData(chan, buffer, buffer.Length);
    if (read == -1) { break; }
}

Bass.StreamFree(chan);

Hanuman

  • Posts: 114
Re: Encoding at full speed
« Reply #13 on: 7 Jun '22 - 06:14 »
Adding BassMix works, but I have an issue getting the stream length.

This works to get the total length on a regular channel.

Code: [Select]
Bass.ChannelGetLength(chan, PositionFlags.Bytes)
If I create a BassMix and encode that, the processing will get to about 50-54% of that only! ChannelGetLength on the mix channel returns -1.

This is my code to plug in the mixer. Why does the mixer channel length is so different than the source channel, and how can I get the length of the mixer stream?

Code: [Select]
var sampleRate = settings.SampleRate > 0 ? settings.SampleRate : chanInfo.Frequency;
var chanMix = BassMix.CreateMixerStream(sampleRate, chanInfo.Channels, BassFlags.MixerEnd | BassFlags.Decode).Valid();
BassMix.MixerAddChannel(chanMix, chan, BassFlags.MixerChanNoRampin | BassFlags.AutoFree);
var length = Bass.ChannelGetLength(chan, PositionFlags.Bytes);
chan = chanMix;

Ian @ un4seen

  • Administrator
  • Posts: 26222
Re: Encoding at full speed
« Reply #14 on: 7 Jun '22 - 16:24 »
It isn't possible to get the length of a mixer in advance because they don't have a defined length, eg. sources may be added and removed at any time. Do you want this info for a progress display? If so, you could use the source's position and length instead (the source's position will advance as the mixer processes it).

Ian @ un4seen

  • Administrator
  • Posts: 26222
Re: Encoding at full speed
« Reply #15 on: 8 Jun '22 - 16:13 »
Here's a BASSmix update that adds some support for getting the length of a mixer from BASS_ChannelGetLength under certain conditions. That is when the BASS_MIXER_END flag is set on the mixer, and the length of all sources is known and none are looping (BASS_SAMPLE_LOOP), which is all true in this case.

   www.un4seen.com/stuff/bassmix.zip

The length may be affected whenever a source is added/removed or its rate (BASS_ATTRIB_FREQ) or position is changed, so it's recalculated in every BASS_ChannelGetLength call. It hasn't been tested much yet, so if you try this, please report how it goes.

Hanuman

  • Posts: 114
Re: Encoding at full speed
« Reply #16 on: 8 Jun '22 - 20:44 »
Actually simply calling this worked just fine. Keeping the same code to get the channel length.

Code: [Select]
var pos = ManagedBass.Bass.ChannelGetPosition(chan, PositionFlags.Bytes);

Hanuman

  • Posts: 114
Re: Encoding at full speed
« Reply #17 on: 8 Jun '22 - 21:07 »
Getting yet another problem... it's coming out as 32-bit MP3.

I tried with EncodeFlags.ConvertFloatAuto and ConvertFloatTo8BitInt, neither worked to bring it back down to 8-bit. Alongside Dither | AutoFree.

Other option is to convert it at the Mixer level?

Ideally I want to pass 32-bit data to the Encoder so that it has best precision and it gets dithered down to 8-bit.

Ian @ un4seen

  • Administrator
  • Posts: 26222
Re: Encoding at full speed
« Reply #18 on: 9 Jun '22 - 12:36 »
Like most "lossy" formats, MP3 doesn't have a defined bitdepth. It's always decoded in floating-point by BASS, and then converted to 16-bit if the BASS_SAMPLE_FLOAT flag isn't set.

Hanuman

  • Posts: 114
Re: Encoding at full speed
« Reply #19 on: 10 Jun '22 - 16:25 »
Everything is working now EXCEPT WAV encoding.

I tried in Windows, it doesn't work either. It's probably something stupid.

Ian @ un4seen

  • Administrator
  • Posts: 26222
Re: Encoding at full speed
« Reply #20 on: 10 Jun '22 - 17:45 »
Is that still the StackOverflow that you mentioned earlier? I'm not a .Net user myself, so I can't try the code you posted above, but I tried doing an MP3-to-WAV conversion with the CONVERT example included in the BASSenc package, and that was fine. I also tried adding BASS_SAMPLE_FLOAT to BASS_StreamCreateFile and BASS_ENCODE_DITHER to BASS_Encode_Start, like in your code, but that was fine too. Can you reproduce the problem with that example there? Open the folder in Terminal and enter "make" to build it. If that works fine, does the problem still happen with your code if you remove the tempo processing? So when just doing something like this:

Code: [Select]
decoder = BASS_StreamCreateFile(false, filename, 0, 0, BASS_STREAM_DECODE); // create decoder
BASS_Encode_Start(decoder, outfile, BASS_ENCODE_PCM | BASS_ENCODE_AUTOFREE, 0, 0); // set a WAV encoder on it
while (true) {
BYTE buf[20000]; // processing buffer
int got = BASS_ChannelGetData(decoder, buf, sizeof(buf)); // process the decoder
if (got == -1) break; // done
}
BASS_StreamFree(decoder); // free the decoder (and encoder due to AUTOFREE)
« Last Edit: 13 Jun '22 - 16:17 by Ian @ un4seen »

Hanuman

  • Posts: 114
Re: Encoding at full speed
« Reply #21 on: 10 Jun '22 - 21:30 »
'make' gives
Code: [Select]
fatal error: bass.h: No such file or directory
Got a minimal reproduction of the problem.

Code: [Select]
public static void Main()
{
    var source = "SourceShort.mp3";
    var destination = "SourceShort_out.wav";

    Bass.Init(0);
    var chan = Bass.CreateStream(source, Flags: BassFlags.Decode);
    BassEnc.EncodeStart(chan, destination, EncodeFlags.PCM | EncodeFlags.AutoFree, null);

    var buffer = new byte[16 * 1024];
    while (Bass.ChannelGetData(chan, buffer, buffer.Length) > -1)
    {
    }

    Bass.StreamFree(chan);
}

I'm pretty sure the StackOverflow is in unmanaged code because if it was in ManagedBass, I would see the stack trace of where the error occurred. I can't see such details so that indicates a problem within the bass DLL. I ran the code above in a plan app Main method, and it crashed without any detail or stack whatsoever.

You could install Visual Studio Code, create a very plain console project with the code above, and link it against your C++ library to debug. If it's a problem on your side. Or it's ManagedBass passing something wrong that causes a weird behavior.
« Last Edit: 11 Jun '22 - 04:58 by Hanuman »

Hanuman

  • Posts: 114
Re: Encoding at full speed
« Reply #22 on: 11 Jun '22 - 08:10 »
I've created a sample console project to reproduce the problem here.

The reference projects in DLL folder automatically copy all required BASS dlls.

You can debug your C++ code while executing the console app.

Ian @ un4seen

  • Administrator
  • Posts: 26222
Re: Encoding at full speed
« Reply #23 on: 13 Jun '22 - 16:16 »
'make' gives
Code: [Select]
fatal error: bass.h: No such file or directory

I should have mentioned that you will need to copy the BASS files from the BASS package to build/run the BASSenc examples, eg. BASS.H should be alongside BASSENC.H and LIBBASS.SO should be alongside LIBBASSENC.SO.

Got a minimal reproduction of the problem.

Code: [Select]
public static void Main()
{
    var source = "SourceShort.mp3";
    var destination = "SourceShort_out.wav";

    Bass.Init(0);
    var chan = Bass.CreateStream(source, Flags: BassFlags.Decode);
    BassEnc.EncodeStart(chan, destination, EncodeFlags.PCM | EncodeFlags.AutoFree, null);

    var buffer = new byte[16 * 1024];
    while (Bass.ChannelGetData(chan, buffer, buffer.Length) > -1)
    {
    }

    Bass.StreamFree(chan);
}

OK, I'll try to get that running here and see if I can reproduce the problem with it.

Ian @ un4seen

  • Administrator
  • Posts: 26222
Re: Encoding at full speed
« Reply #24 on: 16 Jun '22 - 18:00 »
I already had MonoDevelop installed here, so I tried to run your test project with that. It was able to load the ConsoleApp1.csproj project but complained that it was "unable to find project information" for the BassDlls.csproj and BassEncDlls.csproj projects and said they "may be invalid or missing targets required for restore".

So I tried creating a new project with the same calls but using BASS.Net (Bass.Net.Linux.dll) instead of ManagedBass, and that worked fine. If you would like to try it there, this is the code modified for BASS.Net:

Code: [Select]
using System;
using Un4seen.Bass;
using Un4seen.Bass.AddOn.Enc;

namespace test
{
    class MainClass
    {
        public static void Main(string[] args)
        {
            var source = "SourceShort.mp3";
            var destination = "SourceShort_out.wav";

            Bass.BASS_Init(0, 44100, 0, IntPtr.Zero);
            var chan = Bass.BASS_StreamCreateFile(source, 0,0,BASSFlag.BASS_STREAM_DECODE);
            BassEnc.BASS_Encode_Start(chan, destination, BASSEncode.BASS_ENCODE_PCM | BASSEncode.BASS_ENCODE_AUTOFREE, null, IntPtr.Zero);

            var buffer = new byte[16 * 1024];
            while (Bass.BASS_ChannelGetData(chan, buffer, buffer.Length) > -1)
            {
            }

            Bass.BASS_StreamFree(chan);
        }
    }
}

If you can reproduce the problem with that, are you also able to reproduce it with the CONVERT example?