0

I have an application that playback audio. It takes encoded audio data over RTP and decode it to 16bit array. The decoded 16bit array is converted to 8 bit array (byte array) as this is required for some other functionality.

Even though audio playback is working it is breaking continuously and very hard to recognise audio output. If I listen carefully I can tell it is playing the correct audio.

I suspect this is due to the fact I convert 16 bit data stream into a byte array and use the write(byte[], int, int, AudioTrack.WRITE_NON_BLOCKING) of AudioTrack class for audio playback.

Therefore I converted the byte array back to a short array and used write(short[], int, int, AudioTrack.WRITE_NON_BLOCKING) method to see if it could resolve the problem.

However now there is no audio sound at all. In the debug output I can see the short array has data.

What could be the reason?

Here is the AUdioTrak initialization

            sampleRate      =AudioTrack.getNativeOutputSampleRate(AudioManager.STREAM_MUSIC);

            minimumBufferSize = AudioTrack.getMinBufferSize(sampleRate, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT);

            audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate,
                    AudioFormat.CHANNEL_OUT_STEREO,
                    AudioFormat.ENCODING_PCM_16BIT,
                    minimumBufferSize,
                    AudioTrack.MODE_STREAM);

Here is the code converts short array to byte array

for (int i=0;i<internalBuffer.length;i++){

        bufferIndex = i*2;
        buffer[bufferIndex] = shortToByte(internalBuffer[i])[0];
        buffer[bufferIndex+1] = shortToByte(internalBuffer[i])[1];
    }

Here is the method that converts byte array to short array.

public short[] getShortAudioBuffer(byte[] b){
    short audioBuffer[] = null;
    int index = 0;
    int audioSize = 0;
    ByteBuffer byteBuffer = ByteBuffer.allocate(2);

    if ((b ==null) && (b.length<2)){
        return null;
    }else{
        audioSize = (b.length - (b.length%2));
        audioBuffer   = new short[audioSize/2];
    }

    if ((audioSize/2) < 2)
        return null;

    byteBuffer.order(ByteOrder.LITTLE_ENDIAN);


    for(int i=0;i<audioSize/2;i++){
        index = i*2;
        byteBuffer.put(b[index]);
        byteBuffer.put(b[index+1]);
        audioBuffer[i] = byteBuffer.getShort(0);
        byteBuffer.clear();
        System.out.print(Integer.toHexString(audioBuffer[i]) + " ");
    }
    System.out.println();

    return audioBuffer;
}

Audio is decoded using opus library and the configuration is as follows;

opus_decoder_ctl(dec,OPUS_SET_APPLICATION(OPUS_APPLICATION_AUDIO));
    opus_decoder_ctl(dec,OPUS_SET_SIGNAL(OPUS_SIGNAL_MUSIC));
    opus_decoder_ctl(dec,OPUS_SET_FORCE_CHANNELS(OPUS_AUTO));
    opus_decoder_ctl(dec,OPUS_SET_MAX_BANDWIDTH(OPUS_BANDWIDTH_FULLBAND));
    opus_decoder_ctl(dec,OPUS_SET_PACKET_LOSS_PERC(0));
    opus_decoder_ctl(dec,OPUS_SET_COMPLEXITY(10));     // highest complexity
    opus_decoder_ctl(dec,OPUS_SET_LSB_DEPTH(16)); // 16bit = two byte samples
    opus_decoder_ctl(dec,OPUS_SET_DTX(0));             // default - not using discontinuous transmission
    opus_decoder_ctl(dec,OPUS_SET_VBR(1));             // use variable bit rate
    opus_decoder_ctl(dec,OPUS_SET_VBR_CONSTRAINT(0));  // unconstrained
    opus_decoder_ctl(dec,OPUS_SET_INBAND_FEC(0));      // no forward error correction
Jason Nanay
  • 115
  • 1
  • 12
  • some information on converting content of short to byte is described by Neil Townsend but not what I want. https://stackoverflow.com/questions/15124050/audiotrack-short-array-to-byte-array-distortion-using-jlayerjava-mp3-decoder – Jason Nanay Jul 11 '18 at 09:04

2 Answers2

0

Let's assume you have a short[] array which contains the 16-bit one channel data to be played. Then each sample is a value between -32768 and 32767 which represents the signal amplitude at the exact moment. And 0 value represents a middle point (no signal). This array can be passed to the audio track with ENCODING_PCM_16BIT format encoding.

But things are going weird when playing ENCODING_PCM_8BIT is used (See AudioFormat)

In this case each sample encoded by one byte. But each byte is unsigned. That means, it's value is between 0 and 255, while 128 represents the middle point.

