19 May '13 - 16:38 *
Welcome, Guest. Please login or register.
Did you miss your activation email?

Login with username, password and session length
 
   Home   Help Search Login Register  
Pages: [1]
  Reply  |  Print  
Author Topic: mp3 and recording and missing data  (Read 942 times)
jakob
Posts: 68


« on: 29 Aug '12 - 18:31 »
Reply with quoteQuote

hi
i record via ACM mp3 codec but when i stop the recording and go through the bytes in the resulting file, then the last frame is not complete, it's missing data.
Is there a reason, that i can't figure out, for this?

I'm using a callback to write to the file.
BassEnc.BASS_Encode_StartACM(m_Stream, m_Codec, BASSEncode.BASS_ENCODE_NOHEAD, m_EncCallback, IntPtr.Zero);

private void EncodingCallback(int handle, int channel, IntPtr buffer, int length, IntPtr user)
        {
            if (m_EncWriter != null)
            {
                try
                {
                    byte[] resBuffer = new byte[length];
                    Marshal.Copy(buffer, resBuffer, 0, length);
                    m_EncWriter.Write(resBuffer);
                    m_EncWriter.Flush();
                    //Debug.WriteLine("time: " + DateTime.Now + " len in ms: " + LengthInMilliseconds + " pos in ms: " + PositionInMilliseconds);

                }
                catch (Exception ex)
                {
                    log.Error("Error processing recorder data", ex);
                }
            }
        }
what happens if i pause the recording in between callbacks. will i then miss out on some data?
and if so, is there a way to get the last data?
Logged
Ian @ un4seen
Administrator
Posts: 15244


« Reply #1 on: 30 Aug '12 - 14:13 »
Reply with quoteQuote

When you stop the recording, are you also stopping/freeing the encoder, eg. calling BASS_Encode_Stop? If not, there may be some sample data sitting in the encoder's buffer, waiting for more data to join it before being processed. For example, if an MP3 encoder doesn't have enough data to complete a frame, it will wait for more data to complete the frame before producing more output. If the encoder is freed, then it knows there is no more data coming, and it can end with a partial frame.
Logged
jakob
Posts: 68


« Reply #2 on: 30 Aug '12 - 14:18 »
Reply with quoteQuote

hi Ian
yes i free the encoder after i have paused the recording.
Is it possible to setup so i only recieve full frames in the callback?
and nothing if the frame doesn't get filled up.
Logged
Ian @ un4seen
Administrator
Posts: 15244


« Reply #3 on: 30 Aug '12 - 14:25 »
Reply with quoteQuote

Do you want to drop the final partial frame? If so, I think that could be achieved by using a global variable to signal to the ENCODEPROC that it should drop the data. Something like this...

bool dropdata=false;

...

dropdata=true; // signal that the data should be dropped
BASS_Encode_Stop(encoder); // stop/free the encoder

...

void CALLBACK EncodeProc(HENCODE handle, DWORD channel, void *buffer, DWORD length, void *user)
{
if (!dropdata) {
// process the data
}
}
Logged
jakob
Posts: 68


« Reply #4 on: 30 Aug '12 - 14:42 »
Reply with quoteQuote

The problem is that the callback does not return hole frames.
So at any point during recording, i can't be sure, when the amount of data i'v recieved from the callback equals something devidable with hole frames.
your approach is fine if i know that i always get hole frames in the callback.
I don't know this, but as i see it, the callback will always give something like this: |----|----|-- (some hole frames and last one not hole)
is this correctly understood by my part
Logged
jakob
Posts: 68


« Reply #5 on: 31 Aug '12 - 10:03 »
Reply with quoteQuote

hi Ian

I solved it by making a MP3Writer that would only write hole frames to the file.

But i have a serious problem when i am done recording, the recorder gives back one length in ms, but when i load it in the player the player gives back another length in ms, It's very frustrating Huh

