web-dev-qa-db-ja.com

オーディオストリームのバッファリング

ライブオーディオストリームを再生する必要があります。実際はラジオです。問題は、ストリーミング用に20分のバッファーも管理する必要があることです。私の知る限り、Androidで実装するのは簡単ではありません。

最初にMediaPlayerをチェックしましたが、バッファ管理のメソッドが提供されていません。実際、バッファサイズを直接設定することさえできません。

次に、ローカルファイルを使用してバッファを管理しようとしました。ストリームを一時ファイルに段階的にダウンロードして切り替えます。ただし、次のファイルに切り替えたい場合(MediaPlayerでデータソースを変更した場合)、オーディオは継続的に再生されません。短い中断が聞こえます。

最後のアイデアは、ストリームプロキシを使用することです。通常、これはAndroidバージョン8未満のバージョンでストリームを再生するために使用されます。ストリームプロキシでは、ServerSocketを作成し、オーディオストリームから読み取り、プレーヤーに書き込みます。したがって、実際にはそこでバッファリングを管理できます。ストリームをキャッシュできます。そして、MediaPlayerに好きなように書き込みます。しかし、Android 8では機能しません。

例外が発生しました:ピアによって接続がリセットされましたJava.net.SocketException:ピアによって接続がリセットされました。 MediaPlayer 8は、ソケットからデータを読み取りたくありません。

したがって、2つの質問があります:1)ストリームバッファリングを実装する他の方法は何ですか? 2)StreamProxyをAndroid 8?

どんなアイデアでも大歓迎です。

ありがとう

21
Eugenious

私はみんながNPRプロジェクトに使用したのと同じStreamProxyを使用しています--- 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のみを消費します。ファイルを使用したソリューションは機能しません。そのため、ストリーム送信にはソケットを使用する必要があります。

ありがとう

8
Eugenious

次の修正をテストしたところ、機能します。この問題は、StreamProxyprocessRequest()メソッドのヘッダーで使用されている "\ n"が原因で発生します。これを "\ r\n"に変更すると、エラーが解消されます。

カスタムストリーミングの実装に関しては、これは過去に使用したことがありますが、主に無限ストリーミングラジオ用のようです。 http://blog.pocketjourney.com/2009/12/27/Android-streaming-mediaplayer-tutorial-updated-to-v1-5-cupcake/

5
brack

シークまたはスキップするか、接続が失われ、MediaPlayerがプロキシサーバーに再接続し続ける場合、クライアントから要求とrange(int)を取得した後、ステータス206でこの応答を送信する必要があります。

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";

また、HTTPヘッダーにRangeが含まれていないMediaPlayerからのリクエストを受信すると、新しいストリームファイルをリクエストしています。この場合、レスポンスヘッダーは次のようになります。

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";

楽しい!

3
user1995726

SDK 8のMediaplayerは、プロキシURLを読み取ることができません。しかし、私の現在の作業に基づくと、これはデバイスごとに異なります。私のSamsungACE(SDK 8)では、プロキシ接続は正常に機能しますが、私のHTC Incredible Sでは、プロキシ接続で同じ問題が発生します。オーディオストリームへの直接接続は正常に機能しますが、これにより、スプリントのEVOなどの一部のデバイスでブリップやブープが発生します。

この問題の解決策はありましたか?これをどのように処理しましたか?

-ハリ

2
HariKJ

この質問は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.htmlhttps://github.com/google/ExoPlayer

1
FRR