我正在尝试构建一个实时视频流应用程序,用于传输来自Android的实时视频。
使用MediaRecorder类,我可以使用h263编解码器以3gp的形式捕获视频数据。
然而,当我运行我的应用程序和流媒体时,我在服务器端得到2-3秒的延迟。
为什么我得到这个延迟?是否需要刷新内部缓冲区?除了使用MediaRecorder类之外,还有其他方法可以播放视频吗?
答案 0 :(得分:1)
如果你设置了Android的RTMP流媒体,最好的解决方案是MediaCodec + FFmpeg + librtmp。这避免了任何hacky“在字节流中检测NAL单元”业务,但需要Android 4.3。滑冰冰球的地方......
我开发了open source SDK,用FFmpeg + librtmp as pre-built shared libraries演示了RTMP流媒体。 SDK专注于HLS流,但存在RTMP支持。
如果你想帮助自己为Android建立FFmpeg(有或没有librtmp),check out my guide。
答案 1 :(得分:0)
如果Android应用程序运行良好,并且代码被认可,那么延迟的原因是您正在向服务器发送大量数据,并且对服务器的HTTP请求具有要发送的MB限制。当这个类自动发生时,或者程序员必须将HTTP请求分成多个HTTP请求,显然速度较慢。
答案 2 :(得分:-4)
首先创建这个类。
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>