【Android】使用MediaCodec硬编码实现视频直播推流端(一)

【音视频开发(二)】---MediaCodec NDK编码 简单介绍使用NDK进行编码成H26裸流的过程,或者输出至mp4 阅读详情

废话不说,直接上代码。

布局文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.gitzzp.MainActivity">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="publish"
        android:id="@+id/publish"
        android:layout_alignParentTop="true" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="stop"
        android:id="@+id/stop"
        android:layout_toRightOf="@id/publish"
        android:layout_marginTop="0dp" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="switch"
        android:id="@+id/swCam"
        android:layout_alignBottom="@id/stop"
        android:layout_toRightOf="@id/stop" />

    <EditText
        android:id="@+id/vbitrate"
        android:textSize="14dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/publish"
        android:layout_marginTop="0dp" />

    <EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="14dp"
        android:id="@+id/url"
        android:layout_below="@id/publish"
        android:layout_above="@+id/frameLayout"
        android:layout_toRightOf="@id/vbitrate" />

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@id/vbitrate"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="0dp"
        android:id="@+id/frameLayout">

    <com.example.gitzzp.CameraPreview
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/preview" />
    </FrameLayout>
</RelativeLayout>

非常简单的布局,没什么可说的,唯一要说的就是最后边这个CameraPreview,这是一个自定义控件,继承自SurfaceView,用于显示摄像头的预览,具体代码后边会贴出来。



MainActivity:

package com.example.gitzzp;

import android.app.Activity;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

public class MainActivity extends Activity {
    private static final String TAG = "MainActivity";
    private SharedPreferences sp;
    private CameraPreview mCameraView = null;
    private String mNotifyMsg;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //保持屏幕常亮
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        setContentView(R.layout.activity_main1);

        // 响应屏幕旋转事件
//        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);

        // 本地存储数据
        sp = getSharedPreferences("SrsPublisher", MODE_PRIVATE);
        SrsEncoder.rtmpUrl = sp.getString("rtmpUrl", SrsEncoder.rtmpUrl);
        SrsEncoder.vbitrate = sp.getInt("vbitrate", SrsEncoder.vbitrate);
        Log.i(TAG, String.format("init rtmp url %s, vbitrate=%dkbps", SrsEncoder.rtmpUrl, SrsEncoder.vbitrate));

        // 设置程序刚开始显示的url
        final EditText efu = (EditText) findViewById(R.id.url);
        efu.setText(SrsEncoder.rtmpUrl);

        // 设置初始化时的视频码率
        final EditText evb = (EditText) findViewById(R.id.vbitrate);
        evb.setText(String.format("%dkbps", SrsEncoder.vbitrate / 1000));

        // for camera, @see https://developer.android.com/reference/android/hardware/Camera.html
        final Button btnPublish = (Button) findViewById(R.id.publish);
        final Button btnStop = (Button) findViewById(R.id.stop);
        final Button btnSwitch = (Button) findViewById(R.id.swCam);
        //布局中的surfaceview
        mCameraView = (CameraPreview) findViewById(R.id.preview);
        btnPublish.setEnabled(true);
        btnStop.setEnabled(false);

