android开发,通过摄像头实时采集视频并使用MediaCodec硬编码为H264 心已赠人 2022-06-15 07:46 186阅读 0赞 最近研究视频通话,写一下关于摄像头采集视频并使用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客户端安装。然后设置一下,如下图: 先点击工具-->首选项 ![Center][] 然后点 媒体-->打开网络串流 ,输入地址播放就可以了。 我这里的 端口是5000,因为是本机,所以省略了IP:udp://@:5000 ![Center 1][] 发送你的数据就能看到视频了 应项目无法全部给出,这里给出关键源码部分,请谅解:http://download.csdn.net/download/kokjuis/9990177 [Center]: /images/20220615/a41cf31a8b5f4cb0b8d78de24c452478.png [Center 1]: /images/20220615/5377839642f4420d9c9fd64a1e661f1d.png
还没有评论,来说两句吧...