to keep it simple, i tried to only record to one file and then load that file with the player. ACM format i'm currently using is MPEG Layer III 64kbps 44.1kHz Mono CBR.
Do you have any suggestions?
« Last Edit: 31 Aug '12 - 10:20 by jakob » Logged
Ian @ un4seen
Administrator
Posts: 15244


« Reply #6 on: 31 Aug '12 - 13:33 »
Reply with quoteQuote

The problem is that the callback does not return hole frames.
So at any point during recording, i can't be sure, when the amount of data i'v recieved from the callback equals something devidable with hole frames.
your approach is fine if i know that i always get hole frames in the callback.
I don't know this, but as i see it, the callback will always give something like this: |----|----|-- (some hole frames and last one not hole)
is this correctly understood by my part

I would have expected the MP3 codec to output whole frames, but checking it myself just now, that appears not to be the case, as you say. The codec seems to be holding some data back for some reason.

I also noticed that the final data isn't being flushed from the codec (to the ENCODEPROC) when freeing the encoder, eg. when calling BASS_Encode_Stop. Here's an update that should fix that...

   www.un4seen.com/stuff/bassenc.dll

If you would like to drop the final frame, what you could do is to locate and drop it from the final block of data received in your ENCODEPROC. Something like this...

void CALLBACK EncodeProc(HENCODE handle, DWORD channel, void *buffer, DWORD length, void *user)
{
if (dropdata) {
BYTE *b=(BYTE*)buffer;
for (int a=length-4; a>=0; a--) {
if (b[a]==0xff && (b[a+1]>>5)==7 && ((b[a+1]>>3)&3)!=1 && ((b[a+1]>>1)&3)==1 && (b[a+2]>>4)!=15 && ((b[a+2]>>2)&3)!=3) { // found an MP3 frame header (probably) *
length=a; // limit the length to this place
break;
}
}
}
// process the data
}

* explanation: here

But i have a serious problem when i am done recording, the recorder gives back one length in ms, but when i load it in the player the player gives back another length in ms, It's very frustrating Huh

Do you mean the length (BASS_ChannelGetLength) of the playback stream doesn't match the final position (BASS_ChannelGetPosition) of the recording channel? If so, is it shorter? That could be due to the final data not being flushed when the encoder was freed, in which case, the update above should help.

Note the MP3 file's length is unlikely to match exactly due to it being frame-based, ie. the length will be a mulitple of the frame length. The LAME encoder adds some info to the file's header to allow the decoder (eg. BASS) to skip the extra padding, but the ACM codec doesn't do that.
Logged
jakob
Posts: 68


« Reply #7 on: 31 Aug '12 - 14:30 »
Reply with quoteQuote

hi Ian

unfortunatly it didn't seem to work. This is how i check it:
this code is in my stop recording method
                   bool res = Bass.BASS_ChannelPause(m_Stream);
                    if (!res)
                    {
                        BASSError error = Bass.BASS_ErrorGetCode();
                        log.Error("Bass error " + error.ToString() + " when pausing recorder");
                    }
                    else
                    {
                        double posR = Bass.BASS_ChannelBytes2Seconds(m_Stream, Bass.BASS_ChannelGetPosition(m_Stream, BASSMode.BASS_POS_BYTES)) * 1000.0f;
                                              
                        res = BassEnc.BASS_Encode_Stop(m_EncStream);
                        if (!res)
                        {
                            BASSError error = Bass.BASS_ErrorGetCode();
                            log.Error("Bass error " + error.ToString() + " when stopping recorder encoder");
                        }
                        
                        m_EncWriter.Flush();
                        m_EncWriter.Close();
                        double lenP = AudioBase.SoundFileLengthInMilliseconds(CurrentAudioRecordingFile);

                        res = Bass.BASS_ChannelStop(m_Stream);
                        if (!res)
                        {
                            BASSError error = Bass.BASS_ErrorGetCode();
                            log.Error("Bass error " + error.ToString() + " when stopping recorder");
                        }
                        double lenPAfter = AudioBase.SoundFileLengthInMilliseconds(CurrentAudioRecordingFile);
                        log.Info("Recorder stopped");
                    }
