Search code examples
androidaudio-streamingaacmediaextractor

MediaExtractor does not understand audio/aacp streams


I have my own MediaDataSource:



    class MyDataSource extends MediaDataSource {
        private static final String TAG = "MyDataSource";
        private HttpURLConnection connection;
        private BufferedInputStream inputStream;

        MyDataSource(@NonNull URL streamURL) throws Throwable {
            this.connection = (HttpURLConnection) streamURL.openConnection();
            this.connection.setRequestMethod("GET");
            this.connection.addRequestProperty("Icy-Metadata", "0");
            this.connection.connect();
            int responseCode = this.connection.getResponseCode();
            if (responseCode != 200)
                throw new IOException("http response code " + responseCode);
            for (Map.Entry<String, List<String>> header: this.connection.getHeaderFields().entrySet()) {
                for (String headerValue : header.getValue())
                    Log.v(TAG, "responseHeader(" + header.getKey() + ") = \"" + headerValue + "\"");
            }
            this.inputStream = new BufferedInputStream(connection.getInputStream());
        }

        @Override
        public long getSize() {
            return -1;
        }

        @Override
        public int readAt(long position, @NonNull byte[] buffer, int offset, int size) throws IOException {
            int bytesRead;
            int bytesReadTotal = 0;
            do {
                bytesRead = this.inputStream.read(buffer, offset + bytesReadTotal, size - bytesReadTotal);
                bytesReadTotal += bytesRead;
            } while(bytesRead != 0 && bytesReadTotal < size);
            return bytesReadTotal;
        }

        @Override
        public void close() {
            try {
                if (inputStream != null) {
                    inputStream.close();
                    inputStream = null;
                }
                if (connection != null) {
                    connection.disconnect();
                    connection = null;
                }
            } catch(IOException e) {
                Log.e(TAG, "close", e);
            }
        }
    }

And when I'm trying to play MP3 stream (ex. A.0.0.00Radio):



    MyDataSource dataSource = new MyDataSource(new URL("http://streaming.shoutcast.com/80sPlanet"));
    MediaExtractor mediaExtractor = new MediaExtractor();
    mediaExtractor.setDataSource();
    MediaFormat mediaFormat = mediaExtractor.getTrackFormat(0);
    String mime = mediaFormat.getString(MediaFormat.KEY_MIME);
    Log.v("Player", "mime: " + mime);
    mediaExtractor.selectTrack(0);
    MediaCodec mediaCodec = MediaCodec.createDecoderByType(mime);
    mediaCodec.configure(this.mediaFormat, null, null, 0);
    int sampleRate = mediaFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);
    AudioTrack audioTrack = new AudioTrack(
        AudioManager.STREAM_MUSIC,
        sampleRate,
        AudioFormat.CHANNEL_OUT_STEREO,
        AudioFormat.ENCODING_PCM_16BIT,
        AudioTrack.getMinBufferSize(sampleRate, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT),
        AudioTrack.MODE_STREAM);
    mediaCodec.setCallback(new MyCodecCallback());
    mediaCodec.start();
    audioTrack.play();

I see the following Logcat trace:


    07-24 18:11:49.958 7408-7671/com.sample.sandbox V/MyDataSource: responseHeader(null) = "HTTP/1.1 200 OK"
    07-24 18:11:49.958 7408-7671/com.sample.sandbox V/MyDataSource: responseHeader(Access-Control-Allow-Headers) = "Origin, Accept, X-Requested-With, Content-Type"
    07-24 18:11:49.958 7408-7671/com.sample.sandbox V/MyDataSource: responseHeader(Access-Control-Allow-Methods) = "GET, OPTIONS, HEAD"
    07-24 18:11:49.958 7408-7671/com.sample.sandbox V/MyDataSource: responseHeader(Access-Control-Allow-Origin) = "*"
    07-24 18:11:49.958 7408-7671/com.sample.sandbox V/MyDataSource: responseHeader(Cache-Control) = "no-cache, no-store"
    07-24 18:11:49.958 7408-7671/com.sample.sandbox V/MyDataSource: responseHeader(Connection) = "close"
    07-24 18:11:49.958 7408-7671/com.sample.sandbox V/MyDataSource: responseHeader(Content-Type) = "audio/mpeg"
    07-24 18:11:49.958 7408-7671/com.sample.sandbox V/MyDataSource: responseHeader(Date) = "Mon, 24 Jul 2017 18:11:59 GMT"
    07-24 18:11:49.959 7408-7671/com.sample.sandbox V/MyDataSource: responseHeader(Expires) = "Mon, 26 Jul 1997 05:00:00 GMT"
    07-24 18:11:49.959 7408-7671/com.sample.sandbox V/MyDataSource: responseHeader(icy-br) = "128"
    07-24 18:11:49.959 7408-7671/com.sample.sandbox V/MyDataSource: responseHeader(icy-genre) = "Decades,80s"
    07-24 18:11:49.959 7408-7671/com.sample.sandbox V/MyDataSource: responseHeader(icy-name) = "A.0.0.00Radio:All 80s All The Time"
    07-24 18:11:49.959 7408-7671/com.sample.sandbox V/MyDataSource: responseHeader(icy-notice1) = "<BR>This stream requires <a href="http://www.winamp.com">Winamp</a><BR>"
    07-24 18:11:49.959 7408-7671/com.sample.sandbox V/MyDataSource: responseHeader(icy-notice2) = "SHOUTcast DNAS/posix(linux x64) v2.5.1.725<BR>"
    07-24 18:11:49.959 7408-7671/com.sample.sandbox V/MyDataSource: responseHeader(icy-pub) = "1"
    07-24 18:11:49.959 7408-7671/com.sample.sandbox V/MyDataSource: responseHeader(icy-sr) = "44100"
    07-24 18:11:49.959 7408-7671/com.sample.sandbox V/MyDataSource: responseHeader(icy-url) = "http://a.0.00radio.com/80s/"
    07-24 18:11:49.959 7408-7671/com.sample.sandbox V/MyDataSource: responseHeader(Pragma) = "no-cache"
    07-24 18:11:49.959 7408-7671/com.sample.sandbox V/MyDataSource: responseHeader(Server) = "Icecast 2.3.3-kh8"
    07-24 18:11:49.959 7408-7671/com.sample.sandbox V/MyDataSource: responseHeader(X-Android-Received-Millis) = "1500919909958"
    07-24 18:11:49.959 7408-7671/com.sample.sandbox V/MyDataSource: responseHeader(X-Android-Response-Source) = "NETWORK 200"
    07-24 18:11:49.959 7408-7671/com.sample.sandbox V/MyDataSource: responseHeader(X-Android-Selected-Protocol) = "http/1.1"
    07-24 18:11:49.959 7408-7671/com.sample.sandbox V/MyDataSource: responseHeader(X-Android-Sent-Millis) = "1500919909816"
    07-24 18:11:49.961 7408-7671/com.sample.sandbox E/WVMExtractor: Failed to open libwvm.so: dlopen failed: library "libwvm.so" not found
    07-24 18:11:49.962 7408-7671/com.sample.sandbox V/Player: mime: audio/mpeg
    07-24 18:11:49.964 7408-7681/com.sample.sandbox I/OMXClient: Using client-side OMX mux.
    07-24 18:11:50.170 7408-7681/com.sample.sandbox I/MediaCodec: MediaCodec will operate in async mode
    [...]

It seems that everything is OK (the stream is actually playing through the device).
But if I try to open AAC stream (ex. COOLfahrenheit 93):



    MyDataSource dataSource = new MyDataSource(new URL("http://111.223.51.8:8005"));
    [...]

the MediaExtractor goes mad:

    07-24 18:38:43.863 32284-32690/com.sample.sandbox V/MyDataSource: responseHeader(null) = "HTTP/1.0 200 OK"
    07-24 18:38:43.863 32284-32690/com.sample.sandbox V/MyDataSource: responseHeader(content-type) = "audio/aacp"
    07-24 18:38:43.863 32284-32690/com.sample.sandbox V/MyDataSource: responseHeader(icy-br) = "128"
    07-24 18:38:43.863 32284-32690/com.sample.sandbox V/MyDataSource: responseHeader(icy-genre) = "Easy Listening, Pop"
    07-24 18:38:43.863 32284-32690/com.sample.sandbox V/MyDataSource: responseHeader(icy-name) = "COOLfahrenheit 93 - (4)"
    07-24 18:38:43.863 32284-32690/com.sample.sandbox V/MyDataSource: responseHeader(icy-notice1) = "<BR>This stream requires <a href="http://www.winamp.com">Winamp</a><BR>"
    07-24 18:38:43.863 32284-32690/com.sample.sandbox V/MyDataSource: responseHeader(icy-notice2) = "SHOUTcast DNAS/posix(linux x64) v2.4.7.256<BR>"
    07-24 18:38:43.863 32284-32690/com.sample.sandbox V/MyDataSource: responseHeader(icy-pub) = "1"
    07-24 18:38:43.863 32284-32690/com.sample.sandbox V/MyDataSource: responseHeader(icy-url) = "http://www.coolism.net"
    07-24 18:38:43.863 32284-32690/com.sample.sandbox V/MyDataSource: responseHeader(X-Android-Received-Millis) = "1500921523862"
    07-24 18:38:43.863 32284-32690/com.sample.sandbox V/MyDataSource: responseHeader(X-Android-Response-Source) = "NETWORK 200"
    07-24 18:38:43.863 32284-32690/com.sample.sandbox V/MyDataSource: responseHeader(X-Android-Selected-Protocol) = "http/1.0"
    07-24 18:38:43.863 32284-32690/com.sample.sandbox V/MyDataSource: responseHeader(X-Android-Sent-Millis) = "1500921523387"
    07-24 18:38:43.863 32284-32690/com.sample.sandbox V/MyDataSource: responseHeader(X-Clacks-Overhead) = "GNU Terry Pratchett"
    07-24 18:38:45.424 32284-32690/com.sample.sandbox E/WVMExtractor: Failed to open libwvm.so: dlopen failed: library "libwvm.so" not found
    07-24 18:38:45.425 32284-32690/com.sample.sandbox E/PlayerThread: error
                                                                              java.io.IOException: Failed to instantiate extractor.
                                                                                  at android.media.MediaExtractor.setDataSource(Native Method)
                                                                                  at com.sample.sandbox.Player.open(Player.java:204)
                                                                                  at com.sample.sandbox.Player.<init>(Player.java:231)
                                                                                  at com.sample.sandbox.PlayerThread.run(PlayerThread.java:28)

Does anybody know what is the issue? The issue is definitely is not in the stream itself - it is fully valid.


Solution

  • My main mistake was to ignore the position argument of the MediaDataSource::readAt() method.
    It turned out that MediaExtractor performs A LOT of skips through the file (forward as well as backward! - and this is very annoying when you are streaming HLS).
    I've been noticed that amount of these skips (and its' range) depend on the content type as well as particular codec. And my main point in this question was "MediaExtractor does not understand aacp" because AAC codec strongly requires skipping, but MP3 codec does not.