主頁 > 移動端開發 > 使用OpenGL同時對兩路攝像頭進行錄像(動態水印)、并且兩路錄像檔案都帶音軌

使用OpenGL同時對兩路攝像頭進行錄像(動態水印)、并且兩路錄像檔案都帶音軌

2021-09-04 07:44:40 移動端開發

本文介紹在Android平臺下,使用OpenGL同時對兩個攝像頭同時預覽、錄像,并且錄像檔案均帶音軌,并將錄像檔案(帶聲音)、音頻檔案保存下來,預覽與錄像均添加時間水印:

先放一個demo效果圖:

1.先初始化兩個攝像頭預覽:


import android.content.Context;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import com.reach.mutilcamerarecord.MainActivity;
import com.reach.mutilcamerarecord.media.base.RecordManageBase;
import com.reach.mutilcamerarecord.media.encode.IAudioListener;
import com.reach.mutilcamerarecord.media.manager.RecordVideoAndAudioManager;
import com.reach.mutilcamerarecord.media.setting.CameraSetting;
import com.reach.mutilcamerarecord.media.setting.RecordSetting;
import com.reach.mutilcamerarecord.media.setting.RenderSetting;

import java.io.File;
import java.io.IOException;

/**
 * 本地內置攝像頭
 */
public class GLRecordManager {
    private MainActivity mActivity;
    private Context mContext;
    //view
    private SurfaceView surfaceView;
    private SurfaceHolder holder;
    private RecordVideoAndAudioManager recorder;

    private final static int TargetLongWidth = 1280;//480;//640;//1920;   這個是錄像解析度
    private int TargetShortWidth = 720;//320;//480;//1080;

    private int cameraWidth = 1280;//320;//640;//這個是攝像頭原始資料
    private int cameraHeight = 720;//240;//480;


    public GLRecordManager(MainActivity activity, Context context, SurfaceView sv){
        mActivity = activity;
        mContext = context;
        surfaceView = sv;
        RecordSetting recordSetting = new RecordSetting();
        CameraSetting cameraSetting = new CameraSetting();
        cameraSetting.fps = 15;//30;
        cameraSetting.cameraW = cameraWidth;
        cameraSetting.cameraH = cameraHeight;
        cameraSetting.cameraPosition = 0;//1;
        RenderSetting renderSetting = new RenderSetting();

        /*String s = Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + System.currentTimeMillis() + ".mp4";
        File file = new File(s);
        try {
            file.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }*/

        recorder = new RecordVideoAndAudioManager(mActivity, null, recordSetting, cameraSetting, renderSetting, surfaceView);
        recorder.setCallBackEvent(new RecordManageBase.CallBackEvent() {
            @Override
            public void startRecordSuccess() {
                Log.e("main", "startRecordSuccess");
            }

            @Override
            public void onDuringUpdate(float time) {
                Log.e("main", "onDuringUpdate => time = " + time);
            }

            @Override
            public void stopRecordFinish(File file) {
                Log.e("main", "stopRecordFinish => path = " + file.getPath());
            }

            @Override
            public void recordError(String errorMsg) {
                Log.e("main", "recordError => msg = " + errorMsg);
            }

            @Override
            public void openCameraSuccess(int cameraPosition) {

                recorder.getRecordSetting().setVideoSetting(TargetLongWidth,TargetShortWidth,
                        recorder.getCameraManager().getRealFps() / 1000, RecordSetting.ColorFormatDefault);
                recorder.getRecordSetting().setVideoBitRate(3000 * 1024);
                recorder.switchOnBeauty(cameraPosition == 1);
                Log.e("main", "openCameraSuccess => cameraPosition = " + cameraPosition);
            }

            @Override
            public void openCameraFailure(int cameraPosition) {
                Log.e("main", "openCameraFailure => cameraPosition = " + cameraPosition);
            }

            @Override
            public void onVideoSizeChange(int width, int height) {
                Log.e("main", "onVideoSizeChange => width = " + width + ", height = " + height);
            }

            @Override
            public void onPhotoSizeChange(int width, int height) {
                Log.e("main", "onPhotoSizeChange => width = " + width + ", height = " + height);
            }
        });

        holder = surfaceView.getHolder();
        holder.addCallback(new CustomCallBack());
    }

    public void onDestroy() {
        if (recorder != null)
            recorder.destroy();
    }

    public void recordVideo(){
        if (recorder.isRecord){
            stopRecord();
        } else {
            startRecord();
        }
    }

    /**
     * 錄像前先創建個檔案先
     */
    public void setVideoFileName() {
        String path = "/sdcard/test_A.mp4";
        String pathMp3 = "/sdcard/test.mp3";
        File file = new File(path);
        File mp3 = new File(pathMp3);
        try {
            file.createNewFile();
            mp3.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
        recorder.setFileName(file, mp3);
    }

    public void startRecord(){
        recorder.startRecord();
    }

    public void stopRecord(){
        recorder.stopRecord();
    }

    public IAudioListener getGLAudioListener(){
        return recorder.getGLAudioListener();
    }

    private class CustomCallBack implements SurfaceHolder.Callback {

        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            recorder.init();
            //recorder.getCameraManager().getEvent().openCameraFailure(0);
        }

        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
            recorder.getRenderSetting().setRenderSize(width < height ? TargetShortWidth : TargetLongWidth, width < height ? TargetLongWidth : TargetShortWidth);
            recorder.getRenderSetting().setDisplaySize(width, height);
        }

        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
            recorder.destroy();
        }

    }
}

第二路攝像頭預覽代碼:

import android.content.Context;
import android.content.res.Configuration;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.os.Environment;
import android.util.Log;
import android.view.TextureView;
import android.view.View;

import androidx.annotation.NonNull;

import com.reach.mutilcamerarecord.MainActivity;
import com.reach.mutilcamerarecord.R;
import com.reach.mutilcamerarecord.mediausb.client.RecorderClient;
import com.reach.mutilcamerarecord.mediausb.code.listener.IVideoChange;
import com.reach.mutilcamerarecord.mediausb.code.listener.UvcPcmDataCallBack;
import com.reach.mutilcamerarecord.mediausb.filter.filter.DrawMultiImageFilter;
import com.reach.mutilcamerarecord.mediausb.model.MediaConfig;
import com.reach.mutilcamerarecord.mediausb.model.RecordConfig;
import com.reach.mutilcamerarecord.mediausb.model.Size;
import com.reach.mutilcamerarecord.mediausb.view.AspectTextureView;

/**
 * Author:lzh on 2021/6/10 10:04
 */
public class GLUsbManager {
    private MainActivity mActivity;
    private Context mContext;

    protected RecorderClient mRecorderClient;
    protected AspectTextureView mTextureView;
    RecordConfig recordConfig;

    boolean isStart = false;

    public GLUsbManager(MainActivity activity, Context context){
        mActivity = activity;
        mContext = context;

        initView();
    }

    private void initView(){

        mTextureView = mActivity.findViewById(R.id.camera_usb);
        mTextureView.setVisibility(View.VISIBLE);
        mTextureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
            @Override
            public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) {
                if (mRecorderClient != null) {
                    mRecorderClient.startPreview(surface, width, height);
                }
                updateOutsideCamState(1);
                Log.i("textureAvailable", "usb camera start to preview...");
            }