        btnPublish.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int vb = Integer.parseInt(evb.getText().toString().replaceAll("kbps", ""));
                SrsEncoder.vbitrate = vb * 1000;
                SrsEncoder.rtmpUrl = "替换为你自己想要推流的rtmp服务器地址";
                Log.i(TAG, String.format("RTMP URL changed to %s", SrsEncoder.rtmpUrl));
                Log.i(TAG, String.format("Video bitrate changed to %skbps", SrsEncoder.vbitrate / 1000));
                SharedPreferences.Editor editor = sp.edit();
                editor.putInt("vbitrate", SrsEncoder.vbitrate);
                editor.putString("rtmpUrl", SrsEncoder.rtmpUrl);
                editor.commit();
                btnPublish.setEnabled(false);
                btnStop.setEnabled(true);
                //发布直播
                mCameraView.startPublish();
            }
        });

        btnStop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mCameraView.stopPublish();
                btnPublish.setEnabled(true);
                btnStop.setEnabled(false);
            }
        });

        btnSwitch.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mCameraView.initCameraPreview(1);
            }
        });

        Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread thread, Throwable ex) {
                mNotifyMsg = ex.getMessage();
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(getApplicationContext(), mNotifyMsg,Toast.LENGTH_SHORT).show();
                        btnPublish.setEnabled(true);
                        btnStop.setEnabled(false);
                        mCameraView.stopPublish();
                    }
                });
            }
        });

    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();

        if (id == R.id.action_settings) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }


    @Override
    protected void onResume() {
        super.onResume();
        final Button btn = (Button) findViewById(R.id.publish);
        btn.setEnabled(true);
        mCameraView.handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                mCameraView.initCameraPreview();
            }
        },100);

    }

    @Override
    protected void onPause() {
        super.onPause();
        mCameraView.stopPublish();
        //停止预览
        mCameraView.stopCamera();
    }

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

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        mCameraView.stopPublish();
        mCameraView.mEncoder.setScreenOrientation(newConfig.orientation);
        mCameraView.startPublish();
    }

}

在MainActivity中做了一些控件和数据的初始化,监听事件的添加,以及在各个生命周期中对surfaceview和camera的控制等操作。
当我们点击publish按钮的时候,会根据我们设置的推流地址,码率等,调用surfaceview中的startPublish()来开始推流。swtich用于切换前置后置摄像头。



CameraPreview:接收camera中的数据,并显示出来,作为camera的预览图像。

package com.example.gitzzp;

import android.app.Activity;
import android.content.Context;
import android.hardware.Camera;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.widget.Toast;

import com.example.gitzzp.rtmp.RtmpPublisher;

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

/**
 * 相机预览图像
 * Created by gitzzp on 16/6/2.
 */
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback, Camera.PreviewCallback {

    private static final String TAG = "CameraPreview";
    private SurfaceHolder mHolder;
    private Activity mContext;

    private AudioRecord mic = null;
    private boolean isPublish = false;
    private Thread aworker = null;//音频录制的线程
    public Camera mCamera = null;

    private int mPreviewRotation = 90;
    private int mDisplayRotation = 90;
    private int mCamId = Camera.getNumberOfCameras() - 1; // default camera 该设备的摄像头数量
    //    private byte[] mYuvFrameBuffer = new byte[SrsEncoder.VWIDTH * SrsEncoder.VHEIGHT * 3 / 2];
    private byte[] mYuvFrameBuffer = new byte[SrsEncoder.VWIDTH * SrsEncoder.VHEIGHT * 3 *10];

    private String mNotifyMsg;


    Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case 1:
                    initCameraPreview();
                    break;
            }
        }
    };

    public CameraPreview(Context context) {
        this(context,null);
    }

    public CameraPreview(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public CameraPreview(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mContext = (Activity) context;
        mHolder = getHolder();
        mHolder.addCallback(this);
        mHolder.setKeepScreenOn(true);
    }

    public void initCameraPreview(){
        if (mCamera != null && mEncoder != null) {
            stopCamera();
            startCamera();
        }else if(mCamera == null && mEncoder != null) {
            startCamera();
        }
    }

    public void initCameraPreview(int num) {
        if (mCamera != null && mEncoder != null) {
            mCamId = (mCamId + num) % Camera.getNumberOfCameras();
            stopCamera();
            mEncoder.swithCameraFace();
            startCamera();
        }else if(mCamera == null && mEncoder != null) {
            mCamId = (mCamId + num) % Camera.getNumberOfCameras();
            mEncoder.swithCameraFace();
            startCamera();
        }
    }

    //开始推流
    public void startPublish() {
        int ret = mEncoder.start();
        //小于0表示有某个地方出错 但是SrsEncoder中没有做具体区分 统一以-1来进行返回 后期可以根据需求分别进行处理
        //返回0表示流创建成功
        if (ret < 0) {
            return;
        }
        //开启摄像头
        startCamera();

        aworker = new Thread(new Runnable() {
            @Override
            public void run() {
                android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO);
                startAudio();
            }
        });
        isPublish = true;
        aworker.start();
    }


    public void startCamera() {
        if (mCamera != null) {
            Log.d(TAG, "start camera, already started. return");//摄像头已经开启
            return;
        }
        if (mCamId > (Camera.getNumberOfCameras() - 1) || mCamId < 0) {
            Log.e(TAG, "####### start camera failed, inviald params, camera No.="+ mCamId);
            return;
        }

        mCamera = Camera.open(mCamId);

        Camera.CameraInfo info = new Camera.CameraInfo();
        //获取摄像头信息
        Camera.getCameraInfo(mCamId, info);
        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT){//前置摄像头
            mDisplayRotation = (mPreviewRotation + 180) % 360;
            mDisplayRotation = (360 - mDisplayRotation) % 360;
        } else {
            mDisplayRotation = mPreviewRotation;
        }

        Camera.Parameters params = mCamera.getParameters();
        /* preview size  */
        Camera.Size size = mCamera.new Size(SrsEncoder.VWIDTH, SrsEncoder.VHEIGHT);
        if (!params.getSupportedPreviewSizes().contains(size)) {
            //摄像头预览尺寸小于我们设置的预览尺寸 抛出异常
            Thread.getDefaultUncaughtExceptionHandler().uncaughtException(Thread.currentThread(),
                    new IllegalArgumentException(String.format("Unsupported preview size %dx%d", size.width, size.height)));
        }

        /* picture size  */
        if (!params.getSupportedPictureSizes().contains(size)) {
            //图片尺寸小于我们设置的图片尺寸
            Thread.getDefaultUncaughtExceptionHandler().uncaughtException(Thread.currentThread(),
                    new IllegalArgumentException(String.format("Unsupported picture size %dx%d", size.width, size.height)));
        }

        /***** set parameters *****/
        //params.set("orientation", "portrait");
        //params.set("orientation", "landscape");
//        params.setRotation(180);
        params.setPictureSize(SrsEncoder.VWIDTH, SrsEncoder.VHEIGHT);
        //设置预览时的大小
        params.setPreviewSize(SrsEncoder.VWIDTH, SrsEncoder.VHEIGHT);
        //设置帧数
        int[] range = findClosestFpsRange(SrsEncoder.VFPS, params.getSupportedPreviewFpsRange());
        params.setPreviewFpsRange(range[0], range[1]);
        //预览图格式
        params.setPreviewFormat(SrsEncoder.VFORMAT);
        //闪光灯控制
        params.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
        //白平衡控制
        params.setWhiteBalance(Camera.Parameters.WHITE_BALANCE_AUTO);
        //设置情景模式 改变该参数可以覆盖上边的几个参数 例如 最初闪光灯是开启的 在变成夜间模式之后 会关闭
        params.setSceneMode(Camera.Parameters.SCENE_MODE_AUTO);
        //设置自动对焦 共分两步 这是第一步
        params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);

        mCamera.setParameters(params);
        //预览图显示的角度
        mCamera.setDisplayOrientation(mPreviewRotation);
        //缓冲区大小
        mCamera.addCallbackBuffer(mYuvFrameBuffer);
        //设置自动对焦的第二步
        mCamera.cancelAutoFocus();
        mCamera.setPreviewCallbackWithBuffer(this);
        try {
            //设置surfacebview显示摄像头的预览界面
            mCamera.setPreviewDisplay(mHolder);
        } catch (IOException e) {
            e.printStackTrace();
        }
        mCamera.startPreview();
    }
    private int[] findClosestFpsRange(int expectedFps, List<int[]> fpsRanges) {
        expectedFps *= 1000;
        int[] closestRange = fpsRanges.get(0);
        int measure = Math.abs(closestRange[0] - expectedFps) + Math.abs(closestRange[1] - expectedFps);
        for (int[] range : fpsRanges) {
            if (range[0] <= expectedFps && range[1] >= expectedFps) {
                int curMeasure = Math.abs(range[0] - expectedFps) + Math.abs(range[1] - expectedFps);
                if (curMeasure < measure) {
                    closestRange = range;
                    measure = curMeasure;
                }
            }
        }
        return closestRange;
    }

    public void stopCamera() {
        if (mCamera != null) {
            // need to SET NULL CB before stop preview!!!
            mCamera.setPreviewCallback(null);
            mCamera.stopPreview();
            mCamera.release();
            mCamera = null;
        }
    }

    private void onGetYuvFrame(byte[] data) {
        mEncoder.onGetYuvFrame(data);
    }

    @Override
    public void onPreviewFrame(byte[] data, Camera c) {
//        onGetYuvFrame(data);
        //点击推流之后开始推流
        if(isPublish){
            onGetYuvFrame(data);
        }
        c.addCallbackBuffer(mYuvFrameBuffer);
    }

    private void onGetPcmFrame(byte[] pcmBuffer, int size) {
        mEncoder.onGetPcmFrame(pcmBuffer, size);
    }

    //开始录音
    private void startAudio() {
        if (mic != null) {
            return;
        }

        int bufferSize = 2 * AudioRecord.getMinBufferSize(SrsEncoder.ASAMPLERATE, SrsEncoder.ACHANNEL, SrsEncoder.AFORMAT);
        mic = new AudioRecord(MediaRecorder.AudioSource.MIC, SrsEncoder.ASAMPLERATE, SrsEncoder.ACHANNEL, SrsEncoder.AFORMAT, bufferSize);
        mic.startRecording();

        byte pcmBuffer[] = new byte[4096];
        while (isPublish && !Thread.interrupted()) {
            int size = mic.read(pcmBuffer, 0, pcmBuffer.length);
            if (size <= 0) {
                Log.e(TAG, "***** audio ignored, no data to read.");
                break;
            }
            onGetPcmFrame(pcmBuffer, size);
        }
    }

    private void stopAudio() {
        isPublish = false;
        if (aworker != null) {
            Log.i(TAG, "stop audio worker thread");
            aworker.interrupt();
            try {
                aworker.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
                aworker.interrupt();
            }
            aworker = null;
        }

        if (mic != null) {
            mic.setRecordPositionUpdateListener(null);
            mic.stop();
            mic.release();
            mic = null;
        }
    }

    //停止推流之后 预览状态不应该停 也就是说我们应该在destory中停止摄像头 而不是在这里
    public void stopPublish() {
        stopAudio();
//        stopCamera();
        mEncoder.stop();
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {

    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {

    }

    public SrsEncoder mEncoder = new SrsEncoder(new RtmpPublisher.EventHandler() {
        @Override
        public void onRtmpConnecting(String msg) {
            mNotifyMsg = msg;
            mContext.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(mContext, mNotifyMsg, Toast.LENGTH_SHORT).show();
                }
            });
        }

        @Override
        public void onRtmpConnected(String msg) {
            mNotifyMsg = msg;
            mContext.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(mContext, mNotifyMsg, Toast.LENGTH_SHORT).show();
                }
            });
        }

        @Override
        public void onRtmpVideoStreaming(String msg) {
        }

        @Override
        public void onRtmpAudioStreaming(String msg) {
        }

        @Override
        public void onRtmpStopped(String msg) {
            mNotifyMsg = msg;
            mContext.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(mContext, mNotifyMsg, Toast.LENGTH_SHORT).show();
                }
            });
        }

        @Override
        public void onRtmpDisconnected(String msg) {
            mNotifyMsg = msg;
            mContext.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(mContext, mNotifyMsg, Toast.LENGTH_SHORT).show();
                }
            });
        }

        @Override
        public void onRtmpOutputFps(final double fps) {
            Log.i(TAG, String.format("Output Fps: %f", fps));
        }
    });
}

CameraPreview继承自SurfaceView并且实现了SurfaceHolder.Callback和 Camera.PreviewCallback两个接口。
SurfaceHolder.Callback用于监听SurfaceView的创建、改变、销毁等操作,本来应该在这里完成对摄像头的init和release等操作,这里偷了个懒。
Camera.PreviewCallback用于接收camera的预览,对应onPreviewFrame()方法,我们可以在这里对摄像头传回的数据做一些自己需要的处理,我们在这里调用了另外的类对我们摄像头传回的数据进行了YUV编码。

参考资料:https://github.com/begeekmyfriend/yasea

Android 直播调研 1 概述 视频直播,可以分为 采集,前处理,编码,传输,解码,渲染 这几个环节。 推流端:采集、前处理、编码、推流 服务端:转码、录制、截图、鉴黄 播放器:拉流、解码、渲染 互动:聊天室、礼物系统、赞等 2 技术点 2.1 协议 音频采用AAC编码、视频采用H264、H265编码。 推送采用RTMP协议 2.2 推流端 采集:音频:AudioRecord,视频:Camera。Android要做机型适配工作 手机直播SDK通过手机摄像头和麦克风直接采集视频数据和音频数. 阅读详情

相关推荐

Android 内置RTSP/RTMP服务器,实现局域网内视频推流与播放 Demo

