android开发,通过摄像头实时采集视频并使用MediaCodec硬编码为H264

13 篇文章 1 订阅
5 篇文章 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客户端安装。然后设置一下,如下图:


先点击工具-->首选项




然后点 媒体-->打开网络串流 ,输入地址播放就可以了。 我这里的 端口是5000,因为是本机,所以省略了IP:udp://@:5000





发送你的数据就能看到视频了


应项目无法全部给出,这里给出关键源码部分,请谅解:http://download.csdn.net/download/kokjuis/9990177


  • 7
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 53
    评论
评论 53
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值