Приложение для потокового видео на Android

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

Используя класс MediaRecorder, я могу захватить видеоданные в виде 3gp с кодеками h263.

Однако, когда я запускаю приложение и поток, я получаю 2-3-секундную задержку на стороне сервера.

Почему я получаю эту задержку? Есть ли внутренние буферы, которые мне нужны для очистки? Существуют ли другие способы потокового видео помимо использования класса MediaRecorder?

Ответ 1

Если вы настроили RTMP-потоковое воспроизведение с Android, лучшим решением является MediaCodec + FFmpeg + librtmp. Это позволяет избежать хакерских "обнаружения NAL Unit в бизнесе bytestream", но требует Android 4.3. Скейт, где шайба идет...

Я разработал SDK с открытым исходным кодом, который демонстрирует потоки RTMP с FFmpeg + librtmp как готовые общие библиотеки. SDK ориентирован на потоки HLS, но поддерживается поддержка RTMP.

Если вам нужна помощь в создании FFmpeg для Android (с или без librtmp), просмотрите мой путеводитель.

Ответ 2

Если приложение Android работает хорошо, и код, который он оптимизировал, доказуемо причиной этой задержки является то, что вы отвлекаете тяжелые данные на сервер, а HTTP-запрос на сервер имеет ограничение на отправку MB. Когда это происходит автоматически, или программист должен фракционировать HTTP-запрос на несколько HTTP-запросов и, очевидно, медленнее.

Ответ 3

сначала создайте этот класс.

MediaPlayerDemo_Video.java:-


package com.videostreaming.player;

import android.app.Activity;
import android.content.Intent;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnBufferingUpdateListener;
import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaPlayer.OnPreparedListener;
import android.media.MediaPlayer.OnVideoSizeChangedListener;
import android.os.Bundle;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.widget.Toast;


public class MediaPlayerDemo_Video extends Activity implements
        OnBufferingUpdateListener, OnCompletionListener,
        OnPreparedListener, OnVideoSizeChangedListener, SurfaceHolder.Callback {

     private String path1 = "http://podcast.20min-tv.ch/podcast/20min/199733.mp4";
    // private String path2 = "http://podcast.20min-tv.ch/podcast/20min/199752.mp4";
     private String path2 = "http://podcast.20min-tv.ch/podcast/20min/199693.mp4";
     private String path = "";

    private static final String TAG = "MediaPlayerDemo";
    private int mVideoWidth;
    private int mVideoHeight;
    private MediaPlayer mMediaPlayer;
    private SurfaceView mPreview;
    private SurfaceHolder holder;
//    private String path;
    private Bundle extras;
    private static final String MEDIA = "media";
    private static final int LOCAL_AUDIO = 1;
    private static final int STREAM_AUDIO = 2;
    private static final int RESOURCES_AUDIO = 3;
    private static final int LOCAL_VIDEO = 4;
    private static final int STREAM_VIDEO = 5;
    private boolean mIsVideoSizeKnown = false;
    private boolean mIsVideoReadyToBePlayed = false;

    private Bundle bdlReceivedData = null;
    private Intent self = null;

    /**
     * 
     * Called when the activity is first created.
     */
    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        setContentView(R.layout.mediaplayer_2);

        mPreview = (SurfaceView) findViewById(R.id.surface);
        holder = mPreview.getHolder();
        holder.addCallback(this);
        holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

        extras = getIntent().getExtras();

        self = this.getIntent();
        bdlReceivedData = self.getExtras();

        if (bdlReceivedData != null && bdlReceivedData.getInt("video") > 0)
        {
            if (bdlReceivedData.getInt("video") == 1)
            {
                Toast.makeText(MediaPlayerDemo_Video.this,"playing Video 1", Toast.LENGTH_SHORT);
                path = path1;
            }
            else if (bdlReceivedData.getInt("video") == 2)
            {
                Toast.makeText(MediaPlayerDemo_Video.this,"playing Video 2", Toast.LENGTH_SHORT);
                path = path2;
            }
        }
    }

    private void playVideo(Integer Media) {
        doCleanUp();
        try {

//            switch (Media) {
//                case LOCAL_VIDEO:
//                    /*
//                     * TODO: Set the path variable to a local media file path.
//                     */
//                    path = "";
//                    if (path == "") {
//                        // Tell the user to provide a media file URL.
//                        Toast
//                                .makeText(
//                                        MediaPlayerDemo_Video.this,
//                                        "Please edit MediaPlayerDemo_Video Activity, "
//                                                + "and set the path variable to your media file path."
//                                                + " Your media file must be stored on sdcard.",
//                                        Toast.LENGTH_LONG).show();
//
//                    }
//                    break;
//                case STREAM_VIDEO:
//                    /*
//                     * TODO: Set path variable to progressive streamable mp4 or
//                     * 3gpp format URL. Http protocol should be used.
//                     * Mediaplayer can only play "progressive streamable
//                     * contents" which basically means: 1. the movie atom has to
//                     * precede all the media data atoms. 2. The clip has to be
//                     * reasonably interleaved.
//                     * 
//                     */
//                    path = "";
//                    if (path == "") {
//                        // Tell the user to provide a media file URL.
//                        Toast
//                                .makeText(
//                                        MediaPlayerDemo_Video.this,
//                                        "Please edit MediaPlayerDemo_Video Activity,"
//                                                + " and set the path variable to your media file URL.",
//                                        Toast.LENGTH_LONG).show();
//
//                    }
//
//                    break;
//
//
//            }

            // Create a new media player and set the listeners
            mMediaPlayer = new MediaPlayer();
            mMediaPlayer.setDataSource(path);
            mMediaPlayer.setDisplay(holder);
            mMediaPlayer.prepare();
            mMediaPlayer.setOnBufferingUpdateListener(this);
            mMediaPlayer.setOnCompletionListener(this);
            mMediaPlayer.setOnPreparedListener(this);
            mMediaPlayer.setOnVideoSizeChangedListener(this);
            mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);

        } catch (Exception e) {
            Log.e(TAG, "error: " + e.getMessage(), e);
        }
    }

    public void onBufferingUpdate(MediaPlayer arg0, int percent) {
        Log.d(TAG, "onBufferingUpdate percent:" + percent);

    }

    public void onCompletion(MediaPlayer arg0) {
        Log.d(TAG, "onCompletion called");
    }

    public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
        Log.v(TAG, "onVideoSizeChanged called");
        if (width == 0 || height == 0) {
            Log.e(TAG, "invalid video width(" + width + ") or height(" + height + ")");
            return;
        }
        mIsVideoSizeKnown = true;
        mVideoWidth = width;
        mVideoHeight = height;
        if (mIsVideoReadyToBePlayed && mIsVideoSizeKnown) {
            startVideoPlayback();
        }
    }

    public void onPrepared(MediaPlayer mediaplayer) {
        Log.d(TAG, "onPrepared called");
        mIsVideoReadyToBePlayed = true;
        if (mIsVideoReadyToBePlayed && mIsVideoSizeKnown) {
            startVideoPlayback();
        }
    }

    public void surfaceChanged(SurfaceHolder surfaceholder, int i, int j, int k) {
        Log.d(TAG, "surfaceChanged called");

    }

    public void surfaceDestroyed(SurfaceHolder surfaceholder) {
        Log.d(TAG, "surfaceDestroyed called");
    }


    public void surfaceCreated(SurfaceHolder holder) {
        Log.d(TAG, "surfaceCreated called");
        playVideo(extras.getInt(MEDIA));
    }

    @Override
    protected void onPause() {
        super.onPause();
        releaseMediaPlayer();
        doCleanUp();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        releaseMediaPlayer();
        doCleanUp();
    }

    private void releaseMediaPlayer() {
        if (mMediaPlayer != null) {
            mMediaPlayer.release();
            mMediaPlayer = null;
        }
    }

    private void doCleanUp() {
        mVideoWidth = 0;
        mVideoHeight = 0;
        mIsVideoReadyToBePlayed = false;
        mIsVideoSizeKnown = false;
    }

    private void startVideoPlayback() {
        Log.v(TAG, "startVideoPlayback");
        holder.setFixedSize(mVideoWidth, mVideoHeight);
        mMediaPlayer.start();
    }
}

теперь следующий класс

MainMenu.java


package com.videostreaming.player;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainMenu extends Activity {

    Button btn_videoViewDemo1;
    Button btn_videoViewDemo2;
    Button btn_MediaPlayerDemo1;    
    Button btn_MediaPlayerDemo2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        btn_videoViewDemo1 = (Button)findViewById(R.id.btn_videoViewDemo1);
        btn_videoViewDemo2 = (Button)findViewById(R.id.btn_videoViewDemo2);
        btn_MediaPlayerDemo1 = (Button)findViewById(R.id.btn_MediaPlayerDemo1);
        btn_MediaPlayerDemo2 = (Button)findViewById(R.id.btn_MediaPlayerDemo2);

        btn_videoViewDemo1.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                Intent Navigation1 = new Intent(MainMenu.this,streamplayer.class);
                Navigation1.putExtra("video",1);
                startActivity(Navigation1);             
            }
        });

        btn_videoViewDemo2.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                Intent Navigation1 = new Intent(MainMenu.this,streamplayer.class);
                Navigation1.putExtra("video",2);
                startActivity(Navigation1);
            }
        });

        btn_MediaPlayerDemo1.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                Intent Navigation1 = new Intent(MainMenu.this,MediaPlayerDemo_Video.class);
                Navigation1.putExtra("video",1);
                startActivity(Navigation1);             
            }
        });

        btn_MediaPlayerDemo2.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                Intent Navigation1 = new Intent(MainMenu.this,MediaPlayerDemo_Video.class);
                Navigation1.putExtra("video",2);
                startActivity(Navigation1);             
            }
        });     
    }
}

streamplayer.java

package com.videostreaming.player;

import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.MediaController;
import android.widget.Toast;
import android.widget.VideoView;

public class streamplayer extends Activity {
    /** Called when the activity is first created. */

     private String path1 = "http://podcast.20min-tv.ch/podcast/20min/199733.mp4";
     //private String path2 = "http://podcast.20min-tv.ch/podcast/20min/199752.mp4";
     private String path2 = "http://podcast.20min-tv.ch/podcast/20min/199693.mp4";
     private String path = "";

     //// Method 1 - Default Method
     private VideoView mVideoView;

     private Bundle bdlReceivedData = null;
     private Intent self = null;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        try
        {
            setContentView(R.layout.videoview);
            mVideoView = (VideoView) findViewById(R.id.surface_view);

            self = this.getIntent();
            bdlReceivedData = self.getExtras();

            if (bdlReceivedData != null && bdlReceivedData.getInt("video") > 0)
            {
                if (bdlReceivedData.getInt("video") == 1)
                {
                    Toast.makeText(streamplayer.this,"playing Video 1", Toast.LENGTH_SHORT);
                    path = path1;
                }
                else if (bdlReceivedData.getInt("video") == 2)
                {
                    Toast.makeText(streamplayer.this,"playing Video 2", Toast.LENGTH_SHORT);
                    path = path2;
                }

                /*
                 * Alternatively,for streaming media you can use
                 * mVideoView.setVideoURI(Uri.parse(URLstring));
                 */

                //mVideoView.setVideoPath(path1);
                ///ELSE
                mVideoView.setVideoURI(Uri.parse(path));                
                mVideoView.setMediaController(new MediaController(this));
                mVideoView.requestFocus();              
                mVideoView.postInvalidateDelayed(100);        
                mVideoView.start();

            }

        }catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
            Toast.makeText(streamplayer.this,"Error Occured:- " + e.getMessage(),Toast.LENGTH_SHORT).show();
        } 
    }
}

mediaplayer_2.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <SurfaceView android:id="@+id/surface"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_gravity="center">
    </SurfaceView>

</LinearLayout>

videoview.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">

    <VideoView 
        android:id="@+id/surface_view" 
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_gravity="center"/>

</LinearLayout>

main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >

<TextView  
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:text="@string/hello"/>

    <Button
        android:id="@+id/btn_videoViewDemo1"
        android:layout_width="fill_parent"
        android:layout_height="40dip"
        android:text="VideoView1"
        android:layout_margin="10dip">
    </Button>

    <Button
        android:id="@+id/btn_videoViewDemo2"
        android:layout_width="fill_parent"
        android:layout_height="40dip"
        android:text="VideoView2"
        android:layout_margin="10dip">
    </Button>

    <Button
        android:id="@+id/btn_MediaPlayerDemo1"
        android:layout_width="fill_parent"
        android:layout_height="40dip"
        android:text="MediaPlayerDemo1"
        android:layout_margin="10dip">
    </Button>



    <Button
        android:id="@+id/btn_MediaPlayerDemo2"
        android:layout_width="fill_parent"
        android:layout_height="40dip"
        android:text="MediaPlayerDemo2"
        android:layout_margin="10dip">
    </Button>


</LinearLayout>