Буферизация аудиопотока - PullRequest
21 голосов
/ 29 декабря 2010

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

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

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

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

Я получил исключение: Сброс соединения по пиру java.net.SocketException: Сброс соединения по пиру.MediaPlayer 8 не хочет читать данные из сокета.

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

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

Спасибо

Ответы [ 5 ]

8 голосов
/ 30 декабря 2010

Я использую тот же 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-адреса.Решение с файлами не работает.Поэтому я должен использовать сокет для потоковой передачи.

Спасибо

5 голосов
/ 09 июня 2011

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

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

3 голосов
/ 21 января 2013

Когда вы ищете или пропускаете, или соединение потеряно, и MediaPlayer продолжает переподключаться к прокси-серверу, вы должны отправить этот ответ со статусом 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";

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

2 голосов
/ 06 июня 2011

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

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

-Hari

1 голос
/ 22 января 2016

Я понимаю, что этому Вопросу уже 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

...