            @Override
            public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, int height) {
                if (mRecorderClient != null) {
                    mRecorderClient.updatePreview(width, height);
                }
            }

            @Override
            public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {
                if (mRecorderClient != null) {
                    mRecorderClient.stopPreview(true);
                }
                updateOutsideCamState(0);
                return false;
            }

            @Override
            public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {

            }
        });
        prepareStreamingClient();
        onSetFilters();
    }

    private void prepareStreamingClient() {
        try {
            mRecorderClient = new RecorderClient(this);

            recordConfig = RecordConfig.obtain();
            recordConfig.setTargetVideoSize(new Size(1280, 720));
            int bitRate = 1280 * 720;
            recordConfig.setSquare(true);//方正,不圓角
            recordConfig.setBitRate(bitRate);
            recordConfig.setVideoFPS(15);
            recordConfig.setVideoGOP(1);
            recordConfig.setRenderingMode(MediaConfig.Rending_Model_OpenGLES);
            //camera
            recordConfig.setDefaultCamera(Camera.CameraInfo.CAMERA_FACING_FRONT);
            int frontDirection, backDirection;
            Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
            Camera.getCameraInfo(Camera.CameraInfo.CAMERA_FACING_FRONT, cameraInfo);
            frontDirection = cameraInfo.orientation;
            Camera.getCameraInfo(Camera.CameraInfo.CAMERA_FACING_BACK, cameraInfo);
            backDirection = cameraInfo.orientation;
            if (mContext.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
                recordConfig.setFrontCameraDirectionMode((frontDirection == 90 ? MediaConfig.DirectionMode.FLAG_DIRECTION_ROATATION_270 : MediaConfig.DirectionMode.FLAG_DIRECTION_ROATATION_90) | MediaConfig.DirectionMode.FLAG_DIRECTION_FLIP_HORIZONTAL);
                recordConfig.setBackCameraDirectionMode((backDirection == 90 ? MediaConfig.DirectionMode.FLAG_DIRECTION_ROATATION_90 : MediaConfig.DirectionMode.FLAG_DIRECTION_ROATATION_270));
            } else {
                recordConfig.setBackCameraDirectionMode((backDirection == 90 ? MediaConfig.DirectionMode.FLAG_DIRECTION_ROATATION_0 : MediaConfig.DirectionMode.FLAG_DIRECTION_ROATATION_180));
                recordConfig.setFrontCameraDirectionMode((frontDirection == 90 ? MediaConfig.DirectionMode.FLAG_DIRECTION_ROATATION_180 : MediaConfig.DirectionMode.FLAG_DIRECTION_ROATATION_0) | MediaConfig.DirectionMode.FLAG_DIRECTION_FLIP_HORIZONTAL);
            }
            //save video
            String mSaveVideoPath = Environment.getExternalStorageDirectory().getPath() + "/usb_video" + System.currentTimeMillis() + ".mp4";
            recordConfig.setSaveVideoPath(mSaveVideoPath);

            if (!mRecorderClient.prepare(mContext, recordConfig)) {
                mRecorderClient = null;
                Log.e("RecordingActivity", "prepare,failed!!");
                updateOutsideCamState(0);
                return;
            }
            
            Size s = mRecorderClient.getVideoSize();
            mTextureView.setAspectRatio(AspectTextureView.MODE_FITXY, ((double) s.getWidth()) / s.getHeight());

            mRecorderClient.setVideoChangeListener(new IVideoChange() {
                @Override
                public void onVideoSizeChanged(int width, int height) {
                    mTextureView.setAspectRatio(AspectTextureView.MODE_FITXY, ((double) width) / height);
                }
            });
            
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    protected void onSetFilters() {
        mRecorderClient.setHardVideoFilter(new DrawMultiImageFilter(mContext));
    }

    public void recordVideo(){
        if (!isStart) {
            updateOutsideCamState(1);
            String path = "/sdcard/test_B.mp4";
            mRecorderClient.updatePath(path);
            mRecorderClient.startRecording();
        } else{
            mRecorderClient.stopRecording();
        }
        isStart = !isStart;
    }

    public void setUsbAudioCallback(UvcPcmDataCallBack l){
        mActivity.setUsbAudioCallback(l);
    }

    public void updateOutsideCamState(int status){
        try {
           // mActivity.updateOutsideCamState(status);
        }catch (Exception e){
            e.printStackTrace();
        }
    }


}

接下來錄像操作:

/**
     * 開始/結束錄像按鈕
     */
    private void recordFunc(){
        if (isRecording) {
            mRecordBtn.setText("開始錄像");
        }else {
            mRecordBtn.setText("結束錄像");
        }
        recordVideo();
        isRecording = !isRecording;
    }

    private void recordVideo(){
        mAudioManager.record();
        if (glUsbManager != null) {
            glUsbManager.recordVideo();
        } else {
            Log.e("usbCamera", "start usb camera record fail,gl usb manager is null...");
        }
        mGLRecord.setVideoFileName();
        mGLRecord.recordVideo();

        getGLAudioListener();
    }

另外,由于麥克風在Android上是獨占性質,所以拿到pcm音頻流后,需要分出兩路供兩個攝像頭錄像使用:

/**
     * 原始音頻來了轉發給各執行緒分享
     * @param buffer
     * @param readBytes
     */
    public void onFramePcm(ByteBuffer buffer, int readBytes) {
        if (uvcPcmDataCallBack != null){
            uvcPcmDataCallBack.onFramePcm(buffer, readBytes);//UVC
            buffer.flip();
            //buffer.clear();
        }
        if (glAudioCallback != null) {
            glAudioCallback.onPcm(buffer, readBytes);//本地
            buffer.flip();
        }
    }

最后時間水印重要部分代碼如下:

import android.content.Context;
import android.graphics.Rect;
import android.graphics.RectF;
import android.opengl.GLES11Ext;
import android.opengl.GLES20;
import android.util.Log;

import com.reach.mutilcamerarecord.mediausb.filter.hardvideofilter.BaseHardVideoFilter;
import com.reach.mutilcamerarecord.mediausb.tools.GLESTools;
import com.reach.mutilcamerarecord.utils.BitmapUtils;

import java.nio.FloatBuffer;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;

/**
 * Author:lzh on 2021/7/14 17:34
 */
public class DrawMultiImageFilter extends BaseHardVideoFilter {
    protected int glProgram;
    protected int glCamTextureLoc;
    protected int glCamPostionLoc;
    protected int glCamTextureCoordLoc;
    protected int glImageTextureLoc;
    protected int glImageRectLoc;
    protected int glImageAngelLoc;

    protected Context mContext;
    //private ArrayList<ImageDrawData> mImageInfos = new ArrayList<>();
    private ArrayList<ImageTexture> imageTextures = new ArrayList<>();
    private int mSize = 12;

    int textureId;
    int frameBuffer;
    Rect rect;
    int x;
    int y;
    String str;

    private final static SimpleDateFormat formatter   = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public DrawMultiImageFilter(Context context) {
        super();
        mContext = context;
    }

    @Override
    public void onInit(int videoWidth, int videoHeight) {
        super.onInit(videoWidth, videoHeight);
        glProgram = GLESTools.createProgram(GLESTools.uRes(mContext.getResources(), "drawimage_vertex.sh"),
                GLESTools.uRes(mContext.getResources(), "drawimage_fragment.sh"));
        GLES20.glUseProgram(glProgram);
        glCamTextureLoc = GLES20.glGetUniformLocation(glProgram, "uCamTexture");
        glImageTextureLoc = GLES20.glGetUniformLocation(glProgram, "uImageTexture");
        glCamPostionLoc = GLES20.glGetAttribLocation(glProgram, "aCamPosition");
        glCamTextureCoordLoc = GLES20.glGetAttribLocation(glProgram, "aCamTextureCoord");
        glImageRectLoc = GLES20.glGetUniformLocation(glProgram, "imageRect");
        glImageAngelLoc = GLES20.glGetUniformLocation(glProgram, "imageAngel");

        x = 20;
        y = videoHeight - 50;//時間水印設在左下角
Log.e("water", "usb water title,y pos:" + y + ",h=" + videoHeight);
        initImageTexture();
    }

    protected void initImageTexture() {
        imageTextures = new ArrayList<>();
       ImageTexture imageTexture;
        for (int i = 0; i < 19; i++) {
            imageTexture = new ImageTexture(outVideoWidth, outVideoHeight);
            if (i == 10) {
                imageTexture.loadBitmap(BitmapUtils.textToBitmap("-"));
            } else if (i == 11) {
                imageTexture.loadBitmap(BitmapUtils.textToBitmap(":"));
            } else if (i >=0 && i <= 9){
                imageTexture.loadBitmap(BitmapUtils.textToBitmap(i + ""));
            } else {
                imageTexture.loadBitmap(BitmapUtils.textToBitmap("*"));
            }
            imageTextures.add(imageTexture);
        }
        mSize = imageTextures.size();
    }

    @Override
    public void onDraw(int cameraTexture, int targetFrameBuffer, FloatBuffer shapeBuffer, FloatBuffer textureBuffer) {
        GLES20.glViewport(0, 0, outVideoWidth, outVideoHeight);
        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);

        ImageTexture preImageTexture = null;
        String time = formatter.format(new Date());

        if ("".equals(time)) {
            return;
        }
        for (int i = 0; i < mSize; i++) {
            if (preImageTexture == null) {
                textureId = cameraTexture;
            } else {
                textureId = preImageTexture.getTextureId();
            }
            if (i == mSize - 1) {
                frameBuffer = targetFrameBuffer;
            } else {
                frameBuffer = imageTextures.get(i).getFrameBuffer();
            }
            rect = new Rect(x+15*i, y, x+15*i+220, y+40);
            if (rect.left == rect.right || rect.top == rect.bottom) {
                continue;
            }
            str = time.substring(i, i+1);
            if (str.equals("-")){
                str = "10";
            } else if (str.equals(":")){
                str = "11";
            } else if (str.equals(" ") || str.equals("*")){
                continue;
            }
            drawImage(convertToRectF(rect), imageTextures.get(Integer.parseInt(str)).getImageTextureId(), textureId, frameBuffer, shapeBuffer, textureBuffer);
            preImageTexture = imageTextures.get(i);
        }
        GLES20.glFinish();
    }

    protected void drawImage(RectF rectF, int imageTextureId, int cameraTexture, int targetFrameBuffer, FloatBuffer shapeBuffer, FloatBuffer textureBuffer) {
        GLES20.glEnableVertexAttribArray(glCamPostionLoc);
        GLES20.glEnableVertexAttribArray(glCamTextureCoordLoc);
        shapeBuffer.position(0);
        GLES20.glVertexAttribPointer(glCamPostionLoc, 2,
                GLES20.GL_FLOAT, false,
                2 * 4, shapeBuffer);
        textureBuffer.position(0);
        GLES20.glVertexAttribPointer(glCamTextureCoordLoc, 2,
                GLES20.GL_FLOAT, false,
                2 * 4, textureBuffer);
        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, targetFrameBuffer);
        GLES20.glUseProgram(glProgram);
        GLES20.glUniform4f(glImageRectLoc, rectF.left, rectF.top, rectF.right, rectF.bottom);
//        GLES20.glUniform1f(glImageAngelLoc, (float)(30.0f*Math.PI/180));//用來更新旋轉角度的
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, cameraTexture);
        GLES20.glUniform1i(glCamTextureLoc, 0);
        GLES20.glActiveTexture(GLES20.GL_TEXTURE1);
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, imageTextureId);
        GLES20.glUniform1i(glImageTextureLoc, 1);
        GLES20.glDrawElements(GLES20.GL_TRIANGLES, drawIndecesBuffer.limit(), GLES20.GL_UNSIGNED_SHORT, drawIndecesBuffer);
        GLES20.glDisableVertexAttribArray(glCamPostionLoc);
        GLES20.glDisableVertexAttribArray(glCamTextureCoordLoc);
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0);
        GLES20.glUseProgram(0);
        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        GLES20.glDeleteProgram(glProgram);
        destroyImageTexture();
    }

    protected void destroyImageTexture() {
        for (ImageTexture imageTexture : imageTextures) {
            imageTexture.destroy();
        }
    }

    private RectF convertToRectF(Rect iconRect) {
        RectF iconRectF = new RectF();
        iconRectF.top = iconRect.top / (float) outVideoHeight;
        iconRectF.bottom = iconRect.bottom / (float) outVideoHeight;
        iconRectF.left = iconRect.left / (float) outVideoWidth;
        iconRectF.right = iconRect.right / (float) outVideoWidth;
        return iconRectF;
    }

    public static class ImageDrawData {
        public int resId = 0;
        public Rect rect;
    }
}

本文最后也附上demo的下載地址,供大家下載學習,有什么問題可以直接在文章下面評論或留言哈~~

CSDN下載地址:https://download.csdn.net/download/toyauko/21861215

github下載地址:https://github.com/Liangzhuhua/MutilCameraRecordDemo

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/297324.html

標籤:其他

上一篇:Android系統編程入門系列之應用內鍵值對資料的簡單保存

下一篇:cmake編譯NDK初體驗

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more