在同一局域网中,Android推流端(比如平板)内置RTSP/RTMP服务器,Android推流端将摄像头的画面实时推流到内置的RTSP/RTMP服务器上,然后在Android手机上实时进行播放。 本方案实现了如下功能 : 平板端内置RTSP/RTMP服务器 平板端获取摄像头的画面并同步进行RTMP推流 (目前仅支持了RTMP推流,未实现RTSP推流) 手机端支持对RTSP/RTMP视频流的播放 具体可以看我的博客 : https://blog.youkuaiyun.com/EthanCo/article/details/130387829

Android视频编码和直播推流教程

Android音视频编码和推流功能视频培训教程,该课程内容包括:OpenGL ES教程、Android视频滤镜、图片生成视频、添加水印、音频和视频合成、剪切音乐合成视频、利用摄像头实现RTMP直播推流等。

Opencv实战:答题卡自动识别与评分系统(附完整代码解析)

本文详细介绍了如何使用OpenCV实现答题卡自动识别与评分系统,包含完整的代码解析。通过图像预处理、边缘检测、透视变换和答案识别等关键技术,系统能够快速准确地完成阅卷工作,适用于学校考试、资格认证等多种场景,大幅提升工作效率。

weixin_33737134的博客 415

Android使用系统API进行音视频编码

一、前言 上一篇文章我讲到,我用libx264对视频进行h264编码效率非常低下,原因在于libx264采用的是软编码。于是我在网上搜索得知使用系统的API可以对视频进行硬编码,从而减少cpu的压力,达到提高效率的作用。我写了一个demo试了一下,果真效率提高的很明显。 二、思路 1、核心类 MediaCodec 这个类用来进行音/视频编码。 AudioRecord 这个类用来录音得

BlueBerry的专栏 8115

AI赋能传染病建模:从数据到动力学模型的本地实践指南

这次我们来看一个将 AI 与传染病动力学建模结合的前沿方向。想象一下,你手头有一份流感爆发的病例数据,传统的建模方法可能需要复杂的微分方程和大量的手动调参,而 AI 模型能否直接从数据中“学习”出传播规律,甚至自动跑通整个建模流程?这正是当前研究的热点。根据《Nature》的最新综述,人工智能正在重塑传染病流行病学建模,从参数推断、趋势预测到多源数据融合,AI 技术为解决传统建模中的计算瓶颈和数据难题提供了全新路径。 对于开发者、数据科学家或公共卫生领域的研究者而言,最关心的是:这套方法能不能在本地跑起来?

weixin_29051811的博客 297

loadStop的原因导致的SSUSP

Total内存值是可用来调度的可用内存,当前可用内存为 0M。 loadSched和loadStop的阈值是根据 Total 内存和节点内存来做限制的。 loadStop 50G,现在有大量作业SSUSP,说明之前某个时间点该节点内存可能有低于50G的时候,导致作业SSUSP,具体是否是这个原因引起的需要用 bjobs -l JobID 看下作业的详细信息。 如果是loadStop的原因导致的 SSUSP,根据loadStop设置的阈值,Total 值高于loadStop值后,将逐步恢复挂起的作业

Blueberry521的博客 666

【转载】直播技术点之编解码基础(一)—— 硬编码之MediaCodec

作者:罗拙呓链接:https://www.jianshu.com/p/f116b6f81ab3 前言 本文主要介绍直播所需要的编解码基础,后续文章将继续介绍实际的运用。 什么是码? 这里的码指码流(Data Rate),是指视频文件在单位时间内使用的数据流量,也叫码率或码流率,通俗一点的理解就是取样率,是视频编码中画面质量控制中最重要的部分,一般我们用的单位是kb/s或者Mb...

weixin_30906701的博客 362

想在瑞芯微RK3588上跑视频分类模型?避开3D卷积这个坑,试试这几种NPU友好的方案

本文探讨了在瑞芯微RK3588上部署视频分类模型时如何避开3D卷积的性能陷阱,提供了三种NPU友好的替代方案:MobileNetV2+GRU组合、Temporal Shift Module(TSM)和帧级池化。这些方案在保持较高准确率的同时,充分利用RKNPU的硬件加速能力,显著提升推理效率。文章还分享了从模型转换到部署优化的完整流程和实战经验。

weixin_28724319的博客 417

MediaCodec硬编码成H264视频流

android提供了一个强大的编解码类MediaCodec,可以实现对视频数据的编解码,下面讲一下如何对原始视频数据硬编码成h264格式的流 MediaCodec提供两种方式的输入,一种是将数据写入它的输入缓冲队列里,一种是让MediaCodec建立一个输入Surface,MediaCodec会自动从这个输入Surface中读取数据,因为我做的是录制屏幕的需求,所以我是使用一个Surfa

sutao1994 9092

目标跟踪与视频时序分析基础

两帧均有目标,却因快速移动导致检测框无重叠,仅靠交并比(IoU)无法维持身份关联。本文通过手算与纯Python实现最小跟踪流程,揭示IoU仅为位置线索,不能应对快速位移或遮挡。实验表明,即使目标真实存在,低重叠也会触发新编号。强调跟踪需结合运动预测、外观特征与阈值验证,避免误判。

Mr数据杨 2335

Amazon SES 移出沙盒完整指南 & 高通过率模板

AWS SES沙盒模式转正式模式申请指南 申请正式模式需通过AWS审核,常见被拒原因包括邮件用途不清晰、缺少退订机制或发送量不合理。人工申诉需登录AWS Support Center提交服务限额提升请求,填写详细的公司信息、邮件用途及合规措施。 高通过率申诉模板需包含: 公司信息:名称、网址及与AWS的合作情况。 邮件规范:明确类型(交易/营销)、退订链接及退信处理机制。 发送计划:合理预估发送量,确保合规。 关键点:真实信息、用途清晰、退订机制完善、发送量合理。首次被拒可修改后重新提交。

xianyinsuifeng的博客 328

MediaCodec硬编码为H264并通过UDP发送

最近研究视频通话,写一下关于摄像头采集视频并使用MediaCodec硬编码为H264的过程,希望对有需要的朋友有所帮助。 说实话,刚开始不太熟折腾了挺久的,网上这方面的东西比较少,很多都是代码片段或者就是其他语言写的。这里贴的是本人亲测能用的,希望需要的朋友能少走一些弯路吧。 直接来看看代码吧。都有详细的注释的。 package com.kokjuis.travel.activit...

MirkoWug的博客 1823

HTML爱心字母雨(完整代码)

HTML语言实现爱心字母雨的完整代码。

AIGC 2万+

AndroidInstantVideo实战案例:构建完整的Android直播应用终极指南

想要在Android平台上快速构建一个功能完整的直播应用吗?AndroidInstantVideo项目为您提供了完美的解决方案!这个开源项目展示了Android硬件编码下的视频数据流动,支持视频处理、滤镜添加、水印叠加以及RTMP直播推流等核心功能。无论您是Android开发新手还是经验丰富的开发者,这个项目都能帮助您深入理解移动端视频处理与直播技术。 ## 📱 AndroidInstantV

gitblog_00480的博客 878

游戏大厂射击游戏无缝大世界游戏DS服务器架构设计方案详解

摘要: 本文深入解析游戏大厂无缝大世界射击游戏的分布式服务器(DS)架构设计方案。以《和平精英》《量子特攻》为例,阐述如何通过动态分区、兴趣管理、弹性扩缩等核心技术应对高并发、低延迟、无缝切换等挑战。架构分为世界协调层、分区计算层、物理判定层等模块,结合微服务化、快照同步和Kubernetes调度,实现玩家跨区无感知迁移与一致判定。重点优化动态负载均衡、状态同步算法及容灾备份,依托分布式数据库与多云部署保障高可用性,为同类游戏提供可扩展的技术范本。(149字) 关键词: 无缝大世界、分布式服务器、动态分区、

本博客聚焦游戏开发技巧、创业实战心得及大学热门课程干货。这里既有技术深度,也有思维广度,无论你是开发者、创业者还是高校学子,都能在这里找到适合自己的成长之路! 562

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

视频直播推流技术(MediaCodec硬编码+libRTMP,编码器),Demo - Android

- aac audio_codec; h264,video_codec;25 framerate 25帧; - Camera-YUV帧序列-YUV帧预处理(镜像 缩放 旋转)-编码器-H264数据 从摄像头输出的YUV帧经过预处理之后,送入编码器,获得编码好的h264视频流。 >直播,编码器SDK 1.直播 SDK 的开发,尤其是音视频录制、处理、编码、推流等核心功能的研发; ...

ShareUs的专栏 1万+

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

Android视频录制--屏幕录制

上一篇介绍了MediaProjection,这个类可以用来实现安卓屏幕数据的采集,也就是手机一帧帧的截图,并输出成byte流的格式。 有兴趣的同学可以看这篇: Android视频录制--MediaProjection但其实只用MediaProjection,并无法生成一个视频,因为我们得到的只是流,还需要把流编码成视频格式。MediaProjection官方的demo里,也仅仅是把输出内容放到了

A WAY 1万+

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

android平台视频直播,录制库

此文章介绍一个android平台,视频直播和录制的开源库。开源库源码地址为https://github.com/showwaychen/svideostream。 此库      1.采集的视频源有两种:摄像头和屏幕(得需要android系统版本5.0以上)。支持基于opengl es 的图像美颜,opengl es图像获取使用两种方式:每一种是imagereader,需要api>=19

CXW1014的专栏 1434

android硬解码(白平衡java实例)

【Android】使用MediaCodec硬编码实现视频直播推流端(一) 2016年06月15日 18:37:15 gitzzp 阅读数:7162 标签: 视频 直播 推流 MediaCodec 硬编码 更多 个人分类: 多媒体相关 版权声明:本文为博主原创文章,转载请注明来源。 https://blog.youkuaiyun.com/gitzzp/article/details/51684466 ...

u010029439的博客 875

基于EasyDarwin EasyPusher实现Android手机直播推送功能

EasyPusher直播推送在之前就已经稳定支持了Windows、Linux、ARM上的RTSP直播推送功能,配合EasyDarwin开源流媒体服务器,延时基本在1s以内,这个技术方案经过一年多时间,包括开源社区、企业用户的反复检验测试,已经具备很高的稳定性了; 我们在之前已经spydroid实现过一套Android直播推送与EasyDarwin结合的直播方案,...

weixin_30486037的博客 176

Android NDK实战:用MediaCodec硬编码H264的5个关键坑点与解决方案

本文深入探讨了Android NDK中使用MediaCodec进行H264硬编码时常见的5个关键坑点及解决方案。从颜色格式兼容性、帧率控制到内存泄漏问题,再到平台特异性问题和性能调优技巧,提供了实战验证的优化方案,帮助开发者高效实现视频编码,提升应用性能。

pink的博客 387

camera开发系列之三 相机数据采集硬编码h264

章节 Camera开发系列之一-显示摄像头实时画面 Camera开发系列之二-相机预览数据回调 Camera开发系列之三-相机数据硬编码为h264 Camera开发系列之四-使用MediaMuxer封装编码后的音视频到mp4容器 Camera开发系列之五-使用MediaExtractor制作一个简易播放器 Camera开发系列之六-使用mina框架实现视频推流 Camera开发系列之七-使用GLS...

weixin_33854644的博客 2214
上一篇: 【Android】Android Camera实时数据采集及通过MediaCodec硬编码编码数据的流程
下一篇: 【Android】各种小知识点,不间断更新
gitzzp
博客等级 码龄11年 307粉丝 60原创
评论 4
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值