Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android流媒體研究(一),一台手機錄制,另一台手機播放

Android流媒體研究(一),一台手機錄制,另一台手機播放

編輯:關於Android編程

最近無聊,做一下android音視頻方面的學習和研究。網上有很多案例值得我們去學習。我個人先去大概了解了一下音視頻的一些基本概念,然後做了一個小demo,實現了一台手機錄制,另一台手機實時播放。視屏錄制采用的Camera,預覽用的SurfaceView,把采集的視頻壓縮成bitmap,通過socket進行傳輸到另外一台手機上。這個demo只是用來學習android Camera,bitmap傳輸比較耗流量,所以該demo僅供學習。

視頻采集客戶端:
錄制頁面activity

package com.fm.camero;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;

import com.fangming.myframwork.R;
import com.fm.qxtapp.MyApp;

import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.ImageFormat;
import android.graphics.Rect;
import android.graphics.YuvImage;
import android.hardware.Camera;
import android.hardware.Camera.PreviewCallback;
import android.hardware.Camera.Size;
import android.os.Bundle;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.SurfaceHolder.Callback;
import android.view.View.OnClickListener;
import android.view.Window;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;

/**
 * @describe 視頻錄制成圖片壓縮socket傳輸
 * @author fangming
 * @CreateTime 2016年8月16日下午2:32:41
 */
public class TestcamoraActivety2 extends ServiceActivity implements Callback, PreviewCallback {

    private TestcamoraActivety2 _this;
    private String TAG = TestcamoraActivety2.class.getSimpleName();
    private Button btn_start;
    private SurfaceView sf_surfaceView;
    private SurfaceHolder suHolder;
    private int width;// 分辨率寬度
    private int height;// 分辨率高度
    private Camera mCamera;
    private boolean isStreaming = false;
    private MyApp myApp;
    private Button btn_puse;
    private ImageView iv_show;
    private Boolean isShow = true;
    int mwidth, mheight;
    private Button btn_switch;
    private Button btn_conn;
    private Button btn_disconn;
    private EditText et_ip;
    private EditText et_port;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_testcamora);
        _this = this;
        initView();
        myApp = MyApp.getInstance();
    }

    private void initSurfaceholder() {
        suHolder = sf_surfaceView.getHolder();
        suHolder.addCallback(this);
        // width = 352;
        // height = 288;
        width = 320;
        height = 480;
        mwidth = width;
        mheight = height;
        System.out.println("*****相機初始化完成*****");
    }

    private void initView() {
        btn_start = (Button) findViewById(R.id.btn_start);
        btn_puse = (Button) findViewById(R.id.btn_puse);
        iv_show = (ImageView) findViewById(R.id.iv_show);
        sf_surfaceView = (SurfaceView) findViewById(R.id.sf_surfaceView);
        initSurfaceholder();
        btn_start.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                mCamera.startPreview();
                // new DumpFrameTask().execute();
            }
        });
        btn_puse.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                mCamera.stopPreview();
            }
        });
        btn_switch = (Button) findViewById(R.id.btn_switch);
        btn_switch.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {

            }
        });

        btn_conn = (Button) findViewById(R.id.btn_conn);
        btn_disconn = (Button) findViewById(R.id.btn_disconn);
        et_ip = (EditText) findViewById(R.id.et_ip);
        et_port = (EditText) findViewById(R.id.et_port);
        btn_conn.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                ip = et_ip.getText().toString();
                port = et_port.getText().toString();
                bindMyService();
            }
        });
        btn_disconn.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                unBindMyService();
            }
        });

    }

    @Override
    public void onPreviewFrame(byte[] data, Camera camera) {
        isStreaming = true;
        System.out.println("*****相機采集到的數組長度" + data.length + "*****");
        if (isShow) {
            mwidth = camera.getParameters().getPreviewSize().width;
            mheight = camera.getParameters().getPreviewSize().height;
            isShow = false;
        }
        YuvImage image = new YuvImage(data, ImageFormat.NV21, mwidth, height, null);
        if (image != null) {
            ByteArrayOutputStream outstream = new ByteArrayOutputStream();
            image.compressToJpeg(new Rect(0, 0, mwidth, height), 80, outstream);

            byte[] arr=outstream.toByteArray();
//          Bitmap bitmap =BitmapFactory.decodeByteArray(arr,0, arr.length);
//          iv_show.setRotation(90);
//          iv_show.setImageBitmap(bitmap);

            mService.sendDate(arr);
            try {
                outstream.flush();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        mCamera = Camera.open();
        try {
            mCamera.setPreviewDisplay(suHolder);
        } catch (IOException e1) {
            e1.printStackTrace();
        }
        mCamera.setPreviewCallback(this);
        List previewSizes = mCamera.getParameters().getSupportedPreviewSizes();
        width = previewSizes.get(0).width;
        height = previewSizes.get(0).height;
        Camera.Parameters parameters = mCamera.getParameters();
        parameters.setPreviewSize(width, height);
        // 橫豎屏鏡頭自動調整
        if (this.getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE) {
            parameters.set("orientation", "portrait"); //
            parameters.set("rotation", 90); // 鏡頭角度轉90度(默認攝像頭是橫拍)
            mCamera.setDisplayOrientation(90); // 在2.2以上可以使用
        } else// 如果是橫屏
        {
            parameters.set("orientation", "landscape"); //
            mCamera.setDisplayOrientation(0); // 在2.2以上可以使用
        }
        System.out.println("*****surfaceCreated*****");
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        System.out.println("*****系統執行surfaceChanged*****");
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        System.out.println("*****surfaceDestroyed*****");
        if (mCamera != null) {
            mCamera.setPreviewCallback(null);
            mCamera.stopPreview();
            mCamera.release();
            mCamera = null;
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unBindMyService();
    };

    @Override
    public void finish() {
        super.finish();
        Intent mintent = new Intent();
        mintent.setAction(ConnectionService.ACTION);
        stopService(mintent);
    }
}

該activity布局:




    

    

在這個錄制中,遇到的幾個比較坑的地方,第一個是視頻的寬和高,沒有設置好,播放時會導致黑屏或者花屏。還有就是對於camera采集的視頻格式的一些問題。默認是ImageFormat.NV21,也就是YUV420sp。

// yuv420P(YV12)或者yuv420SP(NV21/NV12)
parameters.setPreviewFormat(ImageFormat.NV21);

我們采集的視頻最後通過onPreviewFrame這個回調接口獲得

    @Override
    public void onPreviewFrame(byte[] data, Camera camera) {
        isStreaming = true;
        System.out.println("*****相機采集到的數組長度" + data.length + "*****");
        if (isShow) {
            mwidth = camera.getParameters().getPreviewSize().width;
            mheight = camera.getParameters().getPreviewSize().height;
            isShow = false;
        }
        YuvImage image = new YuvImage(data, ImageFormat.NV21, mwidth, height, null);
        if (image != null) {
            ByteArrayOutputStream outstream = new ByteArrayOutputStream();
            image.compressToJpeg(new Rect(0, 0, mwidth, height), 80, outstream);

            byte[] arr=outstream.toByteArray();
//          Bitmap bitmap =BitmapFactory.decodeByteArray(arr,0, arr.length);
//          iv_show.setRotation(90);
//          iv_show.setImageBitmap(bitmap);
            //視頻發送
            mService.sendDate(arr);
            try {
                outstream.flush();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

采集到的視頻通過mService.sendDate(arr);進行發送。mService是我自己定義的一個service用來後台處理數據。

ConnectionService類

package com.fm.camero;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketException;

import com.fm.constants.Constants;
import com.fm.utill.ByteConvertUtils;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

public class ConnectionService extends Service {
    public final static String ACTION = "com.connectionService";
    private final static int PACKATHEADER_SIGN_LENGTH = 2;// 我的包標識
    private final static int PACKATHEADER_TYPE_LENGTH = 4;// 包類型 4byte
    private final static int PACKATHEADER_DATALEN_LENGTH = 4;// 包長度 4byte
    private final static int PACKATHEADER_LENGTH = PACKATHEADER_SIGN_LENGTH + PACKATHEADER_TYPE_LENGTH
            + PACKATHEADER_DATALEN_LENGTH;// 包頭
    private String ipAdd = "192.168.1.109";
    private int port = 6001;
    private String TAG = "ConnectionService";
    private Socket mSocket;
    private OutputStream outStream;
    private InputStream inStream;
    private InitSocketThread initSocketThread;
    private Boolean isAccepte = true;

    @Override
    public void onCreate() {
        super.onCreate();
        Log.e(TAG, "---->" + "onCreateservice");
    }

    @Override
    public void onDestroy() {
        Log.e(TAG, "---->service停止");
        super.onDestroy();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.e(TAG, "---->" + "onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }

    /**
     * 初始化socket線程
     */
    class InitSocketThread extends Thread {

        @Override
        public void run() {
            super.run();
            initSocket();
        }
    }

    /**
     * 初始化socket
     */
    private synchronized void initSocket() {
        Log.e(TAG, "---->創建socket成功");
        try {
            mSocket = new Socket(ipAdd, port);
            if (mSocket.isConnected()) {
                Log.e(TAG, "---->連接成功");
                outStream = mSocket.getOutputStream();
                inStream = mSocket.getInputStream();
            }
        } catch (SocketException e) {
            Log.d(TAG, "socket conn fail");
            e.printStackTrace();
        } catch (IOException e) {
            Log.d(TAG, "socket conn fail");
            e.printStackTrace();
        }
    }

    public class MyBinder extends Binder {

        public ConnectionService getService() {
            return ConnectionService.this;
        }
    }

    public MyBinder mBinder = new MyBinder();

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    public void sendDate(byte[] data) {
        int datalength = data.length;
        byte[] buffer = new byte[PACKATHEADER_LENGTH + datalength];
        // 設置包頭3000
        System.arraycopy(Constants.APPREQUEST_HEAD, 0, buffer, 0, 2);
        // 設置type
        byte[] type = { 0, 0, 0x26, (byte) 0x48 };
        System.arraycopy(type, 0, buffer, 2, 4);
        // 設置長度
        byte[] len = ByteConvertUtils.intToBytes(datalength);
        System.arraycopy(len, 0, buffer, 6, 4);
        // 設置內容
        System.arraycopy(data, 0, buffer, 10, datalength);
        new sendDataThread(buffer).start();
    }

    public class sendDataThread extends Thread {
        byte byteBuffer[] = null;

        public sendDataThread(byte[] data) {
            this.byteBuffer = data;
        }

        public void run() {
            try {
                outStream.write(byteBuffer, 0, byteBuffer.length);
                System.out.println("發送的數據長度:" + byteBuffer.length);
                outStream.flush();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public void setIpAndPort(String ip, String port) {
        if (ip != null && !ip.equals("")) {
            ipAdd = ip;
        }
        if (port != null && !port.equals("")) {
            this.port = Integer.valueOf(port);
        }
        if (initSocketThread == null) {
            initSocketThread = new InitSocketThread();
            initSocketThread.start();
        }
    }

}

以上就是客戶端錄制,下面是播放客戶端

播放端activity

package com.fm.server;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Toast;

public class MainActivity extends Activity {

    private MainActivity _this;
    private ImageView iv_btmap;
    private EditText et_port;
    private Button btn_bind;
    private Button btn_unbind;
    public String mPort;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        _this = this;
        initView();
    }

    private void initView() {
        iv_btmap = (ImageView) findViewById(R.id.iv_btmap);
        et_port = (EditText) findViewById(R.id.et_port);
        btn_bind = (Button) findViewById(R.id.btn_bind);
        btn_unbind = (Button) findViewById(R.id.btn_unbind);
        btn_bind.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                String port = et_port.getText().toString();
                if (port == null || port.equals("")) {
                    Toast.makeText(_this, "端口為null", Toast.LENGTH_SHORT).show();
                    return;
                }
                mPort = port;
                bindService();
            }
        });
        btn_unbind.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                unBindService();
            }
        });
    }

    Handler mHandler = new Handler() {
        public void handleMessage(Message msg) {
            switch (msg.what) {
            case 0:
                byte[] data = (byte[]) msg.obj;
                if (data == null) {
                    return;
                }
                System.out.println("handle收到的數據:" + data.length);
                BitmapFactory.Options options = new BitmapFactory.Options();
                options.inSampleSize = 2;
                // Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0,
                // data.length, options);
                Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
                if (bitmap != null) {
                    iv_btmap.setRotation(90);
                    iv_btmap.setImageBitmap(bitmap);
                }
                break;

            default:
                break;
            }
        };
    };

    @Override
    public void finish() {
        super.finish();
        if (mService != null) {
            unBindService();
        }
    }

    public void bindService() {
        Intent mintent = new Intent();
        mintent.setClass(_this, BindService.class);
        bindService(mintent, conn, Context.BIND_AUTO_CREATE);
    }

    public void unBindService() {
        if (mService != null) {
            unbindService(conn);
        }
    }

    public BindService mService = null;

    ServiceConnection conn = new ServiceConnection() {

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mService = null;
        }

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mService = ((BindService.MyBinder) service).getService();
            mService.startConn(mPort);
            mService.setHandler(mHandler);
        }
    };

}

播放端service
package com.fm.server;

import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.Arrays;

import com.fm.constants.Constants;
import com.fm.utill.ByteConvertUtils;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.util.Log;

public class BindService extends Service {
private String TAG = “BindService”;
public int port = 6001;
public OutputStream outStream;
public InputStream inStream;
public Socket mSocket;
private InitSocketThread initSocketThread;
private ReadThread readThread;
private Boolean isAccepte = true;
ServerSocket server;
private final static int PACKATHEADER_SIGN_LENGTH = 2;// 我的包標識
private final static int PACKATHEADER_TYPE_LENGTH = 4;// 包類型 4byte
private final static int PACKATHEADER_DATALEN_LENGTH = 4;// 包長度 4byte
private final static int PACKATHEADER_LENGTH = PACKATHEADER_SIGN_LENGTH + PACKATHEADER_TYPE_LENGTH
+ PACKATHEADER_DATALEN_LENGTH;// 包頭

public void setPort(int port) {
    this.port = port;
}

private Handler hander;

public void setHandler(Handler hander) {
    this.hander = hander;
}

public void startConn(String port) {
    if (port != null && !port.equals("")) {
        this.port = Integer.valueOf(port);
    }
    initSocketThread = new InitSocketThread();
    initSocketThread.start();
}

/**
 * 初始化socket線程
 */
class InitSocketThread extends Thread {

    @Override
    public void run() {
        super.run();
        initSocket();
    }
}

/**
 * 初始化socket
 */
private void initSocket() {
    Log.e(TAG, "---->創建socket成功");
    try {
        server = new ServerSocket(port);
        mSocket = server.accept();
        if (mSocket.isConnected()) {
            Log.e(TAG, "---->連接成功");
            outStream = mSocket.getOutputStream();
            inStream = mSocket.getInputStream();
            readThread = new ReadThread();
            readThread.start();
        }
    } catch (SocketException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

class ReadThread extends Thread {
    @Override
    public void run() {
        super.run();
        while (isAccepte) {
            if (mSocket != null && mSocket.isConnected()) {
                devide(new DataInputStream(new BufferedInputStream(inStream)));
            }
        }
    }
}

public synchronized void devide(DataInputStream ds) {
    try {
        if (ds.available() > 0) {
            recv(ds);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

public void recv(DataInputStream dis) throws IOException {
    byte[] head = new byte[PACKATHEADER_LENGTH];
    Log.e(TAG, "---->>接收數據的長度" + dis.available());
    try {
        dis.readFully(head, 0, PACKATHEADER_LENGTH);
    } catch (IOException e) {
        return;
    }
    // 讀取前兩個字節我的數據包標識head[]
    byte[] sign = new byte[2];
    sign = Arrays.copyOfRange(head, 0, 2);
    if (ByteConvertUtils.bytesToInt2(sign) != Constants.APP_CTL_HEAD) {
        return;
    }
    // 讀取4byte包類型
    byte[] type = new byte[4];
    type = Arrays.copyOfRange(head, 2, 6);
    int nType = ByteConvertUtils.bytesToInt(type);

    // 讀取4byte內容長度
    byte[] datalen = new byte[4];
    datalen = Arrays.copyOfRange(head, 6, 10);
    int nDatalen = ByteConvertUtils.bytesToInt(datalen);
    Log.e(TAG, "type:" + nType);

    switch (nType) {// App登陸/查詢網關
    case Constants.VEDIO_STREAM:
        byte[] data = null;
        if (nDatalen > 0) {
            // 接收指定長度的內容
            data = new byte[nDatalen];
            dis.readFully(data, 0, nDatalen);
            Log.e(TAG, "len:" + nDatalen);
            sendMessage(data);
        }
        break;
    default:
        Log.e(TAG, "---->不識別的命令");
        break;
    }
};

public void sendMessage(byte[] data) {
    Message msg = hander.obtainMessage();
    msg.what = 0;
    msg.obj = data;
    hander.sendMessage(msg);
}

@Override
public void onCreate() {
    Log.e(TAG, "---->" + "onCreateservice");
    super.onCreate();
}

@Override
public void onDestroy() {
    Log.e(TAG, "---->service停止");
    super.onDestroy();
    isAccepte = false;
    if (readThread != null && readThread.isAlive()) {
        readThread.interrupt();
        readThread = null;
    }
    initSocketThread.interrupt();
    initSocketThread = null;
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    Log.e(TAG, "---->" + "onStartCommand");
    return super.onStartCommand(intent, flags, startId);
}

public class MyBinder extends Binder {

    public BindService getService() {
        return BindService.this;
    }
}

public MyBinder mBinder = new MyBinder();

@Override
public IBinder onBind(Intent intent) {
    return mBinder;
}

}

主要用來和視頻采集端進行socket通信。

另外附上一個簡單的靜態類

package com.fm.constants;

public class Constants {
public static final int APP_CTL_HEAD = 3000;// 包頭標識
public static final byte[] APPREQUEST_HEAD={0x0b,(byte) 0xB8};//包頭標識

public static final int VEDIO_STREAM = 9800;//服務端將要發送播放視頻命令

}

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved