在Android高版本中,特别是4.1引入了MediaCodec可以对摄像头的图像进行硬件编码,实现直播。一般Android推流到服务器,使用ffmpeg居多,也就是软编码,实际上使

项目首页:https://github.com/simple-rtmp-server/android-publisher

在Android高版本中,特别是4.1引入了MediaCodec可以对摄像头的图像进行硬件编码,实现直播。

一般Android推流到服务器,使用ffmpeg居多,也就是软编码,实际上使用Android的硬件编码会有更好的体验。

看了下网上的文章也不少,但是都缺乏一个整体跑通的方案,特别是如何推送的服务器。本文把Android推直播流的过程梳理一遍。

AndroidPublisher提出了Android直播的新思路,主要配合SRS服务器完成,优势如下:

Android直播有几个大的环节:

打开Camera,进行Preview获取YUV图像数据,也就是未压缩的图像。

设置picture和preview大小后,计算YUV的buffer的尺寸,不能简单乘以1.5而应该按照文档计算。

获取YUV的同时,还可以进行预览,只要绑定到SurfaceHolder就可以。

使用MediaCodec和MediaFormat对YUV进行编码,其中MediaCodec是编码,MediaFormat是打包成annexb封装。

设置MediaCodec的colorFormat需要判断是否MediaCodec支持,也就是从MediaCodec获取colorFormat。

将YUV图像,送入MediaCodec的inputBuffer,并获取outputBuffer中已经编码的数据,格式是annexb。

其中queueInputBuffer时,需要指定pts,否则没有编码数据输出,会被丢弃。

将编码的annexb数据,发送到服务器。

一般使用rtmp(librtmp/srslibrtmp/ffmpeg),因为流媒体服务器的输入一般是rtmp。

若服务器支持http-flv流POST,那么可以直接发送给服务器。

下面是各个重要环节的分解。

YUV图像

第一个环节,打开Camera并预览:

camera = Camera.open();
Camera.Parameters parameters = camera.getParameters();
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
parameters.setWhiteBalance(Camera.Parameters.WHITE_BALANCE_AUTO);
parameters.setSceneMode(Camera.Parameters.SCENE_MODE_AUTO);
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
parameters.setPreviewFormat(ImageFormat.YV12);
Camera.Size size = null;
List sizes = parameters.getSupportedPictureSizes();
for (int i = 0; i < sizes.size(); i++) {
//Log.i(TAG, String.format("camera supported picture size %dx%d", sizes.get(i).width, sizes.get(i).height));
if (sizes.get(i).width == 640) {
size = sizes.get(i);
}
}
parameters.setPictureSize(size.width, size.height);
Log.i(TAG, String.format("set the picture size in %dx%d", size.width, size.height));
sizes = parameters.getSupportedPreviewSizes();
for (int i = 0; i < sizes.size(); i++) {
//Log.i(TAG, String.format("camera supported preview size %dx%d", sizes.get(i).width, sizes.get(i).height));
if (sizes.get(i).width == 640) {
vsize = size = sizes.get(i);
}
}
parameters.setPreviewSize(size.width, size.height);
Log.i(TAG, String.format("set the preview size in %dx%d", size.width, size.height));
camera.setParameters(parameters);
// set the callback and start the preview.
buffer = new byte[getYuvBuffer(size.width, size.height)];
camera.addCallbackBuffer(buffer);
camera.setPreviewCallbackWithBuffer(onYuvFrame);
try {
camera.setPreviewDisplay(preview.getHolder());
} catch (IOException e) {
Log.e(TAG, "preview video failed.");
e.printStackTrace();
return;
}
Log.i(TAG, String.format("start to preview video in %dx%d, buffer %dB", size.width, size.height, buffer.length));
camera.startPreview();
计算YUV的buffer的函数,需要根据文档计算,而不是简单“*3/2”:
// for the buffer for YV12(android YUV), @see below:
// https://developer.android.com/reference/android/hardware/Camera.Parameters.html#setPreviewFormat(int)
// https://developer.android.com/reference/android/graphics/ImageFormat.html#YV12
private int getYuvBuffer(int width, int height) {
// stride = ALIGN(width, 16)
int stride = (int)Math.ceil(width / 16.0) * 16;
// y_size = stride * height
int y_size = stride * height;
// c_stride = ALIGN(stride/2, 16)
int c_stride = (int)Math.ceil(width / 32.0) * 16;
// c_size = c_stride * height/2
int c_size = c_stride * height / 2;
// size = y_size + c_size * 2
return y_size + c_size * 2;
}
图像编码
第二个环节,设置编码器参数,并启动:
// encoder yuv to 264 es stream.
// requires sdk level 16+, Android 4.1, 4.1.1, the JELLY_BEAN
try {
encoder = MediaCodec.createEncoderByType(VCODEC);
} catch (IOException e) {
Log.e(TAG, "create encoder failed.");
e.printStackTrace();
return;
}
ebi = new MediaCodec.BufferInfo();
presentationTimeUs = new Date().getTime() * 1000;
// start the encoder.
// @see https://developer.android.com/reference/android/media/MediaCodec.html
MediaFormat format = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, vsize.width, vsize.height);
format.setInteger(MediaFormat.KEY_BIT_RATE, 125000);
format.setInteger(MediaFormat.KEY_FRAME_RATE, 15);
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, chooseColorFormat());
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5);
encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
encoder.start();
Log.i(TAG, "encoder start");
其中,colorFormat需要从编码器支持的格式中选取,否则会有不支持的错误:
// choose the right supported color format. @see below:
// https://developer.android.com/reference/android/media/MediaCodecInfo.html
// https://developer.android.com/reference/android/media/MediaCodecInfo.CodecCapabilities.html
private int chooseColorFormat() {
MediaCodecInfo ci = null;
int nbCodecs = MediaCodecList.getCodecCount();
for (int i = 0; i < nbCodecs; i++) {
MediaCodecInfo mci = MediaCodecList.getCodecInfoAt(i);
if (!mci.isEncoder()) {
continue;
}
String[] types = mci.getSupportedTypes();
for (int j = 0; j < types.length; j++) {
if (types[j].equalsIgnoreCase(VCODEC)) {
//Log.i(TAG, String.format("encoder %s types: %s", mci.getName(), types[j]));
ci = mci;
break;
}
}
}
int matchedColorFormat = 0;
MediaCodecInfo.CodecCapabilities cc = ci.getCapabilitiesForType(VCODEC);
for (int i = 0; i < cc.colorFormats.length; i++) {
int cf = cc.colorFormats[i];
//Log.i(TAG, String.format("encoder %s supports color fomart %d", ci.getName(), cf));
// choose YUV for h.264, prefer the bigger one.
if (cf >= cc.COLOR_FormatYUV411Planar && cf <= cc.COLOR_FormatYUV422SemiPlanar) {
if (cf > matchedColorFormat) {
matchedColorFormat = cf;
}
}
}
Log.i(TAG, String.format("encoder %s choose color format %d", ci.getName(), matchedColorFormat));
return matchedColorFormat;
}

第三个环节,在YUV图像回调中,送给编码器,并获取输出: