Подтвердить что ты не робот

Буферизация аудиопотоков

Мне нужно играть в прямом эфире, на самом деле это радио. Проблема в том, что мне также нужно управлять 20-минутным буфером для потоковой передачи. Насколько я понимаю, это непросто реализовать с помощью android.

Сначала я проверил MediaPlayer, но он не предоставляет никаких методов управления буфером. Фактически вы даже не можете установить размер буфера напрямую.

Во-вторых, я пытался управлять буфером с использованием локальных файлов: постепенно загружать поток во временные файлы и переключаться между ними. Но когда вы хотите переключиться на следующий файл (изменить источник данных в MediaPlayer), звук не воспроизводится непрерывно. Вы можете услышать короткие перерывы.

Последняя идея - использовать прокси-сервер потока. Обычно он используется для воспроизведения потоков в версиях Android ниже 8. В поточном прокси-сервере вы создаете ServerSocket, читаете из аудиопотока и пишете на плеер. Так что я могу управлять буферизацией там. Я могу кэшировать поток и записывать в MediaPlayer все, что захочу. Но. Он не работает с Android 8.

У меня возникло исключение: Connection reset by peer java.net.SocketException: соединение reset от однорангового узла. MediaPlayer 8 не хочет читать данные из сокета.

Таким образом, у меня есть два вопроса: 1) Какой еще способ реализации буферизации потока? 2) как настроить StreamProxy для Android 8?

Любые идеи приветствуются.

Спасибо

4b9b3361

Ответ 1

Я использую тот же StreamProxy, что ребята использовали для проекта NPR - https://code.google.com/p/npr-android-app/source/browse/Npr/src/org/npr/android/news/StreamProxy.java

Таким образом, он получает исходный аудиопоток:

  String url = request.getRequestLine().getUri();
  HttpResponse realResponse = download(url);
  ...
  InputStream data = realResponse.getEntity().getContent();

И записывает из этого потока в клиентский сокет:

  byte[] buff = new byte[1024 * 50];
  while (isRunning && (readBytes = data.read(buff, 0, buff.length)) != -1) {
    client.getOutputStream().write(buff, 0, readBytes);
  }

(Вы можете получить весь код из указанной выше ссылки.)

И наконец, как они инициализируют плеер (PlaybackService):

  if (stream && sdkVersion < 8) {
    if (proxy == null) {
      proxy = new StreamProxy();
      proxy.init();
      proxy.start();
    }
    String proxyUrl = String.format("http://127.0.0.1:%d/%s", proxy.getPort(), url);
    playUrl = proxyUrl;
   }
  ...
  mediaPlayer.setDataSource(playUrl);
  mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
  mediaPlayer.prepareAsync();

Таким образом, они проверяют версию SDK. Но если я опускаю эту проверку и использую прокси для SDK 8, я также получаю исключение. Странно, что MediaPlayer даже не пытается прочитать поток:

12-30 15:09:41.576: DEBUG/StreamProxy(266): downloading...
12-30 15:09:41.597: DEBUG/StreamProxy(266): reading headers
12-30 15:09:41.597: DEBUG/StreamProxy(266): headers done
12-30 15:09:41.647: DEBUG/StreamProxy(266): writing to client
12-30 15:09:41.857: INFO/AwesomePlayer(34): mConnectingDataSource->connect() returned -    1007
12-30 15:09:41.857: ERROR/MediaPlayer(266): error (1, -1007)
12-30 15:09:41.867: ERROR/MediaPlayer(266): Error (1,-1007)
12-30 15:09:41.867: WARN/AudioService(266): onError(1, -1007)
12-30 15:09:41.867: WARN/AudioService(266): MediaPlayer refused to play current item.  Bailing on prepare.
12-30 15:09:41.867: WARN/AudioService(266): onComplete()
12-30 15:09:42.097: ERROR/StreamProxy(266): Connection reset by peer
        java.net.SocketException: Connection reset by peer
        at org.apache.harmony.luni.platform.OSNetworkSystem.writeSocketImpl(Native Method)
        at org.apache.harmony.luni.platform.OSNetworkSystem.write(OSNetworkSystem.java:723)
        at org.apache.harmony.luni.net.PlainSocketImpl.write(PlainSocketImpl.java:578)
        at org.apache.harmony.luni.net.SocketOutputStream.write(SocketOutputStream.java:59)
        at com.skyblue.service.media.StreamProxy.processRequest(StreamProxy.java:204)
        at com.skyblue.service.media.StreamProxy.run(StreamProxy.java:103)
        at java.lang.Thread.run(Thread.java:1096)