Java has no unsigned byte format. Byte format is signed. I.e. values -128...-1 will represent actual values of 128...255. So you have to be careful when converting to the byte array, otherwise it will be a noise with barely recognizable source sound.

short[] input16 = ... // the source 16-bit audio data;

byte[] output8 = new byte[input16.length];

for (int i = 0 ; i < input16.length ; i++) {
  // To convert 16 bit signed sample to 8 bit unsigned
  // We add 128 (for rounding), then shift it right 8 positions
  // Then add 128 to be in range 0..255
  int sample = ((input16[i] + 128) >> 8) + 128;
  if (sample > 255) sample = 255; // strip out overload
  output8[i] = (byte)(sample); // cast to signed byte type
}

To perform backward conversion all should be the same: each single sample to be converted to exactly one sample of the output signal

byte[] input8 = // source 8-bit unsigned audio data;

short[] output16 = new short[input8.length];

for (int i = 0 ; i < input8.length ; i++) { 
  // to convert signed byte back to unsigned value just use bitwise AND with 0xFF
  // then we need subtract 128 offset
  // Then, just scale up the value by 256 to fit 16-bit range
  output16[i] = (short)(((input8[i] & 0xFF) - 128) * 256);
}
AterLux
  • 4,566
  • 2
  • 10
  • 13
  • Thanks AterLux for detailed explanation on audio encoding. With regard to the conversion of short to byte, I haven't modified content. I only changed the way data representation from short to byte using following method. private byte[] shortToByte(short val){ byte converted[] = new byte[2]; converted[0] = (byte)(val & 0xff); converted[1] = (byte)((val >> 8) & 0xff); return converted; } Therefore, wouldn't it playback properly once converted back to short? – Jason Nanay Jul 12 '18 at 00:53
  • So, you have short[] array which plays ok, then you convert it to byte[] (still playing 16bits per sample, i.e. 2 bytes) and it plays not god, then you convert it back to short[] and it doesn't play at all, yes? If so, can you bring both the code which converts arrays is both ways? – AterLux Jul 12 '18 at 08:13
  • I have converted byte array back to short array and audio fluctuation still remain the same. Here is how I converted back to short. Please note content hasn't been modified. for (int i=0; i – Jason Nanay Jul 12 '18 at 09:31
  • I do not understand, then, what the problem do you have? Is sound is changed after it is converted there and back? You gave different parts of the source code and no one presents the whole process of conversion of a short[] array into byte[] and then back again. – AterLux Jul 12 '18 at 09:38
  • I just added to logic used to convert from short array to byte array to the original question. Sound fluctuates with AudioTrack playback using either short array playback or byte array playback. Could this be an issue with decoder? I have added decoder configuration to the original question as well. – Jason Nanay Jul 12 '18 at 09:55
  • Can you provide an example of sound? And also part of code used to enqueue audio data to AudioTrack. – AterLux Jul 12 '18 at 09:58
  • Here is how I pass data to audio sink audioTrack.write(shortBuffer, 0, shortBuffer.length, AudioTrack.WRITE_BLOCKING); audioTrack.flush(); I haven't got the logic to capture the data into a file. – Jason Nanay Jul 12 '18 at 10:41
  • You should remove audioTrack.flush(); - it discards all the data in the audio buffer which is not played yet. I.e. if write - flush called repeatedly, sound may stutter – AterLux Jul 12 '18 at 11:05
  • After adding silence when not enough data to play the audio is much more clear. With the improved condition I noticed some parts of the voice is clear while the others are still fluctuating. Could this be an issue with number of channels? – Jason Nanay Jul 12 '18 at 12:00
  • After setting the channel config to AudioFormat.CHANNEL_OUT_DEFAULT now audio is playing smoothly but the tone is different. – Jason Nanay Jul 12 '18 at 13:29
  • Use CHANNEL_OUT_MONO or CHANNEL_OUT_STEREO depend on how many channels actually in the data. Adjust tone by setting correct samleRate – AterLux Jul 12 '18 at 14:55
  • Thanks AterLux for the suggestion. I've tried all those options but none of them has given the required outcome. Anyways, I have closed this as initial problem I had was resolved. A new topic is created for discussion playback issues at https://stackoverflow.com/questions/51319208/regulate-android-audiotrack-playback-speed – Jason Nanay Jul 13 '18 at 06:52
0

The issue of not being able to convert data from byte array to short array was resolved when used bitwise operators instead of using ByteArray. It could be due not setting the correct parameters in ByteArray or it is not suitable for such conversion.

Nevertheless implementing conversion using bitwise operators resolved the problem. Since the original question has been resolved by this approach, please consider this as the final answer.

I will raise a separate topic for playback issue.

Thank you for all your support.

Jason Nanay
  • 115
  • 1
  • 12