最近研究视频通话,写一下关于摄像头采集视频并使用MediaCodec硬编码为H264的过程,希望对有需要的朋友有所帮助。
说实话,刚开始不太熟折腾了挺久的,网上这方面的东西比较少,很多都是代码片段或者就是其他语言写的。这里贴的是本人亲测能用的,希望需要的朋友能少走一些弯路吧。
直接来看看代码吧。都有详细的注释的。
package com.kokjuis.travel.activity;
import android.app.Activity;
import android.graphics.ImageFormat;
import android.hardware.Camera;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaCodec;
import android.media.MediaRecorder;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.SurfaceView;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ImageButton;
import android.widget.ImageView;
import com.imsdk.general.ImApplication;
import com.imsdk.handler.UdpMessageHandler;
import com.imsdk.listener.MediaListener;
import com.imsdk.socket.udp.codec.RtspPacketDecode;
import com.imsdk.socket.udp.codec.RtspPacketEncode;
import com.imsdk.utils.AacEncode;
import com.imsdk.utils.AvcDecode;
import com.imsdk.utils.AvcEncoder;
import com.kokjuis.travel.R;
import com.kokjuis.travel.customView.RoundImageView;
import com.kokjuis.travel.entity.User;
import com.kokjuis.travel.utils.T;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
/**
* Created by LONG on 2017/3/31.
*/
public class VideoChatActivity extends Activity implements View.OnClickListener, Camera.PreviewCallback, MediaListener, RtspPacketEncode.H264ToRtpLinsener {
private static final String TAG = "VideoChatActivity";
//这里是为了发送视频到vlc客户端进行测试。
private InetAddress address;
private DatagramSocket socket;
private UdpSendTask netSendTask;
//-----------------------------------------------------------
//开始录制按钮
ImageButton record;
//切换前后摄像头按钮
ImageView change;
// 显示视频预览的SurfaceView
SurfaceView sView, mView;
// 记录是否正在进行录制
private boolean isRecording = false;
private Camera mCamera;
private int cameraPosition = 1;//1代表前置摄像头,0代表后置摄像头
private int displayOrientation = 90;//相机预览方向,默认是横屏的,旋转90度为竖屏
//视频采集分辨率
int width = 320;
int height = 240;
byte[] h264;//接收H264
//h264硬编码器
AvcEncoder avcEncoder;
//h264硬解码器
AvcDecode avcDecode;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 去掉标题栏 ,必须放在setContentView之前
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_video_chat);
// 设置横屏显示
// setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
// 设置全屏
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
// 选择支持半透明模式,在有surfaceview的activity中使用。
//getWindow().setFormat(PixelFormat.TRANSLUCENT);
// 获取程序界面中的按钮
record = (ImageButton) findViewById(R.id.record);
change = (ImageView) findViewById(R.id.change);
// 未开始录制时让切换相机按钮不可用。
change.setEnabled(false);
//把按钮设为灰色
change.setBackground(getResources().getDrawable(R.drawable.agx));
// 为两个按钮的单击事件绑定监听器
record.setOnClickListener(this);
change.setOnClickListener(this);
// 获取程序界面中的大预览SurfaceView
sView = (SurfaceView) this.findViewById(R.id.sView);
// 设置分辨率
sView.getHolder().setFixedSize(width, height);
// 设置该组件让屏幕不会自动关闭
sView.getHolder().setKeepScreenOn(true);
// 获取程序界面中的小的预览SurfaceView
mView = (SurfaceView) this.findViewById(R.id.mView);
// 设置分辨率
mView.getHolder().setFixedSize(width, height);
//-------------启动发送数据线程-----------------
netSendTask = new UdpSendTask();
netSendTask.init();
netSendTask.start();
}
@Override
public void onClick(View source) {
switch (source.getId()) {
// 单击录制按钮
case R.id.record:
initCameara();
break;
case R.id.change:
//切换摄像头
change();
break;
}
}
//初始化相机
private void initCameara() {
try {
mCamera = Camera.open(cameraPosition);
mCamera.setPreviewDisplay(mView.getHolder());
//设置预览方向
mCamera.setDisplayOrientation(displayOrientation);
//获取相机配置参数
Camera.Parameters parameters = mCamera.getParameters();
//这里只是打印摄像头支持的分辨率,实际对程序没有作用,可以删除
List<Camera.Size> supportedPreviewSizes = parameters
.getSupportedPreviewSizes();
for (Camera.Size s : supportedPreviewSizes
) {
Log.v(TAG, s.width + "----" + s.height);
}
parameters.setFlashMode("off"); // 无闪光灯
parameters.setPreviewFormat(ImageFormat.NV21); //设置采集视频的格式,默认为NV21,注意,相机预览只支持NV21和YV12两种格式,其他格式会花屏
parameters.setPreviewFrameRate(10);//设置帧率
parameters.setPreviewSize(width, height);//设置分辨率
parameters.setPictureSize(width, height);
mCamera.setParameters(parameters); // 将Camera.Parameters设定予Camera
//设置预览回调
mCamera.setPreviewCallback((Camera.PreviewCallback) this);
mCamera.startPreview();
//开始采集让摄像头切换按钮可用
change.setEnabled(true);
//变成红色
change.setBackground(getResources().getDrawable(R.drawable.ahv));
//初始化视频编解码器
avcEncoder = new AvcEncoder(width, height, 10, 125000);
avcDecode = new AvcDecode(width, height, sView.getHolder().getSurface());
} catch (Exception e) {
Log.i("jw", "camera error:" + Log.getStackTraceString(e));
}
}
@Override
protected void onDestroy() {
super.onDestroy();
destroyCamera();
isRecording = false;
}
private void destroyCamera() {
if (mCamera == null) {
return;
}
//!!这个必须在前,不然退出出错
mCamera.setPreviewCallback(null);
mCamera.stopPreview();
mCamera.release();
mCamera = null;
}
@Override
public void onPreviewFrame(byte[] bytes, Camera camera) {
try {
if (isRecording) {
//摄像头数据转h264
int ret = avcEncoder.offerEncoder(bytes, h264);
if (ret > 0) {
//发送h264到vlc
netSendTask.pushBuf(h264, ret);
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
//切换前后摄像头
public void change() {
//切换前后摄像头
Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
for (int i = 0; i < Camera.getNumberOfCameras(); i++) {
Camera.getCameraInfo(i, cameraInfo);
if (cameraPosition == 1) {
if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
displayOrientation = 90;
cameraPosition = 0;
break;
}
} else {
if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
displayOrientation = 90;
cameraPosition = 1;
break;
}
}
}
destroyCamera();
initCameara();
}
//发送数据的线程
class UdpSendTask extends Thread {
private ArrayList<ByteBuffer> mList;
public void init() {
try {
socket = new DatagramSocket();
//设置IP
address = InetAddress.getByName("192.168.10.84");
} catch (SocketException e) {
e.printStackTrace();
} catch (UnknownHostException e) {
e.printStackTrace();
}
mList = new ArrayList<ByteBuffer>();
}
//添加数据
public void pushBuf(byte[] buf, int len) {
ByteBuffer buffer = ByteBuffer.allocate(len);
buffer.put(buf, 0, len);
mList.add(buffer);
}
@Override
public void run() {
Log.d(TAG, "fall in udp send thread");
while (true) {
if (mList.size() <= 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
while (mList.size() > 0) {
ByteBuffer sendBuf = mList.get(0);
try {
//发送数据到指定地址
Log.d(TAG, "send udp packet len:" + sendBuf.capacity());
DatagramPacket packet = new DatagramPacket(sendBuf.array(), sendBuf.capacity(), address, 5000);
socket.send(packet);
} catch (Throwable t) {
t.printStackTrace();
}
//移除已经发送的数据
mList.remove(0);
}
}
}
}
}
布局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:imagecontrol="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- 显示视频预览的SurfaceView -->
<SurfaceView
android:id="@+id/sView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/video_bg" />
<SurfaceView
android:id="@+id/mView"
android:layout_width="130dp"
android:layout_height="200dp"
android:background="@drawable/video_bg" />
<ImageView
android:id="@+id/change"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_alignParentRight="true"
android:layout_marginRight="20dp"
android:layout_marginTop="20dp"
android:src="@drawable/a6o" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginBottom="30dp"
android:gravity="center_horizontal"
android:orientation="horizontal">
<ImageButton
android:id="@+id/record"
android:layout_width="66dp"
android:layout_height="66dp"
android:scaleType="fitCenter" />
</LinearLayout>
</RelativeLayout>
编码工具类,这个是这个功能的核心。
package com.imsdk.utils;
import android.annotation.SuppressLint;
import android.graphics.Bitmap;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaCodecList;
import android.media.MediaFormat;
import android.util.Log;
import java.nio.ByteBuffer;
/**
* h264编码器
*
* @author:gj
* @date: 2017/5/27
* @time: 14:49
**/
public class AvcEncoder {
private static final String TAG = "AvcEncoder";
private MediaCodec mediaCodec;
int m_width;//宽
int m_height;//高
int m_framerate;//帧率
byte[] m_info = null;
//转成后的数据
private byte[] yuv420 = null;
//旋转后的数据
private byte[] rotateYuv420 = null;
//编码类型
private String mime = "video/avc";
//pts时间基数
long presentationTimeUs = 0;
/**
* 构造方法
*
* @param width
* @param height
* @param framerate 帧数
* @param bitrate 码流 2500000
*/
@SuppressWarnings("deprecation")
@SuppressLint("NewApi")
public AvcEncoder(int width, int height, int framerate, int bitrate) {
m_width = width;
m_height = height;
m_framerate = framerate;
//这里的大小要通过计算,而不是网上简单的 *3/2
yuv420 = new byte[getYuvBuffer(width, height)];
rotateYuv420 = new byte[getYuvBuffer(width, height)];
//确定当前MediaCodec支持的图像格式
int colorFormat = selectColorFormat(selectCodec(mime), mime);
try {
mediaCodec = MediaCodec.createEncoderByType(mime);
//正常的编码出来是横屏的。因为手机本身采集的数据默认就是横屏的
// MediaFormat mediaFormat = MediaFormat.createVideoFormat(mime, width, height);
//如果你需要旋转90度或者270度,那么需要把宽和高对调。否则会花屏。因为比如你320 X 240,图像旋转90°之后宽高变成了240 X 320。
MediaFormat mediaFormat = MediaFormat.createVideoFormat(mime, height, width);
//设置参数
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, framerate);
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat); //COLOR_FormatYUV420SemiPlanar COLOR_FormatYUV420Planar
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5); //关键帧间隔时间 单位s
mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
mediaCodec.start();
} catch (Exception e) {
e.printStackTrace();
}
}
@SuppressLint("NewApi")
public void close() {
try {
mediaCodec.stop();
mediaCodec.release();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 开始编码
*
* @author:gj
* @date: 2017/5/27
* @time: 14:55
**/
@SuppressLint("NewApi")
public int offerEncoder(byte[] input, byte[] output) {
int pos = 0;
//这里根据你设置的采集格式调用。我这里是nv21
//swapYV12toI420(input, yuv420, m_width, m_height);
NV21ToNV12(input, rotateYuv420, m_width, m_height);
//把视频逆时针旋转90度。(正常视觉效果)
YUV420spRotate90Anticlockwise(rotateYuv420, yuv420, m_width, m_height);
try {
@SuppressWarnings("deprecation")
ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();
@SuppressWarnings("deprecation")
ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers();
int inputBufferIndex = mediaCodec.dequeueInputBuffer(-1);
if (inputBufferIndex >= 0) {
ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
inputBuffer.clear();
inputBuffer.put(yuv420);
//计算pts,这个值是一定要设置的
long pts = computePresentationTime(presentationTimeUs);
mediaCodec.queueInputBuffer(inputBufferIndex, 0, yuv420.length, pts, 0);
presentationTimeUs += 1;
}
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
while (outputBufferIndex >= 0) {
ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
byte[] outData = new byte[bufferInfo.size];
outputBuffer.get(outData);
if (m_info != null) {
System.arraycopy(outData, 0, output, pos, outData.length);
pos += outData.length;
} else {
//保存pps sps 只有开始时 第一个帧里有, 保存起来后面用
ByteBuffer spsPpsBuffer = ByteBuffer.wrap(outData);
if (spsPpsBuffer.getInt() == 0x00000001) {
m_info = new byte[outData.length];
System.arraycopy(outData, 0, m_info, 0, outData.length);
} else {
return -1;
}
}
mediaCodec.releaseOutputBuffer(outputBufferIndex, false);
outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
}
if (output[4] == 0x65) {
//key frame 编码器生成关键帧时只有 00 00 00 01 65 没有pps sps, 要加上
System.arraycopy(output, 0, yuv420, 0, pos);
System.arraycopy(m_info, 0, output, 0, m_info.length);
System.arraycopy(yuv420, 0, output, m_info.length, pos);
pos += m_info.length;
}
} catch (Throwable t) {
t.printStackTrace();
}
return pos;
}
//-----------下面是常用的格式转换方法-----------------------------
//yv12 转 yuv420p yvu -> yuv,yuv420p就是I420格式,使用极其广泛
private void swapYV12toI420(byte[] yv12bytes, byte[] i420bytes, int width, int height) {
System.arraycopy(yv12bytes, 0, i420bytes, 0, width * height);
System.arraycopy(yv12bytes, width * height + width * height / 4, i420bytes, width * height, width * height / 4);
System.arraycopy(yv12bytes, width * height, i420bytes, width * height + width * height / 4, width * height / 4);
}
//选择了YUV420SP作为编码的目标颜色空间,其实YUV420SP就是NV12,咱们CAMERA设置的是NV21,所以需要转一下
private void NV21ToNV12(byte[] nv21, byte[] nv12, int width, int height) {
if (nv21 == null || nv12 == null) return;
int framesize = width * height;
int i = 0, j = 0;
System.arraycopy(nv21, 0, nv12, 0, framesize);
for (i = 0; i < framesize; i++) {
nv12[i] = nv21[i];
}
for (j = 0; j < framesize / 2; j += 2) {
nv12[framesize + j - 1] = nv21[j + framesize];
}
for (j = 0; j < framesize / 2; j += 2) {
nv12[framesize + j] = nv21[j + framesize - 1];
}
}
private void swapYV12toNV12(byte[] yv12bytes, byte[] nv12bytes, int width, int height) {
int nLenY = width * height;
int nLenU = nLenY / 4;
System.arraycopy(yv12bytes, 0, nv12bytes, 0, width * height);
// for (int i = 0; i < nLenU; i++) {
// nv12bytes[nLenY + 2 * i] = yv12bytes[nLenY + i];
// nv12bytes[nLenY + 2 * i + 1] = yv12bytes[nLenY + nLenU + i];
// }
for (int i = 0; i < nLenU; i++) {
nv12bytes[nLenY + 2 * i + 1] = yv12bytes[nLenY + i];
nv12bytes[nLenY + 2 * i] = yv12bytes[nLenY + nLenU + i];
}
}
private void swapNV12toI420(byte[] nv12bytes, byte[] i420bytes, int width, int height) {
int nLenY = width * height;
int nLenU = nLenY / 4;
System.arraycopy(nv12bytes, 0, i420bytes, 0, width * height);
for (int i = 0; i < nLenU; i++) {
i420bytes[nLenY + i] = nv12bytes[nLenY + 2 * i + 1];
i420bytes[nLenY + nLenU + i] = nv12bytes[nLenY + 2 * i];
}
}
public Bitmap rawByteArray2RGBABitmap2(byte[] data, int width, int height) {
int frameSize = width * height;
int[] rgba = new int[frameSize];
for (int i = 0; i < height; i++)
for (int j = 0; j < width; j++) {
int y = (0xff & ((int) data[i * width + j]));
int u = (0xff & ((int) data[frameSize + (i >> 1) * width + (j & ~1) + 0]));
int v = (0xff & ((int) data[frameSize + (i >> 1) * width + (j & ~1) + 1]));
y = y < 16 ? 16 : y;
int r = Math.round(1.164f * (y - 16) + 1.596f * (v - 128));
int g = Math.round(1.164f * (y - 16) - 0.813f * (v - 128) - 0.391f * (u - 128));
int b = Math.round(1.164f * (y - 16) + 2.018f * (u - 128));
r = r < 0 ? 0 : (r > 255 ? 255 : r);
g = g < 0 ? 0 : (g > 255 ? 255 : g);
b = b < 0 ? 0 : (b > 255 ? 255 : b);
rgba[i * width + j] = 0xff000000 + (b << 16) + (g << 8) + r;
}
Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
bmp.setPixels(rgba, 0, width, 0, 0, width, height);
return bmp;
}
//计算YUV的buffer的函数,需要根据文档计算,而不是简单“*3/2”
public 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;
}
//通过mimeType确定支持的格式
private int selectColorFormat(MediaCodecInfo codecInfo, String mimeType) {
MediaCodecInfo.CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(mimeType);
for (int i = 0; i < capabilities.colorFormats.length; i++) {
int colorFormat = capabilities.colorFormats[i];
if (isRecognizedFormat(colorFormat)) {
return colorFormat;
}
}
Log.e(TAG, "couldn't find a good color format for " + codecInfo.getName() + " / " + mimeType);
return 0; // not reached
}
private boolean isRecognizedFormat(int colorFormat) {
switch (colorFormat) {
// these are the formats we know how to handle for this test
case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar:
case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedPlanar:
case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar:
case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar:
case MediaCodecInfo.CodecCapabilities.COLOR_TI_FormatYUV420PackedSemiPlanar:
return true;
default:
return false;
}
}
private MediaCodecInfo selectCodec(String mimeType) {
int numCodecs = MediaCodecList.getCodecCount();
for (int i = 0; i < numCodecs; i++) {
MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
if (!codecInfo.isEncoder()) {
continue;
}
String[] types = codecInfo.getSupportedTypes();
for (int j = 0; j < types.length; j++) {
if (types[j].equalsIgnoreCase(mimeType)) {
return codecInfo;
}
}
}
return null;
}
/**
* 计算视频pts
*/
private long computePresentationTime(long frameIndex) {
return 132 + frameIndex * 1000000 / m_framerate;
}
//顺时针旋转270度
private void YUV420spRotate270(byte[] des, byte[] src, int width, int height) {
int n = 0;
int uvHeight = height >> 1;
int wh = width * height;
//copy y
for (int j = width - 1; j >= 0; j--) {
for (int i = 0; i < height; i++) {
des[n++] = src[width * i + j];
}
}
for (int j = width - 1; j > 0; j -= 2) {
for (int i = 0; i < uvHeight; i++) {
des[n++] = src[wh + width * i + j - 1];
des[n++] = src[wh + width * i + j];
}
}
}
//旋转180度(顺时逆时结果是一样的)
private void YUV420spRotate180(byte[] src, byte[] des, int width, int height) {
int n = 0;
int uh = height >> 1;
int wh = width * height;
//copy y
for (int j = height - 1; j >= 0; j--) {
for (int i = width - 1; i >= 0; i--) {
des[n++] = src[width * j + i];
}
}
for (int j = uh - 1; j >= 0; j--) {
for (int i = width - 1; i > 0; i -= 2) {
des[n] = src[wh + width * j + i - 1];
des[n + 1] = src[wh + width * j + i];
n += 2;
}
}
}
//顺时针旋转90
private void YUV420spRotate90Clockwise(byte[] src, byte[] dst, int srcWidth, int srcHeight) {
// int wh = width * height;
// int k = 0;
// for (int i = 0; i < width; i++) {
// for (int j = height - 1; j >= 0; j--) {
// des[k] = src[width * j + i];
// k++;
// }
// }
// for (int i = 0; i < width; i += 2) {
// for (int j = height / 2 - 1; j >= 0; j--) {
// des[k] = src[wh + width * j + i];
// des[k + 1] = src[wh + width * j + i + 1];
// k += 2;
// }
// }
int wh = srcWidth * srcHeight;
int uvHeight = srcHeight >> 1;
//旋转Y
int k = 0;
for (int i = 0; i < srcWidth; i++) {
int nPos = 0;
for (int j = 0; j < srcHeight; j++) {
dst[k] = src[nPos + i];
k++;
nPos += srcWidth;
}
}
for (int i = 0; i < srcWidth; i += 2) {
int nPos = wh;
for (int j = 0; j < uvHeight; j++) {
dst[k] = src[nPos + i];
dst[k + 1] = src[nPos + i + 1];
k += 2;
nPos += srcWidth;
}
}
}
//逆时针旋转90
private void YUV420spRotate90Anticlockwise(byte[] src, byte[] dst, int width, int height) {
int wh = width * height;
int uvHeight = height >> 1;
//旋转Y
int k = 0;
for (int i = 0; i < width; i++) {
int nPos = width - 1;
for (int j = 0; j < height; j++) {
dst[k] = src[nPos - i];
k++;
nPos += width;
}
}
for (int i = 0; i < width; i += 2) {
int nPos = wh + width - 1;
for (int j = 0; j < uvHeight; j++) {
dst[k] = src[nPos - i - 1];
dst[k + 1] = src[nPos - i];
k += 2;
nPos += width;
}
}
//不进行镜像翻转
// for (int i = 0; i < width; i++) {
// int nPos = width - 1;
// for (int j = 0; j < height; j++) {
// dst[k] = src[nPos - i];
// k++;
// nPos += width;
// }
// }
// for (int i = 0; i < width; i += 2) {
// int nPos = wh + width - 2;
// for (int j = 0; j < uvHeight; j++) {
// dst[k] = src[nPos - i];
// dst[k + 1] = src[nPos - i + 1];
// k += 2;
// nPos += width;
// }
// }
}
//镜像
private void Mirror(byte[] yuv_temp, int w, int h) {
int i, j;
int a, b;
byte temp;
//mirror y
for (i = 0; i < h; i++) {
a = i * w;
b = (i + 1) * w - 1;
while (a < b) {
temp = yuv_temp[a];
yuv_temp[a] = yuv_temp[b];
yuv_temp[b] = temp;
a++;
b--;
}
}
//mirror u
int uindex = w * h;
for (i = 0; i < h / 2; i++) {
a = i * w / 2;
b = (i + 1) * w / 2 - 1;
while (a < b) {
temp = yuv_temp[a + uindex];
yuv_temp[a + uindex] = yuv_temp[b + uindex];
yuv_temp[b + uindex] = temp;
a++;
b--;
}
}
//mirror v
uindex = w * h / 4 * 5;
for (i = 0; i < h / 2; i++) {
a = i * w / 2;
b = (i + 1) * w / 2 - 1;
while (a < b) {
temp = yuv_temp[a + uindex];
yuv_temp[a + uindex] = yuv_temp[b + uindex];
yuv_temp[b + uindex] = temp;
a++;
b--;
}
}
}
}
解码工具类
package com.imsdk.utils;
import android.media.MediaCodec;
import android.media.MediaFormat;
import android.util.Log;
import android.view.Surface;
import java.io.IOException;
import java.nio.ByteBuffer;
/**
* h264解码器,相对编码器要简单
*
* @author:gj
* @date: 2017/5/27
* @time: 14:59
**/
public class AvcDecode {
//解码类型
String MIME_TYPE = "video/avc";
MediaCodec mediaCodec = null;//这里是建立的解码器
ByteBuffer[] inputBuffers = null;
int m_framerate = 10;//帧率
//pts时间基数
long presentationTimeUs = 0;
public AvcDecode(int mWidth, int mHeigh, Surface surface) {
MediaFormat mediaFormat = MediaFormat.createVideoFormat(
MIME_TYPE, mWidth, mHeigh);
try {
mediaCodec = MediaCodec.createDecoderByType(MIME_TYPE);
mediaCodec.configure(mediaFormat, surface, null, 0);//注意上面编码器的注释,看看区别
mediaCodec.start();
} catch (IOException e) {
e.printStackTrace();
}
inputBuffers = mediaCodec.getInputBuffers();
}
public boolean decodeH264(byte[] h264) {
// Get input buffer index
ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();
int inputBufferIndex = mediaCodec.dequeueInputBuffer(100);//-1表示等待
if (inputBufferIndex >= 0) {
ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
inputBuffer.clear();
inputBuffer.put(h264);
//计算pts
long pts = computePresentationTime(presentationTimeUs);
mediaCodec.queueInputBuffer(inputBufferIndex, 0, h264.length, pts, 0);
presentationTimeUs += 1;
} else {
return false;
}
// Get output buffer index
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 100);
while (outputBufferIndex >= 0) {
mediaCodec.releaseOutputBuffer(outputBufferIndex, true);//到这里为止应该有图像显示了
outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
}
Log.e("Media", "onFrame end");
return true;
}
/**
* 计算pts
*/
private long computePresentationTime(long frameIndex) {
return 132 + frameIndex * 1000000 / m_framerate;
}
}
需要测试的,可以自己下一个VLC客户端安装。然后设置一下,如下图:
先点击工具-->首选项
然后点 媒体-->打开网络串流 ,输入地址播放就可以了。 我这里的 端口是5000,因为是本机,所以省略了IP:udp://@:5000
发送你的数据就能看到视频了
应项目无法全部给出,这里给出关键源码部分,请谅解:http://download.csdn.net/download/kokjuis/9990177