Кажется, MediaPlayer стал более умным. И если я передаю такой url "http://127.0.0.1:%d/%s", он хочет получить не только байты, но и "полный" http-ответ.

Также интересно, есть ли другие способы реализации буферизации? Как я знаю, MediaPlayer потребляет только файлы и URL-адреса. Решение с файлами не работает. Поэтому я должен использовать сокет для передачи потока.

Спасибо

Ответ 2

Я только что протестировал следующее исправление, и оно работает. Эта проблема вызвана "\n" , используемой в заголовках в processRequest() методе StreamProxy. Изменение этого параметра на "\ r\n" должно привести к ошибке.

Что касается реализации пользовательской потоковой передачи, я использовал это в прошлом, хотя, похоже, это в основном для бесконечно потокового радио. http://blog.pocketjourney.com/2009/12/27/android-streaming-mediaplayer-tutorial-updated-to-v1-5-cupcake/

Ответ 3

Когда вы ищете или пропускаете или соединение потеряно, и MediaPlayer продолжает переподключение к прокси-серверу, вы должны отправить этот ответ с Status 206 после того, как вы получите запрос и диапазон (int) от клиента.

String headers += "HTTP/1.1 206 Partial Content\r\n";
headers += "Content-Type: audio/mpeg\r\n";
headers += "Accept-Ranges: bytes\r\n";
headers += "Content-Length: " + (fileSize-range) + "\r\n";
headers += "Content-Range: bytes "+range + "-" + fileSize + "/*\r\n";
headers += "\r\n";

И когда вы получаете запрос от MediaPlayer, который не содержит Range в HTTP-заголовке, тогда он запрашивает новый файл потока, в этом случае ваш заголовок ответа должен выглядеть следующим образом:

String headers = "HTTP/1.1 200 OK\r\n";
headers += "Content-Type: audio/mpeg\r\n";
headers += "Accept-Ranges: bytes\r\n";
headers += "Content-Length: " + fileSize + "\r\n";
headers += "\r\n";

Наслаждайтесь!

Ответ 4

Медиаплеер из SDK 8 не сможет прочитать прокси-адрес. Но исходя из моей текущей работы, это отличается от устройства к устройству. В моем Samsung ACE (SDK 8) прокси-соединение работает нормально, но в моем HTC Incredible S прокси-соединение даст ту же проблему, что и у вас. Прямое подключение к аудиопотоку работает нормально, но это вызывает появление бликов и будок в некоторых устройствах, таких как EVO на спринте.

Вы получили разрешение на эту проблему? Как вы справились с этим?

-Hari

Ответ 5

Я понимаю, что этот вопрос похож на 5 лет, но если кто-то еще блуждает: ExoPlayer - это библиотека от Google, которая дает вам гораздо больше контроля над такими параметрами, как размер буфера.

    //DefaultUriDataSource – For playing media that can be either local or loaded over the network.
    DefaultUriDataSource dataSource = new DefaultUriDataSource(WgtechApplication.getAppContext(),
            Util.getUserAgent(WgtechApplication.getAppContext(), WgtechApplication.class.getSimpleName()));
    Allocator allocator = new DefaultAllocator(BUFFER_SEGMENT_SIZE);

    //ExtractorSampleSource – For formats such as MP3, M4A, MP4, WebM, MPEG-TS and AAC.
    ExtractorSampleSource sampleSource = new ExtractorSampleSource(Uri.parse(RADIO_STREAMING_URL),
            dataSource, allocator, BUFFER_SEGMENT_COUNT * BUFFER_SEGMENT_SIZE);
    player.prepare(new MediaCodecAudioTrackRenderer(sampleSource));

Вот небольшой проект, который я создал, чтобы узнать, как его использовать. https://github.com/feresr/MyMediaPlayer

http://developer.android.com/guide/topics/media/exoplayer.html https://github.com/google/ExoPlayer