the "posR" is the position of the recorder stream after pausing it
the "lenP" is the length of the just recorded file opened with BASS_StreamCreateFile(file, 0, 0, BASSFlag.BASS_STREAM_DECODE | BASSFlag.BASS_SAMPLE_FLOAT | BASSFlag.BASS_STREAM_PRESCAN)
the "lenPAfter" is the length og the recorded file after the BASS_ChannelStop has been called.

the results of these are:

posR = 1562.3582 ms
lenP = 1488,9795 ms
lenPAfter = 1488,9795 ms

so the recorder stream is larger than the player stream Shocked

this is how i get lenP and lenPAfter:
        public static double SoundFileLengthInMilliseconds(string file)
        {
            double length = 0;
            BASSError bassResult;
            try
            {
                log.Info("Start getting length of audio file: " + file);
                bool res = Bass.BASS_Init(-1, SampleFreq, BASSInit.BASS_DEVICE_DEFAULT | BASSInit.BASS_DEVICE_MONO, IntPtr.Zero);
                if (!res)
                {
                    BASSError error = Bass.BASS_ErrorGetCode();
                    log.Error("Bass error " + error.ToString() + " when initializing channel: -1");
                }
                int stream = Bass.BASS_StreamCreateFile(file, 0, 0, BASSFlag.BASS_STREAM_DECODE | BASSFlag.BASS_SAMPLE_FLOAT | BASSFlag.BASS_STREAM_PRESCAN);
                if (stream != 0)
                    length = Bass.BASS_ChannelBytes2Seconds(stream, Bass.BASS_ChannelGetLength(stream, BASSMode.BASS_POS_BYTES)) * 1000.0f;
                else
                    length = .0f;

                res = Bass.BASS_StreamFree(stream);
                res = Bass.BASS_Stop();
                if (!res)
                {
                    BASSError error = Bass.BASS_ErrorGetCode();
                    log.Error("Bass error " + error.ToString() + " when stopping channel");
                }
                res = Bass.BASS_Free();
                if (!res)
                {
                    BASSError error = Bass.BASS_ErrorGetCode();
                    log.Error("Bass error " + error.ToString() + " when freeing channel");
                }
                bassResult = Bass.BASS_ErrorGetCode();
                if (bassResult != BASSError.BASS_OK)
                    log.Error("BassError: " + bassResult.ToString());

                log.Info("Ended getting length of audio file");
            }
            catch (Exception ex)
            {
                log.Error("Error getting length of audio file", ex);
            }
            log.Info("Total length of " + file + " is " + length.ToString());
            return length;
        }
I think that the difference in length might be due to the last frame not being complete
The recorder add the last half frame to its position calculation but the player does not.
« Last Edit: 31 Aug '12 - 14:41 by jakob » Logged
Ian @ un4seen
Administrator
Posts: 15244


« Reply #8 on: 31 Aug '12 - 17:34 »
Reply with quoteQuote

Are you starting the recording in a paused state (via the BASS_RECORD_PAUSE flag), and setting the encoder on it before resuming it? If not, there may have been some data recorded before the encoder was running. To confirm how much data has been sent to the encoder, you can use BASS_Encode_GetCount (with BASS_ENCODE_COUNT_IN).
Logged
jakob
Posts: 68


« Reply #9 on: 3 Sep '12 - 08:30 »
Reply with quoteQuote

hi Ian

first i'm starting the recorder in paused state:
Bass.BASS_RecordStart(SampleFreq, 1, BASSFlag.BASS_RECORD_PAUSE, m_RecCallback, IntPtr.Zero);
then im starting the encoder:
BassEnc.BASS_Encode_StartACM(m_Stream, m_Codec, BASSEncode.BASS_ENCODE_NOHEAD, m_EncCallback, IntPtr.Zero);
and last i start it all by:
Bass.BASS_ChannelPlay(m_Stream, false);

This is what i see from BASS_Encode_GetCount:
BASS_ENCODE_COUNT_IN  is the same as the recorder position in bytes.
BASS_ENCODE_COUNT_OUT is much smaller but i guess it the encoded size (not sure how to check if it's the correct size).
all the queue count values are -1.

posR                              4687.4376417233561
lenP                               4623.6734693877552
enc_Count_In               413432                              
enc_Count_Out               36903 (same as the resulting file size)                
enc_Count_Queue               -1                                
enc_Count_Queue_Fail      -1                                
enc_Count_Queue_Limit    -1                                
recorderpos in bytes         413432                                

Does this indicate that the problem is the players ability to calculate the length?
Logged
Ian @ un4seen
Administrator
Posts: 15244


« Reply #10 on: 3 Sep '12 - 17:04 »
Reply with quoteQuote

Please confirm how you are getting the "posR" and "lenP" values. I can see them set in your code above, but what is the "m_Stream" handle and what is "AudioBase.SoundFileLengthInMilliseconds" doing? Also, what is the "m_EncStream" handle?

Also confirm that the updated BASSENC.DLL version (rather than an older version) is being loaded by checking the BASS_Encode_GetVersion return value (it should be 0x02040905).
Logged
jakob
Posts: 68


« Reply #11 on: 4 Sep '12 - 08:14 »
Reply with quoteQuote

hi Ian

I get  posR by:
Bass.BASS_ChannelBytes2Seconds(m_Stream, Bass.BASS_ChannelGetPosition(m_Stream, BASSMode.BASS_POS_BYTES)) * 1000.0f;
m_Stream is what is returned by:
Bass.BASS_RecordStart(SampleFreq, 1, BASSFlag.BASS_RECORD_PAUSE, m_RecCallback, IntPtr.Zero);

I get lenP via a function i made like this:
public static double SoundFileLengthInMilliseconds(string file)
        {
            double length = 0;
            BASSError bassResult;
            try
            {
                log.Info("Start getting length of audio file: " + file);
                bool res = Bass.BASS_Init(-1, SampleFreq, BASSInit.BASS_DEVICE_DEFAULT | BASSInit.BASS_DEVICE_MONO, IntPtr.Zero);
                if (!res)
                {
                    BASSError error = Bass.BASS_ErrorGetCode();
                    log.Error("Bass error " + error.ToString() + " when initializing channel: -1");
                }
                int stream = Bass.BASS_StreamCreateFile(file, 0, 0, BASSFlag.BASS_STREAM_DECODE | BASSFlag.BASS_SAMPLE_FLOAT | BASSFlag.BASS_STREAM_PRESCAN);
                if (stream != 0)
                    length = Bass.BASS_ChannelBytes2Seconds(stream, Bass.BASS_ChannelGetLength(stream, BASSMode.BASS_POS_BYTES)) * 1000.0f;
                else
                    length = .0f;

                res = Bass.BASS_StreamFree(stream);
                res = Bass.BASS_Stop();
                if (!res)
                {
                    BASSError error = Bass.BASS_ErrorGetCode();
                    log.Error("Bass error " + error.ToString() + " when stopping channel");
                }
                res = Bass.BASS_Free();
                if (!res)
                {
                    BASSError error = Bass.BASS_ErrorGetCode();
                    log.Error("Bass error " + error.ToString() + " when freeing channel");
                }
                bassResult = Bass.BASS_ErrorGetCode();
                if (bassResult != BASSError.BASS_OK)
                    log.Error("BassError: " + bassResult.ToString());

                log.Info("Ended getting length of audio file");
            }
            catch (Exception ex)
            {
                log.Error("Error getting length of audio file", ex);
            }
            log.Info("Total length of " + file + " is " + length.ToString());
            return length;
        }

m_EncStream is what is returned by:
/m_EncStream = BassEnc.BASS_Encode_StartACMFile(m_Stream, m_Codec, BASSEncode.BASS_ENCODE_NOHEAD, CurrentAudioRecordingFile);

the version og BassEnc.dll is correct
Logged
Ian @ un4seen
Administrator
Posts: 15244


« Reply #12 on: 4 Sep '12 - 17:28 »
Reply with quoteQuote

OK, that code looks fine.

To investigate, I tried using the MP3 ACM codec myself and compared the lengths, and the resulting MP3 file was actually longer than the recording, which is to be expected due to the padding that MP3 encoding adds to the start and end of the data. That's with the updated BASSENC.DLL posted above. I also tried the release BASSENC.DLL version, and in that case the MP3 file was slightly shorter than the recording, due to the issue of the final data not being flushed from the codec. Are you absolutely certain that the updated BASSENC.DLL is being loaded in your case, ie. you are calling BASS_Encode_GetVersion to confirm it? If so, please state what Windows version you are using, in case that is having some bearing, eg. different codec version.
Logged
jakob
Posts: 68


« Reply #13 on: 5 Sep '12 - 08:52 »
Reply with quoteQuote

hi Ian

i verified that the version was correct (0x02040905) and also im using the updated dll that you posted.

it is still the same.
recording position, after pausing the recording and stopping the encoder is larger than player length.
windows version is professional 64-bit
codec installed are:
ACM Microsoft IMA ADPCM CODEC                                       0011 imaadp32.acm 6.1.7600.16385
ACM Microsoft CCITT G.711 A-Law and u-Law CODEC         0007 msg711.acm 6.1.7600.16385
ACM Microsoft GSM 6.10 Audio CODEC                                0031 msgsm32.acm 6.1.7600.16385
ACM Microsoft ADPCM CODEC                                              0002 msadp32.acm 6.1.7600.16385
ACM Fraunhofer IIS MPEG Layer-3 Codec (decode only)      0055 l3codeca.acm 1.9.0.401
ACM Speex CODEC v1.0.5                                                    A109 speexw.acm 0.1.0.5
ACM Messenger Audio Codec                                              028E sirenacm.dll 15.4.3508.1109
ACM Microsoft PCM-konverter                                              0001   
ACM LAME MP3 Codec v0.9.2 - 3.99.5                                  0001

it must be the LAME codec that i'm using since it's the only encoding codec i'v got installed.
by the way, are you sure that it was the rigth dll that you posted?
Logged
Ian @ un4seen
Administrator
Posts: 15244


« Reply #14 on: 5 Sep '12 - 15:42 »
Reply with quoteQuote

Ah, you have the LAME codec installed. I was using the default Windows/Fraunhofer codec. Trying with the LAME codec, I notice that it isn't outputing anything at the end when the codec is flushed, so that is probably what's resulting in the shorter MP3 file. To get the codec to flush the final data out, you could try feeding in a frame of silence, something like this...

BYTE pad[1152*2]={0}; // an MP3 frame worth of mono 16-bit silence
BASS_Encode_Write(handle, pad, sizeof(pad)); // send the silence to the encoder
Logged
jakob
Posts: 68


« Reply #15 on: 6 Sep '12 - 08:48 »
Reply with quoteQuote

okay thanks, how does the recorder select the codec for encoding if more MP3 codecs are installed?
Logged
Ian @ un4seen
Administrator
Posts: 15244


« Reply #16 on: 6 Sep '12 - 14:04 »
Reply with quoteQuote

BASSenc lets Windows decide which ACM codec to use for the requested format. If there are multiple codecs that support the format, then the one with the highest priority will be used. At least, that is the case on XP and older where a codec's priority can be set via the "Audio Codecs Properties", but I'm not sure if that option is still present in Vista/7 (I don't see it). It looks like the default Windows/Fraunhofer MP3 codec on Vista/7 doesn't support encoding anyway, so there won't be any conflict with that.
Logged
Pages: [1]
  Reply  |  Print  
 
Jump to:  

Powered by SMF 1.1.18 | SMF © 2013, Simple Machines