MediaCodec硬編碼爲H264並通過UDP發送

最近研究視頻通話,寫一下關於攝像頭採集視頻並使用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);
                }
            }
        }
    }
} 
佈局文件
 

```java
<?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 &gt;= 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 &gt;= 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 -&gt; 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 &lt; framesize; i++) {
            nv12[i] = nv21[i];
        }
        for (j = 0; j &lt; framesize / 2; j += 2) {
            nv12[framesize + j - 1] = nv21[j + framesize];
        }
        for (j = 0; j &lt; 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 &lt; nLenU; i++) {
//            nv12bytes[nLenY + 2 * i] = yv12bytes[nLenY + i];
//            nv12bytes[nLenY + 2 * i + 1] = yv12bytes[nLenY + nLenU + i];
//        }
        for (int i = 0; i &lt; 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 &lt; 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 &lt; height; i++)
            for (int j = 0; j &lt; width; j++) {
                int y = (0xff &amp; ((int) data[i * width + j]));
                int u = (0xff &amp; ((int) data[frameSize + (i &gt;&gt; 1) * width + (j &amp; ~1) + 0]));
                int v = (0xff &amp; ((int) data[frameSize + (i &gt;&gt; 1) * width + (j &amp; ~1) + 1]));
                y = y &lt; 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 &lt; 0 ? 0 : (r &gt; 255 ? 255 : r);
                g = g &lt; 0 ? 0 : (g &gt; 255 ? 255 : g);
                b = b &lt; 0 ? 0 : (b &gt; 255 ? 255 : b);

                rgba[i * width + j] = 0xff000000 + (b &lt;&lt; 16) + (g &lt;&lt; 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 &lt; 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 &lt; numCodecs; i++) {
            MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);

            if (!codecInfo.isEncoder()) {
                continue;
            }

            String[] types = codecInfo.getSupportedTypes();
            for (int j = 0; j &lt; 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 &gt;&gt; 1;
        int wh = width * height;
        //copy y
        for (int j = width - 1; j &gt;= 0; j--) {
            for (int i = 0; i &lt; height; i++) {
                des[n++] = src[width * i + j];
            }
        }

        for (int j = width - 1; j &gt; 0; j -= 2) {
            for (int i = 0; i &lt; 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 &gt;&gt; 1;
        int wh = width * height;
        //copy y
        for (int j = height - 1; j &gt;= 0; j--) {
            for (int i = width - 1; i &gt;= 0; i--) {
                des[n++] = src[width * j + i];
            }
        }


        for (int j = uh - 1; j &gt;= 0; j--) {
            for (int i = width - 1; i &gt; 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 &lt; width; i++) {
//            for (int j = height - 1; j &gt;= 0; j--) {
//                des[k] = src[width * j + i];
//                k++;
//            }
//        }
//        for (int i = 0; i &lt; width; i += 2) {
//            for (int j = height / 2 - 1; j &gt;= 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 &gt;&gt; 1;

        //旋轉Y
        int k = 0;
        for (int i = 0; i &lt; srcWidth; i++) {
            int nPos = 0;
            for (int j = 0; j &lt; srcHeight; j++) {
                dst[k] = src[nPos + i];
                k++;
                nPos += srcWidth;
            }
        }

        for (int i = 0; i &lt; srcWidth; i += 2) {
            int nPos = wh;
            for (int j = 0; j &lt; 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 &gt;&gt; 1;

        //旋轉Y
        int k = 0;
        for (int i = 0; i &lt; width; i++) {
            int nPos = width - 1;
            for (int j = 0; j &lt; height; j++) {
                dst[k] = src[nPos - i];
                k++;
                nPos += width;
            }
        }

        for (int i = 0; i &lt; width; i += 2) {
            int nPos = wh + width - 1;
            for (int j = 0; j &lt; uvHeight; j++) {
                dst[k] = src[nPos - i - 1];
                dst[k + 1] = src[nPos - i];
                k += 2;
                nPos += width;
            }
        }

        //不進行鏡像翻轉
//        for (int i = 0; i &lt; width; i++) {
//            int nPos = width - 1;
//            for (int j = 0; j &lt; height; j++) {
//                dst[k] = src[nPos - i];
//                k++;
//                nPos += width;
//            }
//        }
//        for (int i = 0; i &lt; width; i += 2) {
//            int nPos = wh + width - 2;
//            for (int j = 0; j &lt; 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 &lt; h; i++) {
            a = i * w;
            b = (i + 1) * w - 1;
            while (a &lt; 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 &lt; h / 2; i++) {
            a = i * w / 2;
            b = (i + 1) * w / 2 - 1;
            while (a &lt; 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 &lt; h / 2; i++) {
            a = i * w / 2;
            b = (i + 1) * w / 2 - 1;
            while (a &lt; 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 &gt;= 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 &gt;= 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





發送你的數據就能看到視頻了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章