Android - MediaPlayer Buffer Size in ICS 4.0

denizmveli picture denizmveli · Apr 8, 2012 · Viewed 15.1k times · Source

I'm using a socket as a proxy to the MediaPlayer so I can download and decrypt mp3 audio before writing it to the socket. This is similar to the example shown in the NPR news app however I'm using this for all Android version 2.1 - 4 atm.

NPR StreamProxy code - http://code.google.com/p/npr-android-app/source/browse/Npr/src/org/npr/android/news/StreamProxy.java

My issue is that playback is fast for 2.1 - 2.3, but in Android 4.0 ICS the MediaPlayer buffers too much data before firing the onPrepared listener.

An example amount of data written to the Socket OutputStream before onPrepared():

On SGS2 with 2.3.4 - onPrepared() after ~ 133920 bytes

On Nexus S with 4.0.4 - onPrepared() after ~ 961930 bytes

This also occurs on the Galaxy Nexus.

Weirdly the 4.0 emulator doesn't buffer as much data as 4.0 devices. Anyone experience a similar issue with the MediaPlayer on ICS?

EDIT

Here's how the proxy is writing to the socket. In this example it's from a CipherInputStream loaded from a file, but the same occurs when it's loaded from the HttpResponse.

final Socket client = (setup above)

// encrypted file input stream
final CipherInputStream inputStream = getInputStream(file);

// setup the socket output stream
final OutputStream output =  client.getOutputStream();

// Writing the header
final String httpHeader = buildHttpHeader(file.length());
final byte[] buffer = httpHeader.getBytes("UTF-8");
output.write(buffer, 0, buffer.length);

int writtenBytes = 0;
int readBytes;
final byte[] buff = new byte[1024 * 12]; // 12 KB

while (mIsRunning && (readBytes = inputStream.read(buff)) != -1) {
    output.write(buff, 0, readBytes);
    writtenBytes += readBytes;
}

output.flush();
output.close();

The HTTP Headers that are written to the MediaPlayer before the audio..

private String buildHttpHeader(final int contentLength) {
    final StringBuilder sb = new StringBuilder();

    sb.append("HTTP/1.1 200 OK\r\n");
    sb.append("Content-Length: ").append(contentLength).append("\r\n");
    sb.append("Accept-Ranges: bytes\r\n" );
    sb.append("Content-Type: audio/mpeg\r\n");
    sb.append("Connection: close\r\n" );
    sb.append("\r\n");

    return sb.toString();
}

I've looked around for alternate implementations but as I have encrypted audio and the MediaPlayer does not support InputStreams as a data source my only option (I think..) is to use a proxy such as this.

Again, this is working fairly well Android 2.1 - 2.3 but in ICS the MediaPlayer is buffering a huge amount of this data before playing.

EDIT 2 :

Further testing is showing that this is also an issue on the SGS2 once upgraded to Android 4.0.3. So it seems like the MediaPlayer's buffering implementation has changed significantly in 4.0. This is frustrating as the API provides no way to alter the behaviour.

EDIT 3 :

Android bug created. Please add comments and star there as well http://code.google.com/p/android/issues/detail?id=29870

EDIT 4 :

My playback code is fairly standard.. I have the start() call on the MediaPlayer in my onPrepared() method.

mCurrentPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mCurrentPlayer.setDataSource(url);
mCurrentPlayer.prepareAsync();

Have tried it using just prepare() and also ajacian81's recommended way but to no avail.

I should add that recently a Google employee got back to me about my question and confirmed that the buffer size was intentionally increased in ICS (for HD content). It has been requested to the API developers to add the ability to set a buffer size on MediaPlayer.

Though I think this API change request had been around before I came along so I wouldn't advise anyone to hold their breath.

Answer

ajacian81 picture ajacian81 · May 17, 2012

Would it be possible to see the code where you're start()ing the MediaPlayer?

Are you using the STREAM_MUSIC audio stream type?

player.setAudioStreamType(AudioManager.STREAM_MUSIC);

Have you also experimented between player.prepareAsync(); and player.prepare();?

There was a similar issue last year I remember, where the solution was to: start, pause and then onPrepared to start():

player.setAudioStreamType(AudioManager.STREAM_MUSIC); 
player.setDataSource(src); 
player.prepare(); 
player.start(); 
player.pause(); 
player.setOnPreparedListener(new OnPreparedListener() {     
@Override
                public void onPrepared(MediaPlayer mp) {
                    player.start();                
                }
          });

Unlikely to be the fix in this case, but while you're spinning your wheels this might be worth a shot.