Почему для Android MediaPlayer требуется много времени для подготовки некоторых живых потоков для воспроизведения?

Я нахожу большие различия во времени, которое требуется для Media Media Player для подготовки к воспроизведению в реальном времени с различными потоками.

Жесткие данные

Я добавил logging между prepareAsync() и обратным вызовом onPrepared (MediaPlayer mp) и протестировал несколько потоков несколько раз каждый. Времена для каждого потока были очень последовательными (+/- одна секунда), и вот результаты:

  • поток новостей MPR: 27 секунд (http://newsstream1.publicradio.org:80/)
  • MPR классический музыкальный поток: 15 секунд (http://classicalstream1.publicradio.org:80/)
  • MPR Текущий поток: 7 секунд (http://currentstream1.publicradio.org:80/)
  • поток PRI: 52 секунды (http://pri-ice.streamguys.biz/pri1)

Тесты были выполнены на Nexus S с Android 2.3.4 на 3G-соединении (~ 1100 Кбит/с).

Воспроизведение безпоточных аудиофайлов MP3 не является проблемой.

Вот фрагменты того, как я играю в потоки:

Подготовить MediaPlayer:

...
mediaPlayer.setDataSource(playUrl);
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.prepareAsync();
...

Затем в onPrepared (MediaPlayer mp):

mediaPlayer.start();

Почему так долго нужно готовить некоторые потоки, но не другие? Вышеприведенные данные, по-видимому, предполагают, что это может быть основано на количестве данных, которые были буферизованы, а не на продолжительности буферизованного аудиоконтента. Неужели это действительно так?

Обновление: Я тестировал прямую трансляцию на физических устройствах с Android 1.6, 2.2 и 2.3.4 и эмуляторами с 1.6, 2.1, 2.2, 2.3.1 и 2.3.3. Я вижу только длительную задержку на 2.3.3 и 2.3.4. Старшие версии запускают воспроизведение в течение 5 секунд.

Ответ 1

Кажется, что это буферизация фиксированного количества данных, а не фиксированное количество времени. Для тех, кто не знает битрейты различных типов потоков NPR с верхней части головы, данные выглядят так:

Помимо расхождения между двумя потоками 128 Кбит/с, существует очень хорошая корреляция между продолжительностью битрейта и буферизации.

В любом случае Android является открытым исходным кодом, поэтому вы всегда можете посмотреть, что он делает. К сожалению, prepareAsync() и prepare() являются собственными методами, и кажется, что события, связанные с буфером, отправляются также из собственного процесса.

Вы пытались подключить OnBufferingUpdateListener к MediaPlayer для получения более тонких обновлений состояния буфера? Возможно, было бы интересно сравнить скорость, с которой доставляются события, и в каком проценте буфер заполняется каждым событием в разных потоках. Вы можете перекрестно ссылаться на битрусы потока, и если 4 секунды буферизации со скоростью 32 кбит/с заполняет буфер таким же процентом, как и 1 секунда буферизации со скоростью 128 кбит/с, тогда, я думаю, вы найдете свой ответ.

Ответ 2

Переключить MediaPlayer на FFmpegMediaPlayer работает намного лучше, чем MediaPlayer, если вы хотите протестировать свои потоки, вы можете сделать это через demo, что у них есть.

Ответ 3

Недавно я отладил эту же проблему с провайдером потокового аудио. Эта проблема связана со сценарием и потоковыми источниками 32 кбит/с и ниже. Мы прошли через те же потоки, измеряя время отклика на 24, 32, 48, 64 и 128 кбит/с.

  • 24 → 46 секунд для начала потоковой передачи
  • 32 → 24 секунды для начала потоковой передачи.
  • 48 → 2 секунды для начала потоковой передачи
  • 64 → 2 секунды для начала потоковой передачи
  • 128 → 2 секунды для начала потоковой передачи

Это из последовательного беспроводного соединения, усредненного по 10 попыток с каждой скоростью передачи. Ключ, как заметил Тревис, заключался в том, что stagefright не мог понять, как долго буферизовать звук. Иногда я вижу сообщение "error: 1, -21492389" или около того, что, казалось, бесшумно разбивало игрока stagefright. Я попытался отследить это и в конце концов пришел к выводу, что очень медленные потоки (суб 24 кбит/с), по-видимому, вызывают переполнение буфера, потому что они будут буферизоваться до тех пор, пока на устройстве не будет пространства для аудиопотока.

Я хотел добавить, что OnBufferingUpdateListener вообще не срабатывал для меня во время всего теста. Я не знаю, для чего это. Я думаю, что единственный способ узнать, как происходит загрузка, - это прокси-загрузка загрузки, аналогично упомянутому выше приложению NPR.

Ответ 4

Если вы загружаете из Icecast, посмотрите на параметр burst-size:

Размер пакета - это объем данных (в байтах), которые нужно разбить клиенту при подключении. Подобно пакетному подключению, это необходимо для быстрого заполнения предварительный буфер, используемый медиаплеерами. Значение по умолчанию - 64 килобайта, что является типичный размер, используемый большинством клиентов, поэтому его изменение обычно обязательный. Этот параметр применяется ко всем точкам монтирования, если они не переопределены в настройки монтирования. Убедитесь, что это значение меньше размера очереди, если необходимо, увеличьте размер очереди, чтобы быть больше желаемого выбросоопасного размер. Несоблюдение этого требования может привести к прерыванию клиента-слушателя попыток подключения из-за первоначального пакета, приводящего к соединению уже превысив предел размера очереди.

Я увеличил burst-size до 131072 на моем сервере, и теперь мое приложение для Android на основе MediaPlayer воспроизводит потоки без особых задержек.

Ответ 5

Я пробовал это с 10 точками данных, тремя быстрыми, 7 медленными. Это последовательный, то есть быстрый поток быстрый и медленный всегда медленный.

Я думаю, что это связано с тем, что сервер поставил "длину контента", Android не знает, сколько нужно буферировать, если длина содержимого не задана должным образом.

Может быть неправильно, не дошел до проводов.

Ответ 6

Когда у меня возникла эта проблема, я решил проверить, доступен ли поток, прежде чем открывать плеер. Если вы заставите пользователя долго ждать, и музыка начнет его нормально (это не так, но пусть говорят, что все в порядке). Худший сценарий - заставить его долго ждать, и музыка никогда не начнется! Итак, у нас есть две ситуации:

  • Сценарий в прямом эфире, как и радиостанция.
  • Записанный mp3 файл, который доступен онлайн.

В сценарии радио мы можем проверить, принимает ли порт соединения (состояние открытия/закрытия). Если он открыт, подготовьте плеер к музыке, иначе не подготовьте его вообще.

public static boolean isLiveStreamingAvailable() {
        SocketAddress sockaddr = new InetSocketAddress(STREAMING_HOST, STREAMING_PORT);
        // Create your socket
        Socket socket = new Socket();
        boolean online = true;
        // Connect with 10 s timeout
        try {
            socket.connect(sockaddr, 10000);
        } catch (SocketTimeoutException stex) {
            // treating timeout errors separately from other io exceptions
            // may make sense
            return false;
        } catch (IOException iOException) {
            return false;
        } finally {
            // As the close() operation can also throw an IOException
            // it must caught here
            try {
                socket.close();
            } catch (IOException ex) {
                // feel free to do something moderately useful here, eg log the event
            }

        }
        return true;
    }

В сценарии mp3 файл все немного отличается. Вы должны проверить код ответа, который следует после HTTP-запроса.

public static boolean isRecordedStreamingAvailable() {
        try {
            HttpURLConnection.setFollowRedirects(false);
            // note : you may also need
            //        HttpURLConnection.setInstanceFollowRedirects(false)
            HttpURLConnection con =
                    (HttpURLConnection) new URL(RECORDED_URL).openConnection();
            con.setRequestMethod("HEAD");
            return (con.getResponseCode() == HttpURLConnection.HTTP_OK);
        }
        catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }