欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android實(shí)時(shí)獲取攝像頭畫面?zhèn)鬏斨罰C端思路詳解

 更新時(shí)間:2023年07月07日 16:16:32   作者:kason  
這篇文章主要介紹了Android實(shí)時(shí)獲取攝像頭畫面?zhèn)鬏斨罰C端思路詳解,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下

前言

最近在做一個(gè)PC端小應(yīng)用,需要獲取攝像頭畫面,但是電腦攝像頭像素太低,而且位置調(diào)整不方便,又不想為此單獨(dú)買個(gè)攝像頭。于是想起了之前淘汰掉的手機(jī),成像質(zhì)量還是杠杠的,能不能把手機(jī)攝像頭連接到電腦上使用呢?經(jīng)過(guò)搜索,在網(wǎng)上找到了幾款這類應(yīng)用,但是都是閉源的。我一向偏好使用開(kāi)源軟件,但是找了挺久也沒(méi)有找到一個(gè)比較合適的。想著算了,自己開(kāi)發(fā)一個(gè)吧,反正這么個(gè)簡(jiǎn)單的需求,應(yīng)該大概也許不難吧(??

思路

通過(guò)Android的Camera API是可以拿到攝像頭每一幀的原始圖像數(shù)據(jù)的,一般都是YUV格式的數(shù)據(jù),一幀2400x1080的圖片大小為2400x1080x3/2字節(jié),約等于3.7M。25fps的話,帶寬要達(dá)到741mbps,太費(fèi)帶寬了,所以只能壓縮一下再傳輸了。最簡(jiǎn)單的方法,把每一幀壓縮成jpeg再傳輸,就是效率有點(diǎn)低,而更好的方法是壓縮成視頻流后再傳輸,PC端接收到視頻流后再實(shí)時(shí)解壓縮還原回圖片。

實(shí)現(xiàn)

思路有了,那就開(kāi)搞吧。

獲取攝像頭數(shù)據(jù)

新建一個(gè)Android項(xiàng)目,然后在AndroidManifest.xml中聲明攝像頭和網(wǎng)絡(luò)權(quán)限:

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />

界面上搞一個(gè)SurfaceView用于預(yù)覽

<SurfaceView
            android:id="@+id/surfaceview"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent" />

進(jìn)入主Activity時(shí),打開(kāi)攝像頭:

private void openCamera(int cameraId) {
    class CameraHandlerThread extends HandlerThread {
        private Handler mHandler;
        public CameraHandlerThread(String name) {
            super(name);
            start();
            mHandler = new Handler(getLooper());
        }
        synchronized void notifyCameraOpened() {
            notify();
        }
        void openCamera() {
            mHandler.post(() -> {
                camera = Camera.open(cameraId);
                notifyCameraOpened();
            });
            try {
                wait();
            } catch (InterruptedException e) {
                Log.w(TAG, "wait was interrupted");
            }
        }
    }
    if (camera == null) {
        CameraHandlerThread mThread = new CameraHandlerThread("camera thread");
        synchronized (mThread) {
            mThread.openCamera();
        }
    }
}

然后綁定預(yù)覽surface并調(diào)用攝像頭預(yù)覽接口開(kāi)始獲取攝像頭數(shù)據(jù):

camera.setPreviewDisplay(surfaceHolder);
buffer.data = new byte[bufferSize];
camera.setPreviewCallbackWithBuffer(this);
camera.addCallbackBuffer(buffer.data);
camera.startPreview();

每一幀圖像的數(shù)據(jù)準(zhǔn)備好后,會(huì)通過(guò)onPreviewFrame回調(diào)把YUV數(shù)據(jù)傳送過(guò)來(lái),處理完后,一定要再調(diào)一次addCallbackBuffer以獲取下一幀的數(shù)據(jù)。

@Override
public void onPreviewFrame(byte[] data, Camera c) {
    // data就是原始YUV數(shù)據(jù)
    // 這里處理YUV數(shù)據(jù)
    camera.addCallbackBuffer(buffer.data);
}

監(jiān)聽(tīng)PC端連接

直接用ServerSocket就行了,反正也不需要考慮高并發(fā)場(chǎng)景。

try (ServerSocket srvSocket = new ServerSocket(6666)) {
    this.socketServer = srvSocket;
    for (; ; ) {
        Socket socket = srvSocket.accept();
        this.outputStream = new DataOutputStream(socket.getOutputStream());
        // 初始化視頻編碼器
    }
} catch (IOException ex) {
    Log.e(TAG, ex.getMessage(), ex);
}

視頻編碼

Android上可以使用系統(tǒng)自帶的MediaCodec實(shí)現(xiàn)視頻編解碼,但是這里我并不打算使用它,而是使用靈活度更高的ffmpeg(誰(shuí)知道后面有沒(méi)有一些奇奇怪怪的需求??????)。 網(wǎng)上已經(jīng)有大神封裝好適用于Android的ffmpeg了,直接在Gradle上引用javacv庫(kù)就行。

configurations {
    javacpp
}
task javacppExtract(type: Copy) {
    dependsOn configurations.javacpp
    from { configurations.javacpp.collect { zipTree(it) } }
    include "lib/**"
    into "$buildDir/javacpp/"
    android.sourceSets.main.jniLibs.srcDirs += ["$buildDir/javacpp/lib/"]
    tasks.getByName('preBuild').dependsOn javacppExtract
}
dependencies {
    implementation group: 'org.bytedeco', name: 'javacv', version: '1.5.9'
    javacpp group: 'org.bytedeco', name: 'openblas-platform', version: '0.3.23-1.5.9'
    javacpp group: 'org.bytedeco', name: 'opencv-platform', version: '4.7.0-1.5.9'
    javacpp group: 'org.bytedeco', name: 'ffmpeg-platform', version: '6.0-1.5.9'
}

javacv庫(kù)自帶了一個(gè)FFmpegFrameRecorder類可以實(shí)現(xiàn)視頻錄制功能,但是靈活度太低,還是直接調(diào)原生ffmpeg接口吧。

初始化H264編碼器:

public void init(int width, int height, int[] preferredPixFmt) throws IOException {
    int bitRate = width * height * 3 / 2 * 16;
    int frameRate = 25;
    encoder = avcodec_find_encoder(AV_CODEC_ID_H264);
    codecCtx = initCodecCtx(width, height, fmt, bitRate, frameRate);
    tempFrame = av_frame_alloc();
    scaledFrame = av_frame_alloc();
    tempFrame.pts(-1);
    packet = av_packet_alloc();
}
private AVCodecContext initCodecCtx(int width, int height,int pixFmt, int bitRate, int frameRate) {
    AVCodecContext codec_ctx = avcodec_alloc_context3(encoder);
    codec_ctx.codec_id(AV_CODEC_ID_H264);
    codec_ctx.pix_fmt(pixFmt);
    codec_ctx.width(width);
    codec_ctx.height(height);
    codec_ctx.bit_rate(bitRate);
    codec_ctx.rc_buffer_size(bitRate);
    codec_ctx.framerate().num(frameRate);
    codec_ctx.framerate().den(1);
    codec_ctx.gop_size(frameRate);//每秒1個(gè)關(guān)鍵幀
    codec_ctx.time_base().num(1);
    codec_ctx.time_base().den(frameRate);
    codec_ctx.has_b_frames(0);
    codec_ctx.global_quality(1);
    codec_ctx.max_b_frames(0);
    av_opt_set(codec_ctx.priv_data(), "tune", "zerolatency", 0);
    av_opt_set(codec_ctx.priv_data(), "preset", "ultrafast", 0);
    int ret = avcodec_open2(codec_ctx, encoder, (AVDictionary) null);
    return ret == 0 ? codec_ctx : null;
}

把攝像頭數(shù)據(jù)送進(jìn)來(lái)編碼,由于攝像頭獲取到的數(shù)據(jù)格式和視頻編碼需要的數(shù)據(jù)格式往往不一樣,所以,編碼前需要調(diào)用sws_scale對(duì)圖像數(shù)據(jù)進(jìn)行格式轉(zhuǎn)換。

public int recordFrame(Frame frame) {
    byte[] data = frame.data;    // 對(duì)應(yīng)onPreviewFrame回調(diào)里的data
    int pf = frame.pixelFormat;  
    if (tempFrameDataLen < data.length) {
        if (tempFrameData != null) {
            tempFrameData.releaseReference();
        }
        tempFrameData = new BytePointer(data.length);
        tempFrameDataLen = data.length;
    }
    tempFrameData.put(data);
    int width = frame.width;
    int height = frame.height;
    av_image_fill_arrays(tempFrame.data(), tempFrame.linesize(), tempFrameData, pf, width, height, frame.align);
    tempFrame.format(pf);
    tempFrame.width(width);
    tempFrame.height(height);
    tempFrame.pts(tempFrame.pts() + 1);
    return recordFrame(tempFrame);
}
public int recordFrame(AVFrame frame) {
    int res = 0;
    int srcFmt = frame.format();
    int dstFmt = codecCtx.pix_fmt();
    int width = frame.width();
    int height = frame.height();
    if (srcFmt != dstFmt) {
        // 圖像數(shù)據(jù)格式轉(zhuǎn)換
        convertCtx = sws_getCachedContext(
                convertCtx,
                width, height, srcFmt,
                width, height, dstFmt,
                SWS_BILINEAR, null, null, (DoublePointer) null
        );
        int requiredDataLen = width * height * 3 / 2;
        if (scaledFrameDataLen < requiredDataLen) {
            if (scaledFrameData != null) {
                scaledFrameData.releaseReference();
            }
            scaledFrameData = new BytePointer(requiredDataLen);
            scaledFrameDataLen = requiredDataLen;
        }
        av_image_fill_arrays(scaledFrame.data(), scaledFrame.linesize(), scaledFrameData, dstFmt, width, height, 1);
        scaledFrame.format(dstFmt);
        scaledFrame.width(width);
        scaledFrame.height(height);
        scaledFrame.pts(frame.pts());
        res = sws_scale(convertCtx, frame.data(), frame.linesize(), 0, height, scaledFrame.data(), scaledFrame.linesize());
        if (res == 0) {
            throw new RuntimeException("scale frame failed");
        }
        frame = scaledFrame;
    }
    res = avcodec_send_frame(codecCtx, frame);
    scaledFrame.pts(scaledFrame.pts() + 1);
    if (res != 0 && res != AVERROR_EAGAIN()) {
        throw new RuntimeException("Failed to encode frame:" + res);
    }
    res = avcodec_receive_packet(codecCtx, packet);
    if (res != 0 && res != AVERROR_EAGAIN()) {
        return res;
    }
    return res;
}

編碼完一幀圖像后,需要檢查是否有AVPacket生成,如果有,把它回寫給請(qǐng)求端即可。

AVPacket pkg = encoder.getPacket();
if (outBuffer == null || outBuffer.length < pkg.size()) {
    outBuffer = new byte[pkg.size()];
}
BytePointer pkgData = pkg.data();
if (pkgData == null) {
    return;
}
pkgData.get(outBuffer, 0, pkg.size());
os.write(outBuffer, 0, pkg.size());

重點(diǎn)流程的代碼都寫好了,把它們連接起來(lái)就可以收工了。

收尾

請(qǐng)求端還沒(méi)寫好,先在電腦端使用ffplay測(cè)試一下。

ffplay tcp://手機(jī)IP:6666

嗯,一切正常!就是延時(shí)有點(diǎn)大,主要是ffplay不知道視頻流的格式,所以緩沖了很多幀的數(shù)據(jù)來(lái)偵測(cè)視頻格式,造成了較大的延時(shí)。后面有時(shí)間,再寫篇使用ffmpeg api實(shí)時(shí)解碼H264的文章(??

完整項(xiàng)目代碼:https://github.com/kasonyang/net-camera

到此這篇關(guān)于Android實(shí)時(shí)獲取攝像頭畫面?zhèn)鬏斨罰C端的文章就介紹到這了,更多相關(guān)Android獲取攝像頭畫面內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Android BroadcastReceiver常見(jiàn)監(jiān)聽(tīng)整理

    Android BroadcastReceiver常見(jiàn)監(jiān)聽(tīng)整理

    這篇文章主要介紹了Android BroadcastReceiver常見(jiàn)監(jiān)聽(tīng)整理的相關(guān)資料,需要的朋友可以參考下
    2016-10-10
  • Android使用SQLite數(shù)據(jù)庫(kù)的示例

    Android使用SQLite數(shù)據(jù)庫(kù)的示例

    本篇文章主要介紹了Android使用SQLite數(shù)據(jù)庫(kù)的示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-01-01
  • Android自定義PopupWindow小案例

    Android自定義PopupWindow小案例

    這篇文章主要為大家詳細(xì)介紹了Android自定義PopupWindow小案例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-11-11
  • Android 保存WebView中的圖片示例

    Android 保存WebView中的圖片示例

    本篇文章主要介紹了Android 保存WebView中的圖片示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-12-12
  • Android Fragment實(shí)現(xiàn)列表和內(nèi)容聯(lián)動(dòng)

    Android Fragment實(shí)現(xiàn)列表和內(nèi)容聯(lián)動(dòng)

    這篇文章主要為大家詳細(xì)介紹了Android Fragment實(shí)現(xiàn)列表和內(nèi)容聯(lián)動(dòng)效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-01-01
  • 修改Android Studio 的 Logcat 緩沖區(qū)大小操作

    修改Android Studio 的 Logcat 緩沖區(qū)大小操作

    這篇文章主要介紹了修改Android Studio 的 Logcat 緩沖區(qū)大小操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-04-04
  • Android實(shí)現(xiàn)歡迎頁(yè)快速啟動(dòng)的方法

    Android實(shí)現(xiàn)歡迎頁(yè)快速啟動(dòng)的方法

    這篇文章主要給大家介紹了Android實(shí)現(xiàn)歡迎頁(yè)快速啟動(dòng)的方法,文中給出了詳細(xì)的方法介紹,對(duì)大家具有一定的參考價(jià)值,需要的朋友們可以一起來(lái)學(xué)習(xí)學(xué)習(xí)。
    2017-02-02
  • Android自動(dòng)填充短信驗(yàn)證碼功能(demo)

    Android自動(dòng)填充短信驗(yàn)證碼功能(demo)

    在項(xiàng)目開(kāi)發(fā)中為了給用戶帶來(lái)極好的體驗(yàn)效果,通常需要實(shí)現(xiàn)驗(yàn)證碼的自動(dòng)填充功能,怎么實(shí)現(xiàn)呢?今天小編給大家分享Android自動(dòng)填充短信驗(yàn)證碼功能的實(shí)現(xiàn)方法,需要的朋友參考下吧
    2017-02-02
  • Android之Intent附加數(shù)據(jù)的兩種實(shí)現(xiàn)方法

    Android之Intent附加數(shù)據(jù)的兩種實(shí)現(xiàn)方法

    這篇文章主要介紹了Android之Intent附加數(shù)據(jù)的兩種實(shí)現(xiàn)方法,以實(shí)例形式較為詳細(xì)的分析了添加數(shù)據(jù)到Intent的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-09-09
  • Android 組件Gallery和GridView示例講解

    Android 組件Gallery和GridView示例講解

    本文主要講解Android 組件Gallery和GridView,這里詳細(xì)介紹組件Gallery和GridView的知識(shí)要點(diǎn),并附示例代碼和實(shí)現(xiàn)效果圖,有興趣的小伙伴可以參考下
    2016-08-08

最新評(píng)論