libstreaming是一个 API,只需几行代码,您就可以使用 RTP over UDP 流式传输 Android 设备的摄像头和/或麦克风。
要启动与某个对等点的流会话,您需要实现的第一步称为“发信号”。在此步骤中,您将联系接收者并发送传入流的描述。您可以通过三种方式使用 libstreaming 来做到这一点。
API 的完整 javadoc 文档可在此处获取:http://guigui.us/libstreaming/doc
Android 上可以通过三种方式从外设获取编码数据:
MediaRecorder API 不适用于流媒体应用程序,但可用于从手机外围设备检索编码数据。技巧是将 MediaRecorder 实例配置为写入LocalSocket而不是常规文件(请参阅MediaStream.java )。
编辑:从 Android Lollipop 开始,出于安全原因,不再可能使用LocalSocket 。但使用ParcelFileDescriptor就可以解决问题。更多详细信息请参见MediaStream.java文件! (感谢那些人的见解)
这个黑客有一些限制:
很难说这个黑客技术在手机上的效果如何。但它在许多设备上运行良好。
MediaCodec API 没有我刚才提到的限制,但有它自己的问题。实际上有两种使用 MediaCodec API 的方法:使用缓冲区或使用表面。
buffer-to-buffer方法使用对dequeueInputBuffer和queueInputBuffer的调用来向编码器提供原始数据。这看起来很容易吧?事实并非如此,因为您通过此 API 访问的视频编码器使用不同的颜色格式,您需要支持所有这些格式。此处提供了这些颜色格式的列表。此外,许多编码器声称支持颜色格式,但它们实际上并未正确支持,或者可能会出现小故障。
所有硬件包都致力于解决这些问题。特别参见EncoderDebugger类。
如果使用该 API 进行流式传输失败,libstreaming 将使用MediaRecorder API进行流式传输。
表面到缓冲区方法使用 createInputSurface() 方法。此方法可能是对来自相机的原始视频进行编码的最佳方法,但它需要 Android 4.3 及更高版本。
gl包专用于将 MediaCodec API 与表面结合使用。
默认情况下,libstreaming 中尚未启用它,但您可以使用setStreamingMethod(byte)方法强制启用它。
一旦来自外设的原始数据被编码,它就会被封装在适当的 RTP 流中。必须使用的分组算法取决于数据的格式(H.264、H.263、AMR 和 AAC),并且均在各自的 RFC 中指定:
如果您正在寻找上述 RFC 之一的基本实现,请检查相应类的源代码。
从 libstreaming 2.0 版本开始,RTCP 数据包也会发送到接收方。仅实施发件人报告。它们实际上是口型同步所需要的。
rtp包处理 RTP 数据包中编码数据的打包。
< uses-permission android : name = " android.permission.INTERNET " />
< uses-permission android : name = " android.permission.WRITE_EXTERNAL_STORAGE " />
< uses-permission android : name = " android.permission.RECORD_AUDIO " />
< uses-permission android : name = " android.permission.CAMERA " />
这个例子是从这个简单的 Android 应用程序中提取的。这可以是活动、片段或服务的一部分。
protected void onCreate ( Bundle savedInstanceState ) {
...
mSession = SessionBuilder . getInstance ()
. setCallback ( this )
. setSurfaceView ( mSurfaceView )
. setPreviewOrientation ( 90 )
. setContext ( getApplicationContext ())
. setAudioEncoder ( SessionBuilder . AUDIO_NONE )
. setAudioQuality ( new AudioQuality ( 16000 , 32000 ))
. setVideoEncoder ( SessionBuilder . VIDEO_H264 )
. setVideoQuality ( new VideoQuality ( 320 , 240 , 20 , 500000 ))
. build ();
mSurfaceView . getHolder (). addCallback ( this );
...
}
public void onPreviewStarted () {
Log . d ( TAG , "Preview started." );
}
@ Override
public void onSessionConfigured () {
Log . d ( TAG , "Preview configured." );
// Once the stream is configured, you can get a SDP formated session description
// that you can send to the receiver of the stream.
// For example, to receive the stream in VLC, store the session description in a .sdp file
// and open it with VLC while streming.
Log . d ( TAG , mSession . getSessionDescription ());
mSession . start ();
}
@ Override
public void onSessionStarted () {
Log . d ( TAG , "Streaming session started." );
...
}
@ Override
public void onSessionStopped () {
Log . d ( TAG , "Streaming session stopped." );
...
}
@ Override
public void onBitrateUpdate ( long bitrate ) {
// Informs you of the bandwidth consumption of the streams
Log . d ( TAG , "Bitrate: " + bitrate );
}
@ Override
public void onSessionError ( int message , int streamType , Exception e ) {
// Might happen if the streaming at the requested resolution is not supported
// or if the preview surface is not ready...
// Check the Session class for a list of the possible errors.
Log . e ( TAG , "An error occured" , e );
}
@ Override
public void surfaceChanged ( SurfaceHolder holder , int format , int width ,
int height ) {
}
@ Override
public void surfaceCreated ( SurfaceHolder holder ) {
// Starts the preview of the Camera
mSession . startPreview ();
}
@ Override
public void surfaceDestroyed ( SurfaceHolder holder ) {
// Stops the streaming session
mSession . stop ();
}
SessionBuilder只是方便了Session对象的创建。视频流需要调用setSurfaceView ,这并不奇怪,因为 Android 需要有效的表面来录制视频(这是MediaRecorder API 的一个恼人的限制)。在 Android 4.3 上,可以不使用SurfaceView进行流式传输,但尚未实现。对setContext(Context) 的调用是必要的,它允许H264Stream对象和AACStream对象使用SharedPreferences存储和恢复数据。
Session对象代表某个对等方的流会话。它包含一个或多个Stream对象,这些对象在调用start() (或stop() )方法时启动(或停止)。
getSessionDescription()方法将以字符串的形式返回会话的 SDP。在调用之前,必须确保Session已经配置好。在 Session 实例上调用configure()或startPreview()后,将调用回调onSessionConfigured() 。
在上面的示例中,Session 实例以异步方式使用,并且对其方法的调用不会阻塞。当回调被调用时你就知道事情什么时候完成了。
您还可以以同步方式使用 Session 对象,如下所示:
// Blocks until the all streams are configured
try {
mSession . syncConfigure ();
} catch ( Exception e ) {
...
}
Strinf sdp = mSession . getSessionDescription ();
...
// Blocks until streaming actually starts.
try {
mSession . syncStart ();
} catch ( Exception e ) {
...
}
...
mSession . syncStop ();
查看 wiki 的此页面和示例 3。
< service android : name = " net.majorkernelpanic.streaming.rtsp.RtspServer " />
如果您决定覆盖RtspServer,请相应地更改上面的行。
Editor editor = PreferenceManager . getDefaultSharedPreferences ( this ). edit ();
editor . putString ( RtspServer . KEY_PORT , String . valueOf ( 1234 ));
editor . commit ();
该端口确实作为字符串存储在首选项中,这是有充分理由的。 EditTextPreference 对象将其输入保存为字符串,并且不能轻松(需要覆盖它)配置为将其存储为整数。
SessionBuilder . getInstance ()
. setSurfaceHolder ( mSurfaceView . getHolder ())
. setContext ( getApplicationContext ())
. setAudioEncoder ( SessionBuilder . AUDIO_AAC )
. setVideoEncoder ( SessionBuilder . VIDEO_H264 );
// Starts the RTSP server
context . startService ( new Intent ( this , RtspServer . class ));
// Stops the RTSP server
context . stopService ( new Intent ( this , RtspServer . class ));
请访问此 github 页面,了解如何使用此流堆栈及其